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