| /** @file mlanoffload.c |
| * |
| * @brief This files contains mlanutl offload command handling. |
| * |
| * Copyright (C) 2008-2017, Marvell International Ltd. |
| * |
| * This software file (the "File") is distributed by Marvell International |
| * Ltd. under the terms of the GNU General Public License Version 2, June 1991 |
| * (the "License"). You may use, redistribute and/or modify this File in |
| * accordance with the terms and conditions of the License, a copy of which |
| * is available by writing to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the |
| * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. |
| * |
| * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE |
| * ARE EXPRESSLY DISCLAIMED. The License provides additional details about |
| * this warranty disclaimer. |
| * |
| */ |
| /************************************************************************ |
| Change log: |
| 08/11/2009: initial version |
| ************************************************************************/ |
| |
| #include "mlanutl.h" |
| #include "mlanhostcmd.h" |
| #include "mlanoffload.h" |
| |
| /******************************************************** |
| Local Variables |
| ********************************************************/ |
| |
| t_void hexdump(char *prompt, t_void *p, t_s32 len, t_s8 delim); |
| |
| /******************************************************** |
| Global Variables |
| ********************************************************/ |
| |
| /******************************************************** |
| Local Functions |
| ********************************************************/ |
| |
| /** |
| * @brief Remove unwanted spaces, tabs from a line |
| * |
| * @param data A pointer to the starting of the line |
| * @return NA |
| */ |
| static void |
| profile_param_polish(char *data) |
| { |
| t_u8 i, j, len = 0; |
| char *ptr; |
| ptr = strrchr(data, '\r'); |
| if (ptr == NULL) { |
| ptr = strrchr(data, '\n'); |
| if (ptr == NULL) { |
| return; |
| } |
| } |
| len = ptr - data; |
| for (i = 0; i < len; i++) { |
| if ((*(data + i) == ' ') || (*(data + i) == '\t')) { |
| for (j = i; j < len; j++) { |
| data[j] = data[j + 1]; |
| } |
| i--; |
| len--; |
| } |
| } |
| } |
| |
| static int |
| ascii_value(char letter) |
| { |
| if (letter >= '0' && letter <= '9') |
| return letter - '0'; |
| if (letter >= 'a' && letter <= 'f') |
| return letter - 'a' + 10; |
| if (letter >= 'A' && letter <= 'F') |
| return letter - 'A' + 10; |
| return -1; |
| } |
| |
| static int |
| twodigit_ascii(const char *nibble) |
| { |
| int a, b; |
| a = ascii_value(*nibble++); |
| if (a < 0) |
| return -1; |
| b = ascii_value(*nibble++); |
| if (b < 0) |
| return -1; |
| return (a << 4) | b; |
| } |
| |
| /** |
| * @brief Read a network block from the profile configuration file |
| * |
| * @param fp file pointer of the configuration file |
| * @param p_head profile head |
| * @return MLAN_STATUS_SUCCESS |
| */ |
| static int |
| profile_read_block(FILE * fp, profile_entry_t *p_head) |
| { |
| char line[0x100]; |
| char *ptr, *eptr; |
| t_u8 key_cnt = 0; |
| t_u8 i, wep_len; |
| int byte; |
| int tmpIdx; |
| unsigned int mac[ETH_ALEN]; |
| |
| while (fgets(line, sizeof(line), fp)) { |
| /* call function to remove spaces, tabs */ |
| profile_param_polish(line); |
| |
| if (strstr(line, "}") != NULL) { |
| ptr = strstr(line, "}"); |
| /* end of network */ |
| break; |
| |
| } else if (line[0] == '#') { |
| /* comments go ahead */ |
| continue; |
| |
| } else if (strstr(line, "bssid=") != NULL) { |
| ptr = strstr(line, "bssid="); |
| ptr = ptr + strlen("bssid="); |
| sscanf(ptr, "%2x:%2x:%2x:%2x:%2x:%2x", |
| mac + 0, mac + 1, mac + 2, mac + 3, mac + 4, |
| mac + 5); |
| for (tmpIdx = 0; (unsigned int)tmpIdx < NELEMENTS(mac); |
| tmpIdx++) { |
| p_head->bssid[tmpIdx] = (t_u8)mac[tmpIdx]; |
| } |
| |
| } else if (strstr(line, "ssid=") != NULL) { |
| |
| ptr = strstr(line, "ssid="); |
| ptr = ptr + strlen("ssid="); |
| eptr = strrchr(ptr + 1, '"'); |
| |
| if ((*ptr != '"') || (strrchr(ptr + 1, '"') == NULL)) { |
| |
| fprintf(stderr, "ssid not within quotes\n"); |
| break; |
| } |
| |
| p_head->ssid_len = |
| MIN(IW_ESSID_MAX_SIZE, eptr - ptr - 1); |
| strncpy((char *)p_head->ssid, ptr + 1, |
| p_head->ssid_len); |
| p_head->ssid[p_head->ssid_len] = '\0'; |
| |
| } else if (strstr(line, "psk=") != NULL) { |
| ptr = strstr(line, "psk="); |
| ptr = ptr + strlen("psk="); |
| if (*ptr != '"') { |
| p_head->psk_config = 1; |
| strncpy((char *)p_head->psk, ptr, KEY_LEN); |
| } else { |
| eptr = strrchr(ptr + 1, '"'); |
| if (eptr == NULL) { |
| fprintf(stderr, |
| "passphrase not within quotes.\n"); |
| break; |
| } |
| p_head->passphrase_len = |
| MIN(PHRASE_LEN, eptr - ptr - 1); |
| strncpy((char *)p_head->passphrase, ptr + 1, |
| p_head->passphrase_len); |
| } |
| } else if (strstr(line, "wep_key") != NULL) { |
| ptr = strstr(line, "wep_key"); |
| ptr = ptr + strlen("wep_key"); |
| key_cnt = atoi(ptr); |
| ptr++; |
| if (*ptr != '=') { |
| fprintf(stderr, |
| "invalid wep_key, missing =.\n"); |
| break; |
| } |
| eptr = strrchr(ptr + 1, '\r'); |
| if (eptr == NULL) { |
| eptr = strrchr(ptr + 1, '\n'); |
| if (eptr == NULL) { |
| fprintf(stderr, |
| "missing EOL from the wep_key config\n"); |
| break; |
| } |
| } |
| ptr++; |
| if (*ptr == '"') { |
| eptr = strrchr(ptr + 1, '"'); |
| if (eptr == NULL) { |
| fprintf(stderr, |
| "wep key does not end with quote.\n"); |
| break; |
| } |
| *eptr = '\0'; |
| p_head->wep_key_len[key_cnt] = eptr - ptr - 1; |
| strncpy((char *)p_head->wep_key[key_cnt], |
| ptr + 1, p_head->wep_key_len[key_cnt]); |
| } else { |
| while (*eptr == '\r' || *eptr == '\n') |
| eptr--; |
| *(eptr + 1) = '\0'; |
| wep_len = strlen(ptr); |
| if (wep_len & 0x01) { |
| fprintf(stderr, |
| "incorrect wep key %s.\n", ptr); |
| break; |
| } |
| p_head->wep_key_len[key_cnt] = wep_len / 2; |
| for (i = 0; i < wep_len / 2; i++) { |
| byte = twodigit_ascii(ptr); |
| if (byte == -1) { |
| fprintf(stderr, |
| "incorrect wep key %s.\n", |
| ptr); |
| break; |
| } |
| *(p_head->wep_key[key_cnt] + i) = |
| (t_u8)byte; |
| ptr += 2; |
| } |
| } |
| } else if (strstr(line, "key_mgmt=") != NULL) { |
| ptr = strstr(line, "key_mgmt="); |
| ptr = ptr + strlen("key_mgmt="); |
| eptr = strstr(ptr, "WPA-EAP"); |
| if (eptr != NULL) { |
| p_head->key_mgmt |= |
| PROFILE_DB_KEY_MGMT_IEEE8021X; |
| } |
| eptr = strstr(ptr, "WPA-PSK"); |
| if (eptr != NULL) { |
| p_head->key_mgmt |= PROFILE_DB_KEY_MGMT_PSK; |
| } |
| eptr = strstr(ptr, "FT-EAP"); |
| if (eptr != NULL) { |
| p_head->key_mgmt |= |
| PROFILE_DB_KEY_MGMT_FT_IEEE8021X; |
| } |
| eptr = strstr(ptr, "FT-PSK"); |
| if (eptr != NULL) { |
| p_head->key_mgmt |= PROFILE_DB_KEY_MGMT_FT_PSK; |
| } |
| eptr = strstr(ptr, "WPA-EAP-SHA256"); |
| if (eptr != NULL) { |
| p_head->key_mgmt |= |
| PROFILE_DB_KEY_MGMT_SHA256_IEEE8021X; |
| } |
| eptr = strstr(ptr, "WPA-PSK-SHA256"); |
| if (eptr != NULL) { |
| p_head->key_mgmt |= |
| PROFILE_DB_KEY_MGMT_SHA256_PSK; |
| } |
| eptr = strstr(ptr, "CCKM"); |
| if (eptr != NULL) { |
| p_head->key_mgmt |= PROFILE_DB_KEY_MGMT_CCKM; |
| } |
| eptr = strstr(ptr, "NONE"); |
| if (eptr != NULL) { |
| p_head->key_mgmt |= PROFILE_DB_KEY_MGMT_NONE; |
| } |
| } else if (strstr(line, "proto=") != NULL) { |
| ptr = strstr(line, "proto="); |
| ptr = ptr + strlen("proto="); |
| eptr = strstr(ptr, "WPA"); |
| if (eptr != NULL) { |
| p_head->protocol |= PROFILE_DB_PROTO_WPA; |
| } |
| |
| eptr = strstr(ptr, "RSN"); |
| if (eptr != NULL) { |
| p_head->protocol |= PROFILE_DB_PROTO_WPA2; |
| |
| } |
| } else if (strstr(line, "pairwise=") != NULL) { |
| ptr = strstr(line, "pairwise="); |
| ptr = ptr + strlen("pairwise="); |
| eptr = strstr(ptr, "CCMP"); |
| if (eptr != NULL) { |
| p_head->pairwise_cipher |= |
| PROFILE_DB_CIPHER_CCMP; |
| } |
| eptr = strstr(ptr, "TKIP"); |
| if (eptr != NULL) { |
| p_head->pairwise_cipher |= |
| PROFILE_DB_CIPHER_TKIP; |
| } |
| } else if (strstr(line, "groupwise=") != NULL) { |
| ptr = strstr(line, "groupwise="); |
| ptr = ptr + strlen("groupwise="); |
| eptr = strstr(ptr, "CCMP"); |
| if (eptr != NULL) { |
| p_head->groupwise_cipher |= |
| PROFILE_DB_CIPHER_CCMP; |
| } |
| eptr = strstr(ptr, "TKIP"); |
| if (eptr != NULL) { |
| p_head->groupwise_cipher |= |
| PROFILE_DB_CIPHER_TKIP; |
| } |
| } else if (strstr(line, "wep_tx_keyidx=") != NULL) { |
| ptr = strstr(line, "wep_tx_keyidx="); |
| ptr = ptr + strlen("wep_tx_keyidx="); |
| p_head->wep_key_idx = atoi(ptr); |
| } else if (strstr(line, "roaming=") != NULL) { |
| ptr = strstr(line, "roaming="); |
| ptr = ptr + strlen("roaming="); |
| p_head->roaming = atoi(ptr); |
| } else if (strstr(line, "ccx=") != NULL) { |
| ptr = strstr(line, "ccx="); |
| ptr = ptr + strlen("ccx="); |
| p_head->ccx = atoi(ptr); |
| } else if (strstr(line, "mode=") != NULL) { |
| ptr = strstr(line, "mode="); |
| ptr = ptr + strlen("mode="); |
| p_head->mode = atoi(ptr); |
| } |
| } |
| return MLAN_STATUS_SUCCESS; |
| } |
| |
| /** |
| * @brief Issue profile command to add new profile to FW |
| * |
| * @param filename Name of profile file |
| * |
| * @return MLAN_STATUS_SUCCESS--success, otherwise--fail |
| */ |
| static int |
| profile_read_download(char *filename) |
| { |
| int ret = MLAN_STATUS_SUCCESS; |
| struct ifreq ifr; |
| t_u8 *buffer = NULL, *pos = NULL; |
| int i = 0; |
| t_u16 temp, tempc; |
| t_u32 cmd_len = 0, cmd_header_len; |
| struct eth_priv_cmd *cmd = NULL; |
| HostCmd_DS_GEN *hostcmd = NULL; |
| profile_entry_t *p_head = NULL; |
| FILE *fp; |
| char line[0x100]; |
| |
| fp = fopen(filename, "r"); |
| if (fp == NULL) { |
| perror("fopen"); |
| fprintf(stderr, "Cannot open file %s\n", filename); |
| ret = MLAN_STATUS_FAILURE; |
| goto done; |
| } |
| |
| cmd_header_len = strlen(CMD_MARVELL) + strlen(HOSTCMD); |
| |
| buffer = (t_u8 *)malloc(BUFFER_LENGTH); |
| if (buffer == NULL) { |
| fprintf(stderr, "Cannot alloc memory\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| memset(buffer, 0, BUFFER_LENGTH); |
| |
| cmd = (struct eth_priv_cmd *)malloc(sizeof(struct eth_priv_cmd)); |
| if (!cmd) { |
| printf("ERR:Cannot allocate buffer for command!\n"); |
| free(buffer); |
| ret = ENOMEM; |
| goto done; |
| } |
| |
| /* Fill up buffer */ |
| #ifdef USERSPACE_32BIT_OVER_KERNEL_64BIT |
| memset(cmd, 0, sizeof(struct eth_priv_cmd)); |
| memcpy(&cmd->buf, &buffer, sizeof(buffer)); |
| #else |
| cmd->buf = buffer; |
| #endif |
| cmd->used_len = 0; |
| cmd->total_len = BUFFER_LENGTH; |
| |
| /* buffer = MRVL_CMD<cmd> */ |
| strncpy((char *)buffer, CMD_MARVELL, strlen(CMD_MARVELL)); |
| strncpy((char *)buffer + strlen(CMD_MARVELL), HOSTCMD, strlen(HOSTCMD)); |
| |
| /* buffer = MRVL_CMD<cmd><hostcmd_size><HostCmd_DS_GEN><CMD_DS> */ |
| hostcmd = (HostCmd_DS_GEN *)(buffer + cmd_header_len + sizeof(t_u32)); |
| |
| pos = (t_u8 *)hostcmd; |
| while (fgets(line, sizeof(line), fp)) { |
| /* call function to remove spaces, tabs */ |
| profile_param_polish(line); |
| if ((strstr(line, "network={") == NULL) || (line[0] == '#')) { |
| continue; |
| } |
| /* |
| * Memory allocation of every network block |
| */ |
| p_head = (profile_entry_t *)malloc(sizeof(profile_entry_t)); |
| if (p_head == NULL) { |
| fprintf(stderr, "Memory error.\n"); |
| ret = MLAN_STATUS_FAILURE; |
| goto done; |
| } |
| memset(p_head, 0x00, sizeof(profile_entry_t)); |
| |
| ret = profile_read_block(fp, p_head); |
| if (ret || p_head->ssid_len == 0) { |
| free(p_head); |
| continue; |
| } |
| |
| /* |
| * Put all the ssid parameters in the buffer |
| */ |
| memset(pos, 0, |
| (BUFFER_LENGTH - cmd_header_len - sizeof(t_u32))); |
| |
| /* Cmd Header : Command */ |
| hostcmd->command = cpu_to_le16(HostCmd_CMD_PROFILE_DB); |
| cmd_len = sizeof(HostCmd_DS_GEN); |
| |
| /* set action as set */ |
| tempc = cpu_to_le16(HostCmd_ACT_GEN_SET); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| |
| /* ssid */ |
| tempc = cpu_to_le16(TLV_TYPE_SSID); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| temp = strlen((char *)p_head->ssid); |
| tempc = cpu_to_le16(temp); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| memcpy((void *)(pos + cmd_len), p_head->ssid, temp); |
| cmd_len += temp; |
| |
| if (memcmp(p_head->bssid, "\x00\x00\x00\x00\x00\x00", ETH_ALEN)) { |
| /* bssid */ |
| tempc = cpu_to_le16(TLV_TYPE_BSSID); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| temp = ETH_ALEN; |
| tempc = cpu_to_le16(temp); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| memcpy((void *)(pos + cmd_len), p_head->bssid, temp); |
| cmd_len += temp; |
| } |
| |
| /* proto */ |
| if (p_head->protocol == 0) { |
| p_head->protocol = 0xFFFF; |
| } |
| |
| tempc = cpu_to_le16(TLV_TYPE_PROTO); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| temp = 2; |
| tempc = cpu_to_le16(temp); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| memcpy((pos + cmd_len), &(p_head->protocol), temp); |
| cmd_len += temp; |
| |
| /* key_mgmt */ |
| if (p_head->key_mgmt == 0) { |
| p_head->key_mgmt = 0xFFFF; |
| } |
| |
| tempc = cpu_to_le16(TLV_TYPE_AKMP); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| temp = 2; |
| tempc = cpu_to_le16(temp); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| memcpy((pos + cmd_len), &(p_head->key_mgmt), temp); |
| cmd_len += temp; |
| |
| /* pairwise */ |
| if (p_head->pairwise_cipher == 0) { |
| p_head->pairwise_cipher = 0xFF; |
| } |
| |
| /* groupwise */ |
| if (p_head->groupwise_cipher == 0) { |
| p_head->groupwise_cipher = 0xFF; |
| } |
| |
| tempc = cpu_to_le16(TLV_TYPE_CIPHER); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| temp = 2; |
| tempc = cpu_to_le16(temp); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| pos[cmd_len] = p_head->pairwise_cipher; |
| cmd_len += 1; |
| pos[cmd_len] = p_head->groupwise_cipher; |
| cmd_len += 1; |
| |
| if (p_head->passphrase_len) { |
| /* passphrase */ |
| tempc = cpu_to_le16(TLV_TYPE_PASSPHRASE); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| temp = p_head->passphrase_len; |
| tempc = cpu_to_le16(temp); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| memcpy((void *)(pos + cmd_len), p_head->passphrase, |
| temp); |
| cmd_len += temp; |
| } |
| |
| if (p_head->psk_config) { |
| /* psk method */ |
| tempc = cpu_to_le16(TLV_TYPE_PMK); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| temp = 32; |
| tempc = cpu_to_le16(temp); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| memcpy((void *)(pos + cmd_len), p_head->psk, temp); |
| cmd_len += temp; |
| } |
| |
| for (i = 0; i < WEP_KEY_CNT; i++) { |
| if (p_head->wep_key_len[i]) { |
| /* TAG_WEP_KEY */ |
| tempc = cpu_to_le16(TLV_TYPE_WEP_KEY); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| /* wep_key_len + sizeof(keyIndex) + sizeof(IsDefault) */ |
| tempc = cpu_to_le16(p_head->wep_key_len[i] + 1 + |
| 1); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| *(pos + cmd_len) = i; |
| cmd_len += 1; |
| *(pos + cmd_len) = (i == p_head->wep_key_idx); |
| cmd_len += 1; |
| temp = p_head->wep_key_len[i]; |
| memcpy((void *)(pos + cmd_len), |
| p_head->wep_key[i], temp); |
| cmd_len += temp; |
| } |
| } |
| |
| if (p_head->roaming | p_head->ccx) { |
| tempc = cpu_to_le16(TLV_TYPE_OFFLOAD_ENABLE); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| temp = 2; |
| tempc = cpu_to_le16(temp); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| tempc = 0; |
| if (p_head->roaming) |
| tempc |= PROFILE_DB_FEATURE_ROAMING; |
| if (p_head->ccx) |
| tempc |= PROFILE_DB_FEATURE_CCX; |
| tempc = cpu_to_le16(tempc); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += temp; |
| } |
| |
| /* Put buffer length */ |
| memcpy(buffer + cmd_header_len, &cmd_len, sizeof(t_u32)); |
| hostcmd->size = cpu_to_le16(cmd_len); |
| |
| fprintf(stdout, "Downloading profile: %s ... ", p_head->ssid); |
| fflush(stdout); |
| |
| /* Initialize the ifr structure */ |
| memset(&ifr, 0, sizeof(ifr)); |
| strncpy(ifr.ifr_ifrn.ifrn_name, dev_name, strlen(dev_name)); |
| ifr.ifr_ifru.ifru_data = (void *)cmd; |
| /* Perform ioctl */ |
| if (ioctl(sockfd, MLAN_ETH_PRIV, &ifr)) { |
| perror("ioctl[profiledb ioctl]"); |
| printf("ERR:Command sending failed!\n"); |
| ret = -EFAULT; |
| goto done; |
| } else { |
| hostcmd->result = le16_to_cpu(hostcmd->result); |
| if (hostcmd->result != 0) { |
| printf("hostcmd : profiledb ioctl failure, code %d\n", hostcmd->result); |
| ret = -EFAULT; |
| goto done; |
| } |
| } |
| |
| fprintf(stdout, "done.\n"); |
| |
| if (p_head) |
| free(p_head); |
| } |
| |
| done: |
| if (buffer) |
| free(buffer); |
| if (cmd) |
| free(cmd); |
| if (fp) |
| fclose(fp); |
| return ret; |
| } |
| |
| /******************************************************** |
| Global Functions |
| ********************************************************/ |
| |
| /** |
| * @brief Process sub command |
| * |
| * @param sub_cmd Sub command |
| * @param num_sub_cmds Number of subcommands |
| * @param argc Number of arguments |
| * @param argv A pointer to arguments array |
| * |
| * @return MLAN_STATUS_SUCCESS--success, otherwise--fail |
| */ |
| int |
| process_sub_cmd(sub_cmd_exec_t *sub_cmd, int num_sub_cmds, |
| int argc, char *argv[]) |
| { |
| int idx; |
| boolean invalid_cmd = TRUE; |
| int ret = MLAN_STATUS_FAILURE; |
| |
| if (argv[3]) { |
| for (idx = 0; idx < num_sub_cmds; idx++) { |
| if (strncmp(argv[3], |
| sub_cmd[idx].str, |
| sub_cmd[idx].match_len) == 0) { |
| invalid_cmd = FALSE; |
| ret = sub_cmd[idx].callback(argc - 4, argv + 4); |
| break; |
| } |
| } |
| } |
| |
| if (invalid_cmd) { |
| printf("\nUnknown %s command. Valid subcmds:\n", argv[2]); |
| for (idx = 0; idx < num_sub_cmds; idx++) { |
| if (sub_cmd[idx].display) { |
| printf(" - %s\n", sub_cmd[idx].str); |
| } |
| } |
| printf("\n"); |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * @brief select the table's regclass |
| * |
| * @param table_str Reg channel table type |
| * @param pTable Pointer to the Reg channel table |
| * |
| * @return TRUE if success otherwise FALSE |
| */ |
| boolean |
| reg_class_table_select(char *table_str, reg_chan_table_e *pTable) |
| { |
| boolean retval = TRUE; |
| |
| if (strcmp(table_str, "user") == 0) { |
| *pTable = REGTABLE_USER; |
| } else if ((strcmp(table_str, "md") == 0) || |
| (strncmp(table_str, "multidomain", 5) == 0)) { |
| *pTable = REGTABLE_MULTIDOMAIN; |
| } else if (strcmp(table_str, "ess") == 0) { |
| *pTable = REGTABLE_ESS; |
| } else if (strcmp(table_str, "default") == 0) { |
| *pTable = REGTABLE_DEFAULT; |
| } else { /* If no option/wrong option set to default */ |
| *pTable = REGTABLE_DEFAULT; |
| } |
| |
| return retval; |
| } |
| |
| /** |
| * @brief Issue a measurement timing command |
| * |
| * @param argc number of arguments |
| * @param argv A pointer to arguments array |
| * |
| * @return MLAN_STATUS_SUCCESS--success, otherwise--fail |
| */ |
| int |
| process_measurement(int argc, char *argv[]) |
| { |
| int ret = 0; |
| struct ifreq ifr; |
| t_u8 *buffer = NULL, *pos = NULL; |
| t_u32 cmd_len = 0, cmd_header_len; |
| struct eth_priv_cmd *cmd = NULL; |
| HostCmd_DS_GEN *hostcmd = NULL; |
| HostCmd_DS_MEASUREMENT_Timing *timing_cmd = NULL; |
| MrvlIETypes_MeasTiming_t *timing_tlv = NULL; |
| int idx, rsp_len; |
| t_u8 sel = 0; |
| t_u16 tlv_len = 0; |
| timing_sel_t sel_str[] = { {"disconnected", 1}, |
| {"adhoc", 1}, |
| {"fullpower", 1}, |
| {"ieeeps", 1}, |
| {"periodic", 1} |
| }; |
| |
| if ((argc < 4) || strncmp(argv[3], "timing", |
| MAX(strlen("timing"), strlen(argv[3])))) { |
| printf("\nUnknown %s command. Valid subcmd: timing \n", |
| argv[2]); |
| return MLAN_STATUS_FAILURE; |
| } |
| |
| cmd_header_len = strlen(CMD_MARVELL) + strlen(HOSTCMD); |
| cmd_len = S_DS_GEN + sizeof(t_u16); |
| |
| buffer = (t_u8 *)malloc(BUFFER_LENGTH); |
| if (buffer == NULL) { |
| fprintf(stderr, "Cannot alloc memory\n"); |
| return -ENOMEM; |
| } |
| memset(buffer, 0, BUFFER_LENGTH); |
| |
| cmd = (struct eth_priv_cmd *)malloc(sizeof(struct eth_priv_cmd)); |
| if (!cmd) { |
| printf("ERR:Cannot allocate buffer for command!\n"); |
| free(buffer); |
| return -ENOMEM; |
| } |
| |
| /* Fill up buffer */ |
| #ifdef USERSPACE_32BIT_OVER_KERNEL_64BIT |
| memset(cmd, 0, sizeof(struct eth_priv_cmd)); |
| memcpy(&cmd->buf, &buffer, sizeof(buffer)); |
| #else |
| cmd->buf = buffer; |
| #endif |
| cmd->used_len = 0; |
| cmd->total_len = BUFFER_LENGTH; |
| |
| /* buffer = MRVL_CMD<cmd> */ |
| strncpy((char *)buffer, CMD_MARVELL, strlen(CMD_MARVELL)); |
| strncpy((char *)buffer + strlen(CMD_MARVELL), HOSTCMD, strlen(HOSTCMD)); |
| |
| /* buffer = MRVL_CMD<cmd><hostcmd_size><HostCmd_DS_GEN><CMD_DS> */ |
| hostcmd = (HostCmd_DS_GEN *)(buffer + cmd_header_len + sizeof(t_u32)); |
| hostcmd->command = cpu_to_le16(HostCmd_CMD_MEASUREMENT_TIMING_CONFIG); |
| hostcmd->size = cmd_len; |
| hostcmd->seq_num = 0; |
| hostcmd->result = 0; |
| |
| /* Point after host command header */ |
| pos = (t_u8 *)hostcmd + S_DS_GEN; |
| |
| timing_cmd = (HostCmd_DS_MEASUREMENT_Timing *)pos; |
| timing_cmd->action = cpu_to_le16(HostCmd_ACT_GEN_GET); |
| timing_tlv = (MrvlIETypes_MeasTiming_t *)timing_cmd->tlv_buffer; |
| |
| if (argc == 7) { |
| timing_cmd->action = cpu_to_le16(HostCmd_ACT_GEN_SET); |
| timing_tlv->header.type = |
| cpu_to_le16(TLV_TYPE_MEASUREMENT_TIMING); |
| timing_tlv->header.len = |
| cpu_to_le16(sizeof(MrvlIETypes_MeasTiming_t) |
| - sizeof(timing_tlv->header)); |
| |
| for (idx = 1; (unsigned int)idx < NELEMENTS(sel_str); idx++) { |
| if (strncmp |
| (argv[4], sel_str[idx].str, |
| sel_str[idx].match_len) == 0) { |
| sel = idx + 1; |
| break; |
| } |
| } |
| |
| if (idx == NELEMENTS(sel_str)) { |
| printf("Wrong argument for mode selected \"%s\"\n", |
| argv[4]); |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| timing_tlv->mode = cpu_to_le32(sel); |
| timing_tlv->max_off_channel = cpu_to_le32(atoi(argv[5])); |
| timing_tlv->max_on_channel = cpu_to_le32(atoi(argv[6])); |
| cmd_len += sizeof(MrvlIETypes_MeasTiming_t); |
| } |
| |
| /* Put buffer length */ |
| memcpy(buffer + cmd_header_len, &cmd_len, sizeof(t_u32)); |
| hostcmd->size = cpu_to_le16(cmd_len); |
| |
| /* Initialize the ifr structure */ |
| memset(&ifr, 0, sizeof(ifr)); |
| strncpy(ifr.ifr_ifrn.ifrn_name, dev_name, strlen(dev_name)); |
| ifr.ifr_ifru.ifru_data = (void *)cmd; |
| |
| /* Perform ioctl */ |
| if (ioctl(sockfd, MLAN_ETH_PRIV, &ifr)) { |
| perror("ioctl[measurement timing ioctl]"); |
| printf("ERR:Command sending failed!\n"); |
| ret = -EFAULT; |
| goto done; |
| } |
| |
| printf("--------------------------------------------------\n"); |
| printf("%44s\n", "Measurement Timing Profiles (in ms)"); |
| printf("--------------------------------------------------\n"); |
| printf(" Profile | MaxOffChannel | MaxOnChannel\n"); |
| printf("--------------------------------------------------\n"); |
| |
| /* Changed to TLV parsing */ |
| rsp_len = le16_to_cpu(hostcmd->size); |
| rsp_len -= (S_DS_GEN + sizeof(t_u16)); |
| pos = (t_u8 *)hostcmd + S_DS_GEN + sizeof(t_u16); |
| while ((unsigned int)rsp_len > sizeof(MrvlIEtypesHeader_t)) { |
| switch (le16_to_cpu(*(t_u16 *)(pos))) { |
| case TLV_TYPE_MEASUREMENT_TIMING: |
| timing_tlv = (MrvlIETypes_MeasTiming_t *)pos; |
| tlv_len = le16_to_cpu(timing_tlv->header.len); |
| printf("%15s | %14d | %13d\n", |
| sel_str[le32_to_cpu(timing_tlv->mode) - 1].str, |
| (int)le32_to_cpu(timing_tlv->max_off_channel), |
| (int)le32_to_cpu(timing_tlv->max_on_channel)); |
| break; |
| } |
| pos += tlv_len + sizeof(MrvlIEtypesHeader_t); |
| rsp_len -= tlv_len + sizeof(MrvlIEtypesHeader_t); |
| rsp_len = (rsp_len > 0) ? rsp_len : 0; |
| } |
| printf("\n"); |
| |
| done: |
| if (buffer) |
| free(buffer); |
| if (cmd) |
| free(cmd); |
| return ret; |
| } |
| |
| /** |
| * @brief Issue a profile command |
| * |
| * @param argc number of arguments |
| * @param argv A pointer to arguments array |
| * |
| * @return MLAN_STATUS_SUCCESS--success, otherwise--fail |
| */ |
| int |
| process_profile_entry(int argc, char *argv[]) |
| { |
| int ret = MLAN_STATUS_SUCCESS; |
| struct ifreq ifr; |
| unsigned int mac[ETH_ALEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; |
| int idx; |
| t_u16 temp, tempc; |
| char *ssid = NULL; |
| t_u8 *buffer = NULL, *pos = NULL; |
| t_u32 cmd_len = 0, cmd_header_len; |
| struct eth_priv_cmd *cmd = NULL; |
| HostCmd_DS_GEN *hostcmd = NULL; |
| |
| cmd_header_len = strlen(CMD_MARVELL) + strlen(HOSTCMD); |
| |
| if (argc < 4) { |
| fprintf(stderr, "Invalid number of argument!\n"); |
| ret = MLAN_STATUS_FAILURE; |
| goto done; |
| } |
| |
| if (!strncmp(argv[3], "delete", sizeof("delete"))) { |
| if (argc > 4) { |
| if (strncmp(argv[4], "bssid=", strlen("bssid=")) == 0) { |
| /* "bssid" token string handler */ |
| sscanf(argv[4] + strlen("bssid="), |
| "%2x:%2x:%2x:%2x:%2x:%2x", mac + 0, |
| mac + 1, mac + 2, mac + 3, mac + 4, |
| mac + 5); |
| } else if (strncmp(argv[4], "ssid=", strlen("ssid=")) == |
| 0) { |
| /* "ssid" token string handler */ |
| ssid = argv[4] + strlen("ssid="); |
| } else { |
| printf("Error: missing required option for command (ssid, bssid)\n"); |
| ret = -ENOMEM; |
| goto done; |
| } |
| printf("Driver profile delete request\n"); |
| } else { |
| printf("Error: missing required option for command (ssid, bssid)\n"); |
| ret = -ENOMEM; |
| goto done; |
| } |
| } else if (!strncmp(argv[3], "flush", sizeof("flush"))) { |
| printf("Driver profile flush request\n"); |
| } else { |
| ret = profile_read_download(argv[3]); |
| goto done; |
| } |
| |
| buffer = (t_u8 *)malloc(BUFFER_LENGTH); |
| if (buffer == NULL) { |
| fprintf(stderr, "Cannot alloc memory\n"); |
| ret = -ENOMEM; |
| goto done; |
| } |
| memset(buffer, 0, BUFFER_LENGTH); |
| |
| cmd = (struct eth_priv_cmd *)malloc(sizeof(struct eth_priv_cmd)); |
| if (!cmd) { |
| printf("ERR:Cannot allocate buffer for command!\n"); |
| ret = -ENOMEM; |
| goto done; |
| } |
| |
| /* Fill up buffer */ |
| #ifdef USERSPACE_32BIT_OVER_KERNEL_64BIT |
| memset(cmd, 0, sizeof(struct eth_priv_cmd)); |
| memcpy(&cmd->buf, &buffer, sizeof(buffer)); |
| #else |
| cmd->buf = buffer; |
| #endif |
| cmd->used_len = 0; |
| cmd->total_len = BUFFER_LENGTH; |
| |
| /* buffer = MRVL_CMD<cmd> */ |
| strncpy((char *)buffer, CMD_MARVELL, strlen(CMD_MARVELL)); |
| strncpy((char *)buffer + strlen(CMD_MARVELL), HOSTCMD, strlen(HOSTCMD)); |
| |
| /* buffer = MRVL_CMD<cmd><hostcmd_size><HostCmd_DS_GEN><CMD_DS> */ |
| hostcmd = (HostCmd_DS_GEN *)(buffer + cmd_header_len + sizeof(t_u32)); |
| hostcmd->command = cpu_to_le16(HostCmd_CMD_PROFILE_DB); |
| hostcmd->size = 0; |
| hostcmd->seq_num = 0; |
| hostcmd->result = 0; |
| |
| /* Point after host command header */ |
| pos = (t_u8 *)hostcmd; |
| cmd_len = S_DS_GEN; |
| |
| /* set action as del */ |
| tempc = cpu_to_le16(HostCmd_ACT_GEN_REMOVE); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| |
| /* ssid */ |
| if (ssid) { |
| printf("For ssid %s\n", ssid); |
| tempc = cpu_to_le16(TLV_TYPE_SSID); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| temp = strlen((char *)ssid); |
| tempc = cpu_to_le16(temp); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| memcpy((void *)(pos + cmd_len), ssid, temp); |
| cmd_len += temp; |
| } else { |
| /* bssid */ |
| if (mac[0] != 0xFF) { |
| printf("For bssid %02x:%02x:%02x:%02x:%02x:%02x\n", |
| mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); |
| } |
| tempc = cpu_to_le16(TLV_TYPE_BSSID); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| temp = ETH_ALEN; |
| tempc = cpu_to_le16(temp); |
| memcpy((pos + cmd_len), &tempc, sizeof(t_u16)); |
| cmd_len += 2; |
| for (idx = 0; (unsigned int)idx < NELEMENTS(mac); idx++) { |
| pos[cmd_len + idx] = (t_u8)mac[idx]; |
| } |
| cmd_len += temp; |
| } |
| |
| /* Put buffer length */ |
| memcpy(buffer + cmd_header_len, &cmd_len, sizeof(t_u32)); |
| hostcmd->size = cpu_to_le16(cmd_len); |
| |
| /* Initialize the ifr structure */ |
| memset(&ifr, 0, sizeof(ifr)); |
| strncpy(ifr.ifr_ifrn.ifrn_name, dev_name, strlen(dev_name)); |
| ifr.ifr_ifru.ifru_data = (void *)cmd; |
| /* Perform ioctl */ |
| if (ioctl(sockfd, MLAN_ETH_PRIV, &ifr)) { |
| perror("ioctl[profiledb ioctl]"); |
| printf("ERR:Command sending failed!\n"); |
| ret = -EFAULT; |
| goto done; |
| } else { |
| hostcmd->result = le16_to_cpu(hostcmd->result); |
| if (hostcmd->result != 0) { |
| printf("hostcmd : profiledb ioctl failure, code %d\n", |
| hostcmd->result); |
| ret = -EFAULT; |
| goto done; |
| } |
| } |
| |
| done: |
| if (buffer) |
| free(buffer); |
| if (cmd) |
| free(cmd); |
| return ret; |
| } |
| |
| /** |
| * @brief Issue a chan report command |
| * |
| * @param argc number of arguments |
| * @param argv A pointer to arguments array |
| * |
| * @return MLAN_STATUS_SUCCESS--success, otherwise--fail |
| */ |
| int |
| process_chanrpt(int argc, char *argv[]) |
| { |
| int ret = MLAN_STATUS_SUCCESS; |
| int respLen; |
| struct ifreq ifr; |
| t_u8 *buffer = NULL, *pos = NULL; |
| t_u32 cmd_len = 0, cmd_header_len; |
| struct eth_priv_cmd *cmd = NULL; |
| t_u8 *pByte; |
| t_u8 numBins; |
| t_u8 idx; |
| MrvlIEtypes_Data_t *pTlvHdr; |
| HostCmd_DS_GEN *hostcmd; |
| HostCmd_DS_CHAN_RPT_RSP *pChanRptRsp; |
| HostCmd_DS_CHAN_RPT_REQ *pChanRptReq; |
| |
| MrvlIEtypes_ChanRptBcn_t *pBcnRpt; |
| MrvlIEtypes_ChanRptChanLoad_t *pLoadRpt; |
| MrvlIEtypes_ChanRptNoiseHist_t *pNoiseRpt; |
| MrvlIEtypes_ChanRpt11hBasic_t *pBasicRpt; |
| MrvlIEtypes_ChanRptFrame_t *pFrameRpt; |
| |
| cmd_header_len = strlen(CMD_MARVELL) + strlen(HOSTCMD); |
| |
| buffer = (t_u8 *)malloc(BUFFER_LENGTH); |
| if (buffer == NULL) { |
| fprintf(stderr, "Cannot alloc memory\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| memset(buffer, 0, BUFFER_LENGTH); |
| |
| cmd = (struct eth_priv_cmd *)malloc(sizeof(struct eth_priv_cmd)); |
| if (!cmd) { |
| printf("ERR:Cannot allocate buffer for command!\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| |
| /* Fill up buffer */ |
| #ifdef USERSPACE_32BIT_OVER_KERNEL_64BIT |
| memset(cmd, 0, sizeof(struct eth_priv_cmd)); |
| memcpy(&cmd->buf, &buffer, sizeof(buffer)); |
| #else |
| cmd->buf = buffer; |
| #endif |
| cmd->used_len = 0; |
| cmd->total_len = BUFFER_LENGTH; |
| |
| /* buffer = MRVL_CMD<cmd> */ |
| strncpy((char *)buffer, CMD_MARVELL, strlen(CMD_MARVELL)); |
| strncpy((char *)buffer + strlen(CMD_MARVELL), HOSTCMD, strlen(HOSTCMD)); |
| |
| /* buffer = MRVL_CMD<cmd><hostcmd_size><HostCmd_DS_GEN><CMD_DS> */ |
| hostcmd = (HostCmd_DS_GEN *)(buffer + cmd_header_len + sizeof(t_u32)); |
| |
| /* Point after host command header */ |
| pos = (t_u8 *)hostcmd + S_DS_GEN; |
| |
| cmd_len = S_DS_GEN + sizeof(HostCmd_DS_CHAN_RPT_REQ); |
| |
| hostcmd->command = cpu_to_le16(HostCmd_CMD_CHAN_REPORT_REQUEST); |
| hostcmd->size = cpu_to_le16(cmd_len); |
| hostcmd->seq_num = 0; |
| hostcmd->result = 0; |
| |
| pChanRptReq = (HostCmd_DS_CHAN_RPT_REQ *)pos; |
| pChanRptRsp = (HostCmd_DS_CHAN_RPT_RSP *)pos; |
| |
| memset((void *)pChanRptReq, 0x00, sizeof(HostCmd_DS_CHAN_RPT_REQ)); |
| |
| if ((argc != 5) && (argc != 6)) { |
| printf("\nchanrpt syntax: chanrpt <chan#> <millisecs> [sFreq]\n\n"); |
| ret = MLAN_STATUS_FAILURE; |
| goto done; |
| } |
| |
| pChanRptReq->chanDesc.chanNum = atoi(argv[3]); |
| pChanRptReq->millisecDwellTime = cpu_to_le32(atoi(argv[4])); |
| |
| if (argc == 6) { |
| pChanRptReq->chanDesc.startFreq = cpu_to_le16(atoi(argv[5])); |
| } |
| |
| /* Put buffer length */ |
| memcpy(buffer + cmd_header_len, &cmd_len, sizeof(t_u32)); |
| |
| /* Initialize the ifr structure */ |
| memset(&ifr, 0, sizeof(ifr)); |
| strncpy(ifr.ifr_ifrn.ifrn_name, dev_name, strlen(dev_name)); |
| ifr.ifr_ifru.ifru_data = (void *)cmd; |
| /* Perform ioctl */ |
| if (ioctl(sockfd, MLAN_ETH_PRIV, &ifr)) { |
| perror("ioctl[chanrpt hostcmd]"); |
| printf("ERR:Command sending failed!\n"); |
| ret = -EFAULT; |
| goto done; |
| } |
| |
| /* TSF is a t_u64, some formatted printing libs have |
| * trouble printing long longs, so cast and dump as bytes |
| */ |
| pByte = (t_u8 *)&pChanRptRsp->startTsf; |
| |
| printf("\n"); |
| printf("[%03d] TSF: 0x%02x%02x%02x%02x%02x%02x%02x%02x\n", |
| atoi(argv[3]), |
| pByte[7], pByte[6], pByte[5], pByte[4], |
| pByte[3], pByte[2], pByte[1], pByte[0]); |
| printf("[%03d] Dwell: %u us\n", |
| atoi(argv[3]), (unsigned int)le32_to_cpu(pChanRptRsp->duration)); |
| |
| pByte = pChanRptRsp->tlvBuffer; |
| |
| respLen = le16_to_cpu(hostcmd->size) - sizeof(HostCmd_DS_GEN); |
| |
| respLen -= sizeof(pChanRptRsp->commandResult); |
| respLen -= sizeof(pChanRptRsp->startTsf); |
| respLen -= sizeof(pChanRptRsp->duration); |
| |
| pByte = pChanRptRsp->tlvBuffer; |
| |
| while ((unsigned int)respLen >= sizeof(pTlvHdr->header)) { |
| pTlvHdr = (MrvlIEtypes_Data_t *)pByte; |
| pTlvHdr->header.len = le16_to_cpu(pTlvHdr->header.len); |
| |
| switch (le16_to_cpu(pTlvHdr->header.type)) { |
| case TLV_TYPE_CHANRPT_BCN: |
| pBcnRpt = (MrvlIEtypes_ChanRptBcn_t *)pTlvHdr; |
| printf("[%03d] Beacon: scanReqId = %d\n", |
| atoi(argv[3]), pBcnRpt->scanReqId); |
| |
| break; |
| |
| case TLV_TYPE_CHANRPT_CHAN_LOAD: |
| pLoadRpt = (MrvlIEtypes_ChanRptChanLoad_t *)pTlvHdr; |
| printf("[%03d] ChanLoad: %d%%\n", |
| atoi(argv[3]), |
| (pLoadRpt->ccaBusyFraction * 100) / 255); |
| break; |
| |
| case TLV_TYPE_CHANRPT_NOISE_HIST: |
| pNoiseRpt = (MrvlIEtypes_ChanRptNoiseHist_t *)pTlvHdr; |
| numBins = |
| pNoiseRpt->header.len - sizeof(pNoiseRpt->anpi); |
| printf("[%03d] ANPI: %d dB\n", atoi(argv[3]), |
| le16_to_cpu(pNoiseRpt->anpi)); |
| printf("[%03d] NoiseHst:", atoi(argv[3])); |
| for (idx = 0; idx < numBins; idx++) { |
| printf(" %03d", pNoiseRpt->rpiDensities[idx]); |
| } |
| printf("\n"); |
| break; |
| |
| case TLV_TYPE_CHANRPT_11H_BASIC: |
| pBasicRpt = (MrvlIEtypes_ChanRpt11hBasic_t *)pTlvHdr; |
| printf("[%03d] 11hBasic: BSS(%d), OFDM(%d), UnId(%d), Radar(%d): " "[0x%02x]\n", atoi(argv[3]), pBasicRpt->map.BSS, pBasicRpt->map.OFDM_Preamble, pBasicRpt->map.Unidentified, pBasicRpt->map.Radar, *(t_u8 *)&pBasicRpt->map); |
| break; |
| |
| case TLV_TYPE_CHANRPT_FRAME: |
| pFrameRpt = (MrvlIEtypes_ChanRptFrame_t *)pTlvHdr; |
| printf("[%03d] Frame: %02x:%02x:%02x:%02x:%02x:%02x " "%02x:%02x:%02x:%02x:%02x:%02x %3d %02d\n", atoi(argv[3]), pFrameRpt->sourceAddr[0], pFrameRpt->sourceAddr[1], pFrameRpt->sourceAddr[2], pFrameRpt->sourceAddr[3], pFrameRpt->sourceAddr[4], pFrameRpt->sourceAddr[5], pFrameRpt->bssid[0], pFrameRpt->bssid[1], pFrameRpt->bssid[2], pFrameRpt->bssid[3], pFrameRpt->bssid[4], pFrameRpt->bssid[5], pFrameRpt->rssi, pFrameRpt->frameCnt); |
| break; |
| |
| default: |
| printf("[%03d] Other: Id=0x%x, Size = %d\n", |
| atoi(argv[3]), |
| pTlvHdr->header.type, pTlvHdr->header.len); |
| |
| break; |
| } |
| |
| pByte += (pTlvHdr->header.len + sizeof(pTlvHdr->header)); |
| respLen -= (pTlvHdr->header.len + sizeof(pTlvHdr->header)); |
| respLen = (respLen > 0) ? respLen : 0; |
| } |
| |
| printf("\n"); |
| |
| done: |
| if (buffer) |
| free(buffer); |
| if (cmd) |
| free(cmd); |
| return ret; |
| } |
| |
| /** |
| * @brief Issue a assoc timing command |
| * |
| * @param argc number of arguments |
| * @param argv A pointer to arguments array |
| * |
| * @return MLAN_STATUS_SUCCESS--success, otherwise--fail |
| */ |
| int |
| process_assoc_timing(int argc, char *argv[]) |
| { |
| int ret = MLAN_STATUS_SUCCESS; |
| struct ifreq ifr; |
| t_u8 *buffer = NULL, *pos = NULL; |
| t_u32 cmd_len = 0, cmd_header_len; |
| struct eth_priv_cmd *cmd = NULL; |
| HostCmd_DS_GEN *hostcmd; |
| HostCmd_DS_AssociationTiming_t *assoctiming; |
| |
| cmd_header_len = strlen(CMD_MARVELL) + strlen(HOSTCMD); |
| |
| buffer = (t_u8 *)malloc(BUFFER_LENGTH); |
| if (buffer == NULL) { |
| fprintf(stderr, "Cannot alloc memory\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| memset(buffer, 0, BUFFER_LENGTH); |
| |
| cmd = (struct eth_priv_cmd *)malloc(sizeof(struct eth_priv_cmd)); |
| if (!cmd) { |
| printf("ERR:Cannot allocate buffer for command!\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| |
| /* Fill up buffer */ |
| #ifdef USERSPACE_32BIT_OVER_KERNEL_64BIT |
| memset(cmd, 0, sizeof(struct eth_priv_cmd)); |
| memcpy(&cmd->buf, &buffer, sizeof(buffer)); |
| #else |
| cmd->buf = buffer; |
| #endif |
| cmd->used_len = 0; |
| cmd->total_len = BUFFER_LENGTH; |
| |
| /* buffer = MRVL_CMD<cmd> */ |
| strncpy((char *)buffer, CMD_MARVELL, strlen(CMD_MARVELL)); |
| strncpy((char *)buffer + strlen(CMD_MARVELL), HOSTCMD, strlen(HOSTCMD)); |
| |
| /* buffer = MRVL_CMD<cmd><hostcmd_size><HostCmd_DS_GEN><CMD_DS> */ |
| hostcmd = (HostCmd_DS_GEN *)(buffer + cmd_header_len + sizeof(t_u32)); |
| |
| /* Point after host command header */ |
| pos = (t_u8 *)hostcmd + S_DS_GEN; |
| |
| cmd_len = S_DS_GEN + sizeof(HostCmd_DS_AssociationTiming_t); |
| |
| hostcmd->command = cpu_to_le16(HostCmd_CMD_ASSOCIATION_TIMING); |
| hostcmd->size = cpu_to_le16(cmd_len); |
| hostcmd->seq_num = 0; |
| hostcmd->result = 0; |
| |
| assoctiming = (HostCmd_DS_AssociationTiming_t *)pos; |
| assoctiming->Action = cpu_to_le16(HostCmd_ACT_GEN_GET); |
| |
| /* Put buffer length */ |
| memcpy(buffer + cmd_header_len, &cmd_len, sizeof(t_u32)); |
| |
| /* Initialize the ifr structure */ |
| memset(&ifr, 0, sizeof(ifr)); |
| strncpy(ifr.ifr_ifrn.ifrn_name, dev_name, strlen(dev_name)); |
| ifr.ifr_ifru.ifru_data = (void *)cmd; |
| /* Perform ioctl */ |
| if (ioctl(sockfd, MLAN_ETH_PRIV, &ifr)) { |
| perror("ioctl[hostcmd]"); |
| printf("ERR:Command sending failed!\n"); |
| ret = -EFAULT; |
| goto done; |
| } |
| |
| if (argc > 3) { |
| assoctiming->Action = cpu_to_le16(HostCmd_ACT_GEN_SET); |
| switch (argc) { |
| case 9: |
| assoctiming->ReassocDiscMax = |
| cpu_to_le16(atoi(argv[8])); |
| /* No break, do everything below as well */ |
| case 8: |
| assoctiming->PriorApDeauthDelay = |
| cpu_to_le16(atoi(argv[7])); |
| /* No break, do everything below as well */ |
| case 7: |
| assoctiming->FrameExchangeTimeout = |
| cpu_to_le16(atoi(argv[6])); |
| /* No break, do everything below as well */ |
| case 6: |
| assoctiming->HandShakeTimeout = |
| cpu_to_le16(atoi(argv[5])); |
| /* No break, do everything below as well */ |
| case 5: |
| assoctiming->ReassocTimeout = |
| cpu_to_le16(atoi(argv[4])); |
| /* No break, do everything below as well */ |
| case 4: |
| assoctiming->AssocTimeout = cpu_to_le16(atoi(argv[3])); |
| break; |
| } |
| } |
| |
| /* Put buffer length */ |
| memcpy(buffer + cmd_header_len, &cmd_len, sizeof(t_u32)); |
| |
| /* Initialize the ifr structure */ |
| memset(&ifr, 0, sizeof(ifr)); |
| strncpy(ifr.ifr_ifrn.ifrn_name, dev_name, strlen(dev_name)); |
| ifr.ifr_ifru.ifru_data = (void *)cmd; |
| /* Perform ioctl */ |
| if (ioctl(sockfd, MLAN_ETH_PRIV, &ifr)) { |
| perror("ioctl[hostcmd]"); |
| printf("ERR:Command sending failed!\n"); |
| ret = -EFAULT; |
| goto done; |
| } |
| |
| puts(""); |
| printf("------------------------------------------------\n"); |
| printf(" Association Timing Parameters\n"); |
| printf("------------------------------------------------\n"); |
| |
| printf("Association Timeout %5u ms\n" |
| "Reassociation Timeout %5u ms\n" |
| "Handshake Timeout %5u ms\n" |
| "Frame Exchange Timeout %5u ms\n" |
| "Prior AP Deauth Delay %5u ms\n" |
| "Reassoc Disconnect Max %5u ms\n", |
| le16_to_cpu(assoctiming->AssocTimeout), |
| le16_to_cpu(assoctiming->ReassocTimeout), |
| le16_to_cpu(assoctiming->HandShakeTimeout), |
| le16_to_cpu(assoctiming->FrameExchangeTimeout), |
| le16_to_cpu(assoctiming->PriorApDeauthDelay), |
| le16_to_cpu(assoctiming->ReassocDiscMax)); |
| puts(""); |
| |
| done: |
| if (buffer) |
| free(buffer); |
| if (cmd) |
| free(cmd); |
| return ret; |
| } |
| |
| /** |
| * @brief Retrieve the association response from the driver |
| * |
| * Retrieve the buffered (re)association management frame from the driver. |
| * The response is identical to the one received from the AP and conforms |
| * to the IEEE specification. |
| * |
| * @return MLAN_STATUS_SUCCESS or ioctl error code |
| */ |
| int |
| process_get_assocrsp(int argc, char *argv[]) |
| { |
| int ret = 0; |
| t_u8 *buffer = NULL; |
| struct eth_priv_cmd *cmd = NULL; |
| struct ifreq ifr; |
| IEEEtypes_AssocRsp_t *pAssocRsp = NULL; |
| |
| /* Initialize buffer */ |
| buffer = (t_u8 *)malloc(BUFFER_LENGTH); |
| if (!buffer) { |
| printf("ERR:Cannot allocate buffer for command!\n"); |
| return MLAN_STATUS_FAILURE; |
| } |
| |
| pAssocRsp = (IEEEtypes_AssocRsp_t *)buffer; |
| |
| /* buffer = MRVL_CMD<cmd> */ |
| strncpy((char *)buffer, CMD_MARVELL, strlen(CMD_MARVELL)); |
| strncpy((char *)buffer + strlen(CMD_MARVELL), argv[2], strlen(argv[2])); |
| |
| cmd = (struct eth_priv_cmd *)malloc(sizeof(struct eth_priv_cmd)); |
| if (!cmd) { |
| printf("ERR:Cannot allocate buffer for command!\n"); |
| free(buffer); |
| return MLAN_STATUS_FAILURE; |
| } |
| |
| /* Fill up buffer */ |
| #ifdef USERSPACE_32BIT_OVER_KERNEL_64BIT |
| memset(cmd, 0, sizeof(struct eth_priv_cmd)); |
| memcpy(&cmd->buf, &buffer, sizeof(buffer)); |
| #else |
| cmd->buf = buffer; |
| #endif |
| cmd->used_len = 0; |
| cmd->total_len = BUFFER_LENGTH; |
| |
| /* Perform IOCTL */ |
| memset(&ifr, 0, sizeof(struct ifreq)); |
| strncpy(ifr.ifr_ifrn.ifrn_name, dev_name, strlen(dev_name)); |
| ifr.ifr_ifru.ifru_data = (void *)cmd; |
| |
| if (ioctl(sockfd, MLAN_ETH_PRIV, &ifr)) { |
| perror("mlanutl"); |
| fprintf(stderr, "mlanutl: version fail\n"); |
| ret = MLAN_STATUS_FAILURE; |
| goto done; |
| } |
| |
| if (cmd->used_len) { |
| printf("getassocrsp: Status[%d], Cap[0x%04x]:\n", |
| pAssocRsp->StatusCode, |
| le16_to_cpu(*(t_u16 *)&pAssocRsp->Capability)); |
| hexdump(NULL, buffer, cmd->used_len, ' '); |
| } else { |
| printf("getassocrsp: <empty>\n"); |
| } |
| |
| done: |
| if (buffer) |
| free(buffer); |
| if (cmd) |
| free(cmd); |
| |
| return ret; |
| } |
| |
| /* |
| ** Process mlanutl fcontrol command: |
| ** |
| ** mlanutl mlanX fcontrol %d [0xAA 0xBB... ] |
| ** |
| ** Sets and/or retrieves the feature control settings for a specific |
| ** control set (argv[3] decimal argument). |
| ** |
| */ |
| int |
| process_fcontrol(int argc, char *argv[]) |
| { |
| int ret = MLAN_STATUS_SUCCESS; |
| struct ifreq ifr; |
| t_u8 *buffer = NULL, *pos = NULL; |
| t_u32 cmd_len = 0, cmd_header_len; |
| struct eth_priv_cmd *cmd = NULL; |
| t_u8 idx; |
| HostCmd_DS_GEN *hostcmd; |
| HostCmd_DS_OFFLOAD_FEATURE_CONTROL *pFcontrol; |
| |
| cmd_header_len = strlen(CMD_MARVELL) + strlen(HOSTCMD); |
| |
| buffer = (t_u8 *)malloc(BUFFER_LENGTH); |
| if (buffer == NULL) { |
| fprintf(stderr, "Cannot alloc memory\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| memset(buffer, 0, BUFFER_LENGTH); |
| |
| cmd = (struct eth_priv_cmd *)malloc(sizeof(struct eth_priv_cmd)); |
| if (!cmd) { |
| printf("ERR:Cannot allocate buffer for command!\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| |
| /* Fill up buffer */ |
| #ifdef USERSPACE_32BIT_OVER_KERNEL_64BIT |
| memset(cmd, 0, sizeof(struct eth_priv_cmd)); |
| memcpy(&cmd->buf, &buffer, sizeof(buffer)); |
| #else |
| cmd->buf = buffer; |
| #endif |
| cmd->used_len = 0; |
| cmd->total_len = BUFFER_LENGTH; |
| |
| /* buffer = MRVL_CMD<cmd> */ |
| strncpy((char *)buffer, CMD_MARVELL, strlen(CMD_MARVELL)); |
| strncpy((char *)buffer + strlen(CMD_MARVELL), HOSTCMD, strlen(HOSTCMD)); |
| |
| /* buffer = MRVL_CMD<cmd><hostcmd_size><HostCmd_DS_GEN><CMD_DS> */ |
| hostcmd = (HostCmd_DS_GEN *)(buffer + cmd_header_len + sizeof(t_u32)); |
| |
| /* Point after host command header */ |
| pos = (t_u8 *)hostcmd + S_DS_GEN; |
| |
| pFcontrol = (HostCmd_DS_OFFLOAD_FEATURE_CONTROL *)pos; |
| |
| if (argc < 4) { |
| printf("Wrong number of arguments\n"); |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| pFcontrol->controlSelect = atoi(argv[3]); |
| cmd_len = S_DS_GEN + sizeof(pFcontrol->controlSelect); |
| |
| for (idx = 4; idx < argc; idx++) { |
| pFcontrol->controlBitmap[idx - 4] = a2hex_or_atoi(argv[idx]); |
| cmd_len++; |
| } |
| |
| hostcmd->command = cpu_to_le16(HostCmd_CMD_OFFLOAD_FEATURE_CONTROL); |
| hostcmd->size = cpu_to_le16(cmd_len); |
| hostcmd->seq_num = 0; |
| hostcmd->result = 0; |
| |
| /* Put buffer length */ |
| memcpy(buffer + cmd_header_len, &cmd_len, sizeof(t_u32)); |
| |
| /* Initialize the ifr structure */ |
| memset(&ifr, 0, sizeof(ifr)); |
| strncpy(ifr.ifr_ifrn.ifrn_name, dev_name, strlen(dev_name)); |
| ifr.ifr_ifru.ifru_data = (void *)cmd; |
| /* Perform ioctl */ |
| if (ioctl(sockfd, MLAN_ETH_PRIV, &ifr)) { |
| perror("ioctl[fcontrol hostcmd]"); |
| printf("ERR:Command sending failed!\n"); |
| ret = -EFAULT; |
| goto done; |
| } |
| |
| cmd_len = (le16_to_cpu(hostcmd->size) - sizeof(HostCmd_DS_GEN)); |
| |
| printf("Control[%d]", pFcontrol->controlSelect); |
| cmd_len--; |
| |
| for (idx = 0; idx < cmd_len; idx++) { |
| printf("\t0x%02x", pFcontrol->controlBitmap[idx]); |
| } |
| |
| printf("\n"); |
| |
| done: |
| if (buffer) |
| free(buffer); |
| if (cmd) |
| free(cmd); |
| return ret; |
| } |
| |
| /* |
| ** Process mlanutl iapp command: |
| ** |
| ** mlanutl mlanX iapp <timeout> 0xAA 0xBB [0x... 0x.. ] |
| ** |
| ** 0xAA = IAPP type |
| ** 0xBB = IAPP subtype |
| ** 0x.. = Remaning bytes are iapp data |
| ** |
| */ |
| int |
| process_iapp(int argc, char *argv[]) |
| { |
| int ret = MLAN_STATUS_SUCCESS; |
| struct ifreq ifr; |
| t_u8 *buffer = NULL, *pos = NULL; |
| t_u32 cmd_len = 0, cmd_header_len; |
| struct eth_priv_cmd *cmd = NULL; |
| t_u8 idx; |
| t_u8 fixlen; |
| HostCmd_DS_GEN *hostcmd; |
| HostCmd_DS_IAPP_PROXY *pIappProxy; |
| |
| cmd_header_len = strlen(CMD_MARVELL) + strlen(HOSTCMD); |
| |
| buffer = (t_u8 *)malloc(BUFFER_LENGTH); |
| if (buffer == NULL) { |
| fprintf(stderr, "Cannot alloc memory\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| memset(buffer, 0, BUFFER_LENGTH); |
| |
| cmd = (struct eth_priv_cmd *)malloc(sizeof(struct eth_priv_cmd)); |
| if (!cmd) { |
| printf("ERR:Cannot allocate buffer for command!\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| |
| /* Fill up buffer */ |
| #ifdef USERSPACE_32BIT_OVER_KERNEL_64BIT |
| memset(cmd, 0, sizeof(struct eth_priv_cmd)); |
| memcpy(&cmd->buf, &buffer, sizeof(buffer)); |
| #else |
| cmd->buf = buffer; |
| #endif |
| cmd->used_len = 0; |
| cmd->total_len = BUFFER_LENGTH; |
| |
| /* buffer = MRVL_CMD<cmd> */ |
| strncpy((char *)buffer, CMD_MARVELL, strlen(CMD_MARVELL)); |
| strncpy((char *)buffer + strlen(CMD_MARVELL), HOSTCMD, strlen(HOSTCMD)); |
| |
| /* buffer = MRVL_CMD<cmd><hostcmd_size><HostCmd_DS_GEN><CMD_DS> */ |
| hostcmd = (HostCmd_DS_GEN *)(buffer + cmd_header_len + sizeof(t_u32)); |
| |
| /* Point after host command header */ |
| pos = (t_u8 *)hostcmd + S_DS_GEN; |
| |
| cmd_len = S_DS_GEN + sizeof(HostCmd_DS_IAPP_PROXY); |
| |
| pIappProxy = (HostCmd_DS_IAPP_PROXY *)pos; |
| |
| if (argc < 6) { |
| printf("Wrong number of arguments\n"); |
| ret = -EINVAL; |
| } |
| |
| memset(pIappProxy, 0x00, sizeof(HostCmd_DS_IAPP_PROXY)); |
| |
| pIappProxy->iappType = a2hex_or_atoi(argv[4]); |
| pIappProxy->iappSubType = a2hex_or_atoi(argv[5]); |
| |
| /* Fixed len portions of command */ |
| fixlen = (S_DS_GEN + sizeof(HostCmd_DS_IAPP_PROXY) |
| - sizeof(pIappProxy->iappData)); |
| |
| pIappProxy->timeout_ms = cpu_to_le32(a2hex_or_atoi(argv[3])); |
| |
| for (idx = 6; idx < argc; idx++) { |
| pIappProxy->iappData[idx - 6] = a2hex_or_atoi(argv[idx]); |
| pIappProxy->iappDataLen++; |
| } |
| |
| hostcmd->command = cpu_to_le16(HostCmd_CMD_IAPP_PROXY); |
| hostcmd->size = cpu_to_le16(fixlen + pIappProxy->iappDataLen); |
| hostcmd->seq_num = 0; |
| hostcmd->result = 0; |
| |
| pIappProxy->iappDataLen = cpu_to_le32(pIappProxy->iappDataLen); |
| /* Put buffer length */ |
| memcpy(buffer + cmd_header_len, &cmd_len, sizeof(t_u32)); |
| |
| /* Initialize the ifr structure */ |
| memset(&ifr, 0, sizeof(ifr)); |
| strncpy(ifr.ifr_ifrn.ifrn_name, dev_name, strlen(dev_name)); |
| ifr.ifr_ifru.ifru_data = (void *)cmd; |
| /* Perform ioctl */ |
| if (ioctl(sockfd, MLAN_ETH_PRIV, &ifr)) { |
| perror("ioctl[iapp hostcmd]"); |
| printf("ERR:Command sending failed!\n"); |
| ret = -EFAULT; |
| goto done; |
| } |
| |
| printf("\nResult: %d\n", le32_to_cpu(pIappProxy->commandResult)); |
| printf("Type: 0x%02x\n", pIappProxy->iappType); |
| printf("SubType: 0x%02x\n", pIappProxy->iappSubType); |
| |
| printf("IappData: "); |
| hexdump(NULL, pIappProxy->iappData, |
| le32_to_cpu(pIappProxy->iappDataLen), ' '); |
| printf("\n\n"); |
| |
| done: |
| if (buffer) |
| free(buffer); |
| if (cmd) |
| free(cmd); |
| return ret; |
| } |
| |
| /** |
| * @brief Issue a rf tx power command |
| * |
| * @param argc number of arguments |
| * @param argv A pointer to arguments array |
| * |
| * @return MLAN_STATUS_SUCCESS--success, otherwise--fail |
| */ |
| int |
| process_rf_tx_power(int argc, char *argv[]) |
| { |
| int ret = MLAN_STATUS_SUCCESS; |
| struct ifreq ifr; |
| t_u8 *buffer = NULL, *pos = NULL; |
| t_u32 cmd_len = 0, cmd_header_len; |
| struct eth_priv_cmd *cmd = NULL; |
| HostCmd_DS_GEN *hostcmd; |
| HostCmd_DS_802_11_RF_TX_POWER *pRfTxPower; |
| |
| cmd_header_len = strlen(CMD_MARVELL) + strlen(HOSTCMD); |
| |
| buffer = (t_u8 *)malloc(BUFFER_LENGTH); |
| if (buffer == NULL) { |
| fprintf(stderr, "Cannot alloc memory\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| memset(buffer, 0, BUFFER_LENGTH); |
| |
| cmd = (struct eth_priv_cmd *)malloc(sizeof(struct eth_priv_cmd)); |
| if (!cmd) { |
| printf("ERR:Cannot allocate buffer for command!\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| |
| /* Fill up buffer */ |
| #ifdef USERSPACE_32BIT_OVER_KERNEL_64BIT |
| memset(cmd, 0, sizeof(struct eth_priv_cmd)); |
| memcpy(&cmd->buf, &buffer, sizeof(buffer)); |
| #else |
| cmd->buf = buffer; |
| #endif |
| cmd->used_len = 0; |
| cmd->total_len = BUFFER_LENGTH; |
| |
| /* buffer = MRVL_CMD<cmd> */ |
| strncpy((char *)buffer, CMD_MARVELL, strlen(CMD_MARVELL)); |
| strncpy((char *)buffer + strlen(CMD_MARVELL), HOSTCMD, strlen(HOSTCMD)); |
| |
| /* buffer = MRVL_CMD<cmd><hostcmd_size><HostCmd_DS_GEN><CMD_DS> */ |
| hostcmd = (HostCmd_DS_GEN *)(buffer + cmd_header_len + sizeof(t_u32)); |
| |
| /* Point after host command header */ |
| pos = (t_u8 *)hostcmd + S_DS_GEN; |
| |
| cmd_len = S_DS_GEN + sizeof(HostCmd_DS_802_11_RF_TX_POWER); |
| |
| pRfTxPower = (HostCmd_DS_802_11_RF_TX_POWER *)pos; |
| |
| memset(pRfTxPower, 0x00, sizeof(HostCmd_DS_802_11_RF_TX_POWER)); |
| |
| hostcmd->command = cpu_to_le16(HostCmd_CMD_802_11_RF_TX_POWER); |
| hostcmd->size = cpu_to_le16(cmd_len); |
| hostcmd->seq_num = 0; |
| hostcmd->result = 0; |
| |
| /* Put buffer length */ |
| memcpy(buffer + cmd_header_len, &cmd_len, sizeof(t_u32)); |
| |
| /* Initialize the ifr structure */ |
| memset(&ifr, 0, sizeof(ifr)); |
| strncpy(ifr.ifr_ifrn.ifrn_name, dev_name, strlen(dev_name)); |
| ifr.ifr_ifru.ifru_data = (void *)cmd; |
| /* Perform ioctl */ |
| if (ioctl(sockfd, MLAN_ETH_PRIV, &ifr)) { |
| perror("ioctl[hostcmd]"); |
| printf("ERR:Command sending failed!\n"); |
| ret = -EFAULT; |
| goto done; |
| } |
| |
| printf("\n"); |
| printf(" MinPower: %2d\n", pRfTxPower->min_power); |
| printf(" MaxPower: %2d\n", pRfTxPower->max_power); |
| printf(" Current: %2d\n", le16_to_cpu(pRfTxPower->current_level)); |
| printf("\n"); |
| |
| done: |
| if (buffer) |
| free(buffer); |
| if (cmd) |
| free(cmd); |
| return ret; |
| } |
| |
| /** |
| * @brief Issue a authenticate command |
| * |
| * @param argc number of arguments |
| * @param argv A pointer to arguments array |
| * |
| * @return MLAN_STATUS_SUCCESS--success, otherwise--fail |
| */ |
| int |
| process_authenticate(int argc, char *argv[]) |
| { |
| int ret = MLAN_STATUS_SUCCESS; |
| struct ifreq ifr; |
| t_u8 *buffer = NULL, *pos = NULL; |
| t_u32 cmd_len = 0, cmd_header_len; |
| struct eth_priv_cmd *cmd = NULL; |
| HostCmd_DS_GEN *hostcmd; |
| HostCmd_DS_802_11_AUTHENTICATE *pAuth; |
| unsigned int mac[ETH_ALEN]; |
| int tmpIdx; |
| |
| if (argc != 4) { |
| printf("Wrong number of arguments\n"); |
| ret = -EINVAL; |
| goto done; |
| } |
| |
| cmd_header_len = strlen(CMD_MARVELL) + strlen(HOSTCMD); |
| |
| buffer = (t_u8 *)malloc(BUFFER_LENGTH); |
| if (buffer == NULL) { |
| fprintf(stderr, "Cannot alloc memory\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| memset(buffer, 0, BUFFER_LENGTH); |
| |
| cmd = (struct eth_priv_cmd *)malloc(sizeof(struct eth_priv_cmd)); |
| if (!cmd) { |
| printf("ERR:Cannot allocate buffer for command!\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| |
| /* Fill up buffer */ |
| #ifdef USERSPACE_32BIT_OVER_KERNEL_64BIT |
| memset(cmd, 0, sizeof(struct eth_priv_cmd)); |
| memcpy(&cmd->buf, &buffer, sizeof(buffer)); |
| #else |
| cmd->buf = buffer; |
| #endif |
| cmd->used_len = 0; |
| cmd->total_len = BUFFER_LENGTH; |
| |
| /* buffer = MRVL_CMD<cmd> */ |
| strncpy((char *)buffer, CMD_MARVELL, strlen(CMD_MARVELL)); |
| strncpy((char *)buffer + strlen(CMD_MARVELL), HOSTCMD, strlen(HOSTCMD)); |
| |
| /* buffer = MRVL_CMD<cmd><hostcmd_size><HostCmd_DS_GEN><CMD_DS> */ |
| hostcmd = (HostCmd_DS_GEN *)(buffer + cmd_header_len + sizeof(t_u32)); |
| |
| /* Point after host command header */ |
| pos = (t_u8 *)hostcmd + S_DS_GEN; |
| |
| cmd_len = S_DS_GEN + sizeof(HostCmd_DS_802_11_AUTHENTICATE); |
| |
| pAuth = (HostCmd_DS_802_11_AUTHENTICATE *)pos; |
| |
| memset(pAuth, 0x00, sizeof(HostCmd_DS_802_11_AUTHENTICATE)); |
| |
| sscanf(argv[3], |
| "%2x:%2x:%2x:%2x:%2x:%2x", |
| mac + 0, mac + 1, mac + 2, mac + 3, mac + 4, mac + 5); |
| |
| for (tmpIdx = 0; (unsigned int)tmpIdx < NELEMENTS(mac); tmpIdx++) { |
| pAuth->MacAddr[tmpIdx] = (t_u8)mac[tmpIdx]; |
| } |
| |
| hostcmd->command = cpu_to_le16(HostCmd_CMD_802_11_AUTHENTICATE); |
| hostcmd->size = cpu_to_le16(cmd_len); |
| hostcmd->seq_num = 0; |
| hostcmd->result = 0; |
| |
| /* Put buffer length */ |
| memcpy(buffer + cmd_header_len, &cmd_len, sizeof(t_u32)); |
| |
| /* Initialize the ifr structure */ |
| memset(&ifr, 0, sizeof(ifr)); |
| strncpy(ifr.ifr_ifrn.ifrn_name, dev_name, strlen(dev_name)); |
| ifr.ifr_ifru.ifru_data = (void *)cmd; |
| /* Perform ioctl */ |
| if (ioctl(sockfd, MLAN_ETH_PRIV, &ifr)) { |
| perror("ioctl[hostcmd]"); |
| printf("ERR:Command sending failed!\n"); |
| ret = -EFAULT; |
| goto done; |
| } |
| |
| done: |
| if (buffer) |
| free(buffer); |
| if (cmd) |
| free(cmd); |
| return ret; |
| } |
| |
| static void |
| display_channel(void) |
| { |
| FILE *tmpfile; |
| char result[200]; |
| char cmdStr[50]; |
| int ghz, mhz, chan; |
| |
| puts("\n"); |
| |
| sprintf(cmdStr, "iwlist %s chan", dev_name); |
| |
| tmpfile = popen(cmdStr, "r"); |
| |
| if (tmpfile == NULL) { |
| perror("mlanutl: iwlist failed to get current channel"); |
| } else { |
| while (fgets(result, sizeof(result), tmpfile)) { |
| if ((sscanf |
| (result, " Current Frequency=%d.%d ", &ghz, |
| &mhz) == 2) || |
| (sscanf |
| (result, " Current Frequency:%d.%d ", &ghz, |
| &mhz) == 2)) { |
| if (mhz < 10) { |
| mhz *= 100; |
| } else if (mhz < 100) { |
| mhz *= 10; |
| } |
| |
| chan = ghz * 1000 + mhz; |
| if (chan > 5000) { |
| chan -= 5000; |
| chan /= 5; |
| } else if (chan == 2484) { |
| chan = 14; |
| } else { |
| chan -= 2407; |
| chan /= 5; |
| } |
| printf(" Channel: %3d [%d.%d GHz]\n", chan, |
| ghz, mhz); |
| } |
| } |
| pclose(tmpfile); |
| } |
| } |
| |
| static char * |
| get_ratestr(int txRate) |
| { |
| char *pStr; |
| |
| switch (txRate) { |
| case 0: |
| pStr = "1"; |
| break; |
| case 1: |
| pStr = "2"; |
| break; |
| case 2: |
| pStr = "5.5"; |
| break; |
| case 3: |
| pStr = "11"; |
| break; |
| case 4: |
| pStr = "6"; |
| break; |
| case 5: |
| pStr = "9"; |
| break; |
| case 6: |
| pStr = "12"; |
| break; |
| case 7: |
| pStr = "18"; |
| break; |
| case 8: |
| pStr = "24"; |
| break; |
| case 9: |
| pStr = "36"; |
| break; |
| case 10: |
| pStr = "48"; |
| break; |
| case 11: |
| pStr = "54"; |
| break; |
| case 12: |
| pStr = "MCS0"; |
| break; |
| case 13: |
| pStr = "MCS1"; |
| break; |
| case 14: |
| pStr = "MCS2"; |
| break; |
| case 15: |
| pStr = "MCS3"; |
| break; |
| case 16: |
| pStr = "MCS4"; |
| break; |
| case 17: |
| pStr = "MCS5"; |
| break; |
| case 18: |
| pStr = "MCS6"; |
| break; |
| case 19: |
| pStr = "MCS7"; |
| break; |
| |
| case 140: |
| pStr = "MCS0"; |
| break; |
| case 141: |
| pStr = "MCS1"; |
| break; |
| case 142: |
| pStr = "MCS2"; |
| break; |
| case 143: |
| pStr = "MCS3"; |
| break; |
| case 144: |
| pStr = "MCS4"; |
| break; |
| case 145: |
| pStr = "MCS5"; |
| break; |
| case 146: |
| pStr = "MCS6"; |
| break; |
| case 147: |
| pStr = "MCS7"; |
| break; |
| |
| default: |
| pStr = "Unkn"; |
| break; |
| } |
| |
| return pStr; |
| } |
| |
| typedef struct { |
| int rate; |
| int min; |
| int max; |
| |
| } RatePower_t; |
| |
| static int |
| get_txpwrcfg(RatePower_t ratePower[]) |
| { |
| FILE *tmpfile; |
| char result[300]; |
| char cmdStr[50]; |
| int counter = 0; |
| char *pBuf; |
| int r1 = 0, r2 = 0, min = 0, max = 0, rate = 0; |
| int rateIdx = 0; |
| |
| sprintf(cmdStr, "iwpriv %s txpowercfg", dev_name); |
| |
| tmpfile = popen(cmdStr, "r"); |
| |
| if (tmpfile == NULL) { |
| perror("mlanutl: iwpriv failed to get txpowercfg"); |
| } else { |
| while (fgets(result, sizeof(result), tmpfile)) { |
| pBuf = strtok(result, ": "); |
| |
| while (pBuf != NULL) { |
| switch (counter % 5) { |
| case 0: |
| r1 = atoi(pBuf); |
| break; |
| |
| case 1: |
| r2 = atoi(pBuf); |
| break; |
| |
| case 2: |
| min = atoi(pBuf); |
| break; |
| |
| case 3: |
| max = atoi(pBuf); |
| break; |
| |
| case 4: |
| for (rate = r1; rate <= r2; rate++) { |
| ratePower[rateIdx].rate = rate; |
| ratePower[rateIdx].min = min; |
| ratePower[rateIdx].max = max; |
| rateIdx++; |
| } |
| break; |
| } |
| |
| if (isdigit(*pBuf)) { |
| counter++; |
| } |
| pBuf = strtok(NULL, ": "); |
| } |
| } |
| pclose(tmpfile); |
| } |
| |
| return rateIdx; |
| } |
| |
| static void |
| rateSort(RatePower_t rateList[], int numRates) |
| { |
| int inc, i, j; |
| RatePower_t tmp; |
| |
| inc = 3; |
| |
| while (inc > 0) { |
| for (i = 0; i < numRates; i++) { |
| j = i; |
| memcpy(&tmp, &rateList[i], sizeof(RatePower_t)); |
| |
| while ((j >= inc) && |
| (rateList[j - inc].rate > tmp.rate)) { |
| memcpy(&rateList[j], &rateList[j - inc], |
| sizeof(RatePower_t)); |
| j -= inc; |
| } |
| |
| memcpy(&rateList[j], &tmp, sizeof(RatePower_t)); |
| } |
| |
| if (inc >> 1) { |
| inc >>= 1; |
| } else if (inc == 1) { |
| inc = 0; |
| } else { |
| inc = 1; |
| } |
| } |
| } |
| |
| typedef struct { |
| int rate; |
| int modGroup; |
| |
| } RateModPair_t; |
| |
| /* |
| ** |
| ** ModulationGroups |
| ** 0: CCK (1,2,5.5,11 Mbps) |
| ** 1: OFDM (6,9,12,18 Mbps) |
| ** 2: OFDM (24,36 Mbps) |
| ** 3: OFDM (48,54 Mbps) |
| ** 4: HT20 (0,1,2) |
| ** 5: HT20 (3,4) |
| ** 6: HT20 (5,6,7) |
| ** 7: HT40 (0,1,2) |
| ** 8: HT40 (3,4) |
| ** 9: HT40 (5,6,7) |
| */ |
| |
| static RateModPair_t rateModPairs[] = { |
| {0, 0}, /* 1 */ |
| {1, 0}, /* 2 */ |
| {2, 0}, /* 5.5 */ |
| {3, 0}, /* 11 */ |
| {4, 1}, /* 6 */ |
| {5, 1}, /* 9 */ |
| {6, 1}, /* 12 */ |
| {7, 1}, /* 18 */ |
| {8, 2}, /* 24 */ |
| {9, 2}, /* 36 */ |
| {10, 3}, /* 48 */ |
| {11, 3}, /* 54 */ |
| {12, 4}, /* MCS0 */ |
| {13, 4}, /* MCS1 */ |
| {14, 4}, /* MCS2 */ |
| {15, 5}, /* MCS3 */ |
| {16, 5}, /* MCS4 */ |
| {17, 6}, /* MCS5 */ |
| {18, 6}, /* MCS6 */ |
| {19, 6}, /* MCS7 */ |
| |
| {140, 7}, /* MCS0 */ |
| {141, 7}, /* MCS1 */ |
| {142, 7}, /* MCS2 */ |
| {143, 8}, /* MCS3 */ |
| {144, 8}, /* MCS4 */ |
| {145, 9}, /* MCS5 */ |
| {146, 9}, /* MCS6 */ |
| {147, 9}, /* MCS7 */ |
| }; |
| |
| int |
| process_chantrpcdisp(int startRate, int endRate) |
| { |
| int ret = MLAN_STATUS_SUCCESS; |
| struct ifreq ifr; |
| t_u8 *buffer = NULL, *pos = NULL; |
| t_u32 cmd_len = 0, cmd_header_len; |
| struct eth_priv_cmd *cmd = NULL; |
| HostCmd_DS_GEN *hostcmd; |
| HostCmd_DS_CHAN_TRPC_CONFIG *pChanTrpc; |
| MrvlIEtypes_ChanTrpcCfg_t *pChanTrpcTlv; |
| int totalTlvBytes = 0; |
| |
| cmd_header_len = strlen(CMD_MARVELL) + strlen(HOSTCMD); |
| |
| buffer = (t_u8 *)malloc(BUFFER_LENGTH); |
| if (buffer == NULL) { |
| fprintf(stderr, "Cannot alloc memory\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| memset(buffer, 0, BUFFER_LENGTH); |
| |
| cmd = (struct eth_priv_cmd *)malloc(sizeof(struct eth_priv_cmd)); |
| if (!cmd) { |
| printf("ERR:Cannot allocate buffer for command!\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| |
| /* Fill up buffer */ |
| #ifdef USERSPACE_32BIT_OVER_KERNEL_64BIT |
| memset(cmd, 0, sizeof(struct eth_priv_cmd)); |
| memcpy(&cmd->buf, &buffer, sizeof(buffer)); |
| #else |
| cmd->buf = buffer; |
| #endif |
| cmd->used_len = 0; |
| cmd->total_len = BUFFER_LENGTH; |
| |
| /* buffer = MRVL_CMD<cmd> */ |
| strncpy((char *)buffer, CMD_MARVELL, strlen(CMD_MARVELL)); |
| strncpy((char *)buffer + strlen(CMD_MARVELL), HOSTCMD, strlen(HOSTCMD)); |
| |
| /* buffer = MRVL_CMD<cmd><hostcmd_size><HostCmd_DS_GEN><CMD_DS> */ |
| hostcmd = (HostCmd_DS_GEN *)(buffer + cmd_header_len + sizeof(t_u32)); |
| |
| /* Point after host command header */ |
| pos = (t_u8 *)hostcmd + S_DS_GEN; |
| |
| pChanTrpc = (HostCmd_DS_CHAN_TRPC_CONFIG *)pos; |
| pChanTrpc->action = cpu_to_le16(HostCmd_ACT_GEN_GET); |
| |
| cmd_len = S_DS_GEN + sizeof(pChanTrpc->action); |
| hostcmd->command = cpu_to_le16(HostCmd_CMD_CHAN_TRPC_CONFIG); |
| hostcmd->size = cpu_to_le16(cmd_len); |
| hostcmd->seq_num = 0; |
| hostcmd->result = 0; |
| |
| /* Put buffer length */ |
| memcpy(buffer + cmd_header_len, &cmd_len, sizeof(t_u32)); |
| |
| /* Initialize the ifr structure */ |
| memset(&ifr, 0, sizeof(ifr)); |
| strncpy(ifr.ifr_ifrn.ifrn_name, dev_name, strlen(dev_name)); |
| ifr.ifr_ifru.ifru_data = (void *)cmd; |
| /* Perform ioctl */ |
| if (ioctl(sockfd, MLAN_ETH_PRIV, &ifr)) { |
| perror("ioctl[chantrpc hostcmd]"); |
| printf("ERR:Command sending failed!\n"); |
| ret = -EFAULT; |
| goto done; |
| } |
| |
| totalTlvBytes = (le16_to_cpu(hostcmd->size) |
| - sizeof(HostCmd_DS_GEN) |
| - sizeof(pChanTrpc->action) |
| - sizeof(pChanTrpc->reserved)); |
| |
| pChanTrpcTlv = (MrvlIEtypes_ChanTrpcCfg_t *)pChanTrpc->tlv_buffer; |
| |
| while (totalTlvBytes) { |
| int tlvSize, numModGroups, idx, modIdx, numOut; |
| |
| /* Switch to TLV parsing */ |
| printf("%4d.%-3d ", |
| le16_to_cpu(pChanTrpcTlv->chanDesc.startFreq), |
| pChanTrpcTlv->chanDesc.chanNum); |
| |
| numOut = 0; |
| |
| tlvSize = (le16_to_cpu(pChanTrpcTlv->header.len) |
| + sizeof(pChanTrpcTlv->header)); |
| |
| numModGroups = (le16_to_cpu(pChanTrpcTlv->header.len) |
| - sizeof(pChanTrpcTlv->chanDesc)); |
| numModGroups /= sizeof(pChanTrpcTlv->chanTrpcEntry[0]); |
| |
| for (idx = 0; idx < NELEMENTS(rateModPairs); idx++) { |
| if ((rateModPairs[idx].rate >= startRate) && |
| (rateModPairs[idx].rate <= endRate)) { |
| for (modIdx = 0; modIdx < numModGroups; |
| modIdx++) { |
| if (rateModPairs[idx].modGroup == |
| pChanTrpcTlv->chanTrpcEntry[modIdx]. |
| modGroup) { |
| printf("%*d", |
| (numOut == 0) ? 3 : 6, |
| pChanTrpcTlv-> |
| chanTrpcEntry[modIdx]. |
| txPower); |
| numOut++; |
| } |
| } |
| |
| if (numOut == 0) { |
| printf(" -- "); |
| } |
| } |
| } |
| |
| puts(""); |
| |
| pChanTrpcTlv = |
| (MrvlIEtypes_ChanTrpcCfg_t *)((t_u8 *)pChanTrpcTlv + |
| tlvSize); |
| totalTlvBytes -= tlvSize; |
| } |
| |
| done: |
| if (buffer) |
| free(buffer); |
| if (cmd) |
| free(cmd); |
| return ret; |
| } |
| |
| /** |
| * @brief Issue a tx power display command |
| * |
| * @param argc number of arguments |
| * @param argv A pointer to arguments array |
| * |
| * @return MLAN_STATUS_SUCCESS--success, otherwise--fail |
| */ |
| int |
| process_txpowdisp(int argc, char *argv[]) |
| { |
| int ret = MLAN_STATUS_SUCCESS; |
| int rateIdx, rates; |
| int connected; |
| struct ifreq ifr; |
| t_u8 *buffer = NULL, *pos = NULL; |
| t_u32 cmd_len = 0, cmd_header_len; |
| struct eth_priv_cmd *cmd = NULL; |
| HostCmd_DS_GEN *hostcmd; |
| HostCmd_DS_802_11_RF_TX_POWER *pRfTxPower; |
| RatePower_t ratePower[50]; |
| |
| cmd_header_len = strlen(CMD_MARVELL) + strlen(HOSTCMD); |
| |
| buffer = (t_u8 *)malloc(BUFFER_LENGTH); |
| if (buffer == NULL) { |
| fprintf(stderr, "Cannot alloc memory\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| memset(buffer, 0, BUFFER_LENGTH); |
| |
| cmd = (struct eth_priv_cmd *)malloc(sizeof(struct eth_priv_cmd)); |
| if (!cmd) { |
| printf("ERR:Cannot allocate buffer for command!\n"); |
| ret = ENOMEM; |
| goto done; |
| } |
| |
| /* Fill up buffer */ |
| #ifdef USERSPACE_32BIT_OVER_KERNEL_64BIT |
| memset(cmd, 0, sizeof(struct eth_priv_cmd)); |
| memcpy(&cmd->buf, &buffer, sizeof(buffer)); |
| #else |
| cmd->buf = buffer; |
| #endif |
| cmd->used_len = 0; |
| cmd->total_len = BUFFER_LENGTH; |
| |
| /* buffer = MRVL_CMD<cmd> */ |
| strncpy((char *)buffer, CMD_MARVELL, strlen(CMD_MARVELL)); |
| strncpy((char *)buffer + strlen(CMD_MARVELL), HOSTCMD, strlen(HOSTCMD)); |
| |
| /* buffer = MRVL_CMD<cmd><hostcmd_size><HostCmd_DS_GEN><CMD_DS> */ |
| hostcmd = (HostCmd_DS_GEN *)(buffer + cmd_header_len + sizeof(t_u32)); |
| |
| /* Point after host command header */ |
| pos = (t_u8 *)hostcmd + S_DS_GEN; |
| |
| cmd_len = S_DS_GEN + sizeof(HostCmd_DS_802_11_RF_TX_POWER); |
| |
| pRfTxPower = (HostCmd_DS_802_11_RF_TX_POWER *)pos; |
| |
| memset(pRfTxPower, 0x00, sizeof(HostCmd_DS_802_11_RF_TX_POWER)); |
| |
| hostcmd->command = cpu_to_le16(HostCmd_CMD_802_11_RF_TX_POWER); |
| hostcmd->size = cpu_to_le16(cmd_len); |
| hostcmd->seq_num = 0; |
| hostcmd->result = 0; |
| |
| /* Put buffer length */ |
| memcpy(buffer + cmd_header_len, &cmd_len, sizeof(t_u32)); |
| |
| /* Initialize the ifr structure */ |
| memset(&ifr, 0, sizeof(ifr)); |
| strncpy(ifr.ifr_ifrn.ifrn_name, dev_name, strlen(dev_name)); |
| ifr.ifr_ifru.ifru_data = (void *)cmd; |
| /* Perform ioctl */ |
| if (ioctl(sockfd, MLAN_ETH_PRIV, &ifr)) { |
| perror("ioctl[hostcmd]"); |
| printf("ERR:Command sending failed!\n"); |
| ret = -EFAULT; |
| goto done; |
| } |
| |
| connected = le16_to_cpu(pRfTxPower->current_level) ? TRUE : FALSE; |
| |
| if (connected) { |
| display_channel(); |
| |
| printf("\n"); |
| printf(" MinPower: %2d\n", pRfTxPower->min_power); |
| printf(" MaxPower: %2d\n", pRfTxPower->max_power); |
| printf(" Current: %2d\n", |
| le16_to_cpu(pRfTxPower->current_level)); |
| printf("\n"); |
| } |
| |
| rates = get_txpwrcfg(ratePower); |
| |
| puts(""); |
| |
| rateSort(ratePower, rates); |
| |
| printf("20MHz:"); |
| |
| for (rateIdx = 0; rateIdx < 12; rateIdx++) { |
| printf("%6s", get_ratestr(ratePower[rateIdx].rate)); |
| } |
| |
| printf("\n---------------------------------------" |
| "----------------------------------------\n%s", |
| connected ? "Active" : "Max "); |
| |
| for (rateIdx = 0; rateIdx < 12; rateIdx++) { |
| printf("%6d", ratePower[rateIdx].max); |
| } |
| |
| if (!connected) { |
| printf("\n---------------------------------------" |
| "----------------------------------------\n"); |
| |
| process_chantrpcdisp(ratePower[0].rate, ratePower[12 - 1].rate); |
| } |
| |
| puts("\n"); |
| |
| /* |
| ** MCS0 -> MCS7 |
| */ |
| |
| printf("20MHz:"); |
| |
| for (rateIdx = 12; rateIdx < 20; rateIdx++) { |
| printf("%6s", get_ratestr(ratePower[rateIdx].rate)); |
| } |
| |
| printf("\n---------------------------------------" |
| "----------------------------------------\n%s", |
| connected ? "Active" : "Max "); |
| |
| for (rateIdx = 12; rateIdx < 20; rateIdx++) { |
| printf("%6d", ratePower[rateIdx].max); |
| } |
| |
| if (!connected) { |
| printf("\n---------------------------------------" |
| "----------------------------------------\n"); |
| |
| process_chantrpcdisp(ratePower[12].rate, |
| ratePower[20 - 1].rate); |
| } |
| |
| puts("\n"); |
| |
| /* |
| ** MCS0 -> MCS7 @ 40MHz |
| */ |
| |
| printf("40MHz:"); |
| |
| for (rateIdx = 20; rateIdx < rates; rateIdx++) { |
| printf("%6s", get_ratestr(ratePower[rateIdx].rate)); |
| } |
| |
| printf("\n---------------------------------------" |
| "----------------------------------------\n%s", |
| connected ? "Active" : "Max "); |
| |
| for (rateIdx = 20; rateIdx < rates; rateIdx++) { |
| printf("%6d", ratePower[rateIdx].max); |
| } |
| |
| if (!connected) { |
| printf("\n---------------------------------------" |
| "----------------------------------------\n"); |
| |
| process_chantrpcdisp(ratePower[20].rate, |
| ratePower[rates - 1].rate); |
| } |
| |
| puts("\n"); |
| |
| done: |
| if (buffer) |
| free(buffer); |
| if (cmd) |
| free(cmd); |
| return ret; |
| } |