// SPDX-License-Identifier: ISC /* * Copyright (c) 2005-2011 Atheros Communications Inc. * Copyright (c) 2011-2017 Qualcomm Atheros, Inc. * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
ath10k_dbg(ar, ATH10K_DBG_HTC, "%s: ep %d skb %p\n", __func__,
ep->eid, skb);
/* A corner case where the copy completion is reaching to host but still * copy engine is processing it due to which host unmaps corresponding * memory and causes SMMU fault, hence as workaround adding delay * the unmapping memory to avoid SMMU faults.
*/ if (ar->hw_params.delay_unmap_buffer &&
ep->ul_pipe_id == 3)
mdelay(2);
/* Invalid lookahead flags are actually transmitted by * the target in the HTC control message. * Since this will happen at every boot we silently ignore * the lookahead in this case
*/ if (report->pre_valid != ((~report->post_valid) & 0xFF)) return 0;
if (next_lookaheads && next_lookaheads_len) {
ath10k_dbg(ar, ATH10K_DBG_HTC, "htc rx lookahead found pre_valid 0x%x post_valid 0x%x\n",
report->pre_valid, report->post_valid);
/* look ahead bytes are valid, copy them over */
memcpy((u8 *)next_lookaheads, report->lookahead, 4);
*next_lookaheads_len = 1;
}
return 0;
}
staticint
ath10k_htc_process_lookahead_bundle(struct ath10k_htc *htc, conststruct ath10k_htc_lookahead_bundle *report, int len, enum ath10k_htc_ep_id eid, void *next_lookaheads, int *next_lookaheads_len)
{ struct ath10k *ar = htc->ar; int bundle_cnt = len / sizeof(*report);
if (next_lookaheads && next_lookaheads_len) { int i;
for (i = 0; i < bundle_cnt; i++) {
memcpy(((u8 *)next_lookaheads) + 4 * i,
report->lookahead, 4);
report++;
}
*next_lookaheads_len = bundle_cnt;
}
return 0;
}
int ath10k_htc_process_trailer(struct ath10k_htc *htc,
u8 *buffer, int length, enum ath10k_htc_ep_id src_eid, void *next_lookaheads, int *next_lookaheads_len)
{ struct ath10k_htc_lookahead_bundle *bundle; struct ath10k *ar = htc->ar; int status = 0; struct ath10k_htc_record *record;
u8 *orig_buffer; int orig_length;
size_t len;
orig_buffer = buffer;
orig_length = length;
while (length > 0) {
record = (struct ath10k_htc_record *)buffer;
if (length < sizeof(record->hdr)) {
status = -EINVAL; break;
}
if (record->hdr.len > length) { /* no room left in buffer for record */
ath10k_warn(ar, "Invalid record length: %d\n",
record->hdr.len);
status = -EINVAL; break;
}
switch (record->hdr.id) { case ATH10K_HTC_RECORD_CREDITS:
len = sizeof(struct ath10k_htc_credit_report); if (record->hdr.len < len) {
ath10k_warn(ar, "Credit report too long\n");
status = -EINVAL; break;
}
ath10k_htc_process_credit_report(htc,
record->credit_report,
record->hdr.len,
src_eid); break; case ATH10K_HTC_RECORD_LOOKAHEAD:
len = sizeof(struct ath10k_htc_lookahead_report); if (record->hdr.len < len) {
ath10k_warn(ar, "Lookahead report too long\n");
status = -EINVAL; break;
}
status = ath10k_htc_process_lookahead(htc,
record->lookahead_report,
record->hdr.len,
src_eid,
next_lookaheads,
next_lookaheads_len); break; case ATH10K_HTC_RECORD_LOOKAHEAD_BUNDLE:
bundle = record->lookahead_bundle;
status = ath10k_htc_process_lookahead_bundle(htc,
bundle,
record->hdr.len,
src_eid,
next_lookaheads,
next_lookaheads_len); break; default:
ath10k_warn(ar, "Unhandled record: id:%d length:%d\n",
record->hdr.id, record->hdr.len); break;
}
if (status) break;
/* multiple records may be present in a trailer */
buffer += sizeof(record->hdr) + record->hdr.len;
length -= sizeof(record->hdr) + record->hdr.len;
}
if (status)
ath10k_dbg_dump(ar, ATH10K_DBG_HTC, "htc rx bad trailer", "",
orig_buffer, orig_length);
switch (__le16_to_cpu(msg->hdr.message_id)) { case ATH10K_HTC_MSG_READY_ID: case ATH10K_HTC_MSG_CONNECT_SERVICE_RESP_ID: /* handle HTC control message */ if (completion_done(&htc->ctl_resp)) { /* this is a fatal error, target should not be * sending unsolicited messages on the ep 0
*/
ath10k_warn(ar, "HTC rx ctrl still processing\n");
complete(&htc->ctl_resp); goto out;
}
staticconstchar *htc_service_name(enum ath10k_htc_svc_id id)
{ switch (id) { case ATH10K_HTC_SVC_ID_RESERVED: return"Reserved"; case ATH10K_HTC_SVC_ID_RSVD_CTRL: return"Control"; case ATH10K_HTC_SVC_ID_WMI_CONTROL: return"WMI"; case ATH10K_HTC_SVC_ID_WMI_DATA_BE: return"DATA BE"; case ATH10K_HTC_SVC_ID_WMI_DATA_BK: return"DATA BK"; case ATH10K_HTC_SVC_ID_WMI_DATA_VI: return"DATA VI"; case ATH10K_HTC_SVC_ID_WMI_DATA_VO: return"DATA VO"; case ATH10K_HTC_SVC_ID_NMI_CONTROL: return"NMI Control"; case ATH10K_HTC_SVC_ID_NMI_DATA: return"NMI Data"; case ATH10K_HTC_SVC_ID_HTT_DATA_MSG: return"HTT Data"; case ATH10K_HTC_SVC_ID_HTT_DATA2_MSG: return"HTT Data"; case ATH10K_HTC_SVC_ID_HTT_DATA3_MSG: return"HTT Data"; case ATH10K_HTC_SVC_ID_TEST_RAW_STREAMS: return"RAW"; case ATH10K_HTC_SVC_ID_HTT_LOG_MSG: return"PKTLOG";
}
return"Unknown";
}
staticvoid ath10k_htc_reset_endpoint_states(struct ath10k_htc *htc)
{ struct ath10k_htc_ep *ep; int i;
for (i = ATH10K_HTC_EP_0; i < ATH10K_HTC_EP_COUNT; i++) {
ep = &htc->endpoint[i];
ep->service_id = ATH10K_HTC_SVC_ID_UNUSED;
ep->max_ep_message_len = 0;
ep->max_tx_queue_depth = 0;
ep->eid = i;
ep->htc = htc;
ep->tx_credit_flow_enabled = true;
}
}
/* The WMI control service is the only service with flow control. * Let it have all transmit credits.
*/ if (service_id == ATH10K_HTC_SVC_ID_WMI_CONTROL)
allocation = htc->total_transmit_credits;
int ath10k_htc_wait_target(struct ath10k_htc *htc)
{ struct ath10k *ar = htc->ar; int i, status = 0; unsignedlong time_left; struct ath10k_htc_msg *msg;
u16 message_id;
time_left = wait_for_completion_timeout(&htc->ctl_resp,
ATH10K_HTC_WAIT_TIMEOUT_HZ); if (!time_left) { /* Workaround: In some cases the PCI HIF doesn't * receive interrupt for the control response message * even if the buffer was completed. It is suspected * iomap writes unmasking PCI CE irqs aren't propagated * properly in KVM PCI-passthrough sometimes.
*/
ath10k_warn(ar, "failed to receive control response completion, polling..\n");
for (i = 0; i < CE_COUNT; i++)
ath10k_hif_send_complete_check(htc->ar, i, 1);
/* The only way to determine if the ready message is an extended * message is from the size.
*/ if (htc->control_resp_len >= sizeof(msg->hdr) + sizeof(msg->ready_ext)) {
htc->alt_data_credit_size =
__le16_to_cpu(msg->ready_ext.reserved) &
ATH10K_HTC_MSG_READY_EXT_ALT_DATA_MASK;
htc->max_msgs_per_htc_bundle =
min_t(u8, msg->ready_ext.max_msgs_per_htc_bundle,
HTC_HOST_MAX_MSG_PER_RX_BUNDLE);
ath10k_dbg(ar, ATH10K_DBG_HTC, "Extended ready message RX bundle size %d alt size %d\n",
htc->max_msgs_per_htc_bundle,
htc->alt_data_credit_size);
}
/* special case for HTC pseudo control service */ if (conn_req->service_id == ATH10K_HTC_SVC_ID_RSVD_CTRL) {
disable_credit_flow_ctrl = true;
assigned_eid = ATH10K_HTC_EP_0;
max_msg_size = ATH10K_HTC_MAX_CTRL_MSG_LEN;
memset(&resp_msg_dummy, 0, sizeof(resp_msg_dummy)); goto setup;
}
tx_alloc = ath10k_htc_get_credit_allocation(htc,
conn_req->service_id); if (!tx_alloc)
ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot htc service %s does not allocate target credits\n",
htc_service_name(conn_req->service_id));
skb = ath10k_htc_build_tx_ctrl_skb(htc->ar); if (!skb) {
ath10k_err(ar, "Failed to allocate HTC packet\n"); return -ENOMEM;
}
/* Only enable credit flow control for WMI ctrl service */ if (conn_req->service_id != ATH10K_HTC_SVC_ID_WMI_CONTROL) {
flags |= ATH10K_HTC_CONN_FLAGS_DISABLE_CREDIT_FLOW_CTRL;
disable_credit_flow_ctrl = true;
}
/* connect to control service */
conn_req.service_id = ATH10K_HTC_SVC_ID_HTT_LOG_MSG;
status = ath10k_htc_connect_service(&ar->htc, &conn_req, &conn_resp); if (status) {
ath10k_warn(ar, "failed to connect to PKTLOG service: %d\n",
status); return status;
}
if (ar->hif.bus == ATH10K_BUS_SDIO) { /* Extra setup params used by SDIO */
msg->setup_complete_ext.flags =
__cpu_to_le32(ATH10K_HTC_SETUP_COMPLETE_FLAGS_RX_BNDL_EN);
msg->setup_complete_ext.max_msgs_per_bundled_recv =
htc->max_msgs_per_htc_bundle;
}
ath10k_dbg(ar, ATH10K_DBG_HTC, "HTC is using TX credit flow control\n");
status = ath10k_htc_send(htc, ATH10K_HTC_EP_0, skb); if (status) {
kfree_skb(skb); return status;
}
if (ath10k_htc_pktlog_svc_supported(ar)) {
status = ath10k_htc_pktlog_connect(ar); if (status) {
ath10k_err(ar, "failed to connect to pktlog: %d\n", status); return status;
}
}
return 0;
}
/* registered target arrival callback from the HIF layer */ int ath10k_htc_init(struct ath10k *ar)
{ int status; struct ath10k_htc *htc = &ar->htc; struct ath10k_htc_svc_conn_req conn_req; struct ath10k_htc_svc_conn_resp conn_resp;
/* connect fake service */
status = ath10k_htc_connect_service(htc, &conn_req, &conn_resp); if (status) {
ath10k_err(ar, "could not connect to htc service (%d)\n",
status); return status;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.