| /* | |
| * --------------------------------------------------------------------------- | |
| * FILE: mlme.c | |
| * | |
| * PURPOSE: | |
| * This file provides functions to send MLME requests to the UniFi. | |
| * | |
| * Copyright (C) 2007-2008 by Cambridge Silicon Radio Ltd. | |
| * | |
| * Refer to LICENSE.txt included with this source code for details on | |
| * the license terms. | |
| * | |
| * --------------------------------------------------------------------------- | |
| */ | |
| #include "csr_wifi_hip_unifi.h" | |
| #include "unifi_priv.h" | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * unifi_mlme_wait_for_reply | |
| * | |
| * Wait for a reply after sending a signal. | |
| * | |
| * Arguments: | |
| * priv Pointer to device private context struct | |
| * ul_client Pointer to linux client | |
| * sig_reply_id ID of the expected reply (defined in sigs.h). | |
| * timeout timeout in ms | |
| * | |
| * Returns: | |
| * 0 on success, -ve POSIX code on error. | |
| * | |
| * Notes: | |
| * This function waits for a specific (sig_reply_id) signal from UniFi. | |
| * It also match the sequence number of the received (cfm) signal, with | |
| * the latest sequence number of the signal (req) we have sent. | |
| * These two number match be equal. | |
| * Should only be used for waiting xxx.cfm signals and only after | |
| * we have sent the matching xxx.req signal to UniFi. | |
| * If no response is received within the expected time (timeout), we assume | |
| * that the UniFi is busy and return an error. | |
| * If the wait is aborted by a kernel signal arriving, we stop waiting. | |
| * If a response from UniFi is not what we expected, we discard it and | |
| * wait again. This could be a response from an aborted request. If we | |
| * see several bad responses we assume we have lost synchronisation with | |
| * UniFi. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| static int | |
| unifi_mlme_wait_for_reply(unifi_priv_t *priv, ul_client_t *pcli, int sig_reply_id, int timeout) | |
| { | |
| int retries = 0; | |
| long r; | |
| long t = timeout; | |
| unsigned int sent_seq_no; | |
| /* Convert t in ms to jiffies */ | |
| t = msecs_to_jiffies(t); | |
| do { | |
| /* Wait for the confirm or timeout. */ | |
| r = wait_event_interruptible_timeout(pcli->udi_wq, | |
| (pcli->wake_up_wq_id) || (priv->io_aborted == 1), | |
| t); | |
| /* Check for general i/o error */ | |
| if (priv->io_aborted) { | |
| unifi_error(priv, "MLME operation aborted\n"); | |
| return -EIO; | |
| } | |
| /* | |
| * If r=0 the request has timed-out. | |
| * If r>0 the request has completed successfully. | |
| * If r=-ERESTARTSYS an event (kill signal) has interrupted the wait_event. | |
| */ | |
| if ((r == 0) && (pcli->wake_up_wq_id == 0)) { | |
| unifi_error(priv, "mlme_wait: timed-out waiting for 0x%.4X, after %lu msec.\n", | |
| sig_reply_id, jiffies_to_msecs(t)); | |
| pcli->wake_up_wq_id = 0; | |
| return -ETIMEDOUT; | |
| } else if (r == -ERESTARTSYS) { | |
| unifi_error(priv, "mlme_wait: waiting for 0x%.4X was aborted.\n", sig_reply_id); | |
| pcli->wake_up_wq_id = 0; | |
| return -EINTR; | |
| } else { | |
| /* Get the sequence number of the signal that we previously set. */ | |
| if (pcli->seq_no != 0) { | |
| sent_seq_no = pcli->seq_no - 1; | |
| } else { | |
| sent_seq_no = 0x0F; | |
| } | |
| unifi_trace(priv, UDBG5, "Received 0x%.4X, seq: (r:%d, s:%d)\n", | |
| pcli->wake_up_wq_id, | |
| pcli->wake_seq_no, sent_seq_no); | |
| /* The two sequence ids must match. */ | |
| if (pcli->wake_seq_no == sent_seq_no) { | |
| /* and the signal ids must match. */ | |
| if (sig_reply_id == pcli->wake_up_wq_id) { | |
| /* Found the expected signal */ | |
| break; | |
| } else { | |
| /* This should never happen ... */ | |
| unifi_error(priv, "mlme_wait: mismatching signal id (0x%.4X - exp 0x%.4X) (seq %d)\n", | |
| pcli->wake_up_wq_id, | |
| sig_reply_id, | |
| pcli->wake_seq_no); | |
| pcli->wake_up_wq_id = 0; | |
| return -EIO; | |
| } | |
| } | |
| /* Wait for the next signal. */ | |
| pcli->wake_up_wq_id = 0; | |
| retries ++; | |
| if (retries >= 3) { | |
| unifi_error(priv, "mlme_wait: confirm wait retries exhausted (0x%.4X - exp 0x%.4X)\n", | |
| pcli->wake_up_wq_id, | |
| sig_reply_id); | |
| pcli->wake_up_wq_id = 0; | |
| return -EIO; | |
| } | |
| } | |
| } while (1); | |
| pcli->wake_up_wq_id = 0; | |
| return 0; | |
| } /* unifi_mlme_wait_for_reply() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * unifi_mlme_blocking_request | |
| * | |
| * Send a MLME request signal to UniFi. | |
| * | |
| * Arguments: | |
| * priv Pointer to device private context struct | |
| * pcli Pointer to context of calling process | |
| * sig Pointer to the signal to send | |
| * data_ptrs Pointer to the bulk data of the signal | |
| * timeout The request's timeout. | |
| * | |
| * Returns: | |
| * 0 on success, 802.11 result code on error. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| int | |
| unifi_mlme_blocking_request(unifi_priv_t *priv, ul_client_t *pcli, | |
| CSR_SIGNAL *sig, bulk_data_param_t *data_ptrs, | |
| int timeout) | |
| { | |
| int r; | |
| if (sig->SignalPrimitiveHeader.SignalId == 0) { | |
| unifi_error(priv, "unifi_mlme_blocking_request: Invalid Signal Id (0x%x)\n", | |
| sig->SignalPrimitiveHeader.SignalId); | |
| return -EINVAL; | |
| } | |
| down(&priv->mlme_blocking_mutex); | |
| sig->SignalPrimitiveHeader.ReceiverProcessId = 0; | |
| sig->SignalPrimitiveHeader.SenderProcessId = pcli->sender_id | pcli->seq_no; | |
| unifi_trace(priv, UDBG2, "Send client=%d, S:0x%04X, sig 0x%.4X\n", | |
| pcli->client_id, | |
| sig->SignalPrimitiveHeader.SenderProcessId, | |
| sig->SignalPrimitiveHeader.SignalId); | |
| /* Send the signal to UniFi */ | |
| r = ul_send_signal_unpacked(priv, sig, data_ptrs); | |
| if (r) { | |
| up(&priv->mlme_blocking_mutex); | |
| unifi_error(priv, "Error queueing MLME REQUEST signal\n"); | |
| return r; | |
| } | |
| unifi_trace(priv, UDBG5, "Send 0x%.4X, seq = %d\n", | |
| sig->SignalPrimitiveHeader.SignalId, pcli->seq_no); | |
| /* | |
| * Advance the sequence number of the last sent signal, only | |
| * if the signal has been successfully set. | |
| */ | |
| pcli->seq_no++; | |
| if (pcli->seq_no > 0x0F) { | |
| pcli->seq_no = 0; | |
| } | |
| r = unifi_mlme_wait_for_reply(priv, pcli, (sig->SignalPrimitiveHeader.SignalId + 1), timeout); | |
| up(&priv->mlme_blocking_mutex); | |
| if (r) { | |
| unifi_error(priv, "Error waiting for MLME CONFIRM signal\n"); | |
| return r; | |
| } | |
| return 0; | |
| } /* unifi_mlme_blocking_request() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * unifi_mlme_copy_reply_and_wakeup_client | |
| * | |
| * Copy the reply signal from UniFi to the client's structure | |
| * and wake up the waiting client. | |
| * | |
| * Arguments: | |
| * None. | |
| * | |
| * Returns: | |
| * None. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| void | |
| unifi_mlme_copy_reply_and_wakeup_client(ul_client_t *pcli, | |
| CSR_SIGNAL *signal, int signal_len, | |
| const bulk_data_param_t *bulkdata) | |
| { | |
| int i; | |
| /* Copy the signal to the reply */ | |
| memcpy(pcli->reply_signal, signal, signal_len); | |
| /* Get the sequence number of the signal that woke us up. */ | |
| pcli->wake_seq_no = pcli->reply_signal->SignalPrimitiveHeader.ReceiverProcessId & 0x0F; | |
| /* Append any bulk data */ | |
| for (i = 0; i < UNIFI_MAX_DATA_REFERENCES; i++) { | |
| if (bulkdata->d[i].data_length > 0) { | |
| if (bulkdata->d[i].os_data_ptr) { | |
| memcpy(pcli->reply_bulkdata[i]->ptr, bulkdata->d[i].os_data_ptr, bulkdata->d[i].data_length); | |
| pcli->reply_bulkdata[i]->length = bulkdata->d[i].data_length; | |
| } else { | |
| pcli->reply_bulkdata[i]->length = 0; | |
| } | |
| } | |
| } | |
| /* Wake the requesting MLME function. */ | |
| pcli->wake_up_wq_id = pcli->reply_signal->SignalPrimitiveHeader.SignalId; | |
| wake_up_interruptible(&pcli->udi_wq); | |
| } /* unifi_mlme_copy_reply_and_wakeup_client() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * uf_abort_mlme | |
| * | |
| * Abort any MLME operation in progress. | |
| * This is used in the error recovery mechanism. | |
| * | |
| * Arguments: | |
| * priv Pointer to driver context. | |
| * | |
| * Returns: | |
| * 0 on success. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| int | |
| uf_abort_mlme(unifi_priv_t *priv) | |
| { | |
| ul_client_t *ul_cli; | |
| /* Ensure no MLME functions are waiting on a the mlme_event semaphore. */ | |
| priv->io_aborted = 1; | |
| ul_cli = priv->netdev_client; | |
| if (ul_cli) { | |
| wake_up_interruptible(&ul_cli->udi_wq); | |
| } | |
| ul_cli = priv->wext_client; | |
| if (ul_cli) { | |
| wake_up_interruptible(&ul_cli->udi_wq); | |
| } | |
| return 0; | |
| } /* uf_abort_mlme() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * | |
| * Human-readable decoding of Reason and Result codes. | |
| * | |
| * --------------------------------------------------------------------------- | |
| */ | |
| struct mlme_code { | |
| const char *name; | |
| int id; | |
| }; | |
| static const struct mlme_code Result_codes[] = { | |
| { "Success", 0x0000 }, | |
| { "Unspecified Failure", 0x0001 }, | |
| /* (Reserved) 0x0002 - 0x0009 */ | |
| { "Refused Capabilities Mismatch", 0x000A }, | |
| /* (Reserved) 0x000B */ | |
| { "Refused External Reason", 0x000C }, | |
| /* (Reserved) 0x000D - 0x0010 */ | |
| { "Refused AP Out Of Memory", 0x0011 }, | |
| { "Refused Basic Rates Mismatch", 0x0012 }, | |
| /* (Reserved) 0x0013 - 0x001F */ | |
| { "Failure", 0x0020 }, | |
| /* (Reserved) 0x0021 - 0x0024 */ | |
| { "Refused Reason Unspecified", 0x0025 }, | |
| { "Invalid Parameters", 0x0026 }, | |
| { "Rejected With Suggested Changes", 0x0027 }, | |
| /* (Reserved) 0x0028 - 0x002E */ | |
| { "Rejected For Delay Period", 0x002F }, | |
| { "Not Allowed", 0x0030 }, | |
| { "Not Present", 0x0031 }, | |
| { "Not QSTA", 0x0032 }, | |
| /* (Reserved) 0x0033 - 0x7FFF */ | |
| { "Timeout", 0x8000 }, | |
| { "Too Many Simultaneous Requests", 0x8001 }, | |
| { "BSS Already Started Or Joined", 0x8002 }, | |
| { "Not Supported", 0x8003 }, | |
| { "Transmission Failure", 0x8004 }, | |
| { "Refused Not Authenticated", 0x8005 }, | |
| { "Reset Required Before Start", 0x8006 }, | |
| { "LM Info Unavailable", 0x8007 }, | |
| { NULL, -1 } | |
| }; | |
| static const struct mlme_code Reason_codes[] = { | |
| /* (Reserved) 0x0000 */ | |
| { "Unspecified Reason", 0x0001 }, | |
| { "Authentication Not Valid", 0x0002 }, | |
| { "Deauthenticated Leave BSS", 0x0003 }, | |
| { "Disassociated Inactivity", 0x0004 }, | |
| { "AP Overload", 0x0005 }, | |
| { "Class2 Frame Error", 0x0006 }, | |
| { "Class3 Frame Error", 0x0007 }, | |
| { "Disassociated Leave BSS", 0x0008 }, | |
| { "Association Not Authenticated", 0x0009 }, | |
| { "Disassociated Power Capability", 0x000A }, | |
| { "Disassociated Supported Channels", 0x000B }, | |
| /* (Reserved) 0x000C */ | |
| { "Invalid Information Element", 0x000D }, | |
| { "Michael MIC Failure", 0x000E }, | |
| { "Fourway Handshake Timeout", 0x000F }, | |
| { "Group Key Update Timeout", 0x0010 }, | |
| { "Handshake Element Different", 0x0011 }, | |
| { "Invalid Group Cipher", 0x0012 }, | |
| { "Invalid Pairwise Cipher", 0x0013 }, | |
| { "Invalid AKMP", 0x0014 }, | |
| { "Unsupported RSN IE Version", 0x0015 }, | |
| { "Invalid RSN IE Capabilities", 0x0016 }, | |
| { "Dot1X Auth Failed", 0x0017 }, | |
| { "Cipher Rejected By Policy", 0x0018 }, | |
| /* (Reserved) 0x0019 - 0x001F */ | |
| { "QoS Unspecified Reason", 0x0020 }, | |
| { "QoS Insufficient Bandwidth", 0x0021 }, | |
| { "QoS Excessive Not Ack", 0x0022 }, | |
| { "QoS TXOP Limit Exceeded", 0x0023 }, | |
| { "QSTA Leaving", 0x0024 }, | |
| { "End TS, End DLS, End BA", 0x0025 }, | |
| { "Unknown TS, Unknown DLS, Unknown BA", 0x0026 }, | |
| { "Timeout", 0x0027 }, | |
| /* (Reserved) 0x0028 - 0x002C */ | |
| { "STAKey Mismatch", 0x002D }, | |
| { NULL, -1 } | |
| }; | |
| static const char * | |
| lookup_something(const struct mlme_code *n, int id) | |
| { | |
| for (; n->name; n++) { | |
| if (n->id == id) { | |
| return n->name; | |
| } | |
| } | |
| /* not found */ | |
| return NULL; | |
| } /* lookup_something() */ | |
| const char * | |
| lookup_result_code(int result) | |
| { | |
| static char fallback[16]; | |
| const char *str; | |
| str = lookup_something(Result_codes, result); | |
| if (str == NULL) { | |
| snprintf(fallback, 16, "%d", result); | |
| str = fallback; | |
| } | |
| return str; | |
| } /* lookup_result_code() */ | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * lookup_reason | |
| * | |
| * Return a description string for a WiFi MLME ReasonCode. | |
| * | |
| * Arguments: | |
| * reason The ReasonCode to interpret. | |
| * | |
| * Returns: | |
| * Pointer to description string. | |
| * --------------------------------------------------------------------------- | |
| */ | |
| const char * | |
| lookup_reason_code(int reason) | |
| { | |
| static char fallback[16]; | |
| const char *str; | |
| str = lookup_something(Reason_codes, reason); | |
| if (str == NULL) { | |
| snprintf(fallback, 16, "%d", reason); | |
| str = fallback; | |
| } | |
| return str; | |
| } /* lookup_reason_code() */ | |