1 /* 2 * Copyright (c) 2015-2017 The Linux Foundation. All rights reserved. 3 * 4 * Previously licensed under the ISC license by Qualcomm Atheros, Inc. 5 * 6 * 7 * Permission to use, copy, modify, and/or distribute this software for 8 * any purpose with or without fee is hereby granted, provided that the 9 * above copyright notice and this permission notice appear in all 10 * copies. 11 * 12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 13 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 14 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 15 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 16 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 17 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 18 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 19 * PERFORMANCE OF THIS SOFTWARE. 20 */ 21 22 /* 23 * This file was originally distributed by Qualcomm Atheros, Inc. 24 * under proprietary terms before Copyright ownership was assigned 25 * to the Linux Foundation. 26 */ 27 28 #include <linux/pci.h> 29 #include <linux/slab.h> 30 #include <linux/interrupt.h> 31 #include <linux/if_arp.h> 32 #include "qdf_lock.h" 33 #include "qdf_types.h" 34 #include "qdf_status.h" 35 #include "regtable.h" 36 #include "hif.h" 37 #include "hif_io32.h" 38 #include "ce_main.h" 39 #include "ce_api.h" 40 #include "ce_reg.h" 41 #include "ce_internal.h" 42 #include "ce_tasklet.h" 43 #include "pld_common.h" 44 #include "hif_debug.h" 45 #include "hif_napi.h" 46 47 48 /** 49 * struct tasklet_work 50 * 51 * @id: ce_id 52 * @work: work 53 */ 54 struct tasklet_work { 55 enum ce_id_type id; 56 void *data; 57 struct work_struct work; 58 }; 59 60 61 /** 62 * reschedule_ce_tasklet_work_handler() - reschedule work 63 * @work: struct work_struct 64 * 65 * Return: N/A 66 */ 67 static void reschedule_ce_tasklet_work_handler(struct work_struct *work) 68 { 69 struct tasklet_work *ce_work = container_of(work, struct tasklet_work, 70 work); 71 struct hif_softc *scn = ce_work->data; 72 struct HIF_CE_state *hif_ce_state; 73 74 if (NULL == scn) { 75 HIF_ERROR("%s: tasklet scn is null", __func__); 76 return; 77 } 78 79 hif_ce_state = HIF_GET_CE_STATE(scn); 80 81 if (scn->hif_init_done == false) { 82 HIF_ERROR("%s: wlan driver is unloaded", __func__); 83 return; 84 } 85 tasklet_schedule(&hif_ce_state->tasklets[ce_work->id].intr_tq); 86 } 87 88 static struct tasklet_work tasklet_workers[CE_ID_MAX]; 89 static bool work_initialized; 90 91 /** 92 * init_tasklet_work() - init_tasklet_work 93 * @work: struct work_struct 94 * @work_handler: work_handler 95 * 96 * Return: N/A 97 */ 98 static void init_tasklet_work(struct work_struct *work, 99 work_func_t work_handler) 100 { 101 INIT_WORK(work, work_handler); 102 } 103 104 /** 105 * init_tasklet_workers() - init_tasklet_workers 106 * @scn: HIF Context 107 * 108 * Return: N/A 109 */ 110 void init_tasklet_workers(struct hif_opaque_softc *scn) 111 { 112 uint32_t id; 113 114 for (id = 0; id < CE_ID_MAX; id++) { 115 tasklet_workers[id].id = id; 116 tasklet_workers[id].data = scn; 117 init_tasklet_work(&tasklet_workers[id].work, 118 reschedule_ce_tasklet_work_handler); 119 } 120 work_initialized = true; 121 } 122 123 #ifdef HIF_CONFIG_SLUB_DEBUG_ON 124 /** 125 * ce_schedule_tasklet() - schedule ce tasklet 126 * @tasklet_entry: struct ce_tasklet_entry 127 * 128 * Return: N/A 129 */ 130 static inline void ce_schedule_tasklet(struct ce_tasklet_entry *tasklet_entry) 131 { 132 if (work_initialized && (tasklet_entry->ce_id < CE_ID_MAX)) 133 schedule_work(&tasklet_workers[tasklet_entry->ce_id].work); 134 else 135 HIF_ERROR("%s: work_initialized = %d, ce_id = %d", 136 __func__, work_initialized, tasklet_entry->ce_id); 137 } 138 #else 139 /** 140 * ce_schedule_tasklet() - schedule ce tasklet 141 * @tasklet_entry: struct ce_tasklet_entry 142 * 143 * Return: N/A 144 */ 145 static inline void ce_schedule_tasklet(struct ce_tasklet_entry *tasklet_entry) 146 { 147 tasklet_schedule(&tasklet_entry->intr_tq); 148 } 149 #endif 150 151 /** 152 * ce_tasklet() - ce_tasklet 153 * @data: data 154 * 155 * Return: N/A 156 */ 157 static void ce_tasklet(unsigned long data) 158 { 159 struct ce_tasklet_entry *tasklet_entry = 160 (struct ce_tasklet_entry *)data; 161 struct HIF_CE_state *hif_ce_state = tasklet_entry->hif_ce_state; 162 struct hif_softc *scn = HIF_GET_SOFTC(hif_ce_state); 163 struct CE_state *CE_state = scn->ce_id_to_state[tasklet_entry->ce_id]; 164 165 hif_record_ce_desc_event(scn, tasklet_entry->ce_id, 166 HIF_CE_TASKLET_ENTRY, NULL, NULL, 0); 167 168 if (qdf_atomic_read(&scn->link_suspended)) { 169 HIF_ERROR("%s: ce %d tasklet fired after link suspend.", 170 __func__, tasklet_entry->ce_id); 171 QDF_BUG(0); 172 } 173 174 qdf_spin_lock_bh(&CE_state->lro_unloading_lock); 175 ce_per_engine_service(scn, tasklet_entry->ce_id); 176 177 qdf_lro_flush(CE_state->lro_data); 178 179 qdf_spin_unlock_bh(&CE_state->lro_unloading_lock); 180 181 if (ce_check_rx_pending(CE_state)) { 182 /* 183 * There are frames pending, schedule tasklet to process them. 184 * Enable the interrupt only when there is no pending frames in 185 * any of the Copy Engine pipes. 186 */ 187 hif_record_ce_desc_event(scn, tasklet_entry->ce_id, 188 HIF_CE_TASKLET_RESCHEDULE, NULL, NULL, 0); 189 ce_schedule_tasklet(tasklet_entry); 190 return; 191 } 192 193 if (scn->target_status != TARGET_STATUS_RESET) 194 hif_irq_enable(scn, tasklet_entry->ce_id); 195 196 hif_record_ce_desc_event(scn, tasklet_entry->ce_id, HIF_CE_TASKLET_EXIT, 197 NULL, NULL, 0); 198 199 qdf_atomic_dec(&scn->active_tasklet_cnt); 200 } 201 202 /** 203 * ce_tasklet_init() - ce_tasklet_init 204 * @hif_ce_state: hif_ce_state 205 * @mask: mask 206 * 207 * Return: N/A 208 */ 209 void ce_tasklet_init(struct HIF_CE_state *hif_ce_state, uint32_t mask) 210 { 211 int i; 212 213 for (i = 0; i < CE_COUNT_MAX; i++) { 214 if (mask & (1 << i)) { 215 hif_ce_state->tasklets[i].ce_id = i; 216 hif_ce_state->tasklets[i].inited = true; 217 hif_ce_state->tasklets[i].hif_ce_state = hif_ce_state; 218 tasklet_init(&hif_ce_state->tasklets[i].intr_tq, 219 ce_tasklet, 220 (unsigned long)&hif_ce_state->tasklets[i]); 221 } 222 } 223 } 224 /** 225 * ce_tasklet_kill() - ce_tasklet_kill 226 * @hif_ce_state: hif_ce_state 227 * 228 * Return: N/A 229 */ 230 void ce_tasklet_kill(struct hif_softc *scn) 231 { 232 int i; 233 struct HIF_CE_state *hif_ce_state = HIF_GET_CE_STATE(scn); 234 235 for (i = 0; i < CE_COUNT_MAX; i++) 236 if (hif_ce_state->tasklets[i].inited) { 237 tasklet_kill(&hif_ce_state->tasklets[i].intr_tq); 238 hif_ce_state->tasklets[i].inited = false; 239 } 240 qdf_atomic_set(&scn->active_tasklet_cnt, 0); 241 } 242 243 #define HIF_CE_DRAIN_WAIT_CNT 20 244 /** 245 * hif_drain_tasklets(): wait untill no tasklet is pending 246 * @scn: hif context 247 * 248 * Let running tasklets clear pending trafic. 249 * 250 * Return: 0 if no bottom half is in progress when it returns. 251 * -EFAULT if it times out. 252 */ 253 int hif_drain_tasklets(struct hif_softc *scn) 254 { 255 uint32_t ce_drain_wait_cnt = 0; 256 int32_t tasklet_cnt; 257 258 while ((tasklet_cnt = qdf_atomic_read(&scn->active_tasklet_cnt))) { 259 if (++ce_drain_wait_cnt > HIF_CE_DRAIN_WAIT_CNT) { 260 HIF_ERROR("%s: CE still not done with access: %d", 261 __func__, tasklet_cnt); 262 263 return -EFAULT; 264 } 265 HIF_INFO("%s: Waiting for CE to finish access", __func__); 266 msleep(10); 267 } 268 return 0; 269 } 270 271 #ifdef WLAN_SUSPEND_RESUME_TEST 272 /** 273 * hif_interrupt_is_ut_resume(): Tests if an irq on the given copy engine should 274 * trigger a unit-test resume. 275 * @scn: The HIF context to operate on 276 * @ce_id: The copy engine Id from the originating interrupt 277 * 278 * Return: true if the raised irq should trigger a unit-test resume 279 */ 280 static bool hif_interrupt_is_ut_resume(struct hif_softc *scn, int ce_id) 281 { 282 int errno; 283 uint8_t wake_ce_id; 284 285 if (!hif_is_ut_suspended(scn)) 286 return false; 287 288 /* ensure passed ce_id matches wake ce_id */ 289 errno = hif_get_wake_ce_id(scn, &wake_ce_id); 290 if (errno) { 291 HIF_ERROR("%s: failed to get wake CE Id: %d", __func__, errno); 292 return false; 293 } 294 295 return ce_id == wake_ce_id; 296 } 297 #else 298 static inline bool 299 hif_interrupt_is_ut_resume(struct hif_softc *scn, int ce_id) 300 { 301 return false; 302 } 303 #endif /* WLAN_SUSPEND_RESUME_TEST */ 304 305 /** 306 * hif_snoc_interrupt_handler() - hif_snoc_interrupt_handler 307 * @irq: irq coming from kernel 308 * @context: context 309 * 310 * Return: N/A 311 */ 312 static irqreturn_t hif_snoc_interrupt_handler(int irq, void *context) 313 { 314 struct ce_tasklet_entry *tasklet_entry = context; 315 struct hif_softc *scn = HIF_GET_SOFTC(tasklet_entry->hif_ce_state); 316 317 return ce_dispatch_interrupt(pld_get_ce_id(scn->qdf_dev->dev, irq), 318 tasklet_entry); 319 } 320 321 /** 322 * hif_ce_increment_interrupt_count() - update ce stats 323 * @hif_ce_state: ce state 324 * @ce_id: ce id 325 * 326 * Return: none 327 */ 328 static inline void 329 hif_ce_increment_interrupt_count(struct HIF_CE_state *hif_ce_state, int ce_id) 330 { 331 int cpu_id = qdf_get_cpu(); 332 333 hif_ce_state->stats.ce_per_cpu[ce_id][cpu_id]++; 334 } 335 336 /** 337 * hif_display_ce_stats() - display ce stats 338 * @hif_ce_state: ce state 339 * 340 * Return: none 341 */ 342 void hif_display_ce_stats(struct HIF_CE_state *hif_ce_state) 343 { 344 #define STR_SIZE 128 345 uint8_t i, j, pos; 346 char str_buffer[STR_SIZE]; 347 int size, ret; 348 349 qdf_debug("CE interrupt statistics:"); 350 for (i = 0; i < CE_COUNT_MAX; i++) { 351 size = STR_SIZE; 352 pos = 0; 353 for (j = 0; j < QDF_MAX_AVAILABLE_CPU; j++) { 354 ret = snprintf(str_buffer + pos, size, "[%d]:%d ", 355 j, hif_ce_state->stats.ce_per_cpu[i][j]); 356 if (ret <= 0 || ret >= size) 357 break; 358 size -= ret; 359 pos += ret; 360 } 361 qdf_debug("CE id[%2d] - %s", i, str_buffer); 362 } 363 #undef STR_SIZE 364 } 365 366 /** 367 * hif_clear_ce_stats() - clear ce stats 368 * @hif_ce_state: ce state 369 * 370 * Return: none 371 */ 372 void hif_clear_ce_stats(struct HIF_CE_state *hif_ce_state) 373 { 374 qdf_mem_zero(&hif_ce_state->stats, sizeof(struct ce_stats)); 375 } 376 377 /** 378 * ce_dispatch_interrupt() - dispatch an interrupt to a processing context 379 * @ce_id: ce_id 380 * @tasklet_entry: context 381 * 382 * Return: N/A 383 */ 384 irqreturn_t ce_dispatch_interrupt(int ce_id, 385 struct ce_tasklet_entry *tasklet_entry) 386 { 387 struct HIF_CE_state *hif_ce_state = tasklet_entry->hif_ce_state; 388 struct hif_softc *scn = HIF_GET_SOFTC(hif_ce_state); 389 struct hif_opaque_softc *hif_hdl = GET_HIF_OPAQUE_HDL(scn); 390 391 if (tasklet_entry->ce_id != ce_id) { 392 HIF_ERROR("%s: ce_id (expect %d, received %d) does not match", 393 __func__, tasklet_entry->ce_id, ce_id); 394 return IRQ_NONE; 395 } 396 if (unlikely(ce_id >= CE_COUNT_MAX)) { 397 HIF_ERROR("%s: ce_id=%d > CE_COUNT_MAX=%d", 398 __func__, tasklet_entry->ce_id, CE_COUNT_MAX); 399 return IRQ_NONE; 400 } 401 402 hif_irq_disable(scn, ce_id); 403 hif_record_ce_desc_event(scn, ce_id, HIF_IRQ_EVENT, NULL, NULL, 0); 404 hif_ce_increment_interrupt_count(hif_ce_state, ce_id); 405 406 if (unlikely(hif_interrupt_is_ut_resume(scn, ce_id))) { 407 hif_ut_fw_resume(scn); 408 hif_irq_enable(scn, ce_id); 409 return IRQ_HANDLED; 410 } 411 412 qdf_atomic_inc(&scn->active_tasklet_cnt); 413 414 if (hif_napi_enabled(hif_hdl, ce_id)) 415 hif_napi_schedule(hif_hdl, ce_id); 416 else 417 tasklet_schedule(&tasklet_entry->intr_tq); 418 419 return IRQ_HANDLED; 420 } 421 422 /** 423 * const char *ce_name 424 * 425 * @ce_name: ce_name 426 */ 427 const char *ce_name[] = { 428 "WLAN_CE_0", 429 "WLAN_CE_1", 430 "WLAN_CE_2", 431 "WLAN_CE_3", 432 "WLAN_CE_4", 433 "WLAN_CE_5", 434 "WLAN_CE_6", 435 "WLAN_CE_7", 436 "WLAN_CE_8", 437 "WLAN_CE_9", 438 "WLAN_CE_10", 439 "WLAN_CE_11", 440 }; 441 /** 442 * ce_unregister_irq() - ce_unregister_irq 443 * @hif_ce_state: hif_ce_state copy engine device handle 444 * @mask: which coppy engines to unregister for. 445 * 446 * Unregisters copy engine irqs matching mask. If a 1 is set at bit x, 447 * unregister for copy engine x. 448 * 449 * Return: QDF_STATUS 450 */ 451 QDF_STATUS ce_unregister_irq(struct HIF_CE_state *hif_ce_state, uint32_t mask) 452 { 453 int id; 454 int ce_count; 455 int ret; 456 struct hif_softc *scn; 457 458 if (hif_ce_state == NULL) { 459 HIF_WARN("%s: hif_ce_state = NULL", __func__); 460 return QDF_STATUS_SUCCESS; 461 } 462 463 scn = HIF_GET_SOFTC(hif_ce_state); 464 ce_count = scn->ce_count; 465 /* we are removing interrupts, so better stop NAPI */ 466 ret = hif_napi_event(GET_HIF_OPAQUE_HDL(scn), 467 NAPI_EVT_INT_STATE, (void *)0); 468 if (ret != 0) 469 HIF_ERROR("%s: napi_event INT_STATE returned %d", 470 __func__, ret); 471 /* this is not fatal, continue */ 472 473 /* filter mask to free only for ce's with irq registered */ 474 mask &= hif_ce_state->ce_register_irq_done; 475 for (id = 0; id < ce_count; id++) { 476 if ((mask & (1 << id)) && hif_ce_state->tasklets[id].inited) { 477 ret = pld_ce_free_irq(scn->qdf_dev->dev, id, 478 &hif_ce_state->tasklets[id]); 479 if (ret < 0) 480 HIF_ERROR( 481 "%s: pld_unregister_irq error - ce_id = %d, ret = %d", 482 __func__, id, ret); 483 } 484 } 485 hif_ce_state->ce_register_irq_done &= ~mask; 486 487 return QDF_STATUS_SUCCESS; 488 } 489 /** 490 * ce_register_irq() - ce_register_irq 491 * @hif_ce_state: hif_ce_state 492 * @mask: which coppy engines to unregister for. 493 * 494 * Registers copy engine irqs matching mask. If a 1 is set at bit x, 495 * Register for copy engine x. 496 * 497 * Return: QDF_STATUS 498 */ 499 QDF_STATUS ce_register_irq(struct HIF_CE_state *hif_ce_state, uint32_t mask) 500 { 501 int id; 502 int ce_count; 503 int ret; 504 unsigned long irqflags = IRQF_TRIGGER_RISING; 505 uint32_t done_mask = 0; 506 struct hif_softc *scn = HIF_GET_SOFTC(hif_ce_state); 507 508 ce_count = scn->ce_count; 509 510 for (id = 0; id < ce_count; id++) { 511 if ((mask & (1 << id)) && hif_ce_state->tasklets[id].inited) { 512 ret = pld_ce_request_irq(scn->qdf_dev->dev, id, 513 hif_snoc_interrupt_handler, 514 irqflags, ce_name[id], 515 &hif_ce_state->tasklets[id]); 516 if (ret) { 517 HIF_ERROR( 518 "%s: cannot register CE %d irq handler, ret = %d", 519 __func__, id, ret); 520 ce_unregister_irq(hif_ce_state, done_mask); 521 return QDF_STATUS_E_FAULT; 522 } 523 done_mask |= 1 << id; 524 } 525 } 526 hif_ce_state->ce_register_irq_done |= done_mask; 527 528 return QDF_STATUS_SUCCESS; 529 } 530