1/* 2 * Copyright (c) 2010 Atheros Communications Inc. 3 * 4 * Permission to use, copy, modify, and/or distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17#include "htc.h" 18 19static const char *wmi_cmd_to_name(enum wmi_cmd_id wmi_cmd) 20{ 21 switch (wmi_cmd) { 22 case WMI_ECHO_CMDID: 23 return "WMI_ECHO_CMDID"; 24 case WMI_ACCESS_MEMORY_CMDID: 25 return "WMI_ACCESS_MEMORY_CMDID"; 26 case WMI_DISABLE_INTR_CMDID: 27 return "WMI_DISABLE_INTR_CMDID"; 28 case WMI_ENABLE_INTR_CMDID: 29 return "WMI_ENABLE_INTR_CMDID"; 30 case WMI_RX_LINK_CMDID: 31 return "WMI_RX_LINK_CMDID"; 32 case WMI_ATH_INIT_CMDID: 33 return "WMI_ATH_INIT_CMDID"; 34 case WMI_ABORT_TXQ_CMDID: 35 return "WMI_ABORT_TXQ_CMDID"; 36 case WMI_STOP_TX_DMA_CMDID: 37 return "WMI_STOP_TX_DMA_CMDID"; 38 case WMI_STOP_DMA_RECV_CMDID: 39 return "WMI_STOP_DMA_RECV_CMDID"; 40 case WMI_ABORT_TX_DMA_CMDID: 41 return "WMI_ABORT_TX_DMA_CMDID"; 42 case WMI_DRAIN_TXQ_CMDID: 43 return "WMI_DRAIN_TXQ_CMDID"; 44 case WMI_DRAIN_TXQ_ALL_CMDID: 45 return "WMI_DRAIN_TXQ_ALL_CMDID"; 46 case WMI_START_RECV_CMDID: 47 return "WMI_START_RECV_CMDID"; 48 case WMI_STOP_RECV_CMDID: 49 return "WMI_STOP_RECV_CMDID"; 50 case WMI_FLUSH_RECV_CMDID: 51 return "WMI_FLUSH_RECV_CMDID"; 52 case WMI_SET_MODE_CMDID: 53 return "WMI_SET_MODE_CMDID"; 54 case WMI_RESET_CMDID: 55 return "WMI_RESET_CMDID"; 56 case WMI_NODE_CREATE_CMDID: 57 return "WMI_NODE_CREATE_CMDID"; 58 case WMI_NODE_REMOVE_CMDID: 59 return "WMI_NODE_REMOVE_CMDID"; 60 case WMI_VAP_REMOVE_CMDID: 61 return "WMI_VAP_REMOVE_CMDID"; 62 case WMI_VAP_CREATE_CMDID: 63 return "WMI_VAP_CREATE_CMDID"; 64 case WMI_BEACON_UPDATE_CMDID: 65 return "WMI_BEACON_UPDATE_CMDID"; 66 case WMI_REG_READ_CMDID: 67 return "WMI_REG_READ_CMDID"; 68 case WMI_REG_WRITE_CMDID: 69 return "WMI_REG_WRITE_CMDID"; 70 case WMI_RC_STATE_CHANGE_CMDID: 71 return "WMI_RC_STATE_CHANGE_CMDID"; 72 case WMI_RC_RATE_UPDATE_CMDID: 73 return "WMI_RC_RATE_UPDATE_CMDID"; 74 case WMI_DEBUG_INFO_CMDID: 75 return "WMI_DEBUG_INFO_CMDID"; 76 case WMI_HOST_ATTACH: 77 return "WMI_HOST_ATTACH"; 78 case WMI_TARGET_IC_UPDATE_CMDID: 79 return "WMI_TARGET_IC_UPDATE_CMDID"; 80 case WMI_TGT_STATS_CMDID: 81 return "WMI_TGT_STATS_CMDID"; 82 case WMI_TX_AGGR_ENABLE_CMDID: 83 return "WMI_TX_AGGR_ENABLE_CMDID"; 84 case WMI_TGT_DETACH_CMDID: 85 return "WMI_TGT_DETACH_CMDID"; 86 case WMI_TGT_TXQ_ENABLE_CMDID: 87 return "WMI_TGT_TXQ_ENABLE_CMDID"; 88 } 89 90 return "Bogus"; 91} 92 93struct wmi *ath9k_init_wmi(struct ath9k_htc_priv *priv) 94{ 95 struct wmi *wmi; 96 97 wmi = kzalloc(sizeof(struct wmi), GFP_KERNEL); 98 if (!wmi) 99 return NULL; 100 101 wmi->drv_priv = priv; 102 wmi->stopped = false; 103 mutex_init(&wmi->op_mutex); 104 mutex_init(&wmi->multi_write_mutex); 105 init_completion(&wmi->cmd_wait); 106 107 return wmi; 108} 109 110void ath9k_deinit_wmi(struct ath9k_htc_priv *priv) 111{ 112 struct wmi *wmi = priv->wmi; 113 114 mutex_lock(&wmi->op_mutex); 115 wmi->stopped = true; 116 mutex_unlock(&wmi->op_mutex); 117 118 kfree(priv->wmi); 119} 120 121void ath9k_wmi_tasklet(unsigned long data) 122{ 123 struct ath9k_htc_priv *priv = (struct ath9k_htc_priv *)data; 124 struct ath_common *common = ath9k_hw_common(priv->ah); 125 struct wmi_cmd_hdr *hdr; 126 struct wmi_swba *swba_hdr; 127 enum wmi_event_id event; 128 struct sk_buff *skb; 129 void *wmi_event; 130 unsigned long flags; 131#ifdef CONFIG_ATH9K_HTC_DEBUGFS 132 __be32 txrate; 133#endif 134 135 spin_lock_irqsave(&priv->wmi->wmi_lock, flags); 136 skb = priv->wmi->wmi_skb; 137 spin_unlock_irqrestore(&priv->wmi->wmi_lock, flags); 138 139 hdr = (struct wmi_cmd_hdr *) skb->data; 140 event = be16_to_cpu(hdr->command_id); 141 wmi_event = skb_pull(skb, sizeof(struct wmi_cmd_hdr)); 142 143 ath_print(common, ATH_DBG_WMI, 144 "WMI Event: 0x%x\n", event); 145 146 switch (event) { 147 case WMI_TGT_RDY_EVENTID: 148 break; 149 case WMI_SWBA_EVENTID: 150 swba_hdr = (struct wmi_swba *) wmi_event; 151 ath9k_htc_swba(priv, swba_hdr->beacon_pending); 152 break; 153 case WMI_FATAL_EVENTID: 154 break; 155 case WMI_TXTO_EVENTID: 156 break; 157 case WMI_BMISS_EVENTID: 158 break; 159 case WMI_WLAN_TXCOMP_EVENTID: 160 break; 161 case WMI_DELBA_EVENTID: 162 break; 163 case WMI_TXRATE_EVENTID: 164#ifdef CONFIG_ATH9K_HTC_DEBUGFS 165 txrate = ((struct wmi_event_txrate *)wmi_event)->txrate; 166 priv->debug.txrate = be32_to_cpu(txrate); 167#endif 168 break; 169 default: 170 break; 171 } 172 173 kfree_skb(skb); 174} 175 176static void ath9k_wmi_rsp_callback(struct wmi *wmi, struct sk_buff *skb) 177{ 178 skb_pull(skb, sizeof(struct wmi_cmd_hdr)); 179 180 if (wmi->cmd_rsp_buf != NULL && wmi->cmd_rsp_len != 0) 181 memcpy(wmi->cmd_rsp_buf, skb->data, wmi->cmd_rsp_len); 182 183 complete(&wmi->cmd_wait); 184} 185 186static void ath9k_wmi_ctrl_rx(void *priv, struct sk_buff *skb, 187 enum htc_endpoint_id epid) 188{ 189 struct wmi *wmi = (struct wmi *) priv; 190 struct wmi_cmd_hdr *hdr; 191 u16 cmd_id; 192 193 if (unlikely(wmi->stopped)) 194 goto free_skb; 195 196 hdr = (struct wmi_cmd_hdr *) skb->data; 197 cmd_id = be16_to_cpu(hdr->command_id); 198 199 if (cmd_id & 0x1000) { 200 spin_lock(&wmi->wmi_lock); 201 wmi->wmi_skb = skb; 202 spin_unlock(&wmi->wmi_lock); 203 tasklet_schedule(&wmi->drv_priv->wmi_tasklet); 204 return; 205 } 206 207 /* Check if there has been a timeout. */ 208 spin_lock(&wmi->wmi_lock); 209 if (cmd_id != wmi->last_cmd_id) { 210 spin_unlock(&wmi->wmi_lock); 211 goto free_skb; 212 } 213 spin_unlock(&wmi->wmi_lock); 214 215 /* WMI command response */ 216 ath9k_wmi_rsp_callback(wmi, skb); 217 218free_skb: 219 kfree_skb(skb); 220} 221 222static void ath9k_wmi_ctrl_tx(void *priv, struct sk_buff *skb, 223 enum htc_endpoint_id epid, bool txok) 224{ 225 kfree_skb(skb); 226} 227 228int ath9k_wmi_connect(struct htc_target *htc, struct wmi *wmi, 229 enum htc_endpoint_id *wmi_ctrl_epid) 230{ 231 struct htc_service_connreq connect; 232 int ret; 233 234 wmi->htc = htc; 235 236 memset(&connect, 0, sizeof(connect)); 237 238 connect.ep_callbacks.priv = wmi; 239 connect.ep_callbacks.tx = ath9k_wmi_ctrl_tx; 240 connect.ep_callbacks.rx = ath9k_wmi_ctrl_rx; 241 connect.service_id = WMI_CONTROL_SVC; 242 243 ret = htc_connect_service(htc, &connect, &wmi->ctrl_epid); 244 if (ret) 245 return ret; 246 247 *wmi_ctrl_epid = wmi->ctrl_epid; 248 249 return 0; 250} 251 252static int ath9k_wmi_cmd_issue(struct wmi *wmi, 253 struct sk_buff *skb, 254 enum wmi_cmd_id cmd, u16 len) 255{ 256 struct wmi_cmd_hdr *hdr; 257 258 hdr = (struct wmi_cmd_hdr *) skb_push(skb, sizeof(struct wmi_cmd_hdr)); 259 hdr->command_id = cpu_to_be16(cmd); 260 hdr->seq_no = cpu_to_be16(++wmi->tx_seq_id); 261 262 return htc_send(wmi->htc, skb, wmi->ctrl_epid, NULL); 263} 264 265int ath9k_wmi_cmd(struct wmi *wmi, enum wmi_cmd_id cmd_id, 266 u8 *cmd_buf, u32 cmd_len, 267 u8 *rsp_buf, u32 rsp_len, 268 u32 timeout) 269{ 270 struct ath_hw *ah = wmi->drv_priv->ah; 271 struct ath_common *common = ath9k_hw_common(ah); 272 u16 headroom = sizeof(struct htc_frame_hdr) + 273 sizeof(struct wmi_cmd_hdr); 274 struct sk_buff *skb; 275 u8 *data; 276 int time_left, ret = 0; 277 unsigned long flags; 278 279 if (wmi->drv_priv->op_flags & OP_UNPLUGGED) 280 return 0; 281 282 skb = alloc_skb(headroom + cmd_len, GFP_ATOMIC); 283 if (!skb) 284 return -ENOMEM; 285 286 skb_reserve(skb, headroom); 287 288 if (cmd_len != 0 && cmd_buf != NULL) { 289 data = (u8 *) skb_put(skb, cmd_len); 290 memcpy(data, cmd_buf, cmd_len); 291 } 292 293 mutex_lock(&wmi->op_mutex); 294 295 /* check if wmi stopped flag is set */ 296 if (unlikely(wmi->stopped)) { 297 ret = -EPROTO; 298 goto out; 299 } 300 301 /* record the rsp buffer and length */ 302 wmi->cmd_rsp_buf = rsp_buf; 303 wmi->cmd_rsp_len = rsp_len; 304 305 spin_lock_irqsave(&wmi->wmi_lock, flags); 306 wmi->last_cmd_id = cmd_id; 307 spin_unlock_irqrestore(&wmi->wmi_lock, flags); 308 309 ret = ath9k_wmi_cmd_issue(wmi, skb, cmd_id, cmd_len); 310 if (ret) 311 goto out; 312 313 time_left = wait_for_completion_timeout(&wmi->cmd_wait, timeout); 314 if (!time_left) { 315 ath_print(common, ATH_DBG_WMI, 316 "Timeout waiting for WMI command: %s\n", 317 wmi_cmd_to_name(cmd_id)); 318 mutex_unlock(&wmi->op_mutex); 319 return -ETIMEDOUT; 320 } 321 322 mutex_unlock(&wmi->op_mutex); 323 324 return 0; 325 326out: 327 ath_print(common, ATH_DBG_WMI, 328 "WMI failure for: %s\n", wmi_cmd_to_name(cmd_id)); 329 mutex_unlock(&wmi->op_mutex); 330 kfree_skb(skb); 331 332 return ret; 333} 334