/* After CSME takes ownership, it won't release it for 60 seconds to avoid * frequent ownership transitions.
*/ #define MEI_OWNERSHIP_RETAKE_TIMEOUT_MS msecs_to_jiffies(60000)
/* * Since iwlwifi calls iwlmei without any context, hold a pointer to the * mei_cl_device structure here. * Define a mutex that will synchronize all the flows between iwlwifi and * iwlmei. * Note that iwlmei can't have several instances, so it ok to have static * variables here.
*/ staticstruct mei_cl_device *iwl_mei_global_cldev; static DEFINE_MUTEX(iwl_mei_mutex); staticunsignedlong iwl_mei_status;
/** * struct iwl_mei - holds the private date for iwl_mei * * @get_nvm_wq: the wait queue for the get_nvm flow * @send_csa_msg_wk: used to defer the transmission of the CHECK_SHARED_AREA * message. Used so that we can send CHECK_SHARED_AREA from atomic * contexts. * @get_ownership_wq: the wait queue for the get_ownership_flow * @shared_mem: the memory that is shared between CSME and the host * @cldev: the pointer to the MEI client device * @nvm: the data returned by the CSME for the NVM * @filters: the filters sent by CSME * @got_ownership: true if we own the device * @amt_enabled: true if CSME has wireless enabled * @csa_throttled: when true, we can't send CHECK_SHARED_AREA over the MEI * bus, but rather need to wait until send_csa_msg_wk runs * @csme_taking_ownership: true when CSME is taking ownership. Used to remember * to send CSME_OWNERSHIP_CONFIRMED when the driver completes its down * flow. * @link_prot_state: true when we are in link protection PASSIVE * @device_down: true if the device is down. Used to remember to send * CSME_OWNERSHIP_CONFIRMED when the driver is already down. * @csa_throttle_end_wk: used when &csa_throttled is true * @pldr_wq: the wait queue for PLDR flow * @pldr_active: PLDR flow is in progress * @data_q_lock: protects the access to the data queues which are * accessed without the mutex. * @netdev_work: used to defer registering and unregistering of the netdev to * avoid taking the rtnl lock in the SAP messages handlers. * @ownership_dwork: used to re-ask for NIC ownership after ownership was taken * by CSME or when a previous ownership request failed. * @sap_seq_no: the sequence number for the SAP messages * @seq_no: the sequence number for the SAP messages * @dbgfs_dir: the debugfs dir entry
*/ struct iwl_mei {
wait_queue_head_t get_nvm_wq; struct work_struct send_csa_msg_wk;
wait_queue_head_t get_ownership_wq; struct iwl_mei_shared_mem_ptrs shared_mem; struct mei_cl_device *cldev; struct iwl_mei_nvm *nvm; struct iwl_mei_filters __rcu *filters; bool got_ownership; bool amt_enabled; bool csa_throttled; bool csme_taking_ownership; bool link_prot_state; bool device_down; struct delayed_work csa_throttle_end_wk;
wait_queue_head_t pldr_wq; bool pldr_active;
spinlock_t data_q_lock; struct work_struct netdev_work; struct delayed_work ownership_dwork;
atomic_t sap_seq_no;
atomic_t seq_no;
struct dentry *dbgfs_dir;
};
/** * struct iwl_mei_cache - cache for the parameters from iwlwifi * @ops: Callbacks to iwlwifi. * @netdev: The netdev that will be used to transmit / receive packets. * @conn_info: The connection info message triggered by iwlwifi's association. * @power_limit: pointer to an array of 10 elements (le16) represents the power * restrictions per chain. * @rf_kill: rf kill state. * @mcc: MCC info * @mac_address: interface MAC address. * @nvm_address: NVM MAC address. * @priv: A pointer to iwlwifi. * @sap_version: The SAP version to use. enum iwl_mei_sap_version. * * This used to cache the configurations coming from iwlwifi's way. The data * is cached here so that we can buffer the configuration even if we don't have * a bind from the mei bus and hence, on iwl_mei structure.
*/ struct iwl_mei_cache { conststruct iwl_mei_ops *ops; struct net_device __rcu *netdev; conststruct iwl_sap_notif_connection_info *conn_info; const __le16 *power_limit;
u32 rf_kill;
u16 mcc;
u8 mac_address[6];
u8 nvm_address[6]; enum iwl_mei_sap_version sap_version; void *priv;
};
if (mei_cldev_dma_unmap(cldev))
dev_err(&cldev->dev, "Couldn't unmap the shared mem properly\n");
memset(&mei->shared_mem, 0, sizeof(mei->shared_mem));
}
iwl_mei_cache.sap_version = version;
mem->ctrl = mei_cldev_dma_map(cldev, HBM_DMA_BUF_ID_WLAN, mem_size); if (IS_ERR(mem->ctrl)) { int ret = PTR_ERR(mem->ctrl);
mem->ctrl = NULL;
return ret;
}
memset(mem->ctrl, 0, mem_size);
return 0;
}
staticint iwl_mei_alloc_shared_mem(struct mei_cl_device *cldev)
{ int ret;
/* * SAP version 4 uses a larger Host to MEI notif queue. * Since it is unknown at this stage which SAP version is used by the * CSME firmware on this platform, try to allocate the version 4 first. * If the CSME firmware uses version 3, this allocation is expected to * fail because the CSME firmware allocated less memory for our driver.
*/
ret = iwl_mei_alloc_mem_for_version(cldev, IWL_MEI_SAP_VERSION_4); if (ret)
ret = iwl_mei_alloc_mem_for_version(cldev,
IWL_MEI_SAP_VERSION_3);
/* we don't have enough room for the data to write */ if (room_in_buf < tx_sz) {
dev_err(&cldev->dev, "Not enough room in the buffer\n"); return -ENOSPC;
}
mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);
/* * We access this path for Rx packets (the more common case) * and from Tx path when we send DHCP packets, the latter is * very unlikely. * Take the lock already here to make sure we see that remove() * might have cleared the IWL_MEI_STATUS_SAP_CONNECTED bit.
*/
spin_lock_bh(&mei->data_q_lock);
if (!iwl_mei_is_connected()) {
spin_unlock_bh(&mei->data_q_lock); return;
}
/* * We are in a RCU critical section and the remove from the CSME bus * which would free this memory waits for the readers to complete (this * is done in netdev_rx_handler_unregister).
*/
dir = &mei->shared_mem.ctrl->dir[SAP_DIRECTION_HOST_TO_ME];
notif_q = &dir->q_ctrl_blk[SAP_QUEUE_IDX_DATA];
q_head = mei->shared_mem.q_head[SAP_DIRECTION_HOST_TO_ME][SAP_QUEUE_IDX_DATA];
q_sz = mei->shared_mem.q_size[SAP_DIRECTION_HOST_TO_ME][SAP_QUEUE_IDX_DATA];
/* we don't have enough room for the data to write */ if (room_in_buf < tx_sz) {
dev_err(&mei->cldev->dev, "Not enough room in the buffer for this data\n"); goto out;
}
if (skb_headroom(skb) < hdr_sz) {
dev_err(&mei->cldev->dev, "Not enough headroom in the skb to write the SAP header\n"); goto out;
}
if (cb_tx) { struct iwl_sap_cb_data *cb_hdr = skb_push(skb, sizeof(*cb_hdr));
/* Called in a RCU read critical section from netif_receive_skb */ static rx_handler_result_t iwl_mei_rx_handler(struct sk_buff **pskb)
{ struct sk_buff *skb = *pskb; struct iwl_mei *mei =
rcu_dereference(skb->dev->rx_handler_data); struct iwl_mei_filters *filters = rcu_dereference(mei->filters); bool rx_for_csme = false;
rx_handler_result_t res;
/* * remove() unregisters this handler and synchronize_net, so this * should never happen.
*/ if (!iwl_mei_is_connected()) {
dev_err(&mei->cldev->dev, "Got an Rx packet, but we're not connected to SAP?\n"); return RX_HANDLER_PASS;
}
if (filters)
res = iwl_mei_rx_filter(skb, &filters->filters, &rx_for_csme); else
res = RX_HANDLER_PASS;
/* * The data is already on the ring of the shared area, all we * need to do is to tell the CSME firmware to check what we have * there.
*/ if (rx_for_csme)
schedule_work(&mei->send_csa_msg_wk);
if (res != RX_HANDLER_PASS) {
trace_iwlmei_sap_data(skb, IWL_SAP_RX_DATA_DROPPED_FROM_AIR);
dev_kfree_skb(skb);
}
/* * First take rtnl and only then the mutex to avoid an ABBA * with iwl_mei_set_netdev()
*/
rtnl_lock();
mutex_lock(&iwl_mei_mutex);
netdev = rcu_dereference_protected(iwl_mei_cache.netdev,
lockdep_is_held(&iwl_mei_mutex)); if (netdev) { if (mei->amt_enabled)
netdev_rx_handler_register(netdev, iwl_mei_rx_handler,
mei); else
netdev_rx_handler_unregister(netdev);
}
mutex_unlock(&iwl_mei_mutex);
rtnl_unlock();
}
staticvoid
iwl_mei_handle_rx_start_ok(struct mei_cl_device *cldev, conststruct iwl_sap_me_msg_start_ok *rsp,
ssize_t len)
{ if (len != sizeof(*rsp)) {
dev_err(&cldev->dev, "got invalid SAP_ME_MSG_START_OK from CSME firmware\n");
dev_err(&cldev->dev, "size is incorrect: %zd instead of %zu\n",
len, sizeof(*rsp)); return;
}
if (rsp->supported_version != iwl_mei_cache.sap_version) {
dev_err(&cldev->dev, "didn't get the expected version: got %d\n",
rsp->supported_version); return;
}
mutex_lock(&iwl_mei_mutex);
set_bit(IWL_MEI_STATUS_SAP_CONNECTED, &iwl_mei_status); /* * We'll receive AMT_STATE SAP message in a bit and * that will continue the flow
*/
mutex_unlock(&iwl_mei_mutex);
}
/* * Update the Rfkill state in case the host does not own the device: * if we are in Link Protection, ask to not touch the device, else, * unblock rfkill. * If the host owns the device, inform the user space whether it can * roam.
*/ if (mei->got_ownership)
iwl_mei_cache.ops->roaming_forbidden(iwl_mei_cache.priv,
status->link_prot_state); else
iwl_mei_cache.ops->rfkill(iwl_mei_cache.priv,
status->link_prot_state, false);
}
staticvoid iwl_mei_handle_can_release_ownership(struct mei_cl_device *cldev, constvoid *payload)
{ /* We can get ownership and driver is registered, go ahead */ if (iwl_mei_cache.ops)
iwl_mei_send_sap_msg(cldev,
SAP_MSG_NOTIF_HOST_ASKS_FOR_NIC_OWNERSHIP);
}
if (iwl_mei_cache.ops && !mei->device_down) { /* * Remember to send CSME_OWNERSHIP_CONFIRMED when the wifi * driver is finished taking the device down.
*/
mei->csme_taking_ownership = true;
/* * This means that we can't use the wifi device right now, CSME is not * ready to let us use it.
*/ if (!dw->val) {
dev_info(&cldev->dev, "Ownership req denied\n"); return;
}
switch (type) {
SAP_MSG_HANDLER(PING, iwl_mei_handle_ping, 0);
SAP_MSG_HANDLER(CSME_FILTERS,
iwl_mei_handle_csme_filters, sizeof(struct iwl_sap_csme_filters));
SAP_MSG_HANDLER(CSME_CONN_STATUS,
iwl_mei_handle_conn_status, sizeof(struct iwl_sap_notif_conn_status));
SAP_MSG_HANDLER_NO_LOCK(AMT_STATE,
iwl_mei_handle_amt_state, sizeof(struct iwl_sap_msg_dw));
SAP_MSG_HANDLER_NO_HANDLER(PONG, 0);
SAP_MSG_HANDLER(NVM, iwl_mei_handle_nvm, sizeof(struct iwl_sap_nvm));
SAP_MSG_HANDLER(CSME_REPLY_TO_HOST_OWNERSHIP_REQ,
iwl_mei_handle_rx_host_own_req, sizeof(struct iwl_sap_msg_dw));
SAP_MSG_HANDLER(NIC_OWNER, iwl_mei_handle_nic_owner, sizeof(struct iwl_sap_msg_dw));
SAP_MSG_HANDLER(CSME_CAN_RELEASE_OWNERSHIP,
iwl_mei_handle_can_release_ownership, 0);
SAP_MSG_HANDLER(CSME_TAKING_OWNERSHIP,
iwl_mei_handle_csme_taking_ownership, 0);
SAP_MSG_HANDLER(PLDR_ACK, iwl_mei_handle_pldr_ack, sizeof(struct iwl_sap_pldr_ack_data)); default: /* * This is not really an error, there are message that we decided * to ignore, yet, it is useful to be able to leave a note if debug * is enabled.
*/
dev_dbg(&cldev->dev, "Unsupported message: type %d, len %d\n",
le16_to_cpu(hdr->type), len);
}
data = skb_put(skb, len);
iwl_mei_read_from_q(q_head, q_sz, &rd, wr, data, len);
/* * Enqueue the skb here so that it can be sent later when we * do not hold the mutex. TX'ing a packet with a mutex held is * possible, but it wouldn't be nice to forbid the TX path to * call any of iwlmei's functions, since every API from iwlmei * needs the mutex.
*/
__skb_queue_tail(tx_skbs, skb);
}
}
/* * Do not hold the mutex here, but rather each and every message * handler takes it. * This allows message handlers to take it at a certain time.
*/
iwl_mei_handle_sap_rx(cldev, notif_q, q_head, NULL, q_sz);
if (skb_queue_empty(&tx_skbs)) {
mutex_unlock(&iwl_mei_mutex); return;
}
/* * Take the RCU read lock before we unlock the mutex to make sure that * even if the netdev is replaced by another non-NULL netdev right after * we unlock the mutex, the old netdev will still be valid when we * transmit the frames. We can't allow to replace the netdev here because * the skbs hold a pointer to the netdev.
*/
rcu_read_lock();
mutex_unlock(&iwl_mei_mutex);
if (!rcu_access_pointer(iwl_mei_cache.netdev)) {
dev_err(&cldev->dev, "Can't Tx without a netdev\n");
skb_queue_purge(&tx_skbs); goto out;
}
while (!skb_queue_empty(&tx_skbs)) { struct sk_buff *skb = __skb_dequeue(&tx_skbs);
trace_iwlmei_me_msg(&msg.hdr, true);
ret = mei_cldev_send(cldev, (void *)&msg, sizeof(msg)); if (ret != sizeof(msg)) {
dev_err(&cldev->dev, "failed to send the SAP_ME_MSG_START message %d\n",
ret); return ret;
}
return 0;
}
staticint iwl_mei_enable(struct mei_cl_device *cldev)
{ int ret;
ret = mei_cldev_enable(cldev); if (ret < 0) {
dev_err(&cldev->dev, "failed to enable the device: %d\n", ret); return ret;
}
ret = mei_cldev_register_rx_cb(cldev, iwl_mei_rx); if (ret) {
dev_err(&cldev->dev, "failed to register to the rx cb: %d\n", ret);
mei_cldev_disable(cldev); return ret;
}
int iwl_mei_pldr_req(void)
{ struct iwl_mei *mei; int ret; struct iwl_sap_pldr_data msg = {
.hdr.type = cpu_to_le16(SAP_MSG_NOTIF_PLDR),
.hdr.len = cpu_to_le16(sizeof(msg) - sizeof(msg.hdr)),
}; int i;
mutex_lock(&iwl_mei_mutex);
/* In case we didn't have a bind */ if (!iwl_mei_is_connected()) {
ret = 0; goto out;
}
mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);
if (!mei) {
ret = -ENODEV; goto out;
}
if (!mei->amt_enabled) {
ret = 0; goto out;
}
for (i = 0; i < IWL_MEI_PLDR_NUM_RETRIES; i++) {
ret = iwl_mei_send_sap_msg_payload(mei->cldev, &msg.hdr);
mutex_unlock(&iwl_mei_mutex); if (ret) return ret;
ret = wait_event_timeout(mei->pldr_wq, mei->pldr_active, HZ / 2); if (ret) break;
/* Take the mutex for the next iteration */
mutex_lock(&iwl_mei_mutex);
}
if (ret) return 0;
ret = -ETIMEDOUT;
out:
mutex_unlock(&iwl_mei_mutex); return ret;
}
EXPORT_SYMBOL_GPL(iwl_mei_pldr_req);
int iwl_mei_get_ownership(void)
{ struct iwl_mei *mei; int ret;
mutex_lock(&iwl_mei_mutex);
/* In case we didn't have a bind */ if (!iwl_mei_is_connected()) {
ret = 0; goto out;
}
mei = mei_cldev_get_drvdata(iwl_mei_global_cldev);
if (!mei) {
ret = -ENODEV; goto out;
}
if (!mei->amt_enabled) {
ret = 0; goto out;
}
if (mei->got_ownership) {
ret = 0; goto out;
}
ret = iwl_mei_send_sap_msg(mei->cldev,
SAP_MSG_NOTIF_HOST_ASKS_FOR_NIC_OWNERSHIP); if (ret) goto out;
mutex_unlock(&iwl_mei_mutex);
ret = wait_event_timeout(mei->get_ownership_wq,
mei->got_ownership, HZ / 2); if (!ret) {
schedule_delayed_work(&mei->ownership_dwork,
MEI_OWNERSHIP_RETAKE_TIMEOUT_MS); return -ETIMEDOUT;
}
if (iwl_mei_global_cldev) { struct iwl_mei *mei =
mei_cldev_get_drvdata(iwl_mei_global_cldev);
/* we have already a SAP connection */ if (iwl_mei_is_connected()) { if (mei->amt_enabled)
iwl_mei_send_sap_msg(mei->cldev,
SAP_MSG_NOTIF_WIFIDR_UP);
ops->rfkill(priv, mei->link_prot_state, false);
}
}
ret = 0;
/* At this point, the wifi driver should have removed the netdev */ if (rcu_access_pointer(iwl_mei_cache.netdev))
pr_err("Still had a netdev pointer set upon unregister\n");
kfree(iwl_mei_cache.conn_info);
iwl_mei_cache.conn_info = NULL;
kfree(iwl_mei_cache.power_limit);
iwl_mei_cache.power_limit = NULL;
iwl_mei_cache.ops = NULL; /* leave iwl_mei_cache.priv non-NULL to prevent any new registration */
/* * iwl_mei_probe - the probe function called by the mei bus enumeration * * This allocates the data needed by iwlmei and sets a pointer to this data * into the mei_cl_device's drvdata. * It starts the SAP protocol by sending the SAP_ME_MSG_START without * waiting for the answer. The answer will be caught later by the Rx callback.
*/ staticint iwl_mei_probe(struct mei_cl_device *cldev, conststruct mei_cl_device_id *id)
{ int alloc_retry = ALLOC_SHARED_MEM_RETRY_MAX_NUM; struct iwl_mei *mei; int ret;
mei = devm_kzalloc(&cldev->dev, sizeof(*mei), GFP_KERNEL); if (!mei) return -ENOMEM;
do {
ret = iwl_mei_alloc_shared_mem(cldev); if (!ret) break; /* * The CSME firmware needs to boot the internal WLAN client. * This can take time in certain configurations (usually * upon resume and when the whole CSME firmware is shut down * during suspend). * * Wait a bit before retrying and hope we'll succeed next time.
*/
dev_dbg(&cldev->dev, "Couldn't allocate the shared memory: %d, attempt %d / %d\n",
ret, alloc_retry, ALLOC_SHARED_MEM_RETRY_MAX_NUM);
msleep(100);
alloc_retry--;
} while (alloc_retry);
if (ret) {
dev_err(&cldev->dev, "Couldn't allocate the shared memory: %d\n",
ret); goto free;
}
iwl_mei_init_shared_mem(mei);
ret = iwl_mei_enable(cldev); if (ret) goto free_shared_mem;
iwl_mei_dbgfs_register(mei);
/* * We now have a Rx function in place, start the SAP protocol * we expect to get the SAP_ME_MSG_START_OK response later on.
*/
mutex_lock(&iwl_mei_mutex);
ret = iwl_mei_send_start(cldev);
mutex_unlock(&iwl_mei_mutex); if (ret) goto debugfs_unregister;
/* * We are being removed while the bus is active, it means we are * going to suspend/ shutdown, so the NIC will disappear.
*/ if (mei_cldev_enabled(cldev) && iwl_mei_cache.ops) { unsignedint iter = IWLMEI_DEVICE_DOWN_WAIT_ITERATION; bool down = false;
/* * In case of suspend, wait for the mac to stop and don't remove * the interface. This will allow the interface to come back * on resume.
*/ while (!down && iter--) {
mdelay(1);
mutex_lock(&iwl_mei_mutex);
down = mei->device_down;
mutex_unlock(&iwl_mei_mutex);
}
if (!down)
iwl_mei_cache.ops->nic_stolen(iwl_mei_cache.priv);
}
if (rcu_access_pointer(iwl_mei_cache.netdev)) { struct net_device *dev;
/* * First take rtnl and only then the mutex to avoid an ABBA * with iwl_mei_set_netdev()
*/
rtnl_lock();
mutex_lock(&iwl_mei_mutex);
/* * If we are suspending and the wifi driver hasn't removed it's netdev * yet, do it now. In any case, don't change the cache.netdev pointer.
*/
dev = rcu_dereference_protected(iwl_mei_cache.netdev,
lockdep_is_held(&iwl_mei_mutex));
/* Tell CSME that we are going down so that it won't access the * memory anymore, make sure this message goes through immediately.
*/
mei->csa_throttled = false;
iwl_mei_send_sap_msg(mei->cldev,
SAP_MSG_NOTIF_HOST_GOES_DOWN);
for (i = 0; i < SEND_SAP_MAX_WAIT_ITERATION; i++) { if (!iwl_mei_host_to_me_data_pending(mei)) break;
msleep(20);
}
/* If we couldn't make sure that CSME saw the HOST_GOES_DOWN * message, it means that it will probably keep reading memory * that we are going to unmap and free, expect IOMMU error * messages.
*/ if (i == SEND_SAP_MAX_WAIT_ITERATION)
dev_err(&mei->cldev->dev, "Couldn't get ACK from CSME on HOST_GOES_DOWN message\n");
mutex_unlock(&iwl_mei_mutex);
/* * This looks strange, but this lock is taken here to make sure that * iwl_mei_add_data_to_ring called from the Tx path sees that we * clear the IWL_MEI_STATUS_SAP_CONNECTED bit. * Rx isn't a problem because the rx_handler can't be called after * having been unregistered.
*/
spin_lock_bh(&mei->data_q_lock);
clear_bit(IWL_MEI_STATUS_SAP_CONNECTED, &iwl_mei_status);
spin_unlock_bh(&mei->data_q_lock);
if (iwl_mei_cache.ops)
iwl_mei_cache.ops->rfkill(iwl_mei_cache.priv, false, false);
/* * mei_cldev_disable will return only after all the MEI Rx is done. * It must be called when iwl_mei_mutex is *not* held, since it waits * for our Rx handler to complete. * After it returns, no new Rx will start.
*/
mei_cldev_disable(cldev);
/* * Since the netdev was already removed and the netdev's removal * includes a call to synchronize_net() so that we know there won't be * any new Rx that will trigger the following workers.
*/
cancel_work_sync(&mei->send_csa_msg_wk);
cancel_delayed_work_sync(&mei->csa_throttle_end_wk);
cancel_work_sync(&mei->netdev_work);
cancel_delayed_work_sync(&mei->ownership_dwork);
/* * If someone waits for the ownership, let him know that we are going * down and that we are not connected anymore. He'll be able to take * the device.
*/
wake_up_all(&mei->get_ownership_wq);
wake_up_all(&mei->pldr_wq);
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.