1 /* 2 * Copyright (c) 2013-2014, 2016-2020 The Linux Foundation. All rights reserved. 3 * 4 * Permission to use, copy, modify, and/or distribute this software for 5 * any purpose with or without fee is hereby granted, provided that the 6 * above copyright notice and this permission notice appear in all 7 * copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL 10 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED 11 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE 12 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL 13 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR 14 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 15 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 16 * PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #if defined(CONFIG_ATH_PROCFS_DIAG_SUPPORT) 20 #include <linux/module.h> /* Specifically, a module */ 21 #include <linux/kernel.h> /* We're doing kernel work */ 22 #include <linux/version.h> /* We're doing kernel work */ 23 #include <linux/proc_fs.h> /* Necessary because we use the proc fs */ 24 #include <linux/uaccess.h> /* for copy_from_user */ 25 #include "hif.h" 26 #include "hif_main.h" 27 #if defined(HIF_USB) 28 #include "if_usb.h" 29 #endif 30 #if defined(HIF_SDIO) 31 #include "if_sdio.h" 32 #endif 33 #include "hif_debug.h" 34 #include "pld_common.h" 35 #include "target_type.h" 36 37 #define PROCFS_NAME "athdiagpfs" 38 #ifdef MULTI_IF_NAME 39 #define PROCFS_DIR "cld" MULTI_IF_NAME 40 #else 41 #define PROCFS_DIR "cld" 42 #endif 43 44 /** 45 * Get op_type, mem_type and offset fields from pos of procfs 46 * It will reuse pos, which is long long type 47 * 48 * op_type: 4 bits 49 * memtype: 8 bits 50 * reserve1: 20 bits 51 * offset: 32 bits 52 */ 53 #define OP_TYPE_LEGACY 0 54 #define OP_TYPE_EXT_QMI 1 55 #define OP_TYPE_EXT_DIRECT 2 56 57 #define ATH_DIAG_EXT_OP_TYPE_BITS 4 58 #define ATH_DIAG_EXT_OP_TYPE_INDEX 60 59 #define ATH_DIAG_EXT_MEM_TYPE_BITS 8 60 #define ATH_DIAG_EXT_MEM_TYPE_INDEX 52 61 #define ATH_DIAG_EXT_OFFSET_BITS 32 62 #define ATH_DIAG_EXT_OFFSET_INDEX 0 63 64 /** 65 * This structure hold information about the /proc file 66 * 67 */ 68 static struct proc_dir_entry *proc_file, *proc_dir; 69 70 static void *get_hif_hdl_from_file(struct file *file) 71 { 72 struct hif_opaque_softc *scn; 73 74 scn = (struct hif_opaque_softc *)PDE_DATA(file_inode(file)); 75 return (void *)scn; 76 } 77 78 static ssize_t ath_procfs_diag_read_legacy(struct file *file, 79 char __user *buf, 80 size_t count, loff_t *pos) 81 { 82 hif_handle_t hif_hdl; 83 int rv; 84 uint8_t *read_buffer = NULL; 85 struct hif_softc *scn; 86 uint32_t offset = 0, memtype = 0; 87 struct hif_target_info *tgt_info; 88 89 hif_hdl = get_hif_hdl_from_file(file); 90 scn = HIF_GET_SOFTC(hif_hdl); 91 92 read_buffer = qdf_mem_malloc(count); 93 if (!read_buffer) 94 return -ENOMEM; 95 96 hif_debug("rd buff 0x%pK cnt %zu offset 0x%x buf 0x%pK", 97 read_buffer, count, (int)*pos, buf); 98 99 tgt_info = hif_get_target_info_handle(GET_HIF_OPAQUE_HDL(hif_hdl)); 100 if ((scn->bus_type == QDF_BUS_TYPE_SNOC) || 101 (scn->bus_type == QDF_BUS_TYPE_PCI && 102 ((tgt_info->target_type == TARGET_TYPE_QCA6290) || 103 (tgt_info->target_type == TARGET_TYPE_QCA6390) || 104 (tgt_info->target_type == TARGET_TYPE_QCA6490) || 105 (tgt_info->target_type == TARGET_TYPE_QCA8074) || 106 (tgt_info->target_type == TARGET_TYPE_QCA8074V2) || 107 (tgt_info->target_type == TARGET_TYPE_QCN9000) || 108 (tgt_info->target_type == TARGET_TYPE_QCN9100) || 109 (tgt_info->target_type == TARGET_TYPE_QCA5018) || 110 (tgt_info->target_type == TARGET_TYPE_QCA6018) || 111 (tgt_info->target_type == TARGET_TYPE_QCN7605))) || 112 (scn->bus_type == QDF_BUS_TYPE_IPCI && 113 (tgt_info->target_type == TARGET_TYPE_QCA6750)) || 114 ((scn->bus_type == QDF_BUS_TYPE_USB) && 115 (tgt_info->target_type == TARGET_TYPE_QCN7605))) { 116 memtype = ((uint32_t)(*pos) & 0xff000000) >> 24; 117 offset = (uint32_t)(*pos) & 0xffffff; 118 hif_debug("offset 0x%x memtype 0x%x, datalen %zu", 119 offset, memtype, count); 120 rv = pld_athdiag_read(scn->qdf_dev->dev, 121 offset, memtype, count, 122 (uint8_t *)read_buffer); 123 goto out; 124 } 125 126 if ((count == 4) && ((((uint32_t) (*pos)) & 3) == 0)) { 127 /* reading a word? */ 128 rv = hif_diag_read_access(hif_hdl, (uint32_t)(*pos), 129 (uint32_t *)read_buffer); 130 } else { 131 rv = hif_diag_read_mem(hif_hdl, (uint32_t)(*pos), 132 (uint8_t *)read_buffer, count); 133 } 134 135 out: 136 if (rv) { 137 qdf_mem_free(read_buffer); 138 return -EIO; 139 } 140 141 if (copy_to_user(buf, read_buffer, count)) { 142 qdf_mem_free(read_buffer); 143 hif_err("copy_to_user error in /proc/%s", PROCFS_NAME); 144 return -EFAULT; 145 } 146 qdf_mem_free(read_buffer); 147 return count; 148 } 149 150 static ssize_t ath_procfs_diag_write_legacy(struct file *file, 151 const char __user *buf, 152 size_t count, loff_t *pos) 153 { 154 hif_handle_t hif_hdl; 155 int rv; 156 uint8_t *write_buffer = NULL; 157 struct hif_softc *scn; 158 uint32_t offset = 0, memtype = 0; 159 struct hif_target_info *tgt_info; 160 161 hif_hdl = get_hif_hdl_from_file(file); 162 scn = HIF_GET_SOFTC(hif_hdl); 163 164 write_buffer = qdf_mem_malloc(count); 165 if (!write_buffer) 166 return -ENOMEM; 167 168 if (copy_from_user(write_buffer, buf, count)) { 169 qdf_mem_free(write_buffer); 170 hif_err("copy_to_user error in /proc/%s", PROCFS_NAME); 171 return -EFAULT; 172 } 173 174 hif_debug("wr buff 0x%pK buf 0x%pK cnt %zu offset 0x%x value 0x%x", 175 write_buffer, buf, count, 176 (int)*pos, *((uint32_t *) write_buffer)); 177 178 tgt_info = hif_get_target_info_handle(GET_HIF_OPAQUE_HDL(hif_hdl)); 179 if ((scn->bus_type == QDF_BUS_TYPE_SNOC) || 180 ((scn->bus_type == QDF_BUS_TYPE_PCI) && 181 ((tgt_info->target_type == TARGET_TYPE_QCA6290) || 182 (tgt_info->target_type == TARGET_TYPE_QCA6390) || 183 (tgt_info->target_type == TARGET_TYPE_QCA6490) || 184 (tgt_info->target_type == TARGET_TYPE_QCA8074) || 185 (tgt_info->target_type == TARGET_TYPE_QCA8074V2) || 186 (tgt_info->target_type == TARGET_TYPE_QCN9000) || 187 (tgt_info->target_type == TARGET_TYPE_QCN9100) || 188 (tgt_info->target_type == TARGET_TYPE_QCA5018) || 189 (tgt_info->target_type == TARGET_TYPE_QCA6018) || 190 (tgt_info->target_type == TARGET_TYPE_QCN7605))) || 191 (scn->bus_type == QDF_BUS_TYPE_IPCI && 192 (tgt_info->target_type == TARGET_TYPE_QCA6750)) || 193 ((scn->bus_type == QDF_BUS_TYPE_USB) && 194 (tgt_info->target_type == TARGET_TYPE_QCN7605))) { 195 memtype = ((uint32_t)(*pos) & 0xff000000) >> 24; 196 offset = (uint32_t)(*pos) & 0xffffff; 197 hif_debug("offset 0x%x memtype 0x%x, datalen %zu", 198 offset, memtype, count); 199 rv = pld_athdiag_write(scn->qdf_dev->dev, 200 offset, memtype, count, 201 (uint8_t *)write_buffer); 202 goto out; 203 } 204 205 if ((count == 4) && ((((uint32_t) (*pos)) & 3) == 0)) { 206 /* reading a word? */ 207 uint32_t value = *((uint32_t *)write_buffer); 208 209 rv = hif_diag_write_access(hif_hdl, (uint32_t)(*pos), value); 210 } else { 211 rv = hif_diag_write_mem(hif_hdl, (uint32_t)(*pos), 212 (uint8_t *)write_buffer, count); 213 } 214 215 out: 216 217 qdf_mem_free(write_buffer); 218 if (rv == 0) 219 return count; 220 else 221 return -EIO; 222 } 223 224 #ifdef ATH_DIAG_EXT_DIRECT 225 /* Used to dump register or SRAM from target directly */ 226 static int ath_procfs_direct_read(struct hif_softc *scn, uint32_t offset, 227 uint8_t *buf, size_t count) 228 { 229 size_t remaining = count; 230 uint32_t *p_val = (uint32_t *)buf; 231 uint32_t val; 232 uint8_t *buf_d, *buf_s; 233 234 if (!scn->bus_ops.hif_reg_read32) 235 return -EIO; 236 237 while (remaining >= 4) { 238 *p_val++ = scn->bus_ops.hif_reg_read32(scn, 239 offset); 240 offset += 4; 241 remaining -= 4; 242 } 243 244 if (remaining) { 245 val = scn->bus_ops.hif_reg_read32(scn, 246 offset); 247 buf_d = (uint8_t *)p_val; 248 buf_s = (uint8_t *)&val; 249 while (remaining) { 250 *buf_d++ = *buf_s++; 251 remaining--; 252 } 253 } 254 255 return 0; 256 } 257 258 /* Used to write register or SRAM to target directly */ 259 static int ath_procfs_direct_write(struct hif_softc *scn, uint32_t offset, 260 uint8_t *buf, size_t count) 261 { 262 size_t remaining = count; 263 uint32_t *p_val = (uint32_t *)buf; 264 uint32_t val; 265 uint8_t *buf_d, *buf_s; 266 267 if (!scn->bus_ops.hif_reg_write32 || !scn->bus_ops.hif_reg_read32) 268 return -EIO; 269 270 while (remaining >= 4) { 271 scn->bus_ops.hif_reg_write32(scn, 272 offset, 273 *p_val++); 274 offset += 4; 275 remaining -= 4; 276 } 277 278 if (remaining) { 279 val = scn->bus_ops.hif_reg_read32(scn, 280 offset); 281 buf_s = (uint8_t *)p_val; 282 buf_d = (uint8_t *)&val; 283 while (remaining) { 284 *buf_d++ = *buf_s++; 285 remaining--; 286 } 287 scn->bus_ops.hif_reg_write32(scn, 288 offset, 289 val); 290 } 291 292 return 0; 293 } 294 #else 295 static int ath_procfs_direct_read(struct hif_softc *scn, uint32_t offset, 296 uint8_t *buf, size_t count) 297 { 298 return -EIO; 299 } 300 301 /* Used to write register or SRAM to target directly */ 302 static int ath_procfs_direct_write(struct hif_softc *scn, uint32_t offset, 303 uint8_t *buf, size_t count) 304 { 305 return -EIO; 306 } 307 308 #endif 309 310 static ssize_t ath_procfs_diag_read_ext(struct file *file, char __user *buf, 311 size_t count, 312 uint32_t op_type, 313 uint32_t memtype, 314 uint32_t offset) 315 { 316 hif_handle_t hif_hdl = get_hif_hdl_from_file(file); 317 int rv = -EINVAL; 318 uint8_t *read_buffer; 319 struct hif_softc *scn; 320 struct hif_target_info *tgt_info; 321 322 if (!hif_hdl) 323 return -EINVAL; 324 325 read_buffer = qdf_mem_malloc(count); 326 if (!read_buffer) 327 return -ENOMEM; 328 329 scn = HIF_GET_SOFTC(hif_hdl); 330 tgt_info = hif_get_target_info_handle(GET_HIF_OPAQUE_HDL(hif_hdl)); 331 switch (scn->bus_type) { 332 case QDF_BUS_TYPE_PCI: 333 switch (tgt_info->target_type) { 334 case TARGET_TYPE_QCA6390: 335 case TARGET_TYPE_QCA6490: 336 /* case Hamiltons: */ 337 if (op_type == OP_TYPE_EXT_DIRECT) 338 rv = ath_procfs_direct_read(scn, 339 offset, 340 read_buffer, 341 count); 342 else 343 rv = pld_athdiag_read(scn->qdf_dev->dev, 344 offset, 345 memtype, 346 count, 347 read_buffer); 348 break; 349 default: 350 hif_err("Unrecognized target type %d", 351 tgt_info->target_type); 352 } 353 break; 354 default: 355 hif_err("Unrecognized bus type %d", scn->bus_type); 356 break; 357 } 358 359 if (rv) { 360 hif_err("fail to read from target %d", rv); 361 } else { 362 rv = count; 363 if (copy_to_user(buf, read_buffer, count)) { 364 hif_err("copy_to_user error in /proc/%s", 365 PROCFS_NAME); 366 rv = -EFAULT; 367 } 368 } 369 370 qdf_mem_free(read_buffer); 371 372 return rv; 373 } 374 375 static ssize_t ath_procfs_diag_write_ext(struct file *file, 376 const char __user *buf, 377 size_t count, 378 uint32_t op_type, 379 uint32_t memtype, 380 uint32_t offset) 381 { 382 hif_handle_t hif_hdl = get_hif_hdl_from_file(file); 383 int rv = -EINVAL; 384 uint8_t *write_buffer; 385 struct hif_softc *scn; 386 struct hif_target_info *tgt_info; 387 388 if (!hif_hdl) 389 return -EINVAL; 390 391 scn = HIF_GET_SOFTC(hif_hdl); 392 393 write_buffer = qdf_mem_malloc(count); 394 if (!write_buffer) 395 return -ENOMEM; 396 397 if (copy_from_user(write_buffer, buf, count)) { 398 qdf_mem_free(write_buffer); 399 hif_err("copy_to_user error in /proc/%s", 400 PROCFS_NAME); 401 return -EFAULT; 402 } 403 404 tgt_info = hif_get_target_info_handle(GET_HIF_OPAQUE_HDL(hif_hdl)); 405 406 switch (scn->bus_type) { 407 case QDF_BUS_TYPE_PCI: 408 switch (tgt_info->target_type) { 409 case TARGET_TYPE_QCA6390: 410 case TARGET_TYPE_QCA6490: 411 /* case Hamiltons: */ 412 if (op_type == OP_TYPE_EXT_DIRECT) 413 rv = ath_procfs_direct_write(scn, 414 offset, 415 write_buffer, 416 count); 417 else 418 rv = pld_athdiag_write(scn->qdf_dev->dev, 419 offset, 420 memtype, 421 count, 422 write_buffer); 423 break; 424 default: 425 hif_err("Unrecognized target type %d", 426 tgt_info->target_type); 427 } 428 break; 429 default: 430 hif_err("Unrecognized bus type %d", scn->bus_type); 431 break; 432 } 433 434 qdf_mem_free(write_buffer); 435 436 return (rv == 0) ? count : -EIO; 437 } 438 439 static void get_fields_from_pos(loff_t pos, 440 uint32_t *op_type, 441 uint32_t *memtype, 442 uint32_t *offset) 443 { 444 *op_type = QDF_GET_BITS64(pos, ATH_DIAG_EXT_OP_TYPE_INDEX, 445 ATH_DIAG_EXT_OP_TYPE_BITS); 446 *memtype = QDF_GET_BITS64(pos, ATH_DIAG_EXT_MEM_TYPE_INDEX, 447 ATH_DIAG_EXT_MEM_TYPE_BITS); 448 *offset = QDF_GET_BITS64(pos, ATH_DIAG_EXT_OFFSET_INDEX, 449 ATH_DIAG_EXT_OFFSET_BITS); 450 } 451 452 static ssize_t ath_procfs_diag_read(struct file *file, char __user *buf, 453 size_t count, loff_t *pos) 454 { 455 hif_handle_t hif_hdl = get_hif_hdl_from_file(file); 456 int rv = -EINVAL; 457 struct hif_softc *scn; 458 uint32_t offset, memtype; 459 uint32_t op_type; 460 461 if (!hif_hdl) 462 return -EINVAL; 463 464 get_fields_from_pos(*pos, &op_type, &memtype, &offset); 465 466 scn = HIF_GET_SOFTC(hif_hdl); 467 if (scn->bus_ops.hif_addr_in_boundary(scn, offset)) 468 return -EINVAL; 469 470 if (offset & 0x3) 471 return -EINVAL; 472 473 hif_info("rd cnt %zu offset 0x%x op_type %d type %d pos %llx", 474 count, offset, op_type, memtype, *pos); 475 476 switch (op_type) { 477 case OP_TYPE_LEGACY: 478 rv = ath_procfs_diag_read_legacy(file, buf, count, pos); 479 break; 480 case OP_TYPE_EXT_QMI: 481 case OP_TYPE_EXT_DIRECT: 482 rv = ath_procfs_diag_read_ext(file, buf, count, op_type, 483 memtype, offset); 484 break; 485 default: 486 hif_err("Unrecognized op type %d", op_type); 487 break; 488 } 489 490 return rv; 491 } 492 493 static ssize_t ath_procfs_diag_write(struct file *file, 494 const char __user *buf, 495 size_t count, loff_t *pos) 496 { 497 hif_handle_t hif_hdl = get_hif_hdl_from_file(file); 498 int rv = -EINVAL; 499 struct hif_softc *scn; 500 uint32_t offset, memtype; 501 uint32_t op_type; 502 503 if (!hif_hdl) 504 return -EINVAL; 505 506 get_fields_from_pos(*pos, &op_type, &memtype, &offset); 507 508 scn = HIF_GET_SOFTC(hif_hdl); 509 if (scn->bus_ops.hif_addr_in_boundary(scn, offset)) 510 return -EINVAL; 511 512 if (offset & 0x3) 513 return -EINVAL; 514 515 hif_info("wr cnt %zu offset 0x%x op_type %d mem_type %d", 516 count, offset, op_type, memtype); 517 518 switch (op_type) { 519 case OP_TYPE_LEGACY: 520 rv = ath_procfs_diag_write_legacy(file, buf, count, pos); 521 break; 522 case OP_TYPE_EXT_QMI: 523 case OP_TYPE_EXT_DIRECT: 524 rv = ath_procfs_diag_write_ext(file, buf, count, op_type, 525 memtype, offset); 526 break; 527 default: 528 hif_err("Unrecognized op type %d", op_type); 529 break; 530 } 531 532 return rv; 533 } 534 535 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)) 536 static const struct proc_ops athdiag_fops = { 537 .proc_read = ath_procfs_diag_read, 538 .proc_write = ath_procfs_diag_write, 539 }; 540 #else 541 static const struct file_operations athdiag_fops = { 542 .read = ath_procfs_diag_read, 543 .write = ath_procfs_diag_write, 544 }; 545 #endif 546 547 /* 548 * This function is called when the module is loaded 549 * 550 */ 551 int athdiag_procfs_init(void *scn) 552 { 553 proc_dir = proc_mkdir(PROCFS_DIR, NULL); 554 if (!proc_dir) { 555 remove_proc_entry(PROCFS_DIR, NULL); 556 hif_err("Could not initialize /proc/%s", PROCFS_DIR); 557 return -ENOMEM; 558 } 559 560 proc_file = proc_create_data(PROCFS_NAME, 0600, proc_dir, 561 &athdiag_fops, (void *)scn); 562 if (!proc_file) { 563 remove_proc_entry(PROCFS_NAME, proc_dir); 564 hif_err("Could not initialize /proc/%s", PROCFS_NAME); 565 return -ENOMEM; 566 } 567 568 hif_debug("/proc/%s/%s created", PROCFS_DIR, PROCFS_NAME); 569 return 0; 570 } 571 572 /* 573 * This function is called when the module is unloaded 574 * 575 */ 576 void athdiag_procfs_remove(void) 577 { 578 if (proc_dir) { 579 remove_proc_entry(PROCFS_NAME, proc_dir); 580 hif_debug("/proc/%s/%s removed", PROCFS_DIR, PROCFS_NAME); 581 remove_proc_entry(PROCFS_DIR, NULL); 582 hif_debug("/proc/%s removed", PROCFS_DIR); 583 proc_dir = NULL; 584 } 585 } 586 #else 587 int athdiag_procfs_init(void *scn) 588 { 589 return 0; 590 } 591 void athdiag_procfs_remove(void) {} 592 #endif 593