| /* |
| * probe_roms - scan for Adapter ROMS |
| * |
| * (based on linux-2.6:arch/x86/kernel/probe_roms_32.c) |
| * |
| * Copyright (C) 2008 Intel Corporation |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms and conditions of the GNU General Public License, |
| * version 2, as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License along with |
| * this program; if not, write to the Free Software Foundation, Inc., |
| * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #include "probe_roms.h" |
| #include "mdadm.h" |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <asm/types.h> |
| |
| static void *rom_mem = MAP_FAILED; |
| static int rom_fd = -1; |
| static const int rom_len = 0xf0000 - 0xc0000; /* option-rom memory region */ |
| static int _sigbus; |
| static unsigned long rom_align; |
| |
| static void roms_deinit(void); |
| static int roms_init(void); |
| |
| static void sigbus(int sig) |
| { |
| _sigbus = 1; |
| } |
| |
| static int probe_address8(const __u8 *ptr, __u8 *val) |
| { |
| int rc = 0; |
| |
| *val = *ptr; |
| if (_sigbus) |
| rc = -1; |
| _sigbus = 0; |
| |
| return rc; |
| } |
| |
| static int probe_address16(const __u16 *ptr, __u16 *val) |
| { |
| int rc = 0; |
| |
| *val = *ptr; |
| if (_sigbus) |
| rc = -1; |
| _sigbus = 0; |
| |
| return rc; |
| } |
| |
| void probe_roms_exit(void) |
| { |
| signal_s(SIGBUS, SIG_DFL); |
| |
| if (rom_fd >= 0) { |
| close(rom_fd); |
| rom_fd = -1; |
| } |
| if (rom_mem != MAP_FAILED) { |
| munmap(rom_mem, rom_len); |
| rom_mem = MAP_FAILED; |
| } |
| roms_deinit(); |
| } |
| |
| int probe_roms_init(unsigned long align) |
| { |
| int fd = -1; |
| int rc = 0; |
| |
| /* valid values are 2048 and 512. 512 is for PCI-3.0 compliant |
| * systems, or systems that do not have dangerous/legacy ISA |
| * devices. 2048 should always be safe |
| */ |
| if (align == 512 || align == 2048) |
| rom_align = align; |
| else |
| return -1; |
| |
| if (roms_init()) |
| return -1; |
| |
| if (signal_s(SIGBUS, sigbus) == SIG_ERR) |
| rc = -1; |
| if (rc == 0) { |
| fd = open("/dev/mem", O_RDONLY); |
| if (fd < 0) |
| rc = -1; |
| } |
| if (rc == 0) { |
| rom_mem = mmap(NULL, rom_len, PROT_READ, MAP_PRIVATE, fd, 0xc0000); |
| if (rom_mem == MAP_FAILED) |
| rc = -1; |
| } |
| |
| if (rc == 0) |
| rom_fd = fd; |
| else { |
| if (fd >= 0) |
| close(fd); |
| probe_roms_exit(); |
| } |
| return rc; |
| } |
| |
| /** |
| * isa_bus_to_virt - convert physical address to mmap'd region |
| * @addr - address to convert |
| * |
| * Only valid between a successful call to probe_roms_init and the |
| * corresponding probe_roms_exit |
| */ |
| static void *isa_bus_to_virt(unsigned long addr) |
| { |
| return rom_mem + (addr - 0xc0000); |
| } |
| |
| struct resource { |
| unsigned long start; |
| unsigned long end; |
| unsigned long data; |
| const char *name; |
| struct resource *next; |
| }; |
| |
| static struct resource system_rom_resource = { |
| .name = "System ROM", |
| .start = 0xf0000, |
| .data = 0, |
| .end = 0xfffff, |
| }; |
| |
| static struct resource extension_rom_resource = { |
| .name = "Extension ROM", |
| .start = 0xe0000, |
| .data = 0, |
| .end = 0xeffff, |
| }; |
| |
| static struct resource *adapter_rom_resources; |
| |
| static struct resource video_rom_resource = { |
| .name = "Video ROM", |
| .start = 0xc0000, |
| .data = 0, |
| .end = 0xc7fff, |
| }; |
| |
| static int roms_init(void) |
| { |
| adapter_rom_resources = malloc(sizeof(struct resource)); |
| if (adapter_rom_resources == NULL) |
| return 1; |
| adapter_rom_resources->name = "Adapter ROM"; |
| adapter_rom_resources->start = 0xc8000; |
| adapter_rom_resources->data = 0; |
| adapter_rom_resources->end = 0; |
| adapter_rom_resources->next = NULL; |
| return 0; |
| } |
| |
| static void roms_deinit(void) |
| { |
| struct resource *res; |
| |
| res = adapter_rom_resources; |
| while (res) { |
| struct resource *tmp = res; |
| |
| res = res->next; |
| free(tmp); |
| } |
| } |
| |
| #define ROMSIGNATURE 0xaa55 |
| |
| |
| static int romsignature(const unsigned char *rom) |
| { |
| const unsigned short * const ptr = (const unsigned short *)rom; |
| unsigned short sig = 0; |
| |
| return probe_address16(ptr, &sig) == 0 && sig == ROMSIGNATURE; |
| } |
| |
| static int romchecksum(const unsigned char *rom, unsigned long length) |
| { |
| unsigned char sum, c; |
| |
| for (sum = 0; length && probe_address8(rom++, &c) == 0; length--) |
| sum += c; |
| return !length && !sum; |
| } |
| |
| int scan_adapter_roms(scan_fn fn) |
| { |
| /* let scan_fn examing each of the adapter roms found by probe_roms */ |
| struct resource *res = adapter_rom_resources; |
| int found; |
| |
| if (rom_fd < 0) |
| return 0; |
| |
| found = 0; |
| while (res) { |
| if (res->start) { |
| found = fn(isa_bus_to_virt(res->start), |
| isa_bus_to_virt(res->end), |
| isa_bus_to_virt(res->data)); |
| if (found) |
| break; |
| } else |
| break; |
| res = res->next; |
| } |
| |
| return found; |
| } |
| |
| static unsigned long align(unsigned long addr, unsigned long alignment) |
| { |
| return (addr + alignment - 1) & ~(alignment - 1); |
| } |
| |
| void probe_roms(void) |
| { |
| const void *rom; |
| unsigned long start, length, upper; |
| unsigned char c; |
| struct resource *res = adapter_rom_resources; |
| __u16 val=0; |
| |
| if (rom_fd < 0) |
| return; |
| |
| /* video rom */ |
| upper = res->start; |
| for (start = video_rom_resource.start; start < upper; start += rom_align) { |
| rom = isa_bus_to_virt(start); |
| if (!romsignature(rom)) |
| continue; |
| |
| video_rom_resource.start = start; |
| |
| if (probe_address8(rom + 2, &c) != 0) |
| continue; |
| |
| /* 0 < length <= 0x7f * 512, historically */ |
| length = c * 512; |
| |
| /* if checksum okay, trust length byte */ |
| if (length && romchecksum(rom, length)) |
| video_rom_resource.end = start + length - 1; |
| break; |
| } |
| |
| start = align(video_rom_resource.end + 1, rom_align); |
| if (start < upper) |
| start = upper; |
| |
| /* system rom */ |
| upper = system_rom_resource.start; |
| |
| /* check for extension rom (ignore length byte!) */ |
| rom = isa_bus_to_virt(extension_rom_resource.start); |
| if (romsignature(rom)) { |
| length = extension_rom_resource.end - extension_rom_resource.start + 1; |
| if (romchecksum(rom, length)) |
| upper = extension_rom_resource.start; |
| } |
| |
| struct resource *prev_res = res; |
| /* check for adapter roms on 2k boundaries */ |
| for (; start < upper; start += rom_align) { |
| rom = isa_bus_to_virt(start); |
| if (!romsignature(rom)) |
| continue; |
| |
| if (probe_address8(rom + 2, &c) != 0) |
| continue; |
| |
| /* 0 < length <= 0x7f * 512, historically */ |
| length = c * 512; |
| |
| /* Retrieve 16-bit pointer to PCI Data Structure (offset 18h-19h) |
| * The data can be within 64KB forward of the first location |
| * of this code image. The pointer is in little-endian order |
| */ |
| |
| if (probe_address16(rom + 0x18, &val) != 0) |
| continue; |
| val = __le16_to_cpu(val); |
| |
| /* but accept any length that fits if checksum okay */ |
| if (!length || start + length > upper || !romchecksum(rom, length)) |
| continue; |
| |
| if (res == NULL) { |
| res = calloc(1, sizeof(struct resource)); |
| if (res == NULL) |
| return; |
| prev_res->next = res; |
| } |
| |
| res->start = start; |
| res->data = start + (unsigned long)val; |
| res->end = start + length - 1; |
| |
| start = res->end & ~(rom_align - 1); |
| prev_res = res; |
| res = res->next; |
| } |
| } |