diff options
Diffstat (limited to 'drivers/staging/wfx/hif_rx.c')
-rw-r--r-- | drivers/staging/wfx/hif_rx.c | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/drivers/staging/wfx/hif_rx.c b/drivers/staging/wfx/hif_rx.c new file mode 100644 index 000000000000..820de216be0c --- /dev/null +++ b/drivers/staging/wfx/hif_rx.c @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Implementation of chip-to-host event (aka indications) of WFxxx Split Mac + * (WSM) API. + * + * Copyright (c) 2017-2019, Silicon Laboratories, Inc. + * Copyright (c) 2010, ST-Ericsson + */ +#include <linux/skbuff.h> +#include <linux/etherdevice.h> + +#include "hif_rx.h" +#include "wfx.h" +#include "scan.h" +#include "bh.h" +#include "sta.h" +#include "data_rx.h" +#include "secure_link.h" +#include "hif_api_cmd.h" + +static int hif_generic_confirm(struct wfx_dev *wdev, struct hif_msg *hif, + void *buf) +{ + // All confirm messages start with status + int status = le32_to_cpu(*((__le32 *) buf)); + int cmd = hif->id; + int len = hif->len - 4; // drop header + + WARN(!mutex_is_locked(&wdev->hif_cmd.lock), "data locking error"); + + if (!wdev->hif_cmd.buf_send) { + dev_warn(wdev->dev, "unexpected confirmation: 0x%.2x\n", cmd); + return -EINVAL; + } + + if (cmd != wdev->hif_cmd.buf_send->id) { + dev_warn(wdev->dev, + "chip response mismatch request: 0x%.2x vs 0x%.2x\n", + cmd, wdev->hif_cmd.buf_send->id); + return -EINVAL; + } + + if (wdev->hif_cmd.buf_recv) { + if (wdev->hif_cmd.len_recv >= len) + memcpy(wdev->hif_cmd.buf_recv, buf, len); + else + status = -ENOMEM; + } + wdev->hif_cmd.ret = status; + + if (!wdev->hif_cmd.async) { + complete(&wdev->hif_cmd.done); + } else { + wdev->hif_cmd.buf_send = NULL; + mutex_unlock(&wdev->hif_cmd.lock); + if (cmd != HIF_REQ_ID_SL_EXCHANGE_PUB_KEYS) + mutex_unlock(&wdev->hif_cmd.key_renew_lock); + } + return status; +} + +static int hif_tx_confirm(struct wfx_dev *wdev, struct hif_msg *hif, void *buf) +{ + struct hif_cnf_tx *body = buf; + struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface); + + WARN_ON(!wvif); + if (!wvif) + return -EFAULT; + + wfx_tx_confirm_cb(wvif, body); + return 0; +} + +static int hif_multi_tx_confirm(struct wfx_dev *wdev, struct hif_msg *hif, + void *buf) +{ + struct hif_cnf_multi_transmit *body = buf; + struct hif_cnf_tx *buf_loc = (struct hif_cnf_tx *) &body->tx_conf_payload; + struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface); + int count = body->num_tx_confs; + int i; + + WARN(count <= 0, "corrupted message"); + WARN_ON(!wvif); + if (!wvif) + return -EFAULT; + + for (i = 0; i < count; ++i) { + wfx_tx_confirm_cb(wvif, buf_loc); + buf_loc++; + } + return 0; +} + +static int hif_startup_indication(struct wfx_dev *wdev, struct hif_msg *hif, + void *buf) +{ + struct hif_ind_startup *body = buf; + + if (body->status || body->firmware_type > 4) { + dev_err(wdev->dev, "received invalid startup indication"); + return -EINVAL; + } + memcpy(&wdev->hw_caps, body, sizeof(struct hif_ind_startup)); + le32_to_cpus(&wdev->hw_caps.status); + le16_to_cpus(&wdev->hw_caps.hardware_id); + le16_to_cpus(&wdev->hw_caps.num_inp_ch_bufs); + le16_to_cpus(&wdev->hw_caps.size_inp_ch_buf); + + complete(&wdev->firmware_ready); + return 0; +} + +static int hif_wakeup_indication(struct wfx_dev *wdev, struct hif_msg *hif, + void *buf) +{ + if (!wdev->pdata.gpio_wakeup + || !gpiod_get_value(wdev->pdata.gpio_wakeup)) { + dev_warn(wdev->dev, "unexpected wake-up indication\n"); + return -EIO; + } + return 0; +} + +static int hif_keys_indication(struct wfx_dev *wdev, struct hif_msg *hif, + void *buf) +{ + struct hif_ind_sl_exchange_pub_keys *body = buf; + + // Compatibility with legacy secure link + if (body->status == SL_PUB_KEY_EXCHANGE_STATUS_SUCCESS) + body->status = 0; + if (body->status) + dev_warn(wdev->dev, "secure link negociation error\n"); + wfx_sl_check_pubkey(wdev, body->ncp_pub_key, body->ncp_pub_key_mac); + return 0; +} + +static int hif_receive_indication(struct wfx_dev *wdev, struct hif_msg *hif, + void *buf, struct sk_buff *skb) +{ + struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface); + struct hif_ind_rx *body = buf; + + if (!wvif) { + dev_warn(wdev->dev, "ignore rx data for non-existent vif %d\n", + hif->interface); + return 0; + } + skb_pull(skb, sizeof(struct hif_msg) + sizeof(struct hif_ind_rx)); + wfx_rx_cb(wvif, body, skb); + + return 0; +} + +static int hif_event_indication(struct wfx_dev *wdev, struct hif_msg *hif, + void *buf) +{ + struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface); + struct hif_ind_event *body = buf; + struct wfx_hif_event *event; + int first; + + WARN_ON(!wvif); + if (!wvif) + return 0; + + event = kzalloc(sizeof(*event), GFP_KERNEL); + if (!event) + return -ENOMEM; + + memcpy(&event->evt, body, sizeof(struct hif_ind_event)); + spin_lock(&wvif->event_queue_lock); + first = list_empty(&wvif->event_queue); + list_add_tail(&event->link, &wvif->event_queue); + spin_unlock(&wvif->event_queue_lock); + + if (first) + schedule_work(&wvif->event_handler_work); + + return 0; +} + +static int hif_pm_mode_complete_indication(struct wfx_dev *wdev, + struct hif_msg *hif, void *buf) +{ + struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface); + + WARN_ON(!wvif); + complete(&wvif->set_pm_mode_complete); + + return 0; +} + +static int hif_scan_complete_indication(struct wfx_dev *wdev, + struct hif_msg *hif, void *buf) +{ + struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface); + struct hif_ind_scan_cmpl *body = buf; + + WARN_ON(!wvif); + wfx_scan_complete_cb(wvif, body); + + return 0; +} + +static int hif_join_complete_indication(struct wfx_dev *wdev, + struct hif_msg *hif, void *buf) +{ + struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface); + + WARN_ON(!wvif); + dev_warn(wdev->dev, "unattended JoinCompleteInd\n"); + + return 0; +} + +static int hif_suspend_resume_indication(struct wfx_dev *wdev, + struct hif_msg *hif, void *buf) +{ + struct wfx_vif *wvif = wdev_to_wvif(wdev, hif->interface); + struct hif_ind_suspend_resume_tx *body = buf; + + WARN_ON(!wvif); + wfx_suspend_resume(wvif, body); + + return 0; +} + +static int hif_error_indication(struct wfx_dev *wdev, struct hif_msg *hif, + void *buf) +{ + struct hif_ind_error *body = buf; + u8 *pRollback = (u8 *) body->data; + u32 *pStatus = (u32 *) body->data; + + switch (body->type) { + case HIF_ERROR_FIRMWARE_ROLLBACK: + dev_err(wdev->dev, + "asynchronous error: firmware rollback error %d\n", + *pRollback); + break; + case HIF_ERROR_FIRMWARE_DEBUG_ENABLED: + dev_err(wdev->dev, "asynchronous error: firmware debug feature enabled\n"); + break; + case HIF_ERROR_OUTDATED_SESSION_KEY: + dev_err(wdev->dev, "asynchronous error: secure link outdated key: %#.8x\n", + *pStatus); + break; + case HIF_ERROR_INVALID_SESSION_KEY: + dev_err(wdev->dev, "asynchronous error: invalid session key\n"); + break; + case HIF_ERROR_OOR_VOLTAGE: + dev_err(wdev->dev, "asynchronous error: out-of-range overvoltage: %#.8x\n", + *pStatus); + break; + case HIF_ERROR_PDS_VERSION: + dev_err(wdev->dev, + "asynchronous error: wrong PDS payload or version: %#.8x\n", + *pStatus); + break; + default: + dev_err(wdev->dev, "asynchronous error: unknown (%d)\n", + body->type); + break; + } + return 0; +} + +static int hif_generic_indication(struct wfx_dev *wdev, struct hif_msg *hif, + void *buf) +{ + struct hif_ind_generic *body = buf; + + switch (body->indication_type) { + case HIF_GENERIC_INDICATION_TYPE_RAW: + return 0; + case HIF_GENERIC_INDICATION_TYPE_STRING: + dev_info(wdev->dev, "firmware says: %s\n", + (char *) body->indication_data.raw_data); + return 0; + case HIF_GENERIC_INDICATION_TYPE_RX_STATS: + mutex_lock(&wdev->rx_stats_lock); + // Older firmware send a generic indication beside RxStats + if (!wfx_api_older_than(wdev, 1, 4)) + dev_info(wdev->dev, "Rx test ongoing. Temperature: %d°C\n", + body->indication_data.rx_stats.current_temp); + memcpy(&wdev->rx_stats, &body->indication_data.rx_stats, + sizeof(wdev->rx_stats)); + mutex_unlock(&wdev->rx_stats_lock); + return 0; + default: + dev_err(wdev->dev, + "generic_indication: unknown indication type: %#.8x\n", + body->indication_type); + return -EIO; + } +} + +static int hif_exception_indication(struct wfx_dev *wdev, + struct hif_msg *hif, void *buf) +{ + size_t len = hif->len - 4; // drop header + dev_err(wdev->dev, "firmware exception\n"); + print_hex_dump_bytes("Dump: ", DUMP_PREFIX_NONE, buf, len); + wdev->chip_frozen = 1; + + return -1; +} + +static const struct { + int msg_id; + int (*handler)(struct wfx_dev *wdev, struct hif_msg *hif, void *buf); +} hif_handlers[] = { + /* Confirmations */ + { HIF_CNF_ID_TX, hif_tx_confirm }, + { HIF_CNF_ID_MULTI_TRANSMIT, hif_multi_tx_confirm }, + /* Indications */ + { HIF_IND_ID_STARTUP, hif_startup_indication }, + { HIF_IND_ID_WAKEUP, hif_wakeup_indication }, + { HIF_IND_ID_JOIN_COMPLETE, hif_join_complete_indication }, + { HIF_IND_ID_SET_PM_MODE_CMPL, hif_pm_mode_complete_indication }, + { HIF_IND_ID_SCAN_CMPL, hif_scan_complete_indication }, + { HIF_IND_ID_SUSPEND_RESUME_TX, hif_suspend_resume_indication }, + { HIF_IND_ID_SL_EXCHANGE_PUB_KEYS, hif_keys_indication }, + { HIF_IND_ID_EVENT, hif_event_indication }, + { HIF_IND_ID_GENERIC, hif_generic_indication }, + { HIF_IND_ID_ERROR, hif_error_indication }, + { HIF_IND_ID_EXCEPTION, hif_exception_indication }, + // FIXME: allocate skb_p from hif_receive_indication and make it generic + //{ HIF_IND_ID_RX, hif_receive_indication }, +}; + +void wfx_handle_rx(struct wfx_dev *wdev, struct sk_buff *skb) +{ + int i; + struct hif_msg *hif = (struct hif_msg *) skb->data; + int hif_id = hif->id; + + if (hif_id == HIF_IND_ID_RX) { + // hif_receive_indication take care of skb lifetime + hif_receive_indication(wdev, hif, hif->body, skb); + return; + } + // Note: mutex_is_lock cause an implicit memory barrier that protect + // buf_send + if (mutex_is_locked(&wdev->hif_cmd.lock) + && wdev->hif_cmd.buf_send + && wdev->hif_cmd.buf_send->id == hif_id) { + hif_generic_confirm(wdev, hif, hif->body); + goto free; + } + for (i = 0; i < ARRAY_SIZE(hif_handlers); i++) { + if (hif_handlers[i].msg_id == hif_id) { + if (hif_handlers[i].handler) + hif_handlers[i].handler(wdev, hif, hif->body); + goto free; + } + } + dev_err(wdev->dev, "unsupported HIF ID %02x\n", hif_id); +free: + dev_kfree_skb(skb); +} |