/* * The PCI Library -- Direct Configuration access via PCIe ECAM * * Copyright (c) 2023 Pali Rohár * * Can be freely distributed and used under the terms of the GNU GPL v2+. * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "internal.h" #include "physmem.h" #include "physmem-access.h" #include #include #include #include #include #include #include #include #if defined (__FreeBSD__) || defined (__DragonFly__) || defined(__NetBSD__) #include #endif #if defined (__FreeBSD__) || defined (__DragonFly__) #include #endif struct acpi_rsdp { char signature[8]; u8 checksum; char oem_id[6]; u8 revision; u32 rsdt_address; struct { u32 length; u64 xsdt_address; u8 ext_checksum; u8 reserved[3]; } rsdp20[0]; } PCI_PACKED; struct acpi_sdt { char signature[4]; u32 length; u8 revision; u8 checksum; char oem_id[6]; char oem_table_id[8]; u32 oem_revision; char asl_compiler_id[4]; u32 asl_compiler_revision; } PCI_PACKED; struct acpi_rsdt { struct acpi_sdt sdt; u32 sdt_addresses[0]; } PCI_PACKED; struct acpi_xsdt { struct acpi_sdt sdt; u64 sdt_addresses[0]; } PCI_PACKED; struct acpi_mcfg { struct acpi_sdt sdt; u64 reserved; struct { u64 address; u16 pci_segment; u8 start_bus_number; u8 end_bus_number; u32 reserved; } allocations[0]; } PCI_PACKED; struct mmap_cache { void *map; u64 addr; u32 length; int domain; u8 bus; int w; }; // Back-end data linked to struct pci_access struct ecam_access { struct acpi_mcfg *mcfg; struct mmap_cache *cache; struct physmem *physmem; long pagesize; }; static unsigned int get_rsdt_addresses_count(struct acpi_rsdt *rsdt) { return (rsdt->sdt.length - ((unsigned char*)&rsdt->sdt_addresses - (unsigned char *)rsdt)) / sizeof(rsdt->sdt_addresses[0]); } static unsigned int get_xsdt_addresses_count(struct acpi_xsdt *xsdt) { return (xsdt->sdt.length - ((unsigned char*)&xsdt->sdt_addresses - (unsigned char *)xsdt)) / sizeof(xsdt->sdt_addresses[0]); } static unsigned int get_mcfg_allocations_count(struct acpi_mcfg *mcfg) { return (mcfg->sdt.length - ((unsigned char *)&mcfg->allocations - (unsigned char *)mcfg)) / sizeof(mcfg->allocations[0]); } static u8 calculate_checksum(const u8 *bytes, int len) { u8 checksum = 0; while (len-- > 0) checksum -= *(bytes++); return checksum; } static struct acpi_sdt * check_and_map_sdt(struct physmem *physmem, long pagesize, u64 addr, const char *signature, void **map_addr, u32 *map_length) { struct acpi_sdt *sdt; char sdt_signature[sizeof(sdt->signature)]; u32 length; void *map; if (addr + sizeof(*sdt) < addr) return NULL; map = physmem_map(physmem, addr & ~(pagesize-1), sizeof(*sdt) + (addr & (pagesize-1)), 0); if (map == (void *)-1) return NULL; sdt = (struct acpi_sdt *)((unsigned char *)map + (addr & (pagesize-1))); length = sdt->length; memcpy(sdt_signature, sdt->signature, sizeof(sdt->signature)); physmem_unmap(physmem, map, sizeof(*sdt) + (addr & (pagesize-1))); if (memcmp(sdt_signature, signature, sizeof(sdt_signature)) != 0) return NULL; if (length < sizeof(*sdt)) return NULL; map = physmem_map(physmem, addr & ~(pagesize-1), length + (addr & (pagesize-1)), 0); if (map == (void *)-1) return NULL; sdt = (struct acpi_sdt *)((unsigned char *)map + (addr & (pagesize-1))); if (calculate_checksum((u8 *)sdt, sdt->length) != 0) { physmem_unmap(physmem, map, length + (addr & (pagesize-1))); return NULL; } *map_addr = map; *map_length = length + (addr & (pagesize-1)); return sdt; } static int check_rsdp(struct acpi_rsdp *rsdp) { if (memcmp(rsdp->signature, "RSD PTR ", sizeof(rsdp->signature)) != 0) return 0; if (calculate_checksum((u8 *)rsdp, sizeof(*rsdp)) != 0) return 0; return 1; } static int check_and_parse_rsdp(struct physmem *physmem, long pagesize, u64 addr, u32 *rsdt_address, u64 *xsdt_address) { struct acpi_rsdp *rsdp; unsigned char buf[sizeof(*rsdp) + sizeof(*rsdp->rsdp20)]; void *map; map = physmem_map(physmem, addr & ~(pagesize-1), sizeof(buf) + (addr & (pagesize-1)), 0); if (map == (void *)-1) return 0; rsdp = (struct acpi_rsdp *)buf; memcpy(rsdp, (unsigned char *)map + (addr & (pagesize-1)), sizeof(buf)); physmem_unmap(physmem, map, sizeof(buf)); if (!check_rsdp(rsdp)) return 0; *rsdt_address = rsdp->rsdt_address; if (rsdp->revision != 0 && (*rsdp->rsdp20).length == sizeof(*rsdp) + sizeof(*rsdp->rsdp20) && calculate_checksum((u8 *)rsdp, (*rsdp->rsdp20).length) == 0) *xsdt_address = (*rsdp->rsdp20).xsdt_address; else *xsdt_address = 0; return 1; } static u64 find_rsdp_address(struct pci_access *a, const char *efisystab, int use_bsd UNUSED, int use_x86bios UNUSED) { u64 ullnum; #if defined (__FreeBSD__) || defined (__DragonFly__) || defined(__NetBSD__) unsigned long ulnum; #endif char buf[1024]; char *endptr; u64 acpi20; u64 acpi; #if defined(__amd64__) || defined(__i386__) struct ecam_access *eacc = a->backend_data; struct physmem *physmem = eacc->physmem; long pagesize = eacc->pagesize; u64 rsdp_addr; u64 addr; void *map; u64 ebda; #endif size_t len; FILE *f; if (efisystab[0]) { acpi = 0; acpi20 = 0; a->debug("reading EFI system table: %s...", efisystab); f = fopen(efisystab, "r"); if (f) { while (fgets(buf, sizeof(buf), f)) { len = strlen(buf); while (len > 0 && buf[len-1] == '\n') buf[--len] = '\0'; if (strncmp(buf, "ACPI20=", 7) == 0 && isxdigit(buf[7])) { errno = 0; ullnum = strtoull(buf+7, &endptr, 16); if (!errno && !*endptr) acpi20 = ullnum; } else if (strncmp(buf, "ACPI=", 5) == 0 && isxdigit(buf[5])) { errno = 0; ullnum = strtoull(buf+5, &endptr, 16); if (!errno && !*endptr) acpi = ullnum; } } fclose(f); } else a->debug("opening failed: %s...", strerror(errno)); if (acpi20) return acpi20; else if (acpi) return acpi; } #if defined (__FreeBSD__) || defined (__DragonFly__) if (use_bsd) { /* First try FreeBSD kenv hint.acpi.0.rsdp */ a->debug("calling kenv hint.acpi.0.rsdp..."); if (kenv(KENV_GET, "hint.acpi.0.rsdp", buf, sizeof(buf)) > 0) { errno = 0; ullnum = strtoull(buf, &endptr, 16); if (!errno && !*endptr) return ullnum; } /* Then try FreeBSD sysctl machdep.acpi_root */ a->debug("calling sysctl machdep.acpi_root..."); len = sizeof(ulnum); if (sysctlbyname("machdep.acpi_root", &ulnum, &len, NULL, 0) == 0) return ulnum; } #endif #if defined(__NetBSD__) if (use_bsd) { /* Try NetBSD sysctl hw.acpi.root */ a->debug("calling sysctl hw.acpi.root..."); len = sizeof(ulnum); if (sysctlbyname("hw.acpi.root", &ulnum, &len, NULL, 0) == 0) return ulnum; } #endif #if defined(__amd64__) || defined(__i386__) if (use_x86bios) { rsdp_addr = 0; /* Scan first kB of Extended BIOS Data Area */ a->debug("reading EBDA location from BDA..."); map = physmem_map(physmem, 0, 0x40E + 2, 0); if (map != (void *)-1) { ebda = (u64)physmem_readw((unsigned char *)map + 0x40E) << 4; if (physmem_unmap(physmem, map, 0x40E + 2) != 0) a->debug("unmapping of BDA failed: %s...", strerror(errno)); if (ebda >= 0x400) { a->debug("scanning first kB of EBDA at 0x%" PCI_U64_FMT_X "...", ebda); map = physmem_map(physmem, ebda & ~(pagesize-1), 1024 + (ebda & (pagesize-1)), 0); if (map != (void *)-1) { for (addr = ebda & (pagesize-1); addr < (ebda & (pagesize-1)) + 1024; addr += 16) { if (check_rsdp((struct acpi_rsdp *)((unsigned char *)map + addr))) { rsdp_addr = (ebda & ~(pagesize-1)) + addr; break; } } if (physmem_unmap(physmem, map, 1024 + (ebda & (pagesize-1))) != 0) a->debug("unmapping of EBDA failed: %s...", strerror(errno)); } else a->debug("mapping of EBDA failed: %s...", strerror(errno)); } else a->debug("EBDA location 0x%" PCI_U64_FMT_X " is insane...", ebda); } else a->debug("mapping of BDA failed: %s...", strerror(errno)); if (rsdp_addr) return rsdp_addr; /* Scan the main BIOS area below 1 MB */ a->debug("scanning BIOS below 1 MB..."); map = physmem_map(physmem, 0xE0000, 0x20000, 0); if (map != (void *)-1) { for (addr = 0x0; addr < 0x20000; addr += 16) { if (check_rsdp((struct acpi_rsdp *)((unsigned char *)map + addr))) { rsdp_addr = 0xE0000 + addr; break; } } if (physmem_unmap(physmem, map, 0x20000) != 0) a->debug("unmapping of BIOS failed: %s...", strerror(errno)); } else a->debug("mapping of BIOS failed: %s...", strerror(errno)); if (rsdp_addr) return rsdp_addr; } #endif return 0; } static struct acpi_mcfg * find_mcfg(struct pci_access *a, const char *acpimcfg, const char *efisystab, int use_bsd, int use_x86bios) { struct ecam_access *eacc = a->backend_data; struct physmem *physmem = eacc->physmem; long pagesize = eacc->pagesize; struct acpi_xsdt *xsdt; struct acpi_rsdt *rsdt; struct acpi_mcfg *mcfg; struct acpi_sdt *sdt; unsigned int i, count; u64 rsdp_address; u64 xsdt_address; u32 rsdt_address; void *map_addr; u32 map_length; void *map2_addr; u32 map2_length; long length; FILE *mcfg_file; const char *path; glob_t mcfg_glob; int ret; if (acpimcfg[0]) { ret = glob(acpimcfg, GLOB_NOCHECK, NULL, &mcfg_glob); if (ret == 0) { path = mcfg_glob.gl_pathv[0]; a->debug("reading acpi mcfg file: %s...", path); mcfg_file = fopen(path, "rb"); globfree(&mcfg_glob); if (mcfg_file) { if (fseek(mcfg_file, 0, SEEK_END) == 0) length = ftell(mcfg_file); else length = -1; if (length > 0 && (size_t)length > sizeof(*mcfg)) { rewind(mcfg_file); mcfg = pci_malloc(a, length); if (fread(mcfg, 1, length, mcfg_file) == (size_t)length && memcmp(mcfg->sdt.signature, "MCFG", 4) == 0 && mcfg->sdt.length <= (size_t)length && calculate_checksum((u8 *)mcfg, mcfg->sdt.length) == 0) { fclose(mcfg_file); return mcfg; } } fclose(mcfg_file); } a->debug("failed..."); } else a->debug("glob(%s) failed: %d...", acpimcfg, ret); } a->debug("searching for ACPI RSDP..."); rsdp_address = find_rsdp_address(a, efisystab, use_bsd, use_x86bios); if (!rsdp_address) { a->debug("not found..."); return NULL; } a->debug("found at 0x%" PCI_U64_FMT_X "...", rsdp_address); if (!check_and_parse_rsdp(physmem, pagesize, rsdp_address, &rsdt_address, &xsdt_address)) { a->debug("invalid..."); return NULL; } mcfg = NULL; a->debug("searching for ACPI MCFG (XSDT=0x%" PCI_U64_FMT_X ", RSDT=0x%lx)...", xsdt_address, (unsigned long)rsdt_address); xsdt = xsdt_address ? (struct acpi_xsdt *)check_and_map_sdt(physmem, pagesize, xsdt_address, "XSDT", &map_addr, &map_length) : NULL; if (xsdt) { a->debug("via XSDT..."); count = get_xsdt_addresses_count(xsdt); for (i = 0; i < count; i++) { sdt = check_and_map_sdt(physmem, pagesize, xsdt->sdt_addresses[i], "MCFG", &map2_addr, &map2_length); if (sdt) { mcfg = pci_malloc(a, sdt->length); memcpy(mcfg, sdt, sdt->length); physmem_unmap(physmem, map2_addr, map2_length); break; } } physmem_unmap(physmem, map_addr, map_length); if (mcfg) { a->debug("found..."); return mcfg; } } rsdt = (struct acpi_rsdt *)check_and_map_sdt(physmem, pagesize, rsdt_address, "RSDT", &map_addr, &map_length); if (rsdt) { a->debug("via RSDT..."); count = get_rsdt_addresses_count(rsdt); for (i = 0; i < count; i++) { sdt = check_and_map_sdt(physmem, pagesize, rsdt->sdt_addresses[i], "MCFG", &map2_addr, &map2_length); if (sdt) { mcfg = pci_malloc(a, sdt->length); memcpy(mcfg, sdt, sdt->length); physmem_unmap(physmem, map2_addr, map2_length); break; } } physmem_unmap(physmem, map_addr, map_length); if (mcfg) { a->debug("found..."); return mcfg; } } a->debug("not found..."); return NULL; } static void get_mcfg_allocation(struct acpi_mcfg *mcfg, unsigned int i, int *domain, u8 *start_bus, u8 *end_bus, u64 *addr, u32 *length) { int buses = (int)mcfg->allocations[i].end_bus_number - (int)mcfg->allocations[i].start_bus_number + 1; if (domain) *domain = mcfg->allocations[i].pci_segment; if (start_bus) *start_bus = mcfg->allocations[i].start_bus_number; if (end_bus) *end_bus = mcfg->allocations[i].end_bus_number; if (addr) *addr = mcfg->allocations[i].address; if (length) *length = (buses > 0) ? (buses * 32 * 8 * 4096) : 0; } static int parse_next_addrs(const char *addrs, const char **next, int *domain, u8 *start_bus, u8 *end_bus, u64 *addr, u32 *length) { u64 ullnum; const char *sep1, *sep2; int addr_len; char *endptr; long num; int buses; u64 start_addr; if (!*addrs) { if (next) *next = NULL; return 0; } endptr = strchr(addrs, ','); if (endptr) addr_len = endptr - addrs; else addr_len = strlen(addrs); if (next) *next = endptr ? (endptr+1) : NULL; sep1 = memchr(addrs, ':', addr_len); if (!sep1) return 0; sep2 = memchr(sep1+1, ':', addr_len - (sep1+1 - addrs)); if (!sep2) { sep2 = sep1; sep1 = NULL; } if (!sep1) { if (domain) *domain = 0; } else { if (!isxdigit(*addrs)) return 0; errno = 0; num = strtol(addrs, &endptr, 16); if (errno || endptr != sep1 || num < 0 || num > INT_MAX) return 0; if (domain) *domain = num; } errno = 0; num = strtol(sep1 ? (sep1+1) : addrs, &endptr, 16); if (errno || num < 0 || num > 0xff) return 0; if (start_bus) *start_bus = num; buses = -num; if (endptr != sep2) { if (*endptr != '-') return 0; errno = 0; num = strtol(endptr+1, &endptr, 16); if (errno || endptr != sep2 || num < 0 || num > 0xff) return 0; buses = num - -buses + 1; if (buses <= 0) return 0; if (end_bus) *end_bus = num; } if (!isxdigit(*(sep2+1))) return 0; errno = 0; ullnum = strtoull(sep2+1, &endptr, 16); if (errno || (ullnum & 3)) return 0; if (addr) *addr = ullnum; start_addr = ullnum; if (endptr == addrs + addr_len) { if (buses <= 0) { buses = 0xff - -buses + 1; if (end_bus) *end_bus = 0xff; } if (start_addr + (unsigned)buses * 32 * 8 * 4096 < start_addr) return 0; if (length) *length = buses * 32 * 8 * 4096; } else { if (*endptr != '+' || !isxdigit(*(endptr+1))) return 0; errno = 0; ullnum = strtoull(endptr+1, &endptr, 16); if (errno || endptr != addrs + addr_len || (ullnum & 3) || ullnum > 256 * 32 * 8 * 4096) return 0; if (start_addr + ullnum < start_addr) return 0; if (buses > 0 && ullnum > (unsigned)buses * 32 * 8 * 4096) return 0; if (buses <= 0 && ullnum > (0xff - (unsigned)-buses + 1) * 32 * 8 * 4096) return 0; if (length) *length = ullnum; if (buses <= 0 && end_bus) *end_bus = -buses + (ullnum + 32 * 8 * 4096 - 1) / (32 * 8 * 4096); } return 1; } static int validate_addrs(const char *addrs) { if (!*addrs) return 1; while (addrs) if (!parse_next_addrs(addrs, &addrs, NULL, NULL, NULL, NULL, NULL)) return 0; return 1; } static int calculate_bus_addr(u8 start_bus, u64 start_addr, u32 total_length, u8 bus, u64 *addr, u32 *length) { u32 offset; offset = 32*8*4096 * (bus - start_bus); if (offset >= total_length) return 0; *addr = start_addr + offset; *length = total_length - offset; if (*length > 32*8*4096) *length = 32*8*4096; return 1; } static int get_bus_addr(struct acpi_mcfg *mcfg, const char *addrs, int domain, u8 bus, u64 *addr, u32 *length) { int cur_domain; u8 start_bus; u8 end_bus; u64 start_addr; u32 total_length; int i, count; if (mcfg) { count = get_mcfg_allocations_count(mcfg); for (i = 0; i < count; i++) { get_mcfg_allocation(mcfg, i, &cur_domain, &start_bus, &end_bus, &start_addr, &total_length); if (domain == cur_domain && bus >= start_bus && bus <= end_bus) return calculate_bus_addr(start_bus, start_addr, total_length, bus, addr, length); } return 0; } else { while (addrs) { if (!parse_next_addrs(addrs, &addrs, &cur_domain, &start_bus, &end_bus, &start_addr, &total_length)) return 0; if (domain == cur_domain && bus >= start_bus && bus <= end_bus) return calculate_bus_addr(start_bus, start_addr, total_length, bus, addr, length); } return 0; } } static void munmap_reg(struct pci_access *a) { struct ecam_access *eacc = a->backend_data; struct mmap_cache *cache = eacc->cache; struct physmem *physmem = eacc->physmem; long pagesize = eacc->pagesize; if (!cache) return; physmem_unmap(physmem, cache->map, cache->length + (cache->addr & (pagesize-1))); pci_mfree(cache); eacc->cache = NULL; } static int mmap_reg(struct pci_access *a, int w, int domain, u8 bus, u8 dev, u8 func, int pos, volatile void **reg) { struct ecam_access *eacc = a->backend_data; struct mmap_cache *cache = eacc->cache; struct physmem *physmem = eacc->physmem; long pagesize = eacc->pagesize; const char *addrs; void *map; u64 addr; u32 length; u32 offset; if (cache && cache->domain == domain && cache->bus == bus && !!cache->w == !!w) { map = cache->map; addr = cache->addr; length = cache->length; } else { addrs = pci_get_param(a, "ecam.addrs"); if (!get_bus_addr(eacc->mcfg, addrs, domain, bus, &addr, &length)) return 0; map = physmem_map(physmem, addr & ~(pagesize-1), length + (addr & (pagesize-1)), w); if (map == (void *)-1) return 0; if (cache) physmem_unmap(physmem, cache->map, cache->length + (cache->addr & (pagesize-1))); else cache = eacc->cache = pci_malloc(a, sizeof(*cache)); cache->map = map; cache->addr = addr; cache->length = length; cache->domain = domain; cache->bus = bus; cache->w = w; } /* * Enhanced Configuration Access Mechanism (ECAM) offset according to: * PCI Express Base Specification, Revision 5.0, Version 1.0, Section 7.2.2, Table 7-1, p. 677 */ offset = ((dev & 0x1f) << 15) | ((func & 0x7) << 12) | (pos & 0xfff); if (offset + 4 > length) return 0; *reg = (unsigned char *)map + (addr & (pagesize-1)) + offset; return 1; } static void ecam_config(struct pci_access *a) { physmem_init_config(a); pci_define_param(a, "ecam.acpimcfg", PCI_PATH_ACPI_MCFG, "Path to the ACPI MCFG table"); pci_define_param(a, "ecam.efisystab", PCI_PATH_EFI_SYSTAB, "Path to the EFI system table"); #if defined (__FreeBSD__) || defined (__DragonFly__) || defined(__NetBSD__) pci_define_param(a, "ecam.bsd", "1", "Use BSD kenv or sysctl to find ACPI MCFG table"); #endif #if defined(__amd64__) || defined(__i386__) pci_define_param(a, "ecam.x86bios", "1", "Scan x86 BIOS memory for ACPI MCFG table"); #endif pci_define_param(a, "ecam.addrs", "", "Physical addresses of memory mapped PCIe ECAM interface"); /* format: [domain:]start_bus[-end_bus]:start_addr[+length],... */ } static int ecam_detect(struct pci_access *a) { int use_addrs = 1, use_acpimcfg = 1, use_efisystab = 1, use_bsd = 1, use_x86bios = 1; const char *acpimcfg = pci_get_param(a, "ecam.acpimcfg"); const char *efisystab = pci_get_param(a, "ecam.efisystab"); #if defined (__FreeBSD__) || defined (__DragonFly__) || defined(__NetBSD__) const char *bsd = pci_get_param(a, "ecam.bsd"); #endif #if defined(__amd64__) || defined(__i386__) const char *x86bios = pci_get_param(a, "ecam.x86bios"); #endif const char *addrs = pci_get_param(a, "ecam.addrs"); struct ecam_access *eacc; glob_t mcfg_glob; int ret; if (!*addrs) { a->debug("ecam.addrs was not specified..."); use_addrs = 0; } if (acpimcfg[0]) { ret = glob(acpimcfg, GLOB_NOCHECK, NULL, &mcfg_glob); if (ret == 0) { if (access(mcfg_glob.gl_pathv[0], R_OK)) { a->debug("cannot access acpimcfg: %s: %s...", mcfg_glob.gl_pathv[0], strerror(errno)); use_acpimcfg = 0; } globfree(&mcfg_glob); } else { a->debug("glob(%s) failed: %d...", acpimcfg, ret); use_acpimcfg = 0; } } else use_acpimcfg = 0; if (!efisystab[0] || access(efisystab, R_OK)) { if (efisystab[0]) a->debug("cannot access efisystab: %s: %s...", efisystab, strerror(errno)); use_efisystab = 0; } #if defined (__FreeBSD__) || defined (__DragonFly__) || defined(__NetBSD__) if (strcmp(bsd, "0") == 0) { a->debug("not using BSD kenv/sysctl..."); use_bsd = 0; } #else use_bsd = 0; #endif #if defined(__amd64__) || defined(__i386__) if (strcmp(x86bios, "0") == 0) { a->debug("not using x86 BIOS..."); use_x86bios = 0; } #else use_x86bios = 0; #endif if (!use_addrs && !use_acpimcfg && !use_efisystab && !use_bsd && !use_x86bios) { a->debug("no ecam source provided"); return 0; } if (!validate_addrs(addrs)) { a->debug("ecam.addrs has invalid format %s", addrs); return 0; } if (physmem_access(a, 0)) { a->debug("cannot access physical memory: %s", strerror(errno)); return 0; } if (!use_addrs) { eacc = pci_malloc(a, sizeof(*eacc)); eacc->physmem = physmem_open(a, a->writeable); if (!eacc->physmem) { a->debug("cannot open physcal memory: %s.", strerror(errno)); pci_mfree(eacc); return 0; } eacc->pagesize = physmem_get_pagesize(eacc->physmem); if (eacc->pagesize <= 0) { a->debug("Cannot get page size: %s.", strerror(errno)); physmem_close(eacc->physmem); pci_mfree(eacc); return 0; } eacc->mcfg = NULL; eacc->cache = NULL; a->backend_data = eacc; eacc->mcfg = find_mcfg(a, acpimcfg, efisystab, use_bsd, use_x86bios); if (!eacc->mcfg) { physmem_close(eacc->physmem); pci_mfree(eacc); a->backend_data = NULL; return 0; } } if (use_addrs) a->debug("using with ecam addresses %s", addrs); else a->debug("using with%s%s%s%s%s%s", use_acpimcfg ? " acpimcfg=" : "", use_acpimcfg ? acpimcfg : "", use_efisystab ? " efisystab=" : "", use_efisystab ? efisystab : "", use_bsd ? " bsd" : "", use_x86bios ? " x86bios" : ""); return 1; } static void ecam_init(struct pci_access *a) { const char *acpimcfg = pci_get_param(a, "ecam.acpimcfg"); const char *efisystab = pci_get_param(a, "ecam.efisystab"); #if defined (__FreeBSD__) || defined (__DragonFly__) || defined(__NetBSD__) const char *bsd = pci_get_param(a, "ecam.bsd"); #endif #if defined(__amd64__) || defined(__i386__) const char *x86bios = pci_get_param(a, "ecam.x86bios"); #endif const char *addrs = pci_get_param(a, "ecam.addrs"); struct physmem *physmem = NULL; struct ecam_access *eacc = a->backend_data; long pagesize = 0; int use_bsd = 0; int use_x86bios = 0; int test_domain = 0; u8 test_bus = 0; volatile void *test_reg; if (!validate_addrs(addrs)) a->error("Option ecam.addrs has invalid address format \"%s\".", addrs); if (!eacc) { physmem = physmem_open(a, a->writeable); if (!physmem) a->error("Cannot open physcal memory: %s.", strerror(errno)); pagesize = physmem_get_pagesize(physmem); if (pagesize <= 0) a->error("Cannot get page size: %s.", strerror(errno)); eacc = pci_malloc(a, sizeof(*eacc)); eacc->mcfg = NULL; eacc->cache = NULL; eacc->physmem = physmem; eacc->pagesize = pagesize; a->backend_data = eacc; } if (!*addrs) { #if defined (__FreeBSD__) || defined (__DragonFly__) if (strcmp(bsd, "0") != 0) use_bsd = 1; #endif #if defined(__amd64__) || defined(__i386__) if (strcmp(x86bios, "0") != 0) use_x86bios = 1; #endif if (!eacc->mcfg) eacc->mcfg = find_mcfg(a, acpimcfg, efisystab, use_bsd, use_x86bios); if (!eacc->mcfg) a->error("Option ecam.addrs was not specified and ACPI MCFG table cannot be found."); } if (eacc->mcfg) get_mcfg_allocation(eacc->mcfg, 0, &test_domain, &test_bus, NULL, NULL, NULL); else parse_next_addrs(addrs, NULL, &test_domain, &test_bus, NULL, NULL, NULL); errno = 0; if (!mmap_reg(a, 0, test_domain, test_bus, 0, 0, 0, &test_reg)) a->error("Cannot map ecam region: %s.", errno ? strerror(errno) : "Unknown error"); } static void ecam_cleanup(struct pci_access *a) { struct ecam_access *eacc = a->backend_data; munmap_reg(a); physmem_close(eacc->physmem); pci_mfree(eacc->mcfg); pci_mfree(eacc); a->backend_data = NULL; } static void ecam_scan(struct pci_access *a) { const char *addrs = pci_get_param(a, "ecam.addrs"); struct ecam_access *eacc = a->backend_data; u32 *segments; int i, j, count; int domain; segments = pci_malloc(a, 0xFFFF/8); memset(segments, 0, 0xFFFF/8); if (eacc->mcfg) { count = get_mcfg_allocations_count(eacc->mcfg); for (i = 0; i < count; i++) segments[eacc->mcfg->allocations[i].pci_segment / 32] |= 1 << (eacc->mcfg->allocations[i].pci_segment % 32); } else { while (addrs) { if (parse_next_addrs(addrs, &addrs, &domain, NULL, NULL, NULL, NULL)) segments[domain / 32] |= 1 << (domain % 32); } } for (i = 0; i < 0xFFFF/32; i++) { if (!segments[i]) continue; for (j = 0; j < 32; j++) if (segments[i] & (1 << j)) pci_generic_scan_domain(a, 32*i + j); } pci_mfree(segments); } static int ecam_read(struct pci_dev *d, int pos, byte *buf, int len) { volatile void *reg; if (pos >= 4096) return 0; if (len != 1 && len != 2 && len != 4) return pci_generic_block_read(d, pos, buf, len); if (!mmap_reg(d->access, 0, d->domain, d->bus, d->dev, d->func, pos, ®)) return 0; switch (len) { case 1: buf[0] = physmem_readb(reg); break; case 2: ((u16 *) buf)[0] = physmem_readw(reg); break; case 4: ((u32 *) buf)[0] = physmem_readl(reg); break; } return 1; } static int ecam_write(struct pci_dev *d, int pos, byte *buf, int len) { volatile void *reg; if (pos >= 4096) return 0; if (len != 1 && len != 2 && len != 4) return pci_generic_block_read(d, pos, buf, len); if (!mmap_reg(d->access, 1, d->domain, d->bus, d->dev, d->func, pos, ®)) return 0; switch (len) { case 1: physmem_writeb(buf[0], reg); break; case 2: physmem_writew(((u16 *) buf)[0], reg); break; case 4: physmem_writel(((u32 *) buf)[0], reg); break; } return 1; } struct pci_methods pm_ecam = { .name = "ecam", .help = "Raw memory mapped access using PCIe ECAM interface", .config = ecam_config, .detect = ecam_detect, .init = ecam_init, .cleanup = ecam_cleanup, .scan = ecam_scan, .fill_info = pci_generic_fill_info, .read = ecam_read, .write = ecam_write, };