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