1/* 2 * (C) Copyright Eric Anholt 2006 3 * (C) Copyright IBM Corporation 2006 4 * (C) Copyright Mark Kettenis 2011 5 * (C) Copyright Robert Millan 2012 6 * All Rights Reserved. 7 * 8 * Permission is hereby granted, free of charge, to any person obtaining a 9 * copy of this software and associated documentation files (the "Software"), 10 * to deal in the Software without restriction, including without limitation 11 * on the rights to use, copy, modify, merge, publish, distribute, sub 12 * license, and/or sell copies of the Software, and to permit persons to whom 13 * the Software is furnished to do so, subject to the following conditions: 14 * 15 * The above copyright notice and this permission notice (including the next 16 * paragraph) shall be included in all copies or substantial portions of the 17 * Software. 18 * 19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL 22 * IBM AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 * DEALINGS IN THE SOFTWARE. 26 */ 27 28/** 29 * \file freebsd_pci.c 30 * 31 * Access the kernel PCI support using /dev/pci's ioctl and mmap interface. 32 * 33 * \author Eric Anholt <eric@anholt.net> 34 */ 35 36#ifdef HAVE_CONFIG_H 37#include "config.h" 38#endif 39 40#include <stdlib.h> 41#include <stdio.h> 42#include <string.h> 43#include <unistd.h> 44#include <fcntl.h> 45#include <errno.h> 46#if defined(__i386__) || defined(__amd64__) 47#include <machine/cpufunc.h> 48#else 49#include <dev/io/iodev.h> 50#endif 51#include <sys/types.h> 52#include <sys/param.h> 53#include <sys/pciio.h> 54#include <sys/mman.h> 55#include <sys/memrange.h> 56 57#include "pciaccess.h" 58#include "pciaccess_private.h" 59 60#define PCIC_DISPLAY 0x03 61#define PCIS_DISPLAY_VGA 0x00 62#define PCIS_DISPLAY_XGA 0x01 63#define PCIS_DISPLAY_3D 0x02 64#define PCIS_DISPLAY_OTHER 0x80 65 66/* Registers taken from pcireg.h */ 67#define PCIR_COMMAND 0x04 68#define PCIM_CMD_PORTEN 0x0001 69#define PCIM_CMD_MEMEN 0x0002 70#define PCIR_BIOS 0x30 71#define PCIM_BIOS_ENABLE 0x01 72#define PCIM_BIOS_ADDR_MASK 0xfffff800 73 74#define PCIR_BARS 0x10 75#define PCIR_BAR(x) (PCIR_BARS + (x) * 4) 76#define PCI_BAR_IO(x) (((x) & PCIM_BAR_SPACE) == PCIM_BAR_IO_SPACE) 77#define PCI_BAR_MEM(x) (((x) & PCIM_BAR_SPACE) == PCIM_BAR_MEM_SPACE) 78#define PCIM_BAR_MEM_TYPE 0x00000006 79#define PCIM_BAR_MEM_64 4 80#define PCIM_BAR_MEM_PREFETCH 0x00000008 81#define PCIM_BAR_SPACE 0x00000001 82#define PCIM_BAR_MEM_SPACE 0 83#define PCIM_BAR_IO_SPACE 1 84 85/** 86 * FreeBSD private pci_system structure that extends the base pci_system 87 * structure. 88 * 89 * It is initialized once and used as a global, just as pci_system is used. 90 */ 91_pci_hidden 92struct freebsd_pci_system { 93 /* This must be the first entry in the structure, as pci_system_cleanup() 94 * frees pci_sys. 95 */ 96 struct pci_system pci_sys; 97 98 int pcidev; /**< fd for /dev/pci */ 99} *freebsd_pci_sys; 100 101/** 102 * Map a memory region for a device using /dev/mem. 103 * 104 * \param dev Device whose memory region is to be mapped. 105 * \param map Parameters of the mapping that is to be created. 106 * 107 * \return 108 * Zero on success or an \c errno value on failure. 109 */ 110static int 111pci_device_freebsd_map_range( struct pci_device *dev, 112 struct pci_device_mapping *map ) 113{ 114 const int prot = ((map->flags & PCI_DEV_MAP_FLAG_WRITABLE) != 0) 115 ? (PROT_READ | PROT_WRITE) : PROT_READ; 116 struct mem_range_desc mrd; 117 struct mem_range_op mro; 118 119 int fd, err = 0; 120 121 fd = open("/dev/mem", O_RDWR | O_CLOEXEC); 122 if (fd == -1) 123 return errno; 124 125 map->memory = mmap(NULL, map->size, prot, MAP_SHARED, fd, map->base); 126 127 if (map->memory == MAP_FAILED) { 128 err = errno; 129 } 130 131 mrd.mr_base = map->base; 132 mrd.mr_len = map->size; 133 strncpy(mrd.mr_owner, "pciaccess", sizeof(mrd.mr_owner)); 134 if (map->flags & PCI_DEV_MAP_FLAG_CACHABLE) 135 mrd.mr_flags = MDF_WRITEBACK; 136 else if (map->flags & PCI_DEV_MAP_FLAG_WRITE_COMBINE) 137 mrd.mr_flags = MDF_WRITECOMBINE; 138 else 139 mrd.mr_flags = MDF_UNCACHEABLE; 140 mro.mo_desc = &mrd; 141 mro.mo_arg[0] = MEMRANGE_SET_UPDATE; 142 143 /* No need to set an MTRR if it's the default mode. */ 144 if (mrd.mr_flags != MDF_UNCACHEABLE) { 145 if (ioctl(fd, MEMRANGE_SET, &mro)) { 146 fprintf(stderr, "failed to set mtrr: %s\n", strerror(errno)); 147 } 148 } 149 150 close(fd); 151 152 return err; 153} 154 155static int 156pci_device_freebsd_unmap_range( struct pci_device *dev, 157 struct pci_device_mapping *map ) 158{ 159 struct mem_range_desc mrd; 160 struct mem_range_op mro; 161 int fd; 162 163 if ((map->flags & PCI_DEV_MAP_FLAG_CACHABLE) || 164 (map->flags & PCI_DEV_MAP_FLAG_WRITE_COMBINE)) 165 { 166 fd = open("/dev/mem", O_RDWR | O_CLOEXEC); 167 if (fd != -1) { 168 mrd.mr_base = map->base; 169 mrd.mr_len = map->size; 170 strncpy(mrd.mr_owner, "pciaccess", sizeof(mrd.mr_owner)); 171 mrd.mr_flags = MDF_UNCACHEABLE; 172 mro.mo_desc = &mrd; 173 mro.mo_arg[0] = MEMRANGE_SET_REMOVE; 174 175 if (ioctl(fd, MEMRANGE_SET, &mro)) { 176 fprintf(stderr, "failed to unset mtrr: %s\n", strerror(errno)); 177 } 178 179 close(fd); 180 } else { 181 fprintf(stderr, "Failed to open /dev/mem\n"); 182 } 183 } 184 185 return pci_device_generic_unmap_range(dev, map); 186} 187 188static int 189pci_device_freebsd_read( struct pci_device * dev, void * data, 190 pciaddr_t offset, pciaddr_t size, 191 pciaddr_t * bytes_read ) 192{ 193 struct pci_io io; 194 195 io.pi_sel.pc_domain = dev->domain; 196 io.pi_sel.pc_bus = dev->bus; 197 io.pi_sel.pc_dev = dev->dev; 198 io.pi_sel.pc_func = dev->func; 199 200 *bytes_read = 0; 201 while ( size > 0 ) { 202 int toread = (size < 4) ? size : 4; 203 204 /* Only power of two allowed. */ 205 if (toread == 3) 206 toread = 2; 207 208 io.pi_reg = offset; 209 io.pi_width = toread; 210 211 if ( ioctl( freebsd_pci_sys->pcidev, PCIOCREAD, &io ) < 0 ) 212 return errno; 213 214 memcpy(data, &io.pi_data, toread ); 215 216 offset += toread; 217 data = (char *)data + toread; 218 size -= toread; 219 *bytes_read += toread; 220 } 221 222 return 0; 223} 224 225 226static int 227pci_device_freebsd_write( struct pci_device * dev, const void * data, 228 pciaddr_t offset, pciaddr_t size, 229 pciaddr_t * bytes_written ) 230{ 231 struct pci_io io; 232 233 io.pi_sel.pc_domain = dev->domain; 234 io.pi_sel.pc_bus = dev->bus; 235 io.pi_sel.pc_dev = dev->dev; 236 io.pi_sel.pc_func = dev->func; 237 238 *bytes_written = 0; 239 while ( size > 0 ) { 240 int towrite = (size < 4 ? size : 4); 241 242 /* Only power of two allowed. */ 243 if (towrite == 3) 244 towrite = 2; 245 246 io.pi_reg = offset; 247 io.pi_width = towrite; 248 memcpy( &io.pi_data, data, towrite ); 249 250 if ( ioctl( freebsd_pci_sys->pcidev, PCIOCWRITE, &io ) < 0 ) 251 return errno; 252 253 offset += towrite; 254 data = (char *)data + towrite; 255 size -= towrite; 256 *bytes_written += towrite; 257 } 258 259 return 0; 260} 261 262/** 263 * Read a VGA rom using the 0xc0000 mapping. 264 * 265 * This function should be extended to handle access through PCI resources, 266 * which should be more reliable when available. 267 */ 268static int 269pci_device_freebsd_read_rom( struct pci_device * dev, void * buffer ) 270{ 271 struct pci_device_private *priv = (struct pci_device_private *) dev; 272 void *bios; 273 pciaddr_t rom_base; 274 uint32_t rom; 275 uint16_t reg; 276 int pci_rom, memfd; 277 278 if ( ( dev->device_class & 0x00ffff00 ) != 279 ( ( PCIC_DISPLAY << 16 ) | ( PCIS_DISPLAY_VGA << 8 ) ) ) 280 { 281 return ENOSYS; 282 } 283 284 if (priv->rom_base == 0) { 285#if defined(__amd64__) || defined(__i386__) 286 rom_base = 0xc0000; 287 pci_rom = 0; 288#else 289 return ENOSYS; 290#endif 291 } else { 292 rom_base = priv->rom_base; 293 pci_rom = 1; 294 295 pci_device_cfg_read_u16( dev, ®, PCIR_COMMAND ); 296 pci_device_cfg_write_u16( dev, reg | PCIM_CMD_MEMEN, PCIR_COMMAND ); 297 pci_device_cfg_read_u32( dev, &rom, PCIR_BIOS ); 298 pci_device_cfg_write_u32( dev, rom | PCIM_BIOS_ENABLE, PCIR_BIOS ); 299 } 300 301 printf("Using rom_base = 0x%lx\n", (long)rom_base); 302 memfd = open( "/dev/mem", O_RDONLY | O_CLOEXEC ); 303 if ( memfd == -1 ) 304 return errno; 305 306 bios = mmap( NULL, dev->rom_size, PROT_READ, 0, memfd, rom_base ); 307 if ( bios == MAP_FAILED ) { 308 close( memfd ); 309 return errno; 310 } 311 312 memcpy( buffer, bios, dev->rom_size ); 313 314 munmap( bios, dev->rom_size ); 315 close( memfd ); 316 317 if (pci_rom) { 318 pci_device_cfg_write_u32( dev, PCIR_BIOS, rom ); 319 pci_device_cfg_write_u16( dev, PCIR_COMMAND, reg ); 320 } 321 322 return 0; 323} 324 325/** Returns the number of regions (base address registers) the device has */ 326 327static int 328pci_device_freebsd_get_num_regions( struct pci_device * dev ) 329{ 330 struct pci_device_private *priv = (struct pci_device_private *) dev; 331 332 switch (priv->header_type) { 333 case 0: 334 return 6; 335 case 1: 336 return 2; 337 case 2: 338 return 1; 339 default: 340 printf("unknown header type %02x\n", priv->header_type); 341 return 0; 342 } 343} 344 345static int 346pci_device_freebsd_probe( struct pci_device * dev ) 347{ 348 struct pci_bar_io bar; 349 uint8_t irq; 350 int err, i; 351 352 bar.pbi_sel.pc_domain = dev->domain; 353 bar.pbi_sel.pc_bus = dev->bus; 354 bar.pbi_sel.pc_dev = dev->dev; 355 bar.pbi_sel.pc_func = dev->func; 356 357 358 /* Many of the fields were filled in during initial device enumeration. 359 * At this point, we need to fill in regions, rom_size, and irq. 360 */ 361 362 err = pci_device_cfg_read_u8( dev, &irq, 60 ); 363 if (err) 364 return errno; 365 dev->irq = irq; 366 367 for (i = 0; i < pci_device_freebsd_get_num_regions( dev ); i++) { 368 bar.pbi_reg = PCIR_BAR(i); 369 if ( ioctl( freebsd_pci_sys->pcidev, PCIOCGETBAR, &bar ) < 0 ) 370 continue; 371 372 if (PCI_BAR_IO(bar.pbi_base)) 373 dev->regions[i].is_IO = 1; 374 375 if ((bar.pbi_base & PCIM_BAR_MEM_TYPE) == PCIM_BAR_MEM_64) 376 dev->regions[i].is_64 = 1; 377 378 if (bar.pbi_base & PCIM_BAR_MEM_PREFETCH) 379 dev->regions[i].is_prefetchable = 1; 380 381 dev->regions[i].base_addr = bar.pbi_base & ~((uint64_t)0xf); 382 dev->regions[i].size = bar.pbi_length; 383 } 384 385 /* If it's a VGA device, set up the rom size for read_rom using the 386 * 0xc0000 mapping. 387 */ 388 if ((dev->device_class & 0x00ffff00) == 389 ((PCIC_DISPLAY << 16) | (PCIS_DISPLAY_VGA << 8))) { 390 dev->rom_size = 64 * 1024; 391 } 392 393 return 0; 394} 395 396static void 397pci_system_freebsd_destroy( void ) 398{ 399 close(freebsd_pci_sys->pcidev); 400 free(freebsd_pci_sys->pci_sys.devices); 401 freebsd_pci_sys = NULL; 402} 403 404static int 405pci_device_freebsd_has_kernel_driver( struct pci_device *dev ) 406{ 407 struct pci_io io; 408 409 io.pi_sel.pc_domain = dev->domain; 410 io.pi_sel.pc_bus = dev->bus; 411 io.pi_sel.pc_dev = dev->dev; 412 io.pi_sel.pc_func = dev->func; 413 414 if ( ioctl( freebsd_pci_sys->pcidev, PCIOCATTACHED, &io ) < 0 ) { 415 return 0; 416 } 417 418 /* if io.pi_data is 0, no driver is attached */ 419 return io.pi_data == 0 ? 0 : 1; 420} 421 422static struct pci_io_handle * 423pci_device_freebsd_open_legacy_io( struct pci_io_handle *ret, 424 struct pci_device *dev, pciaddr_t base, 425 pciaddr_t size ) 426{ 427 ret->fd = open( "/dev/io", O_RDWR | O_CLOEXEC ); 428 if ( ret->fd < 0 ) 429 return NULL; 430 ret->base = base; 431 ret->size = size; 432 ret->is_legacy = 1; 433 return ret; 434} 435 436static struct pci_io_handle * 437pci_device_freebsd_open_io( struct pci_io_handle *ret, 438 struct pci_device *dev, int bar, 439 pciaddr_t base, pciaddr_t size ) 440{ 441 ret = pci_device_freebsd_open_legacy_io( ret, dev, base, size ); 442 if ( ret != NULL ) 443 ret->is_legacy = 0; 444 return ret; 445} 446 447static void 448pci_device_freebsd_close_io( struct pci_device *dev, 449 struct pci_io_handle *handle ) 450{ 451 if ( handle->fd > -1 ) 452 close( handle->fd ); 453} 454 455static uint32_t 456pci_device_freebsd_read32( struct pci_io_handle *handle, uint32_t reg ) 457{ 458#if defined(__i386__) || defined(__amd64__) 459 return inl( handle->base + reg ); 460#else 461 struct iodev_pio_req req = { IODEV_PIO_READ, handle->base + reg, 4, 0 }; 462 if ( handle->fd > -1 ) 463 ioctl( handle->fd, IODEV_PIO, &req ); 464 return req.val; 465#endif 466} 467 468static uint16_t 469pci_device_freebsd_read16( struct pci_io_handle *handle, uint32_t reg ) 470{ 471#if defined(__i386__) || defined(__amd64__) 472 return inw( handle->base + reg ); 473#else 474 struct iodev_pio_req req = { IODEV_PIO_READ, handle->base + reg, 2, 0 }; 475 if ( handle->fd > -1 ) 476 ioctl( handle->fd, IODEV_PIO, &req ); 477 return req.val; 478#endif 479} 480 481static uint8_t 482pci_device_freebsd_read8( struct pci_io_handle *handle, uint32_t reg ) 483{ 484#if defined(__i386__) || defined(__amd64__) 485 return inb( handle->base + reg ); 486#else 487 struct iodev_pio_req req = { IODEV_PIO_READ, handle->base + reg, 1, 0 }; 488 if ( handle->fd > -1 ) 489 ioctl( handle->fd, IODEV_PIO, &req ); 490 return req.val; 491#endif 492} 493 494static void 495pci_device_freebsd_write32( struct pci_io_handle *handle, uint32_t reg, 496 uint32_t data ) 497{ 498#if defined(__i386__) || defined(__amd64__) 499 outl( handle->base + reg, data ); 500#else 501 struct iodev_pio_req req = { IODEV_PIO_WRITE, handle->base + reg, 4, data }; 502 if ( handle->fd > -1 ) 503 ioctl( handle->fd, IODEV_PIO, &req ); 504#endif 505} 506 507static void 508pci_device_freebsd_write16( struct pci_io_handle *handle, uint32_t reg, 509 uint16_t data ) 510{ 511#if defined(__i386__) || defined(__amd64__) 512 outw( handle->base + reg, data ); 513#else 514 struct iodev_pio_req req = { IODEV_PIO_WRITE, handle->base + reg, 2, data }; 515 if ( handle->fd > -1 ) 516 ioctl( handle->fd, IODEV_PIO, &req ); 517#endif 518} 519 520static void 521pci_device_freebsd_write8( struct pci_io_handle *handle, uint32_t reg, 522 uint8_t data ) 523{ 524#if defined(__i386__) || defined(__amd64__) 525 outb( handle->base + reg, data ); 526#else 527 struct iodev_pio_req req = { IODEV_PIO_WRITE, handle->base + reg, 1, data }; 528 if ( handle->fd > -1 ) 529 ioctl( handle->fd, IODEV_PIO, &req ); 530#endif 531} 532 533static int 534pci_device_freebsd_map_legacy( struct pci_device *dev, pciaddr_t base, 535 pciaddr_t size, unsigned map_flags, void **addr ) 536{ 537 struct pci_device_mapping map; 538 int err; 539 540 map.base = base; 541 map.size = size; 542 map.flags = map_flags; 543 map.memory = NULL; 544 err = pci_device_freebsd_map_range( dev, &map ); 545 *addr = map.memory; 546 547 return err; 548} 549 550static int 551pci_device_freebsd_unmap_legacy( struct pci_device *dev, void *addr, 552 pciaddr_t size ) 553{ 554 struct pci_device_mapping map; 555 556 map.memory = addr; 557 map.size = size; 558 map.flags = 0; 559 return pci_device_freebsd_unmap_range( dev, &map ); 560} 561 562static const struct pci_system_methods freebsd_pci_methods = { 563 .destroy = pci_system_freebsd_destroy, 564 .destroy_device = NULL, /* nothing to do for this */ 565 .read_rom = pci_device_freebsd_read_rom, 566 .probe = pci_device_freebsd_probe, 567 .map_range = pci_device_freebsd_map_range, 568 .unmap_range = pci_device_freebsd_unmap_range, 569 570 .read = pci_device_freebsd_read, 571 .write = pci_device_freebsd_write, 572 573 .fill_capabilities = pci_fill_capabilities_generic, 574 .enable = NULL, 575 .boot_vga = NULL, 576 .has_kernel_driver = pci_device_freebsd_has_kernel_driver, 577 578 .open_device_io = pci_device_freebsd_open_io, 579 .open_legacy_io = pci_device_freebsd_open_legacy_io, 580 .close_io = pci_device_freebsd_close_io, 581 .read32 = pci_device_freebsd_read32, 582 .read16 = pci_device_freebsd_read16, 583 .read8 = pci_device_freebsd_read8, 584 .write32 = pci_device_freebsd_write32, 585 .write16 = pci_device_freebsd_write16, 586 .write8 = pci_device_freebsd_write8, 587 588 .map_legacy = pci_device_freebsd_map_legacy, 589 .unmap_legacy = pci_device_freebsd_unmap_legacy, 590}; 591 592/** 593 * Attempt to access the FreeBSD PCI interface. 594 */ 595_pci_hidden int 596pci_system_freebsd_create( void ) 597{ 598 struct pci_conf_io pciconfio; 599 struct pci_conf pciconf[255]; 600 int pcidev; 601 int i; 602 603 /* Try to open the PCI device */ 604 pcidev = open( "/dev/pci", O_RDWR | O_CLOEXEC ); 605 if ( pcidev == -1 ) 606 return ENXIO; 607 608 freebsd_pci_sys = calloc( 1, sizeof( struct freebsd_pci_system ) ); 609 if ( freebsd_pci_sys == NULL ) { 610 close( pcidev ); 611 return ENOMEM; 612 } 613 pci_sys = &freebsd_pci_sys->pci_sys; 614 615 pci_sys->methods = & freebsd_pci_methods; 616 freebsd_pci_sys->pcidev = pcidev; 617 618 /* Probe the list of devices known by the system */ 619 bzero( &pciconfio, sizeof( struct pci_conf_io ) ); 620 pciconfio.match_buf_len = sizeof(pciconf); 621 pciconfio.matches = pciconf; 622 623 if ( ioctl( pcidev, PCIOCGETCONF, &pciconfio ) == -1) { 624 free( pci_sys ); 625 pci_sys = NULL; 626 close( pcidev ); 627 return errno; 628 } 629 630 if (pciconfio.status == PCI_GETCONF_ERROR ) { 631 free( pci_sys ); 632 pci_sys = NULL; 633 close( pcidev ); 634 return EINVAL; 635 } 636 637 /* Translate the list of devices into pciaccess's format. */ 638 pci_sys->num_devices = pciconfio.num_matches; 639 pci_sys->devices = calloc( pciconfio.num_matches, 640 sizeof( struct pci_device_private ) ); 641 642 for ( i = 0; i < pciconfio.num_matches; i++ ) { 643 struct pci_conf *p = &pciconf[ i ]; 644 645 pci_sys->devices[ i ].base.domain = p->pc_sel.pc_domain; 646 pci_sys->devices[ i ].base.bus = p->pc_sel.pc_bus; 647 pci_sys->devices[ i ].base.dev = p->pc_sel.pc_dev; 648 pci_sys->devices[ i ].base.func = p->pc_sel.pc_func; 649 pci_sys->devices[ i ].base.vendor_id = p->pc_vendor; 650 pci_sys->devices[ i ].base.device_id = p->pc_device; 651 pci_sys->devices[ i ].base.subvendor_id = p->pc_subvendor; 652 pci_sys->devices[ i ].base.subdevice_id = p->pc_subdevice; 653 pci_sys->devices[ i ].base.revision = p->pc_revid; 654 pci_sys->devices[ i ].base.device_class = (uint32_t)p->pc_class << 16 | 655 (uint32_t)p->pc_subclass << 8 | (uint32_t)p->pc_progif; 656 pci_sys->devices[ i ].header_type = p->pc_hdr & 0x7f; 657 } 658 659 return 0; 660} 661