/* * This file is part of the coreboot project. * * Copyright 2012 Google Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #define MAP_BYTES (1024*1024) #define IS_ENABLED(x) (defined (x) && (x)) #include "boot/coreboot_tables.h" typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; #include "cbmem.h" #include "timestamp.h" #define CBMEM_VERSION "1.1" /* verbose output? */ static int verbose = 0; #define debug(x...) if(verbose) printf(x) /* File handle used to access /dev/mem */ static int fd; /* * calculate ip checksum (16 bit quantities) on a passed in buffer. In case * the buffer length is odd last byte is excluded from the calculation */ static u16 ipchcksum(const void *addr, unsigned size) { const u16 *p = addr; unsigned i, n = size / 2; /* don't expect odd sized blocks */ u32 sum = 0; for (i = 0; i < n; i++) sum += p[i]; sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); sum = ~sum & 0xffff; return (u16) sum; } /* * Functions to map / unmap physical memory into virtual address space. These * functions always maps 1MB at a time and can only map one area at once. */ static void *mapped_virtual; static size_t mapped_size; static inline size_t size_to_mib(size_t sz) { return sz >> 20; } static void unmap_memory(void) { if (mapped_virtual == NULL) { fprintf(stderr, "Error unmapping memory\n"); return; } debug("Unmapping %zuMB of virtual memory at %p.\n", size_to_mib(mapped_size), mapped_virtual); munmap(mapped_virtual, mapped_size); mapped_virtual = NULL; mapped_size = 0; } static void *map_memory_size(u64 physical, size_t size) { void *v; off_t p; u64 page = getpagesize(); size_t padding; if (mapped_virtual != NULL) unmap_memory(); /* Mapped memory must be aligned to page size */ p = physical & ~(page - 1); padding = physical & (page-1); size += padding; debug("Mapping %zuMB of physical memory at 0x%jx.\n", size_to_mib(size), (intmax_t)p); v = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, p); if (v == MAP_FAILED) { fprintf(stderr, "Failed to mmap /dev/mem: %s\n", strerror(errno)); exit(1); } /* Remember what we actually mapped ... */ mapped_virtual = v; mapped_size = size; /* ... but return address to the physical memory that was requested */ if (padding) debug(" ... padding virtual address with 0x%zx bytes.\n", padding); v += padding; return v; } static void *map_memory(u64 physical) { return map_memory_size(physical, MAP_BYTES); } /* * Try finding the timestamp table and coreboot cbmem console starting from the * passed in memory offset. Could be called recursively in case a forwarding * entry is found. * * Returns pointer to a memory buffer containg the timestamp table or zero if * none found. */ static struct lb_cbmem_ref timestamps; static struct lb_cbmem_ref console; static struct lb_memory_range cbmem; /* This is a work-around for a nasty problem introduced by initially having * pointer sized entries in the lb_cbmem_ref structures. This caused problems * on 64bit x86 systems because coreboot is 32bit on those systems. * When the problem was found, it was corrected, but there are a lot of * systems out there with a firmware that does not produce the right * lb_cbmem_ref structure. Hence we try to autocorrect this issue here. */ static struct lb_cbmem_ref parse_cbmem_ref(struct lb_cbmem_ref *cbmem_ref) { struct lb_cbmem_ref ret; ret = *cbmem_ref; if (cbmem_ref->size < sizeof(*cbmem_ref)) ret.cbmem_addr = (uint32_t)ret.cbmem_addr; debug(" cbmem_addr = %" PRIx64 "\n", ret.cbmem_addr); return ret; } static int parse_cbtable(u64 address) { int i, found = 0; void *buf; debug("Looking for coreboot table at %" PRIx64 "\n", address); buf = map_memory(address); /* look at every 16 bytes within 4K of the base */ for (i = 0; i < 0x1000; i += 0x10) { struct lb_header *lbh; struct lb_record* lbr_p; void *lbtable; int j; lbh = (struct lb_header *)(buf + i); if (memcmp(lbh->signature, "LBIO", sizeof(lbh->signature)) || !lbh->header_bytes || ipchcksum(lbh, sizeof(*lbh))) { continue; } lbtable = buf + i + lbh->header_bytes; if (ipchcksum(lbtable, lbh->table_bytes) != lbh->table_checksum) { debug("Signature found, but wrong checksum.\n"); continue; } found = 1; debug("Found!\n"); for (j = 0; j < lbh->table_bytes; j += lbr_p->size) { lbr_p = (struct lb_record*) ((char *)lbtable + j); debug(" coreboot table entry 0x%02x\n", lbr_p->tag); switch (lbr_p->tag) { case LB_TAG_MEMORY: { int i = 0; debug(" Found memory map.\n"); struct lb_memory *memory = (struct lb_memory *)lbr_p; while ((char *)&memory->map[i] < ((char *)lbr_p + lbr_p->size)) { if (memory->map[i].type == LB_MEM_TABLE) { debug(" LB_MEM_TABLE found.\n"); /* The last one found is CBMEM */ cbmem = memory->map[i]; } i++; } continue; } case LB_TAG_TIMESTAMPS: { debug(" Found timestamp table.\n"); timestamps = parse_cbmem_ref((struct lb_cbmem_ref *) lbr_p); continue; } case LB_TAG_CBMEM_CONSOLE: { debug(" Found cbmem console.\n"); console = parse_cbmem_ref((struct lb_cbmem_ref *) lbr_p); continue; } case LB_TAG_FORWARD: { /* * This is a forwarding entry - repeat the * search at the new address. */ struct lb_forward lbf_p = *(struct lb_forward *) lbr_p; debug(" Found forwarding entry.\n"); unmap_memory(); return parse_cbtable(lbf_p.forward); } default: break; } } } unmap_memory(); return found; } #if defined(__i386__) || defined(__x86_64__) /* * read CPU frequency from a sysfs file, return an frequency in Kilohertz as * an int or exit on any error. */ static u64 get_cpu_freq_KHz(void) { FILE *cpuf; char freqs[100]; int size; char *endp; u64 rv; const char* freq_file = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"; cpuf = fopen(freq_file, "r"); if (!cpuf) { fprintf(stderr, "Could not open %s: %s\n", freq_file, strerror(errno)); exit(1); } memset(freqs, 0, sizeof(freqs)); size = fread(freqs, 1, sizeof(freqs), cpuf); if (!size || (size == sizeof(freqs))) { fprintf(stderr, "Wrong number of bytes(%d) read from %s\n", size, freq_file); exit(1); } fclose(cpuf); rv = strtoull(freqs, &endp, 10); if (*endp == '\0' || *endp == '\n') return rv; fprintf(stderr, "Wrong formatted value ^%s^ read from %s\n", freqs, freq_file); exit(1); } /* On x86 platforms timestamps are stored * in CPU cycles (from rdtsc). Hence the * timestamp divider is the CPU frequency * in MHz. */ u64 arch_convert_raw_ts_entry(u64 ts) { static u64 cpu_freq_mhz = 0; if (!cpu_freq_mhz) cpu_freq_mhz = get_cpu_freq_KHz() / 1000; return ts / cpu_freq_mhz; } #else /* On non-x86 platforms the timestamp entries * are not in clock cycles but in usecs */ u64 arch_convert_raw_ts_entry(u64 ts) { return ts; } #endif /* * Print an integer in 'normalized' form - with commas separating every three * decimal orders. The 'comma' parameter indicates if a comma is needed after * the value is printed. */ static void print_norm(u64 v, int comma) { int first_triple = 1; if (v > 1000) { /* print the higher order sections first */ print_norm(v / 1000, 1); first_triple = 0; } if (first_triple) printf("%d", (u32)(v % 1000)); else printf("%3.3d", (u32)(v % 1000)); if (comma) printf(","); } enum additional_timestamp_id { // Depthcharge entry IDs start at 1000. TS_DC_START = 1000, TS_RO_PARAMS_INIT = 1001, TS_RO_VB_INIT = 1002, TS_RO_VB_SELECT_FIRMWARE = 1003, TS_RO_VB_SELECT_AND_LOAD_KERNEL = 1004, TS_RW_VB_SELECT_AND_LOAD_KERNEL = 1010, TS_VB_SELECT_AND_LOAD_KERNEL = 1020, TS_CROSSYSTEM_DATA = 1100, TS_START_KERNEL = 1101 }; static const struct timestamp_id_to_name { u32 id; const char *name; } timestamp_ids[] = { { TS_START_ROMSTAGE, "start of rom stage" }, { TS_BEFORE_INITRAM, "before ram initialization" }, { TS_AFTER_INITRAM, "after ram initialization" }, { TS_END_ROMSTAGE, "end of romstage" }, { TS_START_VBOOT, "start of verified boot" }, { TS_END_VBOOT, "end of verified boot" }, { TS_START_COPYRAM, "start of copying ram stage" }, { TS_END_COPYRAM, "end of copying ram stage" }, { TS_START_RAMSTAGE, "start of ramstage" }, { TS_DEVICE_ENUMERATE, "device enumeration" }, { TS_DEVICE_CONFIGURE, "device configuration" }, { TS_DEVICE_ENABLE, "device enable" }, { TS_DEVICE_INITIALIZE, "device initialization" }, { TS_DEVICE_DONE, "device setup done" }, { TS_CBMEM_POST, "cbmem post" }, { TS_WRITE_TABLES, "write tables" }, { TS_LOAD_PAYLOAD, "load payload" }, { TS_ACPI_WAKE_JUMP, "ACPI wake jump" }, { TS_SELFBOOT_JUMP, "selfboot jump" }, { TS_DC_START, "depthcharge start" }, { TS_RO_PARAMS_INIT, "RO parameter init" }, { TS_RO_VB_INIT, "RO vboot init" }, { TS_RO_VB_SELECT_FIRMWARE, "RO vboot select firmware" }, { TS_RO_VB_SELECT_AND_LOAD_KERNEL, "RO vboot select&load kernel" }, { TS_RW_VB_SELECT_AND_LOAD_KERNEL, "RW vboot select&load kernel" }, { TS_VB_SELECT_AND_LOAD_KERNEL, "vboot select&load kernel" }, { TS_CROSSYSTEM_DATA, "crossystem data" }, { TS_START_KERNEL, "start kernel" } }; void timestamp_print_entry(uint32_t id, uint64_t stamp, uint64_t prev_stamp) { int i; const char *name; name = ""; for (i = 0; i < ARRAY_SIZE(timestamp_ids); i++) { if (timestamp_ids[i].id == id) { name = timestamp_ids[i].name; break; } } printf("%4d:", id); printf("%-30s", name); print_norm(arch_convert_raw_ts_entry(stamp), 0); if (prev_stamp) { printf(" ("); print_norm(arch_convert_raw_ts_entry(stamp - prev_stamp), 0); printf(")"); } printf("\n"); } /* dump the timestamp table */ static void dump_timestamps(void) { int i; struct timestamp_table *tst_p; if (timestamps.tag != LB_TAG_TIMESTAMPS) { fprintf(stderr, "No timestamps found in coreboot table.\n"); return; } tst_p = (struct timestamp_table *) map_memory((unsigned long)timestamps.cbmem_addr); printf("%d entries total:\n\n", tst_p->num_entries); for (i = 0; i < tst_p->num_entries; i++) { const struct timestamp_entry *tse_p = tst_p->entries + i; timestamp_print_entry(tse_p->entry_id, tse_p->entry_stamp, i ? tse_p[-1].entry_stamp : 0); } unmap_memory(); } /* dump the cbmem console */ static void dump_console(void) { void *console_p; char *console_c; uint32_t size; uint32_t cursor; if (console.tag != LB_TAG_CBMEM_CONSOLE) { fprintf(stderr, "No console found in coreboot table.\n"); return; } console_p = map_memory((unsigned long)console.cbmem_addr); /* The in-memory format of the console area is: * u32 size * u32 cursor * char console[size] * Hence we have to add 8 to get to the actual console string. */ size = ((uint32_t *)console_p)[0]; cursor = ((uint32_t *)console_p)[1]; /* Cursor continues to go on even after no more data fits in * the buffer but the data is dropped in this case. */ if (size > cursor) size = cursor; console_c = malloc(size + 1); if (!console_c) { fprintf(stderr, "Not enough memory for console.\n"); exit(1); } console_p = map_memory_size((unsigned long)console.cbmem_addr, size + sizeof(size) + sizeof(cursor)); memcpy(console_c, console_p + 8, size); console_c[size] = 0; console_c[cursor] = 0; printf("%s\n", console_c); if (size < cursor) printf("%d %s lost\n", cursor - size, (cursor - size) == 1 ? "byte":"bytes"); free(console_c); unmap_memory(); } static void hexdump(unsigned long memory, int length) { int i; uint8_t *m; int all_zero = 0; m = map_memory((intptr_t)memory); if (length > MAP_BYTES) { printf("Truncating hex dump from %d to %d bytes\n\n", length, MAP_BYTES); length = MAP_BYTES; } for (i = 0; i < length; i += 16) { int j; all_zero++; for (j = 0; j < 16; j++) { if(m[i+j] != 0) { all_zero = 0; break; } } if (all_zero < 2) { printf("%08lx:", memory + i); for (j = 0; j < 16; j++) printf(" %02x", m[i+j]); printf(" "); for (j = 0; j < 16; j++) printf("%c", isprint(m[i+j]) ? m[i+j] : '.'); printf("\n"); } else if (all_zero == 2) { printf("...\n"); } } unmap_memory(); } static void dump_cbmem_hex(void) { if (cbmem.type != LB_MEM_TABLE) { fprintf(stderr, "No coreboot CBMEM area found!\n"); return; } hexdump(unpack_lb64(cbmem.start), unpack_lb64(cbmem.size)); } /* The root region is at least DYN_CBMEM_ALIGN_SIZE . */ #define DYN_CBMEM_ALIGN_SIZE (4096) #define ROOT_MIN_SIZE DYN_CBMEM_ALIGN_SIZE #define CBMEM_POINTER_MAGIC 0xc0389479 #define CBMEM_ENTRY_MAGIC ~(CBMEM_POINTER_MAGIC) struct cbmem_root_pointer { uint32_t magic; uint32_t root; } __attribute__((packed)); struct dynamic_cbmem_entry { uint32_t magic; uint32_t start; uint32_t size; uint32_t id; } __attribute__((packed)); struct cbmem_root { uint32_t max_entries; uint32_t num_entries; uint32_t locked; uint32_t size; struct dynamic_cbmem_entry entries[0]; } __attribute__((packed)); #define CBMEM_MAGIC 0x434f5245 #define MAX_CBMEM_ENTRIES 16 struct cbmem_entry { uint32_t magic; uint32_t id; uint64_t base; uint64_t size; } __attribute__((packed)); static const struct cbmem_id_to_name cbmem_ids[] = { CBMEM_ID_TO_NAME_TABLE }; void cbmem_print_entry(int n, uint32_t id, uint64_t base, uint64_t size) { int i; const char *name; name = NULL; for (i = 0; i < ARRAY_SIZE(cbmem_ids); i++) { if (cbmem_ids[i].id == id) { name = cbmem_ids[i].name; break; } } printf("%2d. ", n); if (name == NULL) printf("%08x ", id); else printf("%s", name); printf(" %08" PRIx64 " ", base); printf(" %08" PRIx64 "\n", size); } static void dump_static_cbmem_toc(struct cbmem_entry *entries) { int i; printf("CBMEM table of contents:\n"); printf(" ID START LENGTH\n"); for (i=0; imax_entries, root->num_entries, root->locked, root->size); printf("CBMEM table of contents:\n"); printf(" ID START LENGTH\n"); for (i = 0; i < root->num_entries; i++) { if(root->entries[i].magic != CBMEM_ENTRY_MAGIC) break; cbmem_print_entry(i, root->entries[i].id, root->entries[i].start, root->entries[i].size); } } static void dump_cbmem_toc(void) { uint64_t start; void *cbmem_area; struct cbmem_entry *entries; if (cbmem.type != LB_MEM_TABLE) { fprintf(stderr, "No coreboot CBMEM area found!\n"); return; } start = unpack_lb64(cbmem.start); cbmem_area = map_memory(start); entries = (struct cbmem_entry *)cbmem_area; if (entries[0].magic == CBMEM_MAGIC) { dump_static_cbmem_toc(entries); } else { uint64_t rootptr; rootptr = unpack_lb64(cbmem.start) + unpack_lb64(cbmem.size); rootptr &= ~(DYN_CBMEM_ALIGN_SIZE - 1); rootptr -= sizeof(struct cbmem_root_pointer); unmap_memory(); struct cbmem_root_pointer *r = (struct cbmem_root_pointer *)map_memory(rootptr); if (r->magic == CBMEM_POINTER_MAGIC) { struct cbmem_root *root; uint64_t rootaddr = r->root; unmap_memory(); /* Note that this only works because our default mmap * size is 1MiB which happens to be larger than the * root entry size which is default to be 4KiB. */ root = (struct cbmem_root *)map_memory(rootaddr); dump_dynamic_cbmem_toc(root); } else fprintf(stderr, "No valid coreboot CBMEM root pointer found.\n"); } unmap_memory(); } #define COVERAGE_MAGIC 0x584d4153 struct file { uint32_t magic; uint32_t next; uint32_t filename; uint32_t data; int offset; int len; }; static int mkpath(char *path, mode_t mode) { assert (path && *path); char *p; for (p = strchr(path+1, '/'); p; p = strchr(p + 1, '/')) { *p = '\0'; if (mkdir(path, mode) == -1) { if (errno != EEXIST) { *p = '/'; return -1; } } *p = '/'; } return 0; } static void dump_coverage(void) { int i, found = 0; uint64_t start; struct cbmem_entry *entries; void *coverage; unsigned long phys_offset; #define phys_to_virt(x) ((void *)(unsigned long)(x) + phys_offset) if (cbmem.type != LB_MEM_TABLE) { fprintf(stderr, "No coreboot table area found!\n"); return; } start = unpack_lb64(cbmem.start); entries = (struct cbmem_entry *)map_memory(start); for (i=0; imagic == COVERAGE_MAGIC) { FILE *f; char *filename; debug(" -> %s\n", (char *)phys_to_virt(file->filename)); filename = strdup((char *)phys_to_virt(file->filename)); if (mkpath(filename, 0755) == -1) { perror("Directory for coverage data could " "not be created"); exit(1); } f = fopen(filename, "wb"); if (!f) { printf("Could not open %s: %s\n", filename, strerror(errno)); exit(1); } if (fwrite((void *)phys_to_virt(file->data), file->len, 1, f) != 1) { printf("Could not write to %s: %s\n", filename, strerror(errno)); exit(1); } fclose(f); free(filename); if (file->next) file = (struct file *)phys_to_virt(file->next); else file = NULL; } unmap_memory(); } static void print_version(void) { printf("cbmem v%s -- ", CBMEM_VERSION); printf("Copyright (C) 2012 The ChromiumOS Authors. All rights reserved.\n\n"); printf( "This program is free software: you can redistribute it and/or modify\n" "it under the terms of the GNU General Public License as published by\n" "the Free Software Foundation, version 2 of the License.\n\n" "This program is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License for more details.\n\n" "You should have received a copy of the GNU General Public License\n" "along with this program. If not, see .\n\n"); } static void print_usage(const char *name) { printf("usage: %s [-cCltxVvh?]\n", name); printf("\n" " -c | --console: print cbmem console\n" " -C | --coverage: dump coverage information\n" " -l | --list: print cbmem table of contents\n" " -x | --hexdump: print hexdump of cbmem area\n" " -t | --timestamps: print timestamp information\n" " -V | --verbose: verbose (debugging) output\n" " -v | --version: print the version\n" " -h | --help: print this help\n" "\n"); exit(1); } int main(int argc, char** argv) { int print_defaults = 1; int print_console = 0; int print_coverage = 0; int print_list = 0; int print_hexdump = 0; int print_timestamps = 0; int opt, option_index = 0; static struct option long_options[] = { {"console", 0, 0, 'c'}, {"coverage", 0, 0, 'C'}, {"list", 0, 0, 'l'}, {"timestamps", 0, 0, 't'}, {"hexdump", 0, 0, 'x'}, {"verbose", 0, 0, 'V'}, {"version", 0, 0, 'v'}, {"help", 0, 0, 'h'}, {0, 0, 0, 0} }; while ((opt = getopt_long(argc, argv, "cCltxVvh?", long_options, &option_index)) != EOF) { switch (opt) { case 'c': print_console = 1; print_defaults = 0; break; case 'C': print_coverage = 1; print_defaults = 0; break; case 'l': print_list = 1; print_defaults = 0; break; case 'x': print_hexdump = 1; print_defaults = 0; break; case 't': print_timestamps = 1; print_defaults = 0; break; case 'V': verbose = 1; break; case 'v': print_version(); exit(0); break; case 'h': case '?': default: print_usage(argv[0]); exit(0); break; } } fd = open("/dev/mem", O_RDONLY, 0); if (fd < 0) { fprintf(stderr, "Failed to gain memory access: %s\n", strerror(errno)); return 1; } #ifdef __arm__ int dt_fd; uint32_t cbtable_base; dt_fd = open("/proc/device-tree/firmware/coreboot/coreboot-table", O_RDONLY, 0); if (dt_fd < 0) { fprintf(stderr, "Failed to open device tree node: %s\n", strerror(errno)); return 1; } if (read(dt_fd, &cbtable_base, 4) != 4) { fprintf(stderr, "Failed to read device tree node: %s\n", strerror(errno)); return 1; } close(dt_fd); parse_cbtable(ntohl(cbtable_base)); #else int j; static const int possible_base_addresses[] = { 0, 0xf0000 }; /* Find and parse coreboot table */ for (j = 0; j < ARRAY_SIZE(possible_base_addresses); j++) { if (parse_cbtable(possible_base_addresses[j])) break; } #endif if (print_console) dump_console(); if (print_coverage) dump_coverage(); if (print_list) dump_cbmem_toc(); if (print_hexdump) dump_cbmem_hex(); if (print_defaults || print_timestamps) dump_timestamps(); close(fd); return 0; }