diff options
Diffstat (limited to 'util/cbmem/cbmem.c')
-rw-r--r-- | util/cbmem/cbmem.c | 274 |
1 files changed, 274 insertions, 0 deletions
diff --git a/util/cbmem/cbmem.c b/util/cbmem/cbmem.c new file mode 100644 index 0000000000..ff411db248 --- /dev/null +++ b/util/cbmem/cbmem.c @@ -0,0 +1,274 @@ +/* + * This file is part of the coreboot project. + * + * Copyright (C) 2012 The ChromiumOS Authors. All rights reserved. + * + * 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 <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "stdlib.h" +#include "boot/coreboot_tables.h" + +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +#include "cbmem.h" +#include "timestamp.h" + +/* File descriptor used to access /dev/mem */ +static FILE* 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; +} + +/* + * Starting at 'offset' read 'size' bytes from the previously opened /dev/mem + * into the 'buffer'. + * + * Return zero on success or exit on any error. + */ +static int readmem(void* buffer, u32 offset, int size) +{ + if (fseek(fd, offset, SEEK_SET)) { + fprintf(stderr, "fseek failed(%d) for offset %d\n", + errno, offset); + exit(1); + } + if (fread(buffer, 1, size, fd) != size) { + fprintf(stderr, "failed (%d) to read %d bytes at 0x%x\n", + errno, size, offset); + exit(1); + } + return 0; +} + +/* + * Try finding the timestamp table 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 const struct timestamp_table *find_tstamps(u64 address) +{ + int i; + + /* look at every 16 bytes within 4K of the base */ + for (i = 0; i < 0x1000; i += 0x10) { + void *buf; + struct lb_header lbh; + struct lb_record* lbr_p; + int j; + + readmem(&lbh, address + i, sizeof(lbh)); + if (memcmp(lbh.signature, "LBIO", sizeof(lbh.signature)) || + !lbh.header_bytes || + ipchcksum(&lbh, sizeof(lbh))) + continue; + + /* good lb_header is found, try reading the table */ + buf = malloc(lbh.table_bytes); + if (!buf) { + fprintf(stderr, "failed to allocate %d bytes\n", + lbh.table_bytes); + exit(1); + } + + readmem(buf, address + i + lbh.header_bytes, lbh.table_bytes); + if (ipchcksum(buf, lbh.table_bytes) != + lbh.table_checksum) { + /* False positive or table corrupted... */ + free(buf); + continue; + } + + for (j = 0; j < lbh.table_bytes; j += lbr_p->size) { + /* look for the timestamp table */ + lbr_p = (struct lb_record*) ((char *)buf + j); + switch (lbr_p->tag) { + case LB_TAG_TIMESTAMPS: { + struct lb_cbmem_ref *cbmr_p = + (struct lb_cbmem_ref *) lbr_p; + int new_size; + struct timestamp_table *tst_p; + u32 stamp_addr = (u32) + ((u64)cbmr_p->cbmem_addr); + + readmem(buf, stamp_addr, + sizeof(struct timestamp_table)); + tst_p = (struct timestamp_table *) buf; + new_size = sizeof(struct timestamp_table) + + tst_p->num_entries * + sizeof(struct timestamp_entry); + buf = realloc(buf, new_size); + if (!buf) { + fprintf(stderr, + "failed to reallocate %d bytes\n", + new_size); + exit(1); + } + readmem(buf, stamp_addr, new_size); + return buf; + } + 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; + + free(buf); + return find_tstamps(lbf_p->forward); + } + default: + break; + } + + } + } + return 0; +} + +/* + * 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() +{ + 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\n", freq_file); + 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); +} + +/* + * 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(","); +} + +/* dump the timestamp table */ +static void dump_timestamps(const struct timestamp_table *tst_p) +{ + int i; + u64 cpu_freq_MHz = get_cpu_freq_KHz() / 1000; + + 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; + + printf("%4d:", tse_p->entry_id); + print_norm(tse_p->entry_stamp / cpu_freq_MHz, 0); + if (i) { + printf(" ("); + print_norm((tse_p->entry_stamp - + tse_p[-1].entry_stamp) / + cpu_freq_MHz, 0); + printf(")"); + } + printf("\n"); + } +} + + +int main(int argc, char** argv) +{ + int j; + static const int possible_base_addresses[] = {0, 0xf0000}; + + fd = fopen("/dev/mem", "r"); + if (!fd) { + printf("failed to gain memory access\n"); + return 1; + } + + for (j = 0; j < ARRAY_SIZE(possible_base_addresses); j++) { + const struct timestamp_table * tst_p = + find_tstamps(possible_base_addresses[j]); + + if (tst_p) { + dump_timestamps(tst_p); + free((void*)tst_p); + break; + } + } + + fclose(fd); + return 0; +} |