diff options
author | Michael Brown <mcb30@etherboot.org> | 2005-03-08 18:53:11 +0000 |
---|---|---|
committer | Michael Brown <mcb30@etherboot.org> | 2005-03-08 18:53:11 +0000 |
commit | 3d6123e69ab879c72ff489afc5bf93ef0b7a94ce (patch) | |
tree | 9f3277569153a550fa8d81ebd61bd88f266eb8da /src/firmware | |
download | ipxe-3d6123e69ab879c72ff489afc5bf93ef0b7a94ce.zip ipxe-3d6123e69ab879c72ff489afc5bf93ef0b7a94ce.tar.gz ipxe-3d6123e69ab879c72ff489afc5bf93ef0b7a94ce.tar.bz2 |
Initial revision
Diffstat (limited to 'src/firmware')
-rw-r--r-- | src/firmware/linuxbios/linuxbios.c | 386 | ||||
-rw-r--r-- | src/firmware/linuxbios/linuxbios_tables.h | 183 |
2 files changed, 569 insertions, 0 deletions
diff --git a/src/firmware/linuxbios/linuxbios.c b/src/firmware/linuxbios/linuxbios.c new file mode 100644 index 0000000..0ad66dd --- /dev/null +++ b/src/firmware/linuxbios/linuxbios.c @@ -0,0 +1,386 @@ +#ifdef LINUXBIOS + +#include "etherboot.h" +#include "dev.h" +#include "linuxbios_tables.h" + +struct meminfo meminfo; +static int lb_failsafe = 1; +static unsigned lb_boot[MAX_BOOT_ENTRIES]; +static unsigned lb_boot_index; +static struct cmos_entries lb_countdown; +static struct cmos_checksum lb_checksum; + +#undef DEBUG_LINUXBIOS + +static void set_base_mem_k(struct meminfo *info, unsigned mem_k) +{ + if ((mem_k <= 640) && (info->basememsize <= mem_k)) { + info->basememsize = mem_k; + } +} +static void set_high_mem_k(struct meminfo *info, unsigned mem_k) +{ + /* Shave off a megabyte before playing */ + if (mem_k < 1024) { + return; + } + mem_k -= 1024; + if (info->memsize <= mem_k) { + info->memsize = mem_k; + } +} + +#define for_each_lbrec(head, rec) \ + for(rec = (struct lb_record *)(((char *)head) + sizeof(*head)); \ + (((char *)rec) < (((char *)head) + sizeof(*head) + head->table_bytes)) && \ + (rec->size >= 1) && \ + ((((char *)rec) + rec->size) <= (((char *)head) + sizeof(*head) + head->table_bytes)); \ + rec = (struct lb_record *)(((char *)rec) + rec->size)) + + +#define for_each_crec(tbl, rec) \ + for(rec = (struct lb_record *)(((char *)tbl) + tbl->header_length); \ + (((char *)rec) < (((char *)tbl) + tbl->size)) && \ + (rec->size >= 1) && \ + ((((char *)rec) + rec->size) <= (((char *)tbl) + tbl->size)); \ + rec = (struct lb_record *)(((char *)rec) + rec->size)) + + + +static void read_lb_memory( + struct meminfo *info, struct lb_memory *mem) +{ + int i; + int entries; + entries = (mem->size - sizeof(*mem))/sizeof(mem->map[0]); + for(i = 0; (i < entries); i++) { + if (info->map_count < E820MAX) { + info->map[info->map_count].addr = mem->map[i].start; + info->map[info->map_count].size = mem->map[i].size; + info->map[info->map_count].type = mem->map[i].type; + info->map_count++; + } + switch(mem->map[i].type) { + case LB_MEM_RAM: + { + unsigned long long end; + unsigned long mem_k; + end = mem->map[i].start + mem->map[i].size; +#if defined(DEBUG_LINUXBIOS) + printf("lb: %X%X - %X%X (ram)\n", + (unsigned long)(mem->map[i].start >>32), + (unsigned long)(mem->map[i].start & 0xFFFFFFFF), + (unsigned long)(end >> 32), + (unsigned long)(end & 0xFFFFFFFF)); +#endif /* DEBUG_LINUXBIOS */ + end >>= 10; + mem_k = end; + if (end & 0xFFFFFFFF00000000ULL) { + mem_k = 0xFFFFFFFF; + } + set_base_mem_k(info, mem_k); + set_high_mem_k(info, mem_k); + break; + } + case LB_MEM_RESERVED: + default: +#if defined(DEBUG_LINUXBIOS) + { + unsigned long long end; + end = mem->map[i].start + mem->map[i].size; + printf("lb: %X%X - %X%X (reserved)\n", + (unsigned long)(mem->map[i].start >>32), + (unsigned long)(mem->map[i].start & 0xFFFFFFFF), + (unsigned long)(end >> 32), + (unsigned long)(end & 0xFFFFFFFF)); + } +#endif /* DEBUG_LINUXBIOS */ + break; + } + } +} + +static unsigned cmos_read(unsigned offset, unsigned int size) +{ + unsigned addr, old_addr; + unsigned value; + + addr = offset/8; + + old_addr = inb(0x70); + outb(addr | (old_addr &0x80), 0x70); + value = inb(0x71); + outb(old_addr, 0x70); + + value >>= offset & 0x7; + value &= ((1 << size) - 1); + + return value; +} + +static unsigned cmos_read_checksum(void) +{ + unsigned sum = + (cmos_read(lb_checksum.location, 8) << 8) | + cmos_read(lb_checksum.location +8, 8); + return sum & 0xffff; +} + +static int cmos_valid(void) +{ + unsigned i; + unsigned sum, old_sum; + sum = 0; + if ((lb_checksum.tag != LB_TAG_OPTION_CHECKSUM) || + (lb_checksum.type != CHECKSUM_PCBIOS) || + (lb_checksum.size != sizeof(lb_checksum))) { + return 0; + } + for(i = lb_checksum.range_start; i <= lb_checksum.range_end; i+= 8) { + sum += cmos_read(i, 8); + } + sum = (~sum)&0x0ffff; + old_sum = cmos_read_checksum(); + return sum == old_sum; +} + +static void cmos_write(unsigned offset, unsigned int size, unsigned setting) +{ + unsigned addr, old_addr; + unsigned value, mask, shift; + unsigned sum; + + addr = offset/8; + + shift = offset & 0x7; + mask = ((1 << size) - 1) << shift; + setting = (setting << shift) & mask; + + old_addr = inb(0x70); + sum = cmos_read_checksum(); + sum = (~sum) & 0xffff; + + outb(addr | (old_addr &0x80), 0x70); + value = inb(0x71); + sum -= value; + value &= ~mask; + value |= setting; + sum += value; + outb(value, 0x71); + + sum = (~sum) & 0x0ffff; + outb((lb_checksum.location/8) | (old_addr & 0x80), 0x70); + outb((sum >> 8) & 0xff, 0x71); + outb(((lb_checksum.location +8)/8) | (old_addr & 0x80), 0x70); + outb(sum & 0xff, 0x71); + + outb(old_addr, 0x70); + + return; +} + +static void read_linuxbios_values(struct meminfo *info, + struct lb_header *head) +{ + /* Read linuxbios tables... */ + struct lb_record *rec; + memset(lb_boot, 0, sizeof(lb_boot)); + for_each_lbrec(head, rec) { + switch(rec->tag) { + case LB_TAG_MEMORY: + { + struct lb_memory *mem; + mem = (struct lb_memory *) rec; + read_lb_memory(info, mem); + break; + } + case LB_TAG_CMOS_OPTION_TABLE: + { + struct cmos_option_table *tbl; + struct lb_record *crec; + struct cmos_entries *entry; + tbl = (struct cmos_option_table *)rec; + for_each_crec(tbl, crec) { + /* Pick off the checksum entry and keep it */ + if (crec->tag == LB_TAG_OPTION_CHECKSUM) { + memcpy(&lb_checksum, crec, sizeof(lb_checksum)); + continue; + } + if (crec->tag != LB_TAG_OPTION) + continue; + entry = (struct cmos_entries *)crec; + if ((entry->bit < 112) || (entry->bit > 1020)) + continue; + /* See if LinuxBIOS came up in fallback or normal mode */ + if (memcmp(entry->name, "last_boot", 10) == 0) { + lb_failsafe = cmos_read(entry->bit, entry->length) == 0; + continue; + } + /* Find where the boot countdown is */ + if (memcmp(entry->name, "boot_countdown", 15) == 0) { + lb_countdown = *entry; + continue; + } + /* Find the default boot index */ + if (memcmp(entry->name, "boot_index", 11) == 0) { + lb_boot_index = cmos_read(entry->bit, entry->length); + continue; + } + /* Now filter for the boot order options */ + if (entry->length != 4) + continue; + if (entry->config != 'e') + continue; + if (memcmp(entry->name, "boot_first", 11) == 0) { + lb_boot[0] = cmos_read(entry->bit, entry->length); + } + else if (memcmp(entry->name, "boot_second", 12) == 0) { + lb_boot[1] = cmos_read(entry->bit, entry->length); + } + else if (memcmp(entry->name, "boot_third", 11) == 0) { + lb_boot[2] = cmos_read(entry->bit, entry->length); + } + } + break; + } + default: + break; + }; + } +} + + + +static unsigned long count_lb_records(void *start, unsigned long length) +{ + struct lb_record *rec; + void *end; + unsigned long count; + count = 0; + end = ((char *)start) + length; + for(rec = start; ((void *)rec < end) && + ((signed long)rec->size <= (end - (void *)rec)); + rec = (void *)(((char *)rec) + rec->size)) { + count++; + } + return count; +} + +static int find_lb_table(void *start, void *end, struct lb_header **result) +{ + unsigned char *ptr; + + /* For now be stupid.... */ + for(ptr = start; virt_to_phys(ptr) < virt_to_phys(end); ptr += 16) { + struct lb_header *head = (struct lb_header *)ptr; + if ( (head->signature[0] != 'L') || + (head->signature[1] != 'B') || + (head->signature[2] != 'I') || + (head->signature[3] != 'O')) { + continue; + } + if (head->header_bytes != sizeof(*head)) + continue; +#if defined(DEBUG_LINUXBIOS) + printf("Found canidate at: %X\n", virt_to_phys(head)); +#endif + if (ipchksum((uint16_t *)head, sizeof(*head)) != 0) + continue; +#if defined(DEBUG_LINUXBIOS) + printf("header checksum o.k.\n"); +#endif + if (ipchksum((uint16_t *)(ptr + sizeof(*head)), head->table_bytes) != + head->table_checksum) { + continue; + } +#if defined(DEBUG_LINUXBIOS) + printf("table checksum o.k.\n"); +#endif + if (count_lb_records(ptr + sizeof(*head), head->table_bytes) != + head->table_entries) { + continue; + } +#if defined(DEBUG_LINUXBIOS) + printf("record count o.k.\n"); +#endif + *result = head; + return 1; + }; + return 0; +} + +void get_memsizes(void) +{ + struct lb_header *lb_table; + int found; +#if defined(DEBUG_LINUXBIOS) + printf("\nSearching for linuxbios tables...\n"); +#endif /* DEBUG_LINUXBIOS */ + found = 0; + meminfo.basememsize = 0; + meminfo.memsize = 0; + meminfo.map_count = 0; + /* This code is specific to linuxBIOS but could + * concievably be extended to work under a normal bios. + * but size is important... + */ + if (!found) { + found = find_lb_table(phys_to_virt(0x00000), phys_to_virt(0x01000), &lb_table); + } + if (!found) { + found = find_lb_table(phys_to_virt(0xf0000), phys_to_virt(0x100000), &lb_table); + } + if (found) { +#if defined (DEBUG_LINUXBIOS) + printf("Found LinuxBIOS table at: %X\n", virt_to_phys(lb_table)); +#endif + read_linuxbios_values(&meminfo, lb_table); + } + +#if defined(DEBUG_LINUXBIOS) + printf("base_mem_k = %d high_mem_k = %d\n", + meminfo.basememsize, meminfo.memsize); +#endif /* DEBUG_LINUXBIOS */ + +} + +unsigned long get_boot_order(unsigned long order, unsigned *index) +{ + static int again; + static int checksum_valid; + static unsigned boot_count; + int i; + + if (!lb_failsafe && !again) { + /* Decrement the boot countdown the first time through */ + checksum_valid = cmos_valid(); + boot_count = cmos_read(lb_countdown.bit, lb_countdown.length); + if (boot_count > 0) { + cmos_write(lb_countdown.bit, lb_countdown.length, boot_count -1); + } + again = 1; + } + if (lb_failsafe || !checksum_valid) { + /* When LinuxBIOS is in failsafe mode, or there is an + * invalid cmos checksum ignore all cmos options + */ + return order; + } + for(i = 0; i < MAX_BOOT_ENTRIES; i++) { + unsigned long boot; + boot = order >> (i*BOOT_BITS) & BOOT_MASK; + boot = lb_boot[i] & BOOT_TYPE_MASK; + if (boot >= BOOT_NOTHING) { + boot = BOOT_NOTHING; + } + if (boot_count == 0) { + boot |= BOOT_FAILSAFE; + } + order &= ~(BOOT_MASK << (i * BOOT_BITS)); + order |= (boot << (i*BOOT_BITS)); + } + *index = lb_boot_index; + return order; +} +#endif /* LINUXBIOS */ diff --git a/src/firmware/linuxbios/linuxbios_tables.h b/src/firmware/linuxbios/linuxbios_tables.h new file mode 100644 index 0000000..ee21520 --- /dev/null +++ b/src/firmware/linuxbios/linuxbios_tables.h @@ -0,0 +1,183 @@ +#ifndef LINUXBIOS_TABLES_H +#define LINUXBIOS_TABLES_H + +#include "stdint.h" + +/* The linuxbios table information is for conveying information + * from the firmware to the loaded OS image. Primarily this + * is expected to be information that cannot be discovered by + * other means, such as quering the hardware directly. + * + * All of the information should be Position Independent Data. + * That is it should be safe to relocated any of the information + * without it's meaning/correctnes changing. For table that + * can reasonably be used on multiple architectures the data + * size should be fixed. This should ease the transition between + * 32 bit and 64 bit architectures etc. + * + * The completeness test for the information in this table is: + * - Can all of the hardware be detected? + * - Are the per motherboard constants available? + * - Is there enough to allow a kernel to run that was written before + * a particular motherboard is constructed? (Assuming the kernel + * has drivers for all of the hardware but it does not have + * assumptions on how the hardware is connected together). + * + * With this test it should be straight forward to determine if a + * table entry is required or not. This should remove much of the + * long term compatibility burden as table entries which are + * irrelevant or have been replaced by better alternatives may be + * dropped. Of course it is polite and expidite to include extra + * table entries and be backwards compatible, but it is not required. + */ + + +struct lb_header +{ + uint8_t signature[4]; /* LBIO */ + uint32_t header_bytes; + uint32_t header_checksum; + uint32_t table_bytes; + uint32_t table_checksum; + uint32_t table_entries; +}; + +/* Every entry in the boot enviroment list will correspond to a boot + * info record. Encoding both type and size. The type is obviously + * so you can tell what it is. The size allows you to skip that + * boot enviroment record if you don't know what it easy. This allows + * forward compatibility with records not yet defined. + */ +struct lb_record { + uint32_t tag; /* tag ID */ + uint32_t size; /* size of record (in bytes) */ +}; + +#define LB_TAG_UNUSED 0x0000 + +#define LB_TAG_MEMORY 0x0001 + +struct lb_memory_range { + uint64_t start; + uint64_t size; + uint32_t type; +#define LB_MEM_RAM 1 /* Memory anyone can use */ +#define LB_MEM_RESERVED 2 /* Don't use this memory region */ +#define LB_MEM_TABLE 16 /* Ram configuration tables are kept in */ + +}; + +struct lb_memory { + uint32_t tag; + uint32_t size; + struct lb_memory_range map[0]; +}; + +#define LB_TAG_HWRPB 0x0002 +struct lb_hwrpb { + uint32_t tag; + uint32_t size; + uint64_t hwrpb; +}; + +#define LB_TAG_MAINBOARD 0x0003 +struct lb_mainboard { + uint32_t tag; + uint32_t size; + uint8_t vendor_idx; + uint8_t part_number_idx; + uint8_t strings[0]; +}; + +#define LB_TAG_VERSION 0x0004 +#define LB_TAG_EXTRA_VERSION 0x0005 +#define LB_TAG_BUILD 0x0006 +#define LB_TAG_COMPILE_TIME 0x0007 +#define LB_TAG_COMPILE_BY 0x0008 +#define LB_TAG_COMPILE_HOST 0x0009 +#define LB_TAG_COMPILE_DOMAIN 0x000a +#define LB_TAG_COMPILER 0x000b +#define LB_TAG_LINKER 0x000c +#define LB_TAG_ASSEMBLER 0x000d +struct lb_string { + uint32_t tag; + uint32_t size; + uint8_t string[0]; +}; + +/* The following structures are for the cmos definitions table */ +#define LB_TAG_CMOS_OPTION_TABLE 200 +/* cmos header record */ +struct cmos_option_table { + uint32_t tag; /* CMOS definitions table type */ + uint32_t size; /* size of the entire table */ + uint32_t header_length; /* length of header */ +}; + +/* cmos entry record + This record is variable length. The name field may be + shorter than CMOS_MAX_NAME_LENGTH. The entry may start + anywhere in the byte, but can not span bytes unless it + starts at the beginning of the byte and the length is + fills complete bytes. +*/ +#define LB_TAG_OPTION 201 +struct cmos_entries { + uint32_t tag; /* entry type */ + uint32_t size; /* length of this record */ + uint32_t bit; /* starting bit from start of image */ + uint32_t length; /* length of field in bits */ + uint32_t config; /* e=enumeration, h=hex, r=reserved */ + uint32_t config_id; /* a number linking to an enumeration record */ +#define CMOS_MAX_NAME_LENGTH 32 + uint8_t name[CMOS_MAX_NAME_LENGTH]; /* name of entry in ascii, + variable length int aligned */ +}; + + +/* cmos enumerations record + This record is variable length. The text field may be + shorter than CMOS_MAX_TEXT_LENGTH. +*/ +#define LB_TAG_OPTION_ENUM 202 +struct cmos_enums { + uint32_t tag; /* enumeration type */ + uint32_t size; /* length of this record */ + uint32_t config_id; /* a number identifying the config id */ + uint32_t value; /* the value associated with the text */ +#define CMOS_MAX_TEXT_LENGTH 32 + uint8_t text[CMOS_MAX_TEXT_LENGTH]; /* enum description in ascii, + variable length int aligned */ +}; + +/* cmos defaults record + This record contains default settings for the cmos ram. +*/ +#define LB_TAG_OPTION_DEFAULTS 203 +struct cmos_defaults { + uint32_t tag; /* default type */ + uint32_t size; /* length of this record */ + uint32_t name_length; /* length of the following name field */ + uint8_t name[CMOS_MAX_NAME_LENGTH]; /* name identifying the default */ +#define CMOS_IMAGE_BUFFER_SIZE 128 + uint8_t default_set[CMOS_IMAGE_BUFFER_SIZE]; /* default settings */ +}; + +#define LB_TAG_OPTION_CHECKSUM 204 +struct cmos_checksum { + uint32_t tag; + uint32_t size; + /* In practice everything is byte aligned, but things are measured + * in bits to be consistent. + */ + uint32_t range_start; /* First bit that is checksummed (byte aligned) */ + uint32_t range_end; /* Last bit that is checksummed (byte aligned) */ + uint32_t location; /* First bit of the checksum (byte aligned) */ + uint32_t type; /* Checksum algorithm that is used */ +#define CHECKSUM_NONE 0 +#define CHECKSUM_PCBIOS 1 +}; + + + +#endif /* LINUXBIOS_TABLES_H */ |