1 /* 2 * Copyright (c) 2013-2014, 2016-2021 The Linux Foundation. All rights reserved. 3 * Copyright (c) 2021-2022 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_QCA5018) || 121 (tgt_info->target_type == TARGET_TYPE_QCA5332) || 122 (tgt_info->target_type == TARGET_TYPE_QCA6018) || 123 (tgt_info->target_type == TARGET_TYPE_QCN7605) || 124 (tgt_info->target_type == TARGET_TYPE_KIWI) || 125 (tgt_info->target_type == TARGET_TYPE_MANGO))) || 126 (scn->bus_type == QDF_BUS_TYPE_IPCI && 127 (tgt_info->target_type == TARGET_TYPE_QCA6750)) || 128 ((scn->bus_type == QDF_BUS_TYPE_USB) && 129 (tgt_info->target_type == TARGET_TYPE_QCN7605))) { 130 memtype = ((uint32_t)(*pos) & 0xff000000) >> 24; 131 offset = (uint32_t)(*pos) & 0xffffff; 132 hif_debug("offset 0x%x memtype 0x%x, datalen %zu", 133 offset, memtype, count); 134 rv = pld_athdiag_read(scn->qdf_dev->dev, 135 offset, memtype, count, 136 (uint8_t *)read_buffer); 137 goto out; 138 } 139 140 if ((count == 4) && ((((uint32_t) (*pos)) & 3) == 0)) { 141 /* reading a word? */ 142 rv = hif_diag_read_access(hif_hdl, (uint32_t)(*pos), 143 (uint32_t *)read_buffer); 144 } else { 145 rv = hif_diag_read_mem(hif_hdl, (uint32_t)(*pos), 146 (uint8_t *)read_buffer, count); 147 } 148 149 out: 150 if (rv) { 151 qdf_mem_free(read_buffer); 152 return -EIO; 153 } 154 155 if (copy_to_user(buf, read_buffer, count)) { 156 qdf_mem_free(read_buffer); 157 hif_err("copy_to_user error in /proc/%s", PROCFS_NAME); 158 return -EFAULT; 159 } 160 qdf_mem_free(read_buffer); 161 return count; 162 } 163 164 static ssize_t ath_procfs_diag_write_legacy(struct file *file, 165 const char __user *buf, 166 size_t count, loff_t *pos) 167 { 168 hif_handle_t hif_hdl; 169 int rv; 170 uint8_t *write_buffer = NULL; 171 struct hif_softc *scn; 172 uint32_t offset = 0, memtype = 0; 173 struct hif_target_info *tgt_info; 174 175 hif_hdl = get_hif_hdl_from_file(file); 176 scn = HIF_GET_SOFTC(hif_hdl); 177 178 write_buffer = qdf_mem_malloc(count); 179 if (!write_buffer) 180 return -ENOMEM; 181 182 if (copy_from_user(write_buffer, buf, count)) { 183 qdf_mem_free(write_buffer); 184 hif_err("copy_to_user error in /proc/%s", PROCFS_NAME); 185 return -EFAULT; 186 } 187 188 hif_debug("wr buff 0x%pK buf 0x%pK cnt %zu offset 0x%x value 0x%x", 189 write_buffer, buf, count, 190 (int)*pos, *((uint32_t *) write_buffer)); 191 192 tgt_info = hif_get_target_info_handle(GET_HIF_OPAQUE_HDL(hif_hdl)); 193 if ((scn->bus_type == QDF_BUS_TYPE_SNOC) || 194 ((scn->bus_type == QDF_BUS_TYPE_PCI) && 195 ((tgt_info->target_type == TARGET_TYPE_QCA6290) || 196 (tgt_info->target_type == TARGET_TYPE_QCA6390) || 197 (tgt_info->target_type == TARGET_TYPE_QCA6490) || 198 (tgt_info->target_type == TARGET_TYPE_QCA8074) || 199 (tgt_info->target_type == TARGET_TYPE_QCA8074V2) || 200 (tgt_info->target_type == TARGET_TYPE_QCA9574) || 201 (tgt_info->target_type == TARGET_TYPE_QCN9000) || 202 (tgt_info->target_type == TARGET_TYPE_QCN9224) || 203 (tgt_info->target_type == TARGET_TYPE_QCN6122) || 204 (tgt_info->target_type == TARGET_TYPE_QCA5018) || 205 (tgt_info->target_type == TARGET_TYPE_QCA5332) || 206 (tgt_info->target_type == TARGET_TYPE_QCA6018) || 207 (tgt_info->target_type == TARGET_TYPE_QCN7605) || 208 (tgt_info->target_type == TARGET_TYPE_KIWI) || 209 (tgt_info->target_type == TARGET_TYPE_MANGO))) || 210 (scn->bus_type == QDF_BUS_TYPE_IPCI && 211 (tgt_info->target_type == TARGET_TYPE_QCA6750)) || 212 ((scn->bus_type == QDF_BUS_TYPE_USB) && 213 (tgt_info->target_type == TARGET_TYPE_QCN7605))) { 214 memtype = ((uint32_t)(*pos) & 0xff000000) >> 24; 215 offset = (uint32_t)(*pos) & 0xffffff; 216 hif_debug("offset 0x%x memtype 0x%x, datalen %zu", 217 offset, memtype, count); 218 rv = pld_athdiag_write(scn->qdf_dev->dev, 219 offset, memtype, count, 220 (uint8_t *)write_buffer); 221 goto out; 222 } 223 224 if ((count == 4) && ((((uint32_t) (*pos)) & 3) == 0)) { 225 /* reading a word? */ 226 uint32_t value = *((uint32_t *)write_buffer); 227 228 rv = hif_diag_write_access(hif_hdl, (uint32_t)(*pos), value); 229 } else { 230 rv = hif_diag_write_mem(hif_hdl, (uint32_t)(*pos), 231 (uint8_t *)write_buffer, count); 232 } 233 234 out: 235 236 qdf_mem_free(write_buffer); 237 if (rv == 0) 238 return count; 239 else 240 return -EIO; 241 } 242 243 #ifdef ATH_DIAG_EXT_DIRECT 244 /* Used to dump register or SRAM from target directly */ 245 static int ath_procfs_direct_read(struct hif_softc *scn, uint32_t offset, 246 uint8_t *buf, size_t count) 247 { 248 size_t remaining = count; 249 uint32_t *p_val = (uint32_t *)buf; 250 uint32_t val; 251 uint8_t *buf_d, *buf_s; 252 253 if (!scn->bus_ops.hif_reg_read32) 254 return -EIO; 255 256 while (remaining >= 4) { 257 *p_val++ = scn->bus_ops.hif_reg_read32(scn, 258 offset); 259 offset += 4; 260 remaining -= 4; 261 } 262 263 if (remaining) { 264 val = scn->bus_ops.hif_reg_read32(scn, 265 offset); 266 buf_d = (uint8_t *)p_val; 267 buf_s = (uint8_t *)&val; 268 while (remaining) { 269 *buf_d++ = *buf_s++; 270 remaining--; 271 } 272 } 273 274 return 0; 275 } 276 277 /* Used to write register or SRAM to target directly */ 278 static int ath_procfs_direct_write(struct hif_softc *scn, uint32_t offset, 279 uint8_t *buf, size_t count) 280 { 281 size_t remaining = count; 282 uint32_t *p_val = (uint32_t *)buf; 283 uint32_t val; 284 uint8_t *buf_d, *buf_s; 285 286 if (!scn->bus_ops.hif_reg_write32 || !scn->bus_ops.hif_reg_read32) 287 return -EIO; 288 289 while (remaining >= 4) { 290 scn->bus_ops.hif_reg_write32(scn, 291 offset, 292 *p_val++); 293 offset += 4; 294 remaining -= 4; 295 } 296 297 if (remaining) { 298 val = scn->bus_ops.hif_reg_read32(scn, 299 offset); 300 buf_s = (uint8_t *)p_val; 301 buf_d = (uint8_t *)&val; 302 while (remaining) { 303 *buf_d++ = *buf_s++; 304 remaining--; 305 } 306 scn->bus_ops.hif_reg_write32(scn, 307 offset, 308 val); 309 } 310 311 return 0; 312 } 313 #else 314 static int ath_procfs_direct_read(struct hif_softc *scn, uint32_t offset, 315 uint8_t *buf, size_t count) 316 { 317 return -EIO; 318 } 319 320 /* Used to write register or SRAM to target directly */ 321 static int ath_procfs_direct_write(struct hif_softc *scn, uint32_t offset, 322 uint8_t *buf, size_t count) 323 { 324 return -EIO; 325 } 326 327 #endif 328 329 static ssize_t ath_procfs_diag_read_ext(struct file *file, char __user *buf, 330 size_t count, 331 uint32_t op_type, 332 uint32_t memtype, 333 uint32_t offset) 334 { 335 hif_handle_t hif_hdl = get_hif_hdl_from_file(file); 336 int rv = -EINVAL; 337 uint8_t *read_buffer; 338 struct hif_softc *scn; 339 struct hif_target_info *tgt_info; 340 341 if (!hif_hdl) 342 return -EINVAL; 343 344 read_buffer = qdf_mem_malloc(count); 345 if (!read_buffer) 346 return -ENOMEM; 347 348 scn = HIF_GET_SOFTC(hif_hdl); 349 tgt_info = hif_get_target_info_handle(GET_HIF_OPAQUE_HDL(hif_hdl)); 350 switch (scn->bus_type) { 351 case QDF_BUS_TYPE_PCI: 352 switch (tgt_info->target_type) { 353 case TARGET_TYPE_QCA6390: 354 case TARGET_TYPE_QCA6490: 355 case TARGET_TYPE_KIWI: 356 case TARGET_TYPE_MANGO: 357 if (op_type == OP_TYPE_EXT_DIRECT) 358 rv = ath_procfs_direct_read(scn, 359 offset, 360 read_buffer, 361 count); 362 else 363 rv = pld_athdiag_read(scn->qdf_dev->dev, 364 offset, 365 memtype, 366 count, 367 read_buffer); 368 break; 369 default: 370 hif_err("Unrecognized target type %d", 371 tgt_info->target_type); 372 } 373 break; 374 default: 375 hif_err("Unrecognized bus type %d", scn->bus_type); 376 break; 377 } 378 379 if (rv) { 380 hif_err("fail to read from target %d", rv); 381 } else { 382 rv = count; 383 if (copy_to_user(buf, read_buffer, count)) { 384 hif_err("copy_to_user error in /proc/%s", 385 PROCFS_NAME); 386 rv = -EFAULT; 387 } 388 } 389 390 qdf_mem_free(read_buffer); 391 392 return rv; 393 } 394 395 static ssize_t ath_procfs_diag_write_ext(struct file *file, 396 const char __user *buf, 397 size_t count, 398 uint32_t op_type, 399 uint32_t memtype, 400 uint32_t offset) 401 { 402 hif_handle_t hif_hdl = get_hif_hdl_from_file(file); 403 int rv = -EINVAL; 404 uint8_t *write_buffer; 405 struct hif_softc *scn; 406 struct hif_target_info *tgt_info; 407 408 if (!hif_hdl) 409 return -EINVAL; 410 411 scn = HIF_GET_SOFTC(hif_hdl); 412 413 write_buffer = qdf_mem_malloc(count); 414 if (!write_buffer) 415 return -ENOMEM; 416 417 if (copy_from_user(write_buffer, buf, count)) { 418 qdf_mem_free(write_buffer); 419 hif_err("copy_to_user error in /proc/%s", 420 PROCFS_NAME); 421 return -EFAULT; 422 } 423 424 tgt_info = hif_get_target_info_handle(GET_HIF_OPAQUE_HDL(hif_hdl)); 425 426 switch (scn->bus_type) { 427 case QDF_BUS_TYPE_PCI: 428 switch (tgt_info->target_type) { 429 case TARGET_TYPE_QCA6390: 430 case TARGET_TYPE_QCA6490: 431 case TARGET_TYPE_KIWI: 432 case TARGET_TYPE_MANGO: 433 if (op_type == OP_TYPE_EXT_DIRECT) 434 rv = ath_procfs_direct_write(scn, 435 offset, 436 write_buffer, 437 count); 438 else 439 rv = pld_athdiag_write(scn->qdf_dev->dev, 440 offset, 441 memtype, 442 count, 443 write_buffer); 444 break; 445 default: 446 hif_err("Unrecognized target type %d", 447 tgt_info->target_type); 448 } 449 break; 450 default: 451 hif_err("Unrecognized bus type %d", scn->bus_type); 452 break; 453 } 454 455 qdf_mem_free(write_buffer); 456 457 return (rv == 0) ? count : -EIO; 458 } 459 460 static void get_fields_from_pos(loff_t pos, 461 uint32_t *op_type, 462 uint32_t *memtype, 463 uint32_t *offset) 464 { 465 *op_type = QDF_GET_BITS64(pos, ATH_DIAG_EXT_OP_TYPE_INDEX, 466 ATH_DIAG_EXT_OP_TYPE_BITS); 467 *memtype = QDF_GET_BITS64(pos, ATH_DIAG_EXT_MEM_TYPE_INDEX, 468 ATH_DIAG_EXT_MEM_TYPE_BITS); 469 *offset = QDF_GET_BITS64(pos, ATH_DIAG_EXT_OFFSET_INDEX, 470 ATH_DIAG_EXT_OFFSET_BITS); 471 } 472 473 static ssize_t ath_procfs_diag_read(struct file *file, char __user *buf, 474 size_t count, loff_t *pos) 475 { 476 hif_handle_t hif_hdl = get_hif_hdl_from_file(file); 477 int rv = -EINVAL; 478 struct hif_softc *scn; 479 uint32_t offset, memtype; 480 uint32_t op_type; 481 482 if (!hif_hdl) 483 return -EINVAL; 484 485 get_fields_from_pos(*pos, &op_type, &memtype, &offset); 486 487 scn = HIF_GET_SOFTC(hif_hdl); 488 if (scn->bus_ops.hif_addr_in_boundary(scn, offset)) 489 return -EINVAL; 490 491 if (offset & 0x3) 492 return -EINVAL; 493 494 hif_info("rd cnt %zu offset 0x%x op_type %d type %d pos %llx", 495 count, offset, op_type, memtype, *pos); 496 497 switch (op_type) { 498 case OP_TYPE_LEGACY: 499 rv = ath_procfs_diag_read_legacy(file, buf, count, pos); 500 break; 501 case OP_TYPE_EXT_QMI: 502 case OP_TYPE_EXT_DIRECT: 503 rv = ath_procfs_diag_read_ext(file, buf, count, op_type, 504 memtype, offset); 505 break; 506 default: 507 hif_err("Unrecognized op type %d", op_type); 508 break; 509 } 510 511 return rv; 512 } 513 514 static ssize_t ath_procfs_diag_write(struct file *file, 515 const char __user *buf, 516 size_t count, loff_t *pos) 517 { 518 hif_handle_t hif_hdl = get_hif_hdl_from_file(file); 519 int rv = -EINVAL; 520 struct hif_softc *scn; 521 uint32_t offset, memtype; 522 uint32_t op_type; 523 524 if (!hif_hdl) 525 return -EINVAL; 526 527 get_fields_from_pos(*pos, &op_type, &memtype, &offset); 528 529 scn = HIF_GET_SOFTC(hif_hdl); 530 if (scn->bus_ops.hif_addr_in_boundary(scn, offset)) 531 return -EINVAL; 532 533 if (offset & 0x3) 534 return -EINVAL; 535 536 hif_info("wr cnt %zu offset 0x%x op_type %d mem_type %d", 537 count, offset, op_type, memtype); 538 539 switch (op_type) { 540 case OP_TYPE_LEGACY: 541 rv = ath_procfs_diag_write_legacy(file, buf, count, pos); 542 break; 543 case OP_TYPE_EXT_QMI: 544 case OP_TYPE_EXT_DIRECT: 545 rv = ath_procfs_diag_write_ext(file, buf, count, op_type, 546 memtype, offset); 547 break; 548 default: 549 hif_err("Unrecognized op type %d", op_type); 550 break; 551 } 552 553 return rv; 554 } 555 556 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)) 557 static const struct proc_ops athdiag_fops = { 558 .proc_read = ath_procfs_diag_read, 559 .proc_write = ath_procfs_diag_write, 560 .proc_lseek = default_llseek, 561 }; 562 #else 563 static const struct file_operations athdiag_fops = { 564 .read = ath_procfs_diag_read, 565 .write = ath_procfs_diag_write, 566 }; 567 #endif 568 569 /* 570 * This function is called when the module is loaded 571 * 572 */ 573 int athdiag_procfs_init(void *scn) 574 { 575 proc_dir = proc_mkdir(PROCFS_DIR, NULL); 576 if (!proc_dir) { 577 remove_proc_entry(PROCFS_DIR, NULL); 578 hif_err("Could not initialize /proc/%s", PROCFS_DIR); 579 return -ENOMEM; 580 } 581 582 proc_file = proc_create_data(PROCFS_NAME, 0600, proc_dir, 583 &athdiag_fops, (void *)scn); 584 if (!proc_file) { 585 remove_proc_entry(PROCFS_NAME, proc_dir); 586 hif_err("Could not initialize /proc/%s", PROCFS_NAME); 587 return -ENOMEM; 588 } 589 590 hif_debug("/proc/%s/%s created", PROCFS_DIR, PROCFS_NAME); 591 return 0; 592 } 593 594 /* 595 * This function is called when the module is unloaded 596 * 597 */ 598 void athdiag_procfs_remove(void) 599 { 600 if (proc_dir) { 601 remove_proc_entry(PROCFS_NAME, proc_dir); 602 hif_debug("/proc/%s/%s removed", PROCFS_DIR, PROCFS_NAME); 603 remove_proc_entry(PROCFS_DIR, NULL); 604 hif_debug("/proc/%s removed", PROCFS_DIR); 605 proc_dir = NULL; 606 } 607 } 608 #else 609 int athdiag_procfs_init(void *scn) 610 { 611 return 0; 612 } 613 void athdiag_procfs_remove(void) {} 614 #endif 615