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