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