/** * @file Broadcom Dongle Host Driver (DHD), time sync protocol handler * * timesync mesasages are exchanged between the host and device to synchronize the source time * for ingress and egress packets. * * Copyright (C) 2020, Broadcom. * * Unless you and Broadcom execute a separate written software license * agreement governing use of this software, this software is licensed to you * under the terms of the GNU General Public License version 2 (the "GPL"), * available at http://www.broadcom.com/licenses/GPLv2.php, with the * following added to such license: * * As a special exception, the copyright holders of this software give you * permission to link this software with independent modules, and to copy and * distribute the resulting executable under terms of your choice, provided that * you also meet, for each linked independent module, the terms and conditions of * the license of that module. An independent module is a module which is not * derived from this software. The special exception does not apply to any * modifications of the software. * * * <> * * $Id$: */ #include #include #include #include #include #include #include #include #include #include #include #include extern void dhd_msgbuf_delay_post_ts_bufs(dhd_pub_t *dhd); #define MAX_FW_CLKINFO_TYPES 8 #define MAX_SIZE_FW_CLKINFO_TYPE (MAX_FW_CLKINFO_TYPES * sizeof(ts_fw_clock_info_t)) #define MAX_FW_TS_LOG_SAMPLES 64 #define BCMMSGBUF_HOST_TS_BADTAG 0xF0 #define DHD_DEFAULT_TIMESYNC_TIMER_VALUE 20 /* ms */ #define DHD_DEFAULT_TIMESYNC_TIMER_VALUE_MAX 9000 /* ms */ #define MAX_TS_LOG_SAMPLES_DATA 128 #define TS_NODROP_CONFIG_TO 1 #define TS_DROP_CONFIG_TO 5 typedef struct clksrc_ts_log { uchar name[4]; uint32 inuse; ts_timestamp_srcid_t log[MAX_FW_TS_LOG_SAMPLES]; } clksrc_ts_log_t; typedef struct clk_ts_log { uint32 clk_ts_inited; uint32 cur_idx; uint32 seqnum[MAX_FW_TS_LOG_SAMPLES]; clksrc_ts_log_t ts_log[MAX_CLKSRC_ID+1]; } clk_ts_log_t; typedef struct dhd_ts_xt_id { uint16 host_timestamping_config; uint16 host_clock_selection; uint16 host_clk_info; uint16 d2h_clk_correction; } dhd_ts_xt_id_t; typedef struct dhd_ts_log_ts_item { uint16 flowid; /* interface, Flow ID */ uint8 intf; /* interface */ uint8 rsvd; uint32 ts_low; /* time stamp values */ uint32 ts_high; /* time stamp values */ uint32 proto; uint32 t1; uint32 t2; } dhd_ts_log_ts_item_t; typedef struct dhd_ts_log_ts { uint32 max_idx; uint32 cur_idx; dhd_ts_log_ts_item_t ts_log[MAX_TS_LOG_SAMPLES_DATA]; } dhd_ts_log_ts_t; #define MAX_BUF_SIZE_HOST_CLOCK_INFO 512 #define HOST_TS_CONFIG_FW_TIMESTAMP_PERIOD_DEFAULT 1000 struct dhd_ts { dhd_pub_t *dhdp; osl_t *osh; uint32 xt_id; uint16 host_ts_capture_cnt; uint32 fw_ts_capture_cnt; uint32 fw_ts_disc_cnt; uint32 h_clkid_min; uint32 h_clkid_max; uint32 h_tsconf_period; /* sould these be per clock source */ ts_correction_m_t correction_m; ts_correction_m_t correction_b; ts_fw_clock_info_t fw_tlv[MAX_FW_CLKINFO_TYPES]; uint32 fw_tlv_len; clk_ts_log_t fw_ts_log; uchar host_ts_host_clk_info_buffer[MAX_BUF_SIZE_HOST_CLOCK_INFO]; bool host_ts_host_clk_info_buffer_in_use; dhd_ts_xt_id_t xt_ids; uint32 active_ipc_version; uint32 fwts2hsts_delay; uint32 fwts2hsts_delay_wdcount; uint32 ts_watchdog_calls; uint64 last_ts_watchdog_time; uint32 pending_requests; dhd_ts_log_ts_t tx_timestamps; dhd_ts_log_ts_t rx_timestamps; /* outside modules could stop timesync independent of the user config */ bool timesync_disabled; uint32 host_reset_cnt; bool nodrop_config; uint32 suspend_req; uint32 resume_req; }; struct dhd_ts *g_dhd_ts; static uint32 dhd_timesync_send_D2H_clk_correction(dhd_ts_t *ts); static uint32 dhd_timesync_send_host_clk_info(dhd_ts_t *ts); static uint32 dhd_timesync_send_host_clock_selection(dhd_ts_t *ts); static uint32 dhd_timesync_send_host_timestamping_config(dhd_ts_t *ts, bool inject_err); static void dhd_timesync_ts_log_dump_item(dhd_ts_log_ts_t *tsl, struct bcmstrbuf *b); /* Check for and handle local prot-specific iovar commands */ enum { IOV_TS_INFO_DUMP, IOV_TS_TX_TS_DUMP, IOV_TS_RX_TS_DUMP, IOV_TS_FW_CLKINFO_DUMP, IOV_TS_HCLK_CLKID_MIN, IOV_TS_HCLK_CLKID_MAX, IOV_TS_HTSCONF_PERIOD, IOV_TS_SEND_TSCONFIG, IOV_TS_SEND_HCLK_SEL, IOV_TS_SEND_HCLK_INFO, IOV_TS_SEND_D2H_CRCT, IOV_TS_TXS_LOG, IOV_TS_RXS_LOG, IOV_TS_INJECT_BAD_XTID, IOV_TS_INJECT_BAD_TAG, IOV_TS_FWTS2HSTS_DELAY, IOV_TS_NODROP_CONFIG, IOV_TS_CLEAR_LOGS, IOV_TS_NO_RETRY, IOV_TS_NO_AGGR, IOV_TS_FIXED_RATE, IOV_LAST }; const bcm_iovar_t dhd_ts_iovars[] = { {"ts_info_dump", IOV_TS_INFO_DUMP, 0, 0, IOVT_BUFFER, DHD_IOCTL_MAXLEN }, {"ts_tx_ts_dump", IOV_TS_TX_TS_DUMP, 0, 0, IOVT_BUFFER, DHD_IOCTL_MAXLEN }, {"ts_rx_ts_dump", IOV_TS_RX_TS_DUMP, 0, 0, IOVT_BUFFER, DHD_IOCTL_MAXLEN }, {"ts_fw_clkinfo_dump", IOV_TS_FW_CLKINFO_DUMP, 0, 0, IOVT_BUFFER, DHD_IOCTL_MAXLEN }, {"ts_hclk_clkid_min", IOV_TS_HCLK_CLKID_MIN, 0, 0, IOVT_UINT32, 0 }, {"ts_hclk_clkid_max", IOV_TS_HCLK_CLKID_MAX, 0, 0, IOVT_UINT32, 0 }, {"ts_htsconf_period", IOV_TS_HTSCONF_PERIOD, 0, 0, IOVT_UINT32, 0 }, {"ts_send_tsconfig", IOV_TS_SEND_TSCONFIG, 0, 0, IOVT_UINT32, 0 }, {"ts_send_hostclk_sel", IOV_TS_SEND_HCLK_SEL, 0, 0, IOVT_UINT32, 0 }, {"ts_send_hostclk_info", IOV_TS_SEND_HCLK_INFO, 0, 0, IOVT_UINT32, 0 }, {"ts_send_d2h_corect ", IOV_TS_SEND_D2H_CRCT, 0, 0, IOVT_UINT32, 0 }, {"ts_txs_log", IOV_TS_TXS_LOG, 0, 0, IOVT_UINT32, 0 }, {"ts_rxs_log", IOV_TS_RXS_LOG, 0, 0, IOVT_UINT32, 0 }, /* error injection cases */ {"ts_inject_bad_xtid", IOV_TS_INJECT_BAD_XTID, 0, 0, IOVT_UINT32, 0 }, {"ts_inject_bad_tag", IOV_TS_INJECT_BAD_TAG, 0, 0, IOVT_UINT32, 0 }, {"ts_fwts2hsts_delay", IOV_TS_FWTS2HSTS_DELAY, 0, 0, IOVT_UINT32, 0 }, {"ts_nodrop_config", IOV_TS_NODROP_CONFIG, 0, 0, IOVT_UINT32, 0 }, {"ts_clear_logs", IOV_TS_CLEAR_LOGS, 0, 0, IOVT_UINT32, 0 }, {"ts_set_no_retry", IOV_TS_NO_RETRY, 0, 0, IOVT_UINT32, 0 }, {"ts_set_no_aggr", IOV_TS_NO_AGGR, 0, 0, IOVT_UINT32, 0 }, {"ts_set_fixed_rate", IOV_TS_FIXED_RATE, 0, 0, IOVT_UINT32, 0 }, {NULL, 0, 0, 0, 0, 0 } }; static int dhd_ts_fw_clksrc_dump(dhd_ts_t *ts, char *buf, int buflen); #ifdef CONFIG_PROC_FS static int dhd_open_proc_ts_fw_clk_dump(struct inode *inode, struct file *file); ssize_t dhd_read_proc_ts_fw_clk_dump(struct file *file, char *user_buf, size_t count, loff_t *loff); static int dhd_open_proc_ts_tx_dump(struct inode *inode, struct file *file); ssize_t dhd_read_proc_ts_tx_dump(struct file *file, char *user_buf, size_t count, loff_t *loff); static int dhd_open_proc_ts_rx_dump(struct inode *inode, struct file *file); ssize_t dhd_read_proc_ts_rx_dump(struct file *file, char *user_buf, size_t count, loff_t *loff); static int dhd_open_proc_ts_fw_clk_dump(struct inode *inode, struct file *file) { return single_open(file, 0, NULL); } ssize_t dhd_read_proc_ts_fw_clk_dump(struct file *file, char *user_buf, size_t count, loff_t *loff) { dhd_ts_t *ts; char *buf; ssize_t ret = 0; ts = g_dhd_ts; if (ts == NULL) { return -EAGAIN; } if (DHD_BUS_CHECK_SUSPEND_OR_SUSPEND_IN_PROGRESS(ts->dhdp)) { DHD_INFO(("%s bus is in suspend or suspend in progress\n", __func__)); return -EAGAIN; } if (DHD_BUS_CHECK_DOWN_OR_DOWN_IN_PROGRESS(ts->dhdp)) { DHD_ERROR(("%s rmmod in progress\n", __func__)); return -ENOENT; } buf = kmalloc(count, GFP_KERNEL); if (buf == NULL) { DHD_ERROR(("%s failed to allocate buf with size %zu\n", __func__, count)); return -ENOMEM; } ret = dhd_ts_fw_clksrc_dump(ts, buf, count); if (ret < 0) { return 0; } ret = simple_read_from_buffer(user_buf, count, loff, buf, (count - ret)); kfree(buf); return ret; } static int dhd_open_proc_ts_tx_dump(struct inode *inode, struct file *file) { return single_open(file, 0, NULL); } ssize_t dhd_read_proc_ts_tx_dump(struct file *file, char *user_buf, size_t count, loff_t *loff) { dhd_ts_t *ts; char *buf; ssize_t ret = 0; struct bcmstrbuf strbuf; ts = g_dhd_ts; if (ts == NULL) { return -EAGAIN; } if (DHD_BUS_CHECK_SUSPEND_OR_SUSPEND_IN_PROGRESS(ts->dhdp)) { DHD_INFO(("%s bus is in suspend or suspend in progress\n", __func__)); return -EAGAIN; } if (DHD_BUS_CHECK_DOWN_OR_DOWN_IN_PROGRESS(ts->dhdp)) { DHD_ERROR(("%s rmmod in progress\n", __func__)); return -ENOENT; } buf = kmalloc(count, GFP_KERNEL); if (buf == NULL) { DHD_ERROR(("%s failed to allocate buf with size %zu\n", __func__, count)); return -ENOMEM; } bcm_binit(&strbuf, buf, count); bcm_bprintf(&strbuf, "Tx Log dump\n"); dhd_timesync_ts_log_dump_item(&ts->tx_timestamps, &strbuf); ret = simple_read_from_buffer(user_buf, count, loff, buf, (count - strbuf.size)); kfree(buf); return ret; } static int dhd_open_proc_ts_rx_dump(struct inode *inode, struct file *file) { return single_open(file, 0, NULL); } ssize_t dhd_read_proc_ts_rx_dump(struct file *file, char *user_buf, size_t count, loff_t *loff) { dhd_ts_t *ts; char *buf; ssize_t ret = 0; struct bcmstrbuf strbuf; ts = g_dhd_ts; if (ts == NULL) { return -EAGAIN; } if (DHD_BUS_CHECK_SUSPEND_OR_SUSPEND_IN_PROGRESS(ts->dhdp)) { DHD_INFO(("%s bus is in suspend or suspend in progress\n", __func__)); return -EAGAIN; } if (DHD_BUS_CHECK_DOWN_OR_DOWN_IN_PROGRESS(ts->dhdp)) { DHD_ERROR(("%s rmmod in progress\n", __func__)); return -ENOENT; } buf = kmalloc(count, GFP_KERNEL); if (buf == NULL) { DHD_ERROR(("%s failed to allocate buf with size %zu\n", __func__, count)); return -ENOMEM; } bcm_binit(&strbuf, buf, count); bcm_bprintf(&strbuf, "Rx Log dump\n"); dhd_timesync_ts_log_dump_item(&ts->rx_timestamps, &strbuf); ret = simple_read_from_buffer(user_buf, count, loff, buf, (count - strbuf.size)); kfree(buf); return ret; } static const struct file_operations proc_fops_ts_fw_clk_dump = { .read = dhd_read_proc_ts_fw_clk_dump, .open = dhd_open_proc_ts_fw_clk_dump, .release = seq_release, }; static const struct file_operations proc_fops_ts_tx_dump = { .read = dhd_read_proc_ts_tx_dump, .open = dhd_open_proc_ts_tx_dump, .release = seq_release, }; static const struct file_operations proc_fops_ts_rx_dump = { .read = dhd_read_proc_ts_rx_dump, .open = dhd_open_proc_ts_rx_dump, .release = seq_release, }; #endif /* CONFIG_PROC_FS */ int dhd_timesync_detach(dhd_pub_t *dhdp) { dhd_ts_t *ts; DHD_TRACE(("%s: %d\n", __FUNCTION__, __LINE__)); if (!dhdp) { return BCME_OK; } ts = dhdp->ts; #ifdef CONFIG_PROC_FS remove_proc_entry("ts_fw_clk_dump", NULL); remove_proc_entry("ts_tx_dump", NULL); remove_proc_entry("ts_rx_dump", NULL); #endif /* CONFIG_PROC_FS */ #ifndef CONFIG_DHD_USE_STATIC_BUF MFREE(dhdp->osh, ts, sizeof(dhd_ts_t)); #endif /* CONFIG_DHD_USE_STATIC_BUF */ g_dhd_ts = NULL; dhdp->ts = NULL; DHD_INFO(("Deallocated DHD TS\n")); return BCME_OK; } int dhd_timesync_attach(dhd_pub_t *dhdp) { dhd_ts_t *ts; DHD_TRACE(("%s: %d\n", __FUNCTION__, __LINE__)); /* Allocate prot structure */ if (!(ts = (dhd_ts_t *)DHD_OS_PREALLOC(dhdp, DHD_PREALLOC_PROT, sizeof(dhd_ts_t)))) { DHD_ERROR(("%s: kmalloc failed\n", __FUNCTION__)); goto fail; } memset(ts, 0, sizeof(*ts)); g_dhd_ts = ts; ts->osh = dhdp->osh; dhdp->ts = ts; ts->dhdp = dhdp; ts->correction_m.low = 1; ts->correction_m.high = 1; ts->correction_b.low = 0; ts->correction_m.high = 0; ts->fwts2hsts_delay = DHD_DEFAULT_TIMESYNC_TIMER_VALUE; ts->fwts2hsts_delay_wdcount = 0; ts->tx_timestamps.max_idx = MAX_TS_LOG_SAMPLES_DATA; ts->rx_timestamps.max_idx = MAX_TS_LOG_SAMPLES_DATA; ts->xt_id = 1; DHD_INFO(("allocated DHD TS\n")); #ifdef CONFIG_PROC_FS if (proc_create("ts_fw_clk_dump", S_IRUSR, NULL, &proc_fops_ts_fw_clk_dump) == NULL) { DHD_ERROR(("Failed to create /proc/ts_fw_clk_dump procfs interface\n")); } if (proc_create("ts_tx_dump", S_IRUSR, NULL, &proc_fops_ts_tx_dump) == NULL) { DHD_ERROR(("Failed to create /proc/ts_tx_dump procfs interface\n")); } if (proc_create("ts_rx_dump", S_IRUSR, NULL, &proc_fops_ts_rx_dump) == NULL) { DHD_ERROR(("Failed to create /proc/ts_rx_dump procfs interface\n")); } #endif /* CONFIG_PROC_FS */ return BCME_OK; fail: if (dhdp->ts != NULL) { dhd_timesync_detach(dhdp); } return BCME_NOMEM; } static void dhd_timesync_ts_log_dump_item(dhd_ts_log_ts_t *tsl, struct bcmstrbuf *b) { uint32 i = 0; bcm_bprintf(b, "Max_idx: %d, cur_idx %d\n", tsl->max_idx, tsl->cur_idx); for (i = 0; i < tsl->max_idx; i++) { bcm_bprintf(b, "\t idx: %03d, (%d: %d) timestamp: 0x%08x:0x%08x " " proto: %02d, t1: 0x%08x t2: 0x%08x\n", i, tsl->ts_log[i].intf, tsl->ts_log[i].flowid, tsl->ts_log[i].ts_high, tsl->ts_log[i].ts_low, tsl->ts_log[i].proto, tsl->ts_log[i].t1, tsl->ts_log[i].t2); } } static int dhd_timesync_ts_log_dump(dhd_ts_t *ts, char *buf, int buflen, bool tx) { struct bcmstrbuf b; struct bcmstrbuf *strbuf = &b; bcm_binit(strbuf, buf, buflen); if (tx) { bcm_bprintf(strbuf, "Tx Log dump\t"); dhd_timesync_ts_log_dump_item(&ts->tx_timestamps, strbuf); } else { bcm_bprintf(strbuf, "Rx Log dump\n"); dhd_timesync_ts_log_dump_item(&ts->rx_timestamps, strbuf); } return BCME_OK; } static void dhd_timesync_clear_logs(dhd_ts_t *ts) { dhd_ts_log_ts_t *tsl; tsl = &ts->rx_timestamps; tsl->cur_idx = 0; memset(tsl->ts_log, 0, sizeof(dhd_ts_log_ts_item_t) * MAX_TS_LOG_SAMPLES_DATA); tsl = &ts->tx_timestamps; tsl->cur_idx = 0; memset(tsl->ts_log, 0, sizeof(dhd_ts_log_ts_item_t) * MAX_TS_LOG_SAMPLES_DATA); return; } void dhd_timesync_debug_info_print(dhd_pub_t *dhdp) { dhd_ts_t *ts = dhdp->ts; uint64 current_time; if (!ts) { DHD_ERROR(("%s: %d ts is NULL\n", __FUNCTION__, __LINE__)); return; } DHD_ERROR(("\nts info dump: active_ipc_version %d\n", ts->active_ipc_version)); current_time = OSL_LOCALTIME_NS(); DHD_ERROR(("current_time="SEC_USEC_FMT" last_ts_watchdog_time="SEC_USEC_FMT"\n", GET_SEC_USEC(current_time), GET_SEC_USEC(ts->last_ts_watchdog_time))); DHD_ERROR(("timesync disabled %d\n", ts->timesync_disabled)); DHD_ERROR(("Host TS dump cnt %d, fw TS dump cnt %d, descrepency %d\n", ts->host_ts_capture_cnt, ts->fw_ts_capture_cnt, ts->fw_ts_disc_cnt)); DHD_ERROR(("ts_watchdog calls %d reset cnt %d\n", ts->ts_watchdog_calls, ts->host_reset_cnt)); DHD_ERROR(("xt_ids tag/ID %d/%d, %d/%d, %d/%d, %d/%d\n", BCMMSGBUF_HOST_TIMESTAMPING_CONFIG_TAG, ts->xt_ids.host_timestamping_config, BCMMSGBUF_HOST_CLOCK_SELECT_TAG, ts->xt_ids.host_clock_selection, BCMMSGBUF_HOST_CLOCK_INFO_TAG, ts->xt_ids.host_clk_info, BCMMSGBUF_D2H_CLOCK_CORRECTION_TAG, ts->xt_ids.d2h_clk_correction)); DHD_ERROR(("pending requests %d suspend req %d resume req %d\n", ts->pending_requests, ts->suspend_req, ts->resume_req)); } static int dhd_timesync_dump(dhd_ts_t *ts, char *buf, int buflen) { struct bcmstrbuf b; struct bcmstrbuf *strbuf = &b; bcm_binit(strbuf, buf, buflen); bcm_bprintf(strbuf, "ts info dump: active_ipc_version %d\n", ts->active_ipc_version); bcm_bprintf(strbuf, "timesync disabled %d\n", ts->timesync_disabled); bcm_bprintf(strbuf, "Host TS dump cnt %d, fw TS dump cnt %d, descrepency %d\n", ts->host_ts_capture_cnt, ts->fw_ts_capture_cnt, ts->fw_ts_disc_cnt); bcm_bprintf(strbuf, "ts_watchdog calls %d reset cnt %d\n", ts->ts_watchdog_calls, ts->host_reset_cnt); bcm_bprintf(strbuf, "xt_ids tag/ID %d/%d, %d/%d, %d/%d, %d/%d\n", BCMMSGBUF_HOST_TIMESTAMPING_CONFIG_TAG, ts->xt_ids.host_timestamping_config, BCMMSGBUF_HOST_CLOCK_SELECT_TAG, ts->xt_ids.host_clock_selection, BCMMSGBUF_HOST_CLOCK_INFO_TAG, ts->xt_ids.host_clk_info, BCMMSGBUF_D2H_CLOCK_CORRECTION_TAG, ts->xt_ids.d2h_clk_correction); bcm_bprintf(strbuf, "pending requests %d suspend req %d resume req %d\n", ts->pending_requests, ts->suspend_req, ts->resume_req); return BCME_OK; } static int dhd_timesync_doiovar(dhd_ts_t *ts, const bcm_iovar_t *vi, uint32 actionid, const char *name, void *params, uint plen, void *arg, uint len, int val_size) { int bcmerror = 0; int32 int_val = 0; DHD_TRACE(("%s: Enter\n", __FUNCTION__)); DHD_TRACE(("%s: actionid = %d; name %s\n", __FUNCTION__, actionid, name)); if ((bcmerror = bcm_iovar_lencheck(vi, arg, len, IOV_ISSET(actionid))) != 0) goto exit; if (plen >= sizeof(int_val)) bcopy(params, &int_val, sizeof(int_val)); switch (actionid) { case IOV_GVAL(IOV_TS_INFO_DUMP): dhd_timesync_dump(ts, arg, len); break; case IOV_GVAL(IOV_TS_TX_TS_DUMP): dhd_timesync_ts_log_dump(ts, arg, len, TRUE); break; case IOV_GVAL(IOV_TS_RX_TS_DUMP): dhd_timesync_ts_log_dump(ts, arg, len, FALSE); break; case IOV_GVAL(IOV_TS_FW_CLKINFO_DUMP): dhd_ts_fw_clksrc_dump(ts, arg, len); break; case IOV_SVAL(IOV_TS_SEND_TSCONFIG): if (ts->active_ipc_version < 7) { bcmerror = BCME_ERROR; break; } bcmerror = dhd_timesync_send_host_timestamping_config(ts, FALSE); break; case IOV_SVAL(IOV_TS_SEND_HCLK_SEL): if (ts->active_ipc_version < 7) { bcmerror = BCME_ERROR; break; } bcmerror = dhd_timesync_send_host_clock_selection(ts); break; case IOV_SVAL(IOV_TS_SEND_HCLK_INFO): if (ts->active_ipc_version < 7) { bcmerror = BCME_ERROR; break; } bcmerror = dhd_timesync_send_host_clk_info(ts); break; case IOV_SVAL(IOV_TS_SEND_D2H_CRCT): if (ts->active_ipc_version < 7) { bcmerror = BCME_ERROR; break; } bcmerror = dhd_timesync_send_D2H_clk_correction(ts); break; case IOV_SVAL(IOV_TS_INJECT_BAD_TAG): if (ts->active_ipc_version < 7) { bcmerror = BCME_ERROR; break; } bcmerror = dhd_timesync_send_host_timestamping_config(ts, TRUE); break; case IOV_SVAL(IOV_TS_INJECT_BAD_XTID): { uint16 old_xt_id; if (ts->active_ipc_version < 7) { bcmerror = BCME_ERROR; break; } old_xt_id = ts->xt_id; ts->xt_id += 10; /* will cause the error now */ DHD_ERROR(("generating bad XTID transaction for the device exp %d, sending %d", old_xt_id, ts->xt_id)); bcmerror = dhd_timesync_send_host_timestamping_config(ts, FALSE); ts->xt_id = old_xt_id; break; } case IOV_GVAL(IOV_TS_FWTS2HSTS_DELAY): bcopy(&ts->fwts2hsts_delay, arg, val_size); break; case IOV_SVAL(IOV_TS_FWTS2HSTS_DELAY): if (ts->active_ipc_version < 7) { bcmerror = BCME_ERROR; break; } if (int_val > DHD_DEFAULT_TIMESYNC_TIMER_VALUE_MAX) { bcmerror = BCME_RANGE; break; } if (int_val <= DHD_DEFAULT_TIMESYNC_TIMER_VALUE) { bcmerror = BCME_RANGE; break; } ts->fwts2hsts_delay = int_val; break; case IOV_GVAL(IOV_TS_NODROP_CONFIG): bcopy(&ts->nodrop_config, arg, val_size); break; case IOV_SVAL(IOV_TS_NODROP_CONFIG): ts->nodrop_config = int_val; break; case IOV_GVAL(IOV_TS_NO_RETRY): int_val = dhd_prot_pkt_noretry(ts->dhdp, 0, FALSE); bcopy(&int_val, arg, val_size); break; case IOV_SVAL(IOV_TS_NO_RETRY): dhd_prot_pkt_noretry(ts->dhdp, int_val, TRUE); break; case IOV_GVAL(IOV_TS_NO_AGGR): int_val = dhd_prot_pkt_noaggr(ts->dhdp, 0, FALSE); bcopy(&int_val, arg, val_size); break; case IOV_SVAL(IOV_TS_NO_AGGR): dhd_prot_pkt_noaggr(ts->dhdp, int_val, TRUE); break; case IOV_GVAL(IOV_TS_FIXED_RATE): int_val = dhd_prot_pkt_fixed_rate(ts->dhdp, 0, FALSE); bcopy(&int_val, arg, val_size); break; case IOV_SVAL(IOV_TS_FIXED_RATE): dhd_prot_pkt_fixed_rate(ts->dhdp, int_val, TRUE); break; case IOV_SVAL(IOV_TS_CLEAR_LOGS): dhd_timesync_clear_logs(ts); break; case IOV_GVAL(IOV_TS_TXS_LOG): int_val = dhd_prot_data_path_tx_timestamp_logging(ts->dhdp, 0, FALSE); bcopy(&int_val, arg, val_size); break; case IOV_SVAL(IOV_TS_TXS_LOG): dhd_prot_data_path_tx_timestamp_logging(ts->dhdp, int_val, TRUE); break; case IOV_GVAL(IOV_TS_RXS_LOG): int_val = dhd_prot_data_path_rx_timestamp_logging(ts->dhdp, 0, FALSE); bcopy(&int_val, arg, val_size); break; case IOV_SVAL(IOV_TS_RXS_LOG): dhd_prot_data_path_rx_timestamp_logging(ts->dhdp, int_val, TRUE); break; case IOV_SVAL(IOV_TS_HTSCONF_PERIOD): if (ts->active_ipc_version < 7) { bcmerror = BCME_ERROR; break; } ts->h_tsconf_period = int_val; break; case IOV_GVAL(IOV_TS_HTSCONF_PERIOD): if (ts->active_ipc_version < 7) { bcmerror = BCME_ERROR; break; } bcopy(&ts->h_tsconf_period, arg, val_size); break; case IOV_SVAL(IOV_TS_HCLK_CLKID_MAX): if (ts->active_ipc_version < 7) { bcmerror = BCME_ERROR; break; } ts->h_clkid_max = int_val; break; case IOV_GVAL(IOV_TS_HCLK_CLKID_MAX): if (ts->active_ipc_version < 7) { bcmerror = BCME_ERROR; break; } bcopy(&ts->h_clkid_max, arg, val_size); break; case IOV_SVAL(IOV_TS_HCLK_CLKID_MIN): if (ts->active_ipc_version < 7) { bcmerror = BCME_ERROR; break; } ts->h_clkid_min = int_val; break; case IOV_GVAL(IOV_TS_HCLK_CLKID_MIN): if (ts->active_ipc_version < 7) { bcmerror = BCME_ERROR; break; } bcopy(&ts->h_clkid_min, arg, val_size); break; default: bcmerror = BCME_UNSUPPORTED; break; } exit: DHD_TRACE(("%s: actionid %d, bcmerror %d\n", __FUNCTION__, actionid, bcmerror)); return bcmerror; } int dhd_timesync_iovar_op(dhd_ts_t *ts, const char *name, void *params, int plen, void *arg, int len, bool set) { int bcmerror = 0; int val_size; const bcm_iovar_t *vi = NULL; uint32 actionid; DHD_TRACE(("%s: Enter\n", __FUNCTION__)); ASSERT(name); ASSERT(len >= 0); /* Get MUST have return space */ ASSERT(set || (arg && len)); /* Set does NOT take qualifiers */ ASSERT(!set || (!params && !plen)); if ((vi = bcm_iovar_lookup(dhd_ts_iovars, name)) == NULL) { DHD_TRACE(("%s: not ours\n", name)); bcmerror = BCME_UNSUPPORTED; goto exit; } DHD_CTL(("%s: %s %s, len %d plen %d\n", __FUNCTION__, name, (set ? "set" : "get"), len, plen)); /* set up 'params' pointer in case this is a set command so that * the convenience int and bool code can be common to set and get */ if (params == NULL) { params = arg; plen = len; } if (vi->type == IOVT_VOID) val_size = 0; else if (vi->type == IOVT_BUFFER) val_size = len; else /* all other types are integer sized */ val_size = sizeof(int); actionid = set ? IOV_SVAL(vi->varid) : IOV_GVAL(vi->varid); bcmerror = dhd_timesync_doiovar(ts, vi, actionid, name, params, plen, arg, len, val_size); exit: return bcmerror; } void dhd_timesync_handle_host_ts_complete(dhd_ts_t *ts, uint16 xt_id, uint16 status) { if (ts == NULL) { DHD_ERROR(("%s: called with ts null\n", __FUNCTION__)); return; } DHD_INFO(("Host send TS complete, for ID %d, status %d\n", xt_id, status)); if (xt_id == ts->xt_ids.host_clk_info) { if (ts->host_ts_host_clk_info_buffer_in_use != TRUE) { DHD_ERROR(("same ID as the host clock info, but buffer not in use: %d\n", ts->xt_ids.host_clk_info)); return; } ts->host_ts_host_clk_info_buffer_in_use = FALSE; } ts->pending_requests--; } void dhd_timesync_notify_ipc_rev(dhd_ts_t *ts, uint32 ipc_rev) { if (ts != NULL) ts->active_ipc_version = ipc_rev; } static int dhd_ts_fw_clksrc_dump(dhd_ts_t *ts, char *buf, int buflen) { struct bcmstrbuf b; struct bcmstrbuf *strbuf = &b; clk_ts_log_t *fw_ts_log; uint32 i = 0, j = 0; clksrc_ts_log_t *clk_src; fw_ts_log = &ts->fw_ts_log; bcm_binit(strbuf, buf, buflen); while (i <= MAX_CLKSRC_ID) { clk_src = &fw_ts_log->ts_log[i]; if (clk_src->inuse == FALSE) { bcm_bprintf(strbuf, "clkID %d: not in use\n", i); } else { bcm_bprintf(strbuf, "clkID %d: name %s Max idx %d, cur_idx %d\n", i, clk_src->name, MAX_FW_TS_LOG_SAMPLES, fw_ts_log->cur_idx); j = 0; while (j < MAX_FW_TS_LOG_SAMPLES) { bcm_bprintf(strbuf, "%03d: %03d: 0x%08x-0x%08x\n", j, fw_ts_log->seqnum[j], clk_src->log[j].ts_high, clk_src->log[j].ts_low); j++; } } i++; } return b.size; } static void dhd_ts_fw_clksrc_log(dhd_ts_t *ts, uchar *tlvs, uint32 tlv_len, uint32 seqnum) { ts_fw_clock_info_t *fw_clock_info; clksrc_ts_log_t *clk_src; clk_ts_log_t *fw_ts_log; fw_ts_log = &ts->fw_ts_log; fw_ts_log->seqnum[fw_ts_log->cur_idx] = seqnum; while (tlv_len) { fw_clock_info = (ts_fw_clock_info_t *)tlvs; clk_src = &fw_ts_log->ts_log[(fw_clock_info->ts.ts_high >> 28) & 0xF]; if (clk_src->inuse == FALSE) { bcopy(fw_clock_info->clk_src, clk_src->name, sizeof(clk_src->name)); clk_src->inuse = TRUE; } clk_src->log[fw_ts_log->cur_idx].ts_low = fw_clock_info->ts.ts_low; clk_src->log[fw_ts_log->cur_idx].ts_high = fw_clock_info->ts.ts_high; tlvs += sizeof(*fw_clock_info); tlv_len -= sizeof(*fw_clock_info); } fw_ts_log->cur_idx++; if (fw_ts_log->cur_idx >= MAX_FW_TS_LOG_SAMPLES) fw_ts_log->cur_idx = 0; } void dhd_timesync_handle_fw_timestamp(dhd_ts_t *ts, uchar *tlvs, uint32 tlv_len, uint32 seqnum) { ts_fw_clock_info_t *fw_clock_info; uint16 tag_id; DHD_INFO(("FW sent timestamp message, tlv_len %d, seqnum %d\n", tlv_len, seqnum)); bcm_print_bytes("fw ts", tlvs, tlv_len); /* we are expecting only one TLV type from the firmware side */ /* BCMMSGBUF_FW_CLOCK_INFO_TAG */ /* Validate the tag ID */ if (ts == NULL) { DHD_ERROR(("%s: NULL TS \n", __FUNCTION__)); return; } if (tlvs == NULL) { DHD_ERROR(("%s: NULL TLV \n", __FUNCTION__)); return; } if (tlv_len < BCM_XTLV_HDR_SIZE) { DHD_ERROR(("%s: bad length %d\n", __FUNCTION__, tlv_len)); return; } if (tlv_len > MAX_SIZE_FW_CLKINFO_TYPE) { DHD_ERROR(("tlv_len %d more than what is supported in Host %d\n", tlv_len, (uint32)MAX_SIZE_FW_CLKINFO_TYPE)); return; } if (tlv_len % (sizeof(*fw_clock_info))) { DHD_ERROR(("bad tlv_len for the packet %d, needs to be multiple of %d\n", tlv_len, (uint32)(sizeof(*fw_clock_info)))); return; } /* validate the tag for all the include tag IDs */ { uint32 check_len = 0; uchar *tag_ptr = (uchar *)(tlvs); while (check_len < tlv_len) { bcopy(tag_ptr+check_len, &tag_id, sizeof(uint16)); DHD_INFO(("FWTS: tag_id %d, offset %d \n", tag_id, check_len)); if (tag_id != BCMMSGBUF_FW_CLOCK_INFO_TAG) { DHD_ERROR(("Fatal: invalid tag from FW in TS: %d, offset %d \n", tag_id, check_len)); return; } check_len += sizeof(*fw_clock_info); } } if (seqnum != (ts->fw_ts_capture_cnt + 1)) { DHD_ERROR(("FW TS descrepency: out of sequence exp %d, got %d, resyncing %d\n", ts->fw_ts_capture_cnt + 1, seqnum, seqnum)); ts->fw_ts_disc_cnt++; } ts->fw_ts_capture_cnt = seqnum; /* copy it into local info */ bcopy(tlvs, &ts->fw_tlv[0], tlv_len); ts->fw_tlv_len = tlv_len; dhd_ts_fw_clksrc_log(ts, tlvs, tlv_len, seqnum); /* launch the watchdog to send the host time stamp as per the delay programmed */ if (ts->fwts2hsts_delay_wdcount != 0) { DHD_ERROR(("FATAL: Last Host sync is not sent out yet\n")); return; } if (dhd_watchdog_ms == 0) { DHD_ERROR(("FATAL: WATCHDOG is set to 0, timesync can't work properly \n")); return; } /* schedule sending host time sync values to device */ ts->fwts2hsts_delay_wdcount = ts->fwts2hsts_delay / dhd_watchdog_ms; if (ts->fwts2hsts_delay_wdcount == 0) ts->fwts2hsts_delay_wdcount = 1; } static uint32 dhd_timesync_send_host_timestamping_config(dhd_ts_t *ts, bool inject_err) { ts_host_timestamping_config_t ts_config; int ret_val; if (ts->timesync_disabled) { DHD_ERROR(("Timesync Disabled: Cannot send HOST TS config msg\n")); return BCME_ERROR; } bzero(&ts_config, sizeof(ts_config)); ts_config.xtlv.id = BCMMSGBUF_HOST_TIMESTAMPING_CONFIG_TAG; if (inject_err) ts_config.xtlv.id = BCMMSGBUF_HOST_TS_BADTAG; ts_config.xtlv.len = sizeof(ts_config) - sizeof(_bcm_xtlv_t); ts_config.period_ms = ts->h_tsconf_period; if (ts_config.period_ms) { ts_config.flags |= FLAG_HOST_RESET; ts_config.reset_cnt = ts->host_reset_cnt + 1; } if (ts->nodrop_config) { ts_config.flags |= FLAG_CONFIG_NODROP; ts_config.post_delay = TS_NODROP_CONFIG_TO; } else { ts_config.post_delay = TS_DROP_CONFIG_TO; } DHD_ERROR(("sending Host Timestamping Config: TLV (ID %d, LEN %d), period %d, seq %d\n", ts_config.xtlv.id, ts_config.xtlv.len, ts_config.period_ms, ts->host_ts_capture_cnt)); ret_val = dhd_prot_send_host_timestamp(ts->dhdp, (uchar *)&ts_config, sizeof(ts_config), ts->host_ts_capture_cnt, ts->xt_id); if (ret_val != 0) { DHD_ERROR(("Fatal: Error sending HOST TS config msg to device: %d\n", ret_val)); return BCME_ERROR; } if (ts_config.period_ms) { ts->host_reset_cnt++; } ts->pending_requests++; ts->xt_ids.host_timestamping_config = ts->xt_id; ts->xt_id++; return BCME_OK; } static uint32 dhd_timesync_send_host_clock_selection(dhd_ts_t *ts) { ts_host_clock_sel_t ts_clk_sel; int ret_val; if (ts->timesync_disabled) { DHD_ERROR(("Timesync Disabled: Cannot send HOST clock sel msg\n")); return BCME_ERROR; } bzero(&ts_clk_sel, sizeof(ts_clk_sel)); ts_clk_sel.xtlv.id = BCMMSGBUF_HOST_CLOCK_SELECT_TAG; ts_clk_sel.xtlv.len = sizeof(ts_clk_sel) - sizeof(_bcm_xtlv_t); ts_clk_sel.min_clk_idx = ts->h_clkid_min; ts_clk_sel.max_clk_idx = ts->h_clkid_max; DHD_INFO(("sending Host ClockSel Config: TLV (ID %d, LEN %d), min %d, max %d, seq %d\n", ts_clk_sel.xtlv.id, ts_clk_sel.xtlv.len, ts_clk_sel.min_clk_idx, ts_clk_sel.max_clk_idx, ts->host_ts_capture_cnt)); ret_val = dhd_prot_send_host_timestamp(ts->dhdp, (uchar *)&ts_clk_sel, sizeof(ts_clk_sel), ts->host_ts_capture_cnt, ts->xt_id); if (ret_val != 0) { DHD_ERROR(("Fatal: Error sending HOST ClockSel msg to device: %d\n", ret_val)); return BCME_ERROR; } ts->xt_ids.host_clock_selection = ts->xt_id; ts->xt_id++; ts->pending_requests++; return BCME_OK; } static uint32 dhd_timesync_send_host_clk_info(dhd_ts_t *ts) { ts_host_clock_info_t *host_clock_info; uchar *clk_info_buffer; uint32 clk_info_bufsize; int ret_val; if (ts->timesync_disabled) { DHD_ERROR(("Timesync Disabled: Cannot send HOST clock config msg\n")); return BCME_ERROR; } if (ts->host_ts_host_clk_info_buffer_in_use == TRUE) { DHD_ERROR(("Host Ts Clock info buffer in Use\n")); return BCME_ERROR; } clk_info_buffer = &ts->host_ts_host_clk_info_buffer[0]; clk_info_bufsize = sizeof(ts->host_ts_host_clk_info_buffer); DHD_INFO(("clk_info_buf size %d, tlv_len %d, host clk_info_len %d\n", clk_info_bufsize, ts->fw_tlv_len, (uint32)sizeof(*host_clock_info))); if (clk_info_bufsize < sizeof(*host_clock_info)) { DHD_ERROR(("clock_info_buf_size too small to fit host clock info %d, %d\n", clk_info_bufsize, (uint32)sizeof(*host_clock_info))); return BCME_ERROR; } host_clock_info = (ts_host_clock_info_t *)clk_info_buffer; host_clock_info->xtlv.id = BCMMSGBUF_HOST_CLOCK_INFO_TAG; host_clock_info->xtlv.len = sizeof(*host_clock_info) - sizeof(_bcm_xtlv_t); /* OSL_GET_CYCLES */ host_clock_info->ticks.low = 0; host_clock_info->ticks.high = 0; /* OSL_SYS_UPTIME?? */ host_clock_info->ns.low = 0; host_clock_info->ns.high = 0; clk_info_buffer += (sizeof(*host_clock_info)); clk_info_bufsize -= sizeof(*host_clock_info); /* copy the device clk config as that is the reference for this */ if (clk_info_bufsize < ts->fw_tlv_len) { DHD_ERROR(("clock info buffer is small to fit dev clk info %d, %d\n", clk_info_bufsize, ts->fw_tlv_len)); return BCME_ERROR; } bcopy(ts->fw_tlv, clk_info_buffer, ts->fw_tlv_len); clk_info_bufsize -= ts->fw_tlv_len; DHD_INFO(("sending Host TS msg Len %d, xt_id %d, host_ts_capture_count %d\n", (uint32)(sizeof(ts->host_ts_host_clk_info_buffer) - clk_info_bufsize), ts->xt_id, ts->host_ts_capture_cnt)); bcm_print_bytes("host ts", (uchar *)ts->host_ts_host_clk_info_buffer, sizeof(ts->host_ts_host_clk_info_buffer) - clk_info_bufsize); ret_val = dhd_prot_send_host_timestamp(ts->dhdp, (uchar *)ts->host_ts_host_clk_info_buffer, sizeof(ts->host_ts_host_clk_info_buffer) - clk_info_bufsize, ts->host_ts_capture_cnt, ts->xt_id); if (ret_val != 0) { DHD_ERROR(("Fatal: Error sending HOST ClockSel msg to device: %d\n", ret_val)); return BCME_ERROR; } ts->host_ts_host_clk_info_buffer_in_use = TRUE; ts->xt_ids.host_clk_info = ts->xt_id; ts->xt_id++; ts->pending_requests++; return BCME_OK; } static uint32 dhd_timesync_send_D2H_clk_correction(dhd_ts_t *ts) { ts_d2h_clock_correction_t ts_clk_crtion; int ret_val; if (ts->timesync_disabled) { DHD_ERROR(("Timesync Disabled: Cannot send d2h clock correction msg\n")); return BCME_ERROR; } bzero(&ts_clk_crtion, sizeof(ts_clk_crtion)); /* XXX: should this be sending for all the clock sources */ ts_clk_crtion.xtlv.id = BCMMSGBUF_D2H_CLOCK_CORRECTION_TAG; ts_clk_crtion.xtlv.len = sizeof(ts_clk_crtion) - sizeof(_bcm_xtlv_t); ts_clk_crtion.clk_id = ts->h_clkid_max; ts_clk_crtion.m.low = ts->correction_m.low; ts_clk_crtion.m.high = ts->correction_m.high; ts_clk_crtion.b.low = ts->correction_b.low; ts_clk_crtion.b.high = ts->correction_b.high; DHD_INFO(("sending D2H Correction: ID %d, LEN %d, clkid %d, m %d:%d, b %d:%d, seq %d\n", ts_clk_crtion.xtlv.id, ts_clk_crtion.xtlv.len, ts_clk_crtion.clk_id, ts_clk_crtion.m.high, ts_clk_crtion.m.low, ts_clk_crtion.b.high, ts_clk_crtion.b.low, ts->host_ts_capture_cnt)); ret_val = dhd_prot_send_host_timestamp(ts->dhdp, (uchar *)&ts_clk_crtion, sizeof(ts_clk_crtion), ts->host_ts_capture_cnt, ts->xt_id); if (ret_val != 0) { DHD_ERROR(("Fatal: Error sending HOST ClockSel msg to device: %d\n", ret_val)); return BCME_ERROR; } ts->xt_ids.d2h_clk_correction = ts->xt_id; ts->xt_id++; ts->pending_requests++; return BCME_OK; } bool dhd_timesync_delay_post_bufs(dhd_pub_t *dhdp) { return (dhdp->ts->fwts2hsts_delay != 0); } bool dhd_timesync_watchdog(dhd_pub_t *dhdp) { dhd_ts_t *ts = dhdp->ts; if (ts == NULL) return FALSE; ts->last_ts_watchdog_time = OSL_LOCALTIME_NS(); ts->ts_watchdog_calls++; /* XXX: this is relying the watchdog to be running..which may not hold good */ if (ts->fwts2hsts_delay_wdcount) { ts->fwts2hsts_delay_wdcount--; if (ts->fwts2hsts_delay != 0 && dhdp->busstate == DHD_BUS_DATA && (ts->fwts2hsts_delay_wdcount == 0)) { /* see if we need to send the host clock info */ dhd_timesync_send_host_clk_info(ts); dhd_msgbuf_delay_post_ts_bufs(dhdp); } } return FALSE; } static void dhd_timesync_log_timestamp_item(dhd_ts_log_ts_t *tsl, uint16 flowid, uint8 intf, uint32 ts_low, uint32 ts_high, dhd_pkt_parse_t *pkt) { tsl->ts_log[tsl->cur_idx].ts_low = ts_low; tsl->ts_log[tsl->cur_idx].ts_high = ts_high; tsl->ts_log[tsl->cur_idx].intf = intf; tsl->ts_log[tsl->cur_idx].proto = pkt->proto; tsl->ts_log[tsl->cur_idx].t1 = pkt->t1; tsl->ts_log[tsl->cur_idx].t2 = pkt->t2; tsl->cur_idx++; if (tsl->cur_idx == tsl->max_idx) tsl->cur_idx = 0; } void dhd_timesync_log_tx_timestamp(dhd_ts_t *ts, uint16 flowid, uint8 intf, uint32 ts_low, uint32 ts_high, dhd_pkt_parse_t *pkt) { if (ts != NULL) { dhd_timesync_log_timestamp_item(&ts->tx_timestamps, flowid, intf, ts_low, ts_high, pkt); } } void dhd_timesync_log_rx_timestamp(dhd_ts_t *ts, uint8 intf, uint32 ts_low, uint32 ts_high, dhd_pkt_parse_t *pkt) { if (ts != NULL) { dhd_timesync_log_timestamp_item(&ts->rx_timestamps, 0, intf, ts_low, ts_high, pkt); } } void dhd_timesync_control(dhd_pub_t *dhdp, bool disabled) { dhd_ts_t *ts; if (dhdp == NULL) return; ts = dhdp->ts; if (ts != NULL) { if (disabled) { DHD_ERROR(("resetting the timesync counter, current(%d)\n", ts->fw_ts_capture_cnt)); ts->fw_ts_capture_cnt = 0; /* Suspend case: Disable timesync after the config message */ if ((ts->active_ipc_version >= 7) && (ts->h_tsconf_period != 0)) { uint32 tsconf_period; tsconf_period = ts->h_tsconf_period; ts->h_tsconf_period = 0; dhd_timesync_send_host_timestamping_config(ts, FALSE); ts->h_tsconf_period = tsconf_period; } ts->timesync_disabled = TRUE; ts->suspend_req++; } else { /* Resume case: Enable timesync before the config message */ DHD_ERROR(("enabling the timesync counter, current(%d)\n", ts->fw_ts_capture_cnt)); ts->timesync_disabled = FALSE; ts->resume_req++; if ((ts->active_ipc_version >= 7) && (ts->h_tsconf_period != 0)) dhd_timesync_send_host_timestamping_config(ts, FALSE); } } /* XXX: may be all the other internal iovar calls should check for disabled state */ }