1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2005 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 #include <sys/types.h> 30 #include <sys/mkdev.h> 31 #include <sys/ddi.h> 32 #include <sys/sunddi.h> 33 #include <vm/seg_kmem.h> 34 #include <sys/machparam.h> 35 #include <sys/ontrap.h> 36 #include <sys/pci.h> 37 #include <sys/hotplug/pci/pcihp.h> 38 #include <sys/pci_cfgspace.h> 39 #include <sys/pci_tools.h> 40 #include <sys/pci_tools_var.h> 41 #include "pci_var.h" 42 #include <sys/promif.h> 43 44 #define SUCCESS 0 45 46 int pcitool_debug = 0; 47 48 /* 49 * Offsets of BARS in config space. First entry of 0 means config space. 50 * Entries here correlate to pcitool_bars_t enumerated type. 51 */ 52 static uint8_t pci_bars[] = { 53 0x0, 54 PCI_CONF_BASE0, 55 PCI_CONF_BASE1, 56 PCI_CONF_BASE2, 57 PCI_CONF_BASE3, 58 PCI_CONF_BASE4, 59 PCI_CONF_BASE5, 60 PCI_CONF_ROM 61 }; 62 63 static uint64_t pcitool_swap_endian(uint64_t data, int size); 64 static int pcitool_cfg_access(dev_info_t *dip, pcitool_reg_t *prg, 65 boolean_t write_flag); 66 static int pcitool_io_access(dev_info_t *dip, pcitool_reg_t *prg, 67 boolean_t write_flag); 68 static int pcitool_mem_access(dev_info_t *dip, pcitool_reg_t *prg, 69 uint64_t virt_addr, boolean_t write_flag); 70 static uint64_t pcitool_map(uint64_t phys_addr, size_t size, size_t *num_pages); 71 static void pcitool_unmap(uint64_t virt_addr, size_t num_pages); 72 73 /* 74 * A note about ontrap handling: 75 * 76 * X86 systems on which this module was tested return FFs instead of bus errors 77 * when accessing devices with invalid addresses. Ontrap handling, which 78 * gracefully handles kernel bus errors, is installed anyway, in case future 79 * X86 platforms require it. 80 */ 81 82 /* 83 * Main function for handling interrupt CPU binding requests and queries. 84 * Need to implement later 85 */ 86 /*ARGSUSED*/ 87 int 88 pcitool_intr_admn(dev_t dev, void *arg, int cmd, int mode) 89 { 90 return (ENOTSUP); 91 } 92 93 94 /* 95 * Perform register accesses on the nexus device itself. 96 * No explicit PCI nexus device for X86, so not applicable. 97 */ 98 /*ARGSUSED*/ 99 int 100 pcitool_bus_reg_ops(dev_t dev, void *arg, int cmd, int mode) 101 { 102 return (ENOTSUP); 103 } 104 105 /* Swap endianness. */ 106 static uint64_t 107 pcitool_swap_endian(uint64_t data, int size) 108 { 109 typedef union { 110 uint64_t data64; 111 uint8_t data8[8]; 112 } data_split_t; 113 114 data_split_t orig_data; 115 data_split_t returned_data; 116 int i; 117 118 orig_data.data64 = data; 119 returned_data.data64 = 0; 120 121 for (i = 0; i < size; i++) { 122 returned_data.data8[i] = orig_data.data8[size - 1 - i]; 123 } 124 125 return (returned_data.data64); 126 } 127 128 129 /* Access device. prg is modified. */ 130 /*ARGSUSED*/ 131 static int 132 pcitool_cfg_access(dev_info_t *dip, pcitool_reg_t *prg, boolean_t write_flag) 133 { 134 int size = PCITOOL_ACC_ATTR_SIZE(prg->acc_attr); 135 boolean_t big_endian = PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr); 136 int rval = SUCCESS; 137 uint64_t local_data; 138 139 /* 140 * NOTE: there is no way to verify whether or not the address is valid. 141 * The put functions return void and the get functions return ff on 142 * error. 143 */ 144 prg->status = PCITOOL_SUCCESS; 145 146 if (write_flag) { 147 148 if (big_endian) { 149 local_data = pcitool_swap_endian(prg->data, size); 150 } else { 151 local_data = prg->data; 152 } 153 154 switch (size) { 155 case 1: 156 (*pci_putb_func)(prg->bus_no, prg->dev_no, 157 prg->func_no, prg->offset, local_data); 158 break; 159 case 2: 160 (*pci_putw_func)(prg->bus_no, prg->dev_no, 161 prg->func_no, prg->offset, local_data); 162 break; 163 case 4: 164 (*pci_putl_func)(prg->bus_no, prg->dev_no, 165 prg->func_no, prg->offset, local_data); 166 break; 167 default: 168 rval = ENOTSUP; 169 prg->status = PCITOOL_INVALID_SIZE; 170 break; 171 } 172 } else { 173 switch (size) { 174 case 1: 175 local_data = (*pci_getb_func)(prg->bus_no, prg->dev_no, 176 prg->func_no, prg->offset); 177 break; 178 case 2: 179 local_data = (*pci_getw_func)(prg->bus_no, prg->dev_no, 180 prg->func_no, prg->offset); 181 break; 182 case 4: 183 local_data = (*pci_getl_func)(prg->bus_no, prg->dev_no, 184 prg->func_no, prg->offset); 185 break; 186 default: 187 rval = ENOTSUP; 188 prg->status = PCITOOL_INVALID_SIZE; 189 break; 190 } 191 192 if (rval == SUCCESS) { 193 if (big_endian) { 194 prg->data = 195 pcitool_swap_endian(local_data, size); 196 } else { 197 prg->data = local_data; 198 } 199 } 200 } 201 prg->phys_addr = 0; /* Config space is not memory mapped on X86. */ 202 return (rval); 203 } 204 205 206 /*ARGSUSED*/ 207 static int 208 pcitool_io_access(dev_info_t *dip, pcitool_reg_t *prg, boolean_t write_flag) 209 { 210 int port = (int)prg->phys_addr; 211 size_t size = PCITOOL_ACC_ATTR_SIZE(prg->acc_attr); 212 boolean_t big_endian = PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr); 213 int rval = SUCCESS; 214 on_trap_data_t otd; 215 uint64_t local_data; 216 217 218 /* 219 * on_trap works like setjmp. 220 * 221 * A non-zero return here means on_trap has returned from an error. 222 * 223 * A zero return here means that on_trap has just returned from setup. 224 */ 225 if (on_trap(&otd, OT_DATA_ACCESS)) { 226 no_trap(); 227 if (pcitool_debug) 228 prom_printf( 229 "pcitool_mem_access: on_trap caught an error...\n"); 230 prg->status = PCITOOL_INVALID_ADDRESS; 231 return (EFAULT); 232 } 233 234 if (write_flag) { 235 236 if (big_endian) { 237 local_data = pcitool_swap_endian(prg->data, size); 238 } else { 239 local_data = prg->data; 240 } 241 242 if (pcitool_debug) 243 prom_printf("Writing %ld byte(s) to port 0x%x\n", 244 size, port); 245 246 switch (size) { 247 case 1: 248 outb(port, (uint8_t)local_data); 249 break; 250 case 2: 251 outw(port, (uint16_t)local_data); 252 break; 253 case 4: 254 outl(port, (uint32_t)local_data); 255 break; 256 default: 257 rval = ENOTSUP; 258 prg->status = PCITOOL_INVALID_SIZE; 259 break; 260 } 261 } else { 262 if (pcitool_debug) 263 prom_printf("Reading %ld byte(s) from port 0x%x\n", 264 size, port); 265 266 switch (size) { 267 case 1: 268 local_data = inb(port); 269 break; 270 case 2: 271 local_data = inw(port); 272 break; 273 case 4: 274 local_data = inl(port); 275 break; 276 default: 277 rval = ENOTSUP; 278 prg->status = PCITOOL_INVALID_SIZE; 279 break; 280 } 281 282 if (rval == SUCCESS) { 283 if (big_endian) { 284 prg->data = 285 pcitool_swap_endian(local_data, size); 286 } else { 287 prg->data = local_data; 288 } 289 } 290 } 291 292 no_trap(); 293 return (rval); 294 } 295 296 /*ARGSUSED*/ 297 static int 298 pcitool_mem_access(dev_info_t *dip, pcitool_reg_t *prg, uint64_t virt_addr, 299 boolean_t write_flag) 300 { 301 size_t size = PCITOOL_ACC_ATTR_SIZE(prg->acc_attr); 302 boolean_t big_endian = PCITOOL_ACC_IS_BIG_ENDIAN(prg->acc_attr); 303 int rval = DDI_SUCCESS; 304 on_trap_data_t otd; 305 uint64_t local_data; 306 307 /* 308 * on_trap works like setjmp. 309 * 310 * A non-zero return here means on_trap has returned from an error. 311 * 312 * A zero return here means that on_trap has just returned from setup. 313 */ 314 if (on_trap(&otd, OT_DATA_ACCESS)) { 315 no_trap(); 316 if (pcitool_debug) 317 prom_printf( 318 "pcitool_mem_access: on_trap caught an error...\n"); 319 prg->status = PCITOOL_INVALID_ADDRESS; 320 return (EFAULT); 321 } 322 323 if (write_flag) { 324 325 if (big_endian) { 326 local_data = pcitool_swap_endian(prg->data, size); 327 } else { 328 local_data = prg->data; 329 } 330 331 switch (size) { 332 case 1: 333 *((uint8_t *)(uintptr_t)virt_addr) = local_data; 334 break; 335 case 2: 336 *((uint16_t *)(uintptr_t)virt_addr) = local_data; 337 break; 338 case 4: 339 *((uint32_t *)(uintptr_t)virt_addr) = local_data; 340 break; 341 case 8: 342 *((uint64_t *)(uintptr_t)virt_addr) = local_data; 343 break; 344 default: 345 rval = ENOTSUP; 346 prg->status = PCITOOL_INVALID_SIZE; 347 break; 348 } 349 } else { 350 switch (size) { 351 case 1: 352 local_data = *((uint8_t *)(uintptr_t)virt_addr); 353 break; 354 case 2: 355 local_data = *((uint16_t *)(uintptr_t)virt_addr); 356 break; 357 case 4: 358 local_data = *((uint32_t *)(uintptr_t)virt_addr); 359 break; 360 case 8: 361 local_data = *((uint64_t *)(uintptr_t)virt_addr); 362 break; 363 default: 364 rval = ENOTSUP; 365 prg->status = PCITOOL_INVALID_SIZE; 366 break; 367 } 368 369 if (rval == SUCCESS) { 370 if (big_endian) { 371 prg->data = 372 pcitool_swap_endian(local_data, size); 373 } else { 374 prg->data = local_data; 375 } 376 } 377 } 378 379 no_trap(); 380 return (rval); 381 } 382 383 /* 384 * Map up to 2 pages which contain the address we want to access. 385 * 386 * Mapping should span no more than 8 bytes. With X86 it is possible for an 387 * 8 byte value to start on a 4 byte boundary, so it can cross a page boundary. 388 * We'll never have to map more than two pages. 389 */ 390 391 static uint64_t 392 pcitool_map(uint64_t phys_addr, size_t size, size_t *num_pages) 393 { 394 395 uint64_t page_base = phys_addr & ~MMU_PAGEOFFSET; 396 uint64_t offset = phys_addr & MMU_PAGEOFFSET; 397 void *virt_base; 398 uint64_t returned_addr; 399 400 if (pcitool_debug) 401 prom_printf("pcitool_map: Called with PA:0x%p\n", 402 (uint8_t *)(uintptr_t)phys_addr); 403 404 *num_pages = 1; 405 406 /* Desired mapping would span more than two pages. */ 407 if ((offset + size) > (MMU_PAGESIZE * 2)) { 408 if (pcitool_debug) 409 prom_printf("boundary violation: " 410 "offset:0x%" PRIx64 ", size:%ld, pagesize:0x%x\n", 411 offset, size, MMU_PAGESIZE); 412 return (NULL); 413 414 } else if ((offset + size) > MMU_PAGESIZE) { 415 (*num_pages)++; 416 } 417 418 /* Get page(s) of virtual space. */ 419 virt_base = vmem_alloc(heap_arena, ptob(*num_pages), VM_NOSLEEP); 420 if (virt_base == NULL) { 421 if (pcitool_debug) 422 prom_printf("Couldn't get virtual base address.\n"); 423 return (NULL); 424 } 425 426 if (pcitool_debug) 427 prom_printf("Got base virtual address:0x%p\n", virt_base); 428 429 /* Now map the allocated virtual space to the physical address. */ 430 hat_devload(kas.a_hat, virt_base, mmu_ptob(*num_pages), 431 mmu_btop(page_base), PROT_READ | PROT_WRITE | HAT_STRICTORDER, 432 HAT_LOAD_LOCK); 433 434 returned_addr = ((uintptr_t)(virt_base)) + offset; 435 436 if (pcitool_debug) 437 prom_printf("pcitool_map: returning VA:0x%p\n", 438 (void *)(uintptr_t)returned_addr); 439 440 return (returned_addr); 441 } 442 443 /* Unmap the mapped page(s). */ 444 static void 445 pcitool_unmap(uint64_t virt_addr, size_t num_pages) 446 { 447 void *base_virt_addr = (void *)(uintptr_t)(virt_addr & ~MMU_PAGEOFFSET); 448 449 hat_unload(kas.a_hat, base_virt_addr, ptob(num_pages), 450 HAT_UNLOAD_UNLOCK); 451 vmem_free(heap_arena, base_virt_addr, ptob(num_pages)); 452 } 453 454 455 /* Perform register accesses on PCI leaf devices. */ 456 int 457 pcitool_dev_reg_ops(dev_t dev, void *arg, int cmd, int mode) 458 { 459 pci_state_t *pci_p = PCI_DEV_TO_STATE(dev); 460 dev_info_t *dip = pci_p->pci_dip; 461 boolean_t write_flag = B_FALSE; 462 int rval = 0; 463 pcitool_reg_t prg; 464 uint8_t size; 465 466 uint64_t base_addr; 467 uint64_t virt_addr; 468 size_t num_virt_pages; 469 470 switch (cmd) { 471 case (PCITOOL_DEVICE_SET_REG): 472 write_flag = B_TRUE; 473 474 /*FALLTHRU*/ 475 case (PCITOOL_DEVICE_GET_REG): 476 if (pcitool_debug) 477 prom_printf("pci_dev_reg_ops set/get reg\n"); 478 if (ddi_copyin(arg, &prg, sizeof (pcitool_reg_t), mode) != 479 DDI_SUCCESS) { 480 if (pcitool_debug) 481 prom_printf("Error reading arguments\n"); 482 return (EFAULT); 483 } 484 485 if (prg.barnum >= (sizeof (pci_bars) / sizeof (pci_bars[0]))) { 486 prg.status = PCITOOL_OUT_OF_RANGE; 487 rval = EINVAL; 488 goto done_reg; 489 } 490 491 if (pcitool_debug) 492 prom_printf("raw bus:0x%x, dev:0x%x, func:0x%x\n", 493 prg.bus_no, prg.dev_no, prg.func_no); 494 /* Validate address arguments of bus / dev / func */ 495 if (((prg.bus_no & 496 (PCI_REG_BUS_M >> PCI_REG_BUS_SHIFT)) != 497 prg.bus_no) || 498 ((prg.dev_no & 499 (PCI_REG_DEV_M >> PCI_REG_DEV_SHIFT)) != 500 prg.dev_no) || 501 ((prg.func_no & 502 (PCI_REG_FUNC_M >> PCI_REG_FUNC_SHIFT)) != 503 prg.func_no)) { 504 prg.status = PCITOOL_INVALID_ADDRESS; 505 rval = EINVAL; 506 goto done_reg; 507 } 508 509 size = PCITOOL_ACC_ATTR_SIZE(prg.acc_attr); 510 511 /* Proper config space desired. */ 512 if (prg.barnum == 0) { 513 514 if (prg.offset > 0xFF) { 515 prg.status = PCITOOL_OUT_OF_RANGE; 516 rval = EINVAL; 517 goto done_reg; 518 } 519 520 if (pcitool_debug) 521 prom_printf( 522 "config access: offset:0x%" PRIx64 ", " 523 "phys_addr:0x%" PRIx64 "\n", 524 prg.offset, prg.phys_addr); 525 /* Access device. prg is modified. */ 526 rval = pcitool_cfg_access(dip, &prg, write_flag); 527 528 if (pcitool_debug) 529 prom_printf( 530 "config access: data:0x%" PRIx64 "\n", 531 prg.data); 532 533 /* IO/ MEM/ MEM64 space. */ 534 } else { 535 536 pcitool_reg_t prg2; 537 bcopy(&prg, &prg2, sizeof (pcitool_reg_t)); 538 539 /* 540 * Translate BAR number into offset of the BAR in 541 * the device's config space. 542 */ 543 prg2.offset = pci_bars[prg2.barnum]; 544 prg2.acc_attr = 545 PCITOOL_ACC_ATTR_SIZE_4 | PCITOOL_ACC_ATTR_ENDN_LTL; 546 547 if (pcitool_debug) 548 prom_printf( 549 "barnum:%d, bar_offset:0x%" PRIx64 "\n", 550 prg2.barnum, prg2.offset); 551 /* 552 * Get Bus Address Register (BAR) from config space. 553 * prg2.offset is the offset into config space of the 554 * BAR desired. prg.status is modified on error. 555 */ 556 rval = pcitool_cfg_access(dip, &prg2, B_FALSE); 557 if (rval != SUCCESS) { 558 if (pcitool_debug) 559 prom_printf("BAR access failed\n"); 560 prg.status = prg2.status; 561 goto done_reg; 562 } 563 /* 564 * Reference proper PCI space based on the BAR. 565 * If 64 bit MEM space, need to load other half of the 566 * BAR first. 567 */ 568 569 if (pcitool_debug) 570 prom_printf("bar returned is 0x%" PRIx64 "\n", 571 prg2.data); 572 if (!prg2.data) { 573 if (pcitool_debug) 574 prom_printf("BAR data == 0\n"); 575 rval = EINVAL; 576 prg.status = PCITOOL_INVALID_ADDRESS; 577 goto done_reg; 578 } 579 if (prg2.data == 0xffffffff) { 580 if (pcitool_debug) 581 prom_printf("BAR data == -1\n"); 582 rval = EINVAL; 583 prg.status = PCITOOL_INVALID_ADDRESS; 584 goto done_reg; 585 } 586 587 /* 588 * BAR has bits saying this space is IO space, unless 589 * this is the ROM address register. 590 */ 591 if (((PCI_BASE_SPACE_M & prg2.data) == 592 PCI_BASE_SPACE_IO) && 593 (prg2.offset != PCI_CONF_ROM)) { 594 if (pcitool_debug) 595 prom_printf("IO space\n"); 596 597 prg2.data &= PCI_BASE_IO_ADDR_M; 598 prg.phys_addr = prg2.data + prg.offset; 599 600 rval = pcitool_io_access(dip, &prg, write_flag); 601 if ((rval != SUCCESS) && (pcitool_debug)) 602 prom_printf("IO access failed\n"); 603 604 goto done_reg; 605 606 607 /* 608 * BAR has bits saying this space is 64 bit memory 609 * space, unless this is the ROM address register. 610 * 611 * The 64 bit address stored in two BAR cells is not 612 * necessarily aligned on an 8-byte boundary. 613 * Need to keep the first 4 bytes read, 614 * and do a separate read of the high 4 bytes. 615 */ 616 617 } else if ((PCI_BASE_TYPE_ALL & prg2.data) && 618 (prg2.offset != PCI_CONF_ROM)) { 619 620 uint32_t low_bytes = 621 (uint32_t)(prg2.data & ~PCI_BASE_TYPE_ALL); 622 623 /* 624 * Don't try to read the next 4 bytes 625 * past the end of BARs. 626 */ 627 if (prg2.offset >= PCI_CONF_BASE5) { 628 prg.status = PCITOOL_OUT_OF_RANGE; 629 rval = EIO; 630 goto done_reg; 631 } 632 633 /* 634 * Access device. 635 * prg2.status is modified on error. 636 */ 637 prg2.offset += 4; 638 rval = pcitool_cfg_access(dip, &prg2, B_FALSE); 639 if (rval != SUCCESS) { 640 prg.status = prg2.status; 641 goto done_reg; 642 } 643 644 if (prg2.data == 0xffffffff) { 645 prg.status = PCITOOL_INVALID_ADDRESS; 646 prg.status = EFAULT; 647 goto done_reg; 648 } 649 650 prg2.data = (prg2.data << 32) + low_bytes; 651 if (pcitool_debug) 652 prom_printf( 653 "64 bit mem space. " 654 "64-bit bar is 0x%" PRIx64 "\n", 655 prg2.data); 656 657 /* Mem32 space, including ROM */ 658 } else { 659 660 if (prg2.offset == PCI_CONF_ROM) { 661 if (pcitool_debug) 662 prom_printf( 663 "Additional ROM " 664 "checking\n"); 665 /* Can't write to ROM */ 666 if (write_flag) { 667 prg.status = PCITOOL_ROM_WRITE; 668 rval = EIO; 669 goto done_reg; 670 671 /* ROM disabled for reading */ 672 } else if (!(prg2.data & 0x00000001)) { 673 prg.status = 674 PCITOOL_ROM_DISABLED; 675 rval = EIO; 676 goto done_reg; 677 } 678 } 679 680 if (pcitool_debug) 681 prom_printf("32 bit mem space\n"); 682 } 683 684 /* Common code for all IO/MEM range spaces. */ 685 686 base_addr = prg2.data; 687 if (pcitool_debug) 688 prom_printf( 689 "addr portion of bar is 0x%" PRIx64 ", " 690 "base=0x%" PRIx64 ", " 691 "offset:0x%" PRIx64 "\n", 692 prg2.data, base_addr, prg.offset); 693 /* 694 * Use offset provided by caller to index into 695 * desired space, then access. 696 * Note that prg.status is modified on error. 697 */ 698 prg.phys_addr = base_addr + prg.offset; 699 700 virt_addr = pcitool_map(prg.phys_addr, size, 701 &num_virt_pages); 702 if (virt_addr == NULL) { 703 prg.status = PCITOOL_IO_ERROR; 704 rval = EIO; 705 goto done_reg; 706 } 707 708 rval = pcitool_mem_access(dip, &prg, virt_addr, 709 write_flag); 710 pcitool_unmap(virt_addr, num_virt_pages); 711 } 712 done_reg: 713 if (ddi_copyout(&prg, arg, sizeof (pcitool_reg_t), mode) != 714 DDI_SUCCESS) { 715 if (pcitool_debug) 716 prom_printf("Error returning arguments.\n"); 717 rval = EFAULT; 718 } 719 break; 720 default: 721 rval = ENOTTY; 722 break; 723 } 724 return (rval); 725 } 726