/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * This file contains the functionality that mimics the boot operations * on SPARC systems or the old boot.bin/multiboot programs on x86 systems. * The x86 kernel now does everything on its own. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "acpi_fw.h" static int have_console = 0; /* set once primitive console is initialized */ static char *boot_args = ""; /* * Debugging macros */ static uint_t kbm_debug = 0; #define DBG_MSG(s) { if (kbm_debug) bop_printf(NULL, "%s", s); } #define DBG(x) { if (kbm_debug) \ bop_printf(NULL, "%s is %" PRIx64 "\n", #x, (uint64_t)(x)); \ } #define PUT_STRING(s) { \ char *cp; \ for (cp = (s); *cp; ++cp) \ bcons_putchar(*cp); \ } struct xboot_info *xbootp; /* boot info from "glue" code in low memory */ bootops_t bootop; /* simple bootops we'll pass on to kernel */ struct bsys_mem bm; static uintptr_t next_virt; /* next available virtual address */ static paddr_t next_phys; /* next available physical address from dboot */ static paddr_t high_phys = -(paddr_t)1; /* last used physical address */ /* * buffer for vsnprintf for console I/O */ #define BUFFERSIZE 256 static char buffer[BUFFERSIZE]; /* * stuff to store/report/manipulate boot property settings. */ typedef struct bootprop { struct bootprop *bp_next; char *bp_name; uint_t bp_vlen; char *bp_value; } bootprop_t; static bootprop_t *bprops = NULL; static char *curr_page = NULL; /* ptr to avail bprop memory */ static int curr_space = 0; /* amount of memory at curr_page */ /* * some allocator statistics */ static ulong_t total_bop_alloc_scratch = 0; static ulong_t total_bop_alloc_kernel = 0; static void build_firmware_properties(void); static int early_allocation = 1; /* * Allocate aligned physical memory at boot time. This allocator allocates * from the highest possible addresses. This avoids exhausting memory that * would be useful for DMA buffers. */ paddr_t do_bop_phys_alloc(uint64_t size, uint64_t align) { paddr_t pa = 0; paddr_t start; paddr_t end; struct memlist *ml = (struct memlist *)xbootp->bi_phys_install; /* * Be careful if high memory usage is limited in startup.c * Since there are holes in the low part of the physical address * space we can treat physmem as a pfn (not just a pgcnt) and * get a conservative upper limit. */ if (physmem != 0 && high_phys > pfn_to_pa(physmem)) high_phys = pfn_to_pa(physmem); /* * find the lowest or highest available memory in physinstalled */ size = P2ROUNDUP(size, align); for (; ml; ml = ml->next) { start = P2ROUNDUP(ml->address, align); end = P2ALIGN(ml->address + ml->size, align); if (start < next_phys) start = P2ROUNDUP(next_phys, align); if (end > high_phys) end = P2ALIGN(high_phys, align); if (end <= start) continue; if (end - start < size) continue; /* * Early allocations need to use low memory, since * physmem might be further limited by bootenv.rc */ if (early_allocation) { if (pa == 0 || start < pa) pa = start; } else { if (end - size > pa) pa = end - size; } } if (pa != 0) { if (early_allocation) next_phys = pa + size; else high_phys = pa; return (pa); } panic("do_bop_phys_alloc(0x%" PRIx64 ", 0x%" PRIx64 ") Out of memory\n", size, align); /*NOTREACHED*/ } static uintptr_t alloc_vaddr(size_t size, paddr_t align) { uintptr_t rv; next_virt = P2ROUNDUP(next_virt, (uintptr_t)align); rv = (uintptr_t)next_virt; next_virt += size; return (rv); } /* * Allocate virtual memory. The size is always rounded up to a multiple * of base pagesize. */ /*ARGSUSED*/ static caddr_t do_bsys_alloc(bootops_t *bop, caddr_t virthint, size_t size, int align) { paddr_t a = align; /* same type as pa for masking */ uint_t pgsize; paddr_t pa; uintptr_t va; ssize_t s; /* the aligned size */ uint_t level; uint_t is_kernel = (virthint != 0); if (a < MMU_PAGESIZE) a = MMU_PAGESIZE; else if (!ISP2(a)) prom_panic("do_bsys_alloc() incorrect alignment"); size = P2ROUNDUP(size, MMU_PAGESIZE); /* * Use the next aligned virtual address if we weren't given one. */ if (virthint == NULL) { virthint = (caddr_t)alloc_vaddr(size, a); total_bop_alloc_scratch += size; } else { total_bop_alloc_kernel += size; } /* * allocate the physical memory */ pa = do_bop_phys_alloc(size, a); /* * Add the mappings to the page tables, try large pages first. */ va = (uintptr_t)virthint; s = size; level = 1; pgsize = xbootp->bi_use_pae ? TWO_MEG : FOUR_MEG; if (xbootp->bi_use_largepage && a == pgsize) { while (IS_P2ALIGNED(pa, pgsize) && IS_P2ALIGNED(va, pgsize) && s >= pgsize) { kbm_map(va, pa, level, is_kernel); va += pgsize; pa += pgsize; s -= pgsize; } } /* * Map remaining pages use small mappings */ level = 0; pgsize = MMU_PAGESIZE; while (s > 0) { kbm_map(va, pa, level, is_kernel); va += pgsize; pa += pgsize; s -= pgsize; } return (virthint); } /* * Free virtual memory - we'll just ignore these. */ /*ARGSUSED*/ static void do_bsys_free(bootops_t *bop, caddr_t virt, size_t size) { bop_printf(NULL, "do_bsys_free(virt=0x%p, size=0x%lx) ignored\n", (void *)virt, size); } /* * Old interface */ /*ARGSUSED*/ static caddr_t do_bsys_ealloc( bootops_t *bop, caddr_t virthint, size_t size, int align, int flags) { prom_panic("unsupported call to BOP_EALLOC()\n"); return (0); } static void bsetprop(char *name, int nlen, void *value, int vlen) { uint_t size; uint_t need_size; bootprop_t *b; /* * align the size to 16 byte boundary */ size = sizeof (bootprop_t) + nlen + 1 + vlen; size = (size + 0xf) & ~0xf; if (size > curr_space) { need_size = (size + (MMU_PAGEOFFSET)) & MMU_PAGEMASK; curr_page = do_bsys_alloc(NULL, 0, need_size, MMU_PAGESIZE); curr_space = need_size; } /* * use a bootprop_t at curr_page and link into list */ b = (bootprop_t *)curr_page; curr_page += sizeof (bootprop_t); curr_space -= sizeof (bootprop_t); b->bp_next = bprops; bprops = b; /* * follow by name and ending zero byte */ b->bp_name = curr_page; bcopy(name, curr_page, nlen); curr_page += nlen; *curr_page++ = 0; curr_space -= nlen + 1; /* * copy in value, but no ending zero byte */ b->bp_value = curr_page; b->bp_vlen = vlen; if (vlen > 0) { bcopy(value, curr_page, vlen); curr_page += vlen; curr_space -= vlen; } /* * align new values of curr_page, curr_space */ while (curr_space & 0xf) { ++curr_page; --curr_space; } } static void bsetprops(char *name, char *value) { bsetprop(name, strlen(name), value, strlen(value) + 1); } static void bsetprop64(char *name, uint64_t value) { bsetprop(name, strlen(name), (void *)&value, sizeof (value)); } static void bsetpropsi(char *name, int value) { char prop_val[32]; (void) snprintf(prop_val, sizeof (prop_val), "%d", value); bsetprops(name, prop_val); } /* * to find the size of the buffer to allocate */ /*ARGSUSED*/ int do_bsys_getproplen(bootops_t *bop, char *name) { bootprop_t *b; for (b = bprops; b; b = b->bp_next) { if (strcmp(name, b->bp_name) != 0) continue; return (b->bp_vlen); } return (-1); } /* * get the value associated with this name */ /*ARGSUSED*/ int do_bsys_getprop(bootops_t *bop, char *name, void *value) { bootprop_t *b; for (b = bprops; b; b = b->bp_next) { if (strcmp(name, b->bp_name) != 0) continue; bcopy(b->bp_value, value, b->bp_vlen); return (0); } return (-1); } /* * get the name of the next property in succession from the standalone */ /*ARGSUSED*/ static char * do_bsys_nextprop(bootops_t *bop, char *name) { bootprop_t *b; /* * A null name is a special signal for the 1st boot property */ if (name == NULL || strlen(name) == 0) { if (bprops == NULL) return (NULL); return (bprops->bp_name); } for (b = bprops; b; b = b->bp_next) { if (name != b->bp_name) continue; b = b->bp_next; if (b == NULL) return (NULL); return (b->bp_name); } return (NULL); } /* * Parse numeric value from a string. Understands decimal, hex, octal, - and ~ */ static int parse_value(char *p, uint64_t *retval) { int adjust = 0; uint64_t tmp = 0; int digit; int radix = 10; *retval = 0; if (*p == '-' || *p == '~') adjust = *p++; if (*p == '0') { ++p; if (*p == 0) return (0); if (*p == 'x' || *p == 'X') { radix = 16; ++p; } else { radix = 8; ++p; } } while (*p) { if ('0' <= *p && *p <= '9') digit = *p - '0'; else if ('a' <= *p && *p <= 'f') digit = 10 + *p - 'a'; else if ('A' <= *p && *p <= 'F') digit = 10 + *p - 'A'; else return (-1); if (digit >= radix) return (-1); tmp = tmp * radix + digit; ++p; } if (adjust == '-') tmp = -tmp; else if (adjust == '~') tmp = ~tmp; *retval = tmp; return (0); } /* * 2nd part of building the table of boot properties. This includes: * - values from /boot/solaris/bootenv.rc (ie. eeprom(1m) values) * * lines look like one of: * ^$ * ^# comment till end of line * setprop name 'value' * setprop name value * setprop name "value" * * we do single character I/O since this is really just looking at memory */ void boot_prop_finish(void) { int fd; char *line; int c; int bytes_read; char *name; int n_len; char *value; int v_len; char *inputdev; /* these override the comand line if serial ports */ char *outputdev; char *consoledev; uint64_t lvalue; DBG_MSG("Opening /boot/solaris/bootenv.rc\n"); fd = BRD_OPEN(bfs_ops, "/boot/solaris/bootenv.rc", 0); DBG(fd); line = do_bsys_alloc(NULL, NULL, MMU_PAGESIZE, MMU_PAGESIZE); while (fd >= 0) { /* * get a line */ for (c = 0; ; ++c) { bytes_read = BRD_READ(bfs_ops, fd, line + c, 1); if (bytes_read == 0) { if (c == 0) goto done; break; } if (line[c] == '\n') break; } line[c] = 0; /* * ignore comment lines */ c = 0; while (ISSPACE(line[c])) ++c; if (line[c] == '#' || line[c] == 0) continue; /* * must have "setprop " or "setprop\t" */ if (strncmp(line + c, "setprop ", 8) != 0 && strncmp(line + c, "setprop\t", 8) != 0) continue; c += 8; while (ISSPACE(line[c])) ++c; if (line[c] == 0) continue; /* * gather up the property name */ name = line + c; n_len = 0; while (line[c] && !ISSPACE(line[c])) ++n_len, ++c; /* * gather up the value, if any */ value = ""; v_len = 0; while (ISSPACE(line[c])) ++c; if (line[c] != 0) { value = line + c; while (line[c] && !ISSPACE(line[c])) ++v_len, ++c; } if (v_len >= 2 && value[0] == value[v_len - 1] && (value[0] == '\'' || value[0] == '"')) { ++value; v_len -= 2; } name[n_len] = 0; if (v_len > 0) value[v_len] = 0; else continue; /* * ignore "boot-file" property, it's now meaningless */ if (strcmp(name, "boot-file") == 0) continue; if (strcmp(name, "boot-args") == 0 && strlen(boot_args) > 0) continue; /* * If a property was explicitly set on the command line * it will override a setting in bootenv.rc */ if (do_bsys_getproplen(NULL, name) > 0) continue; bsetprop(name, n_len, value, v_len + 1); } done: if (fd >= 0) BRD_CLOSE(bfs_ops, fd); /* * Check if we have to limit the boot time allocator */ if (do_bsys_getproplen(NULL, "physmem") != -1 && do_bsys_getprop(NULL, "physmem", line) >= 0 && parse_value(line, &lvalue) != -1) { if (0 < lvalue && (lvalue < physmem || physmem == 0)) { physmem = (pgcnt_t)lvalue; DBG(physmem); } } early_allocation = 0; /* * check to see if we have to override the default value of the console */ inputdev = line; v_len = do_bsys_getproplen(NULL, "input-device"); if (v_len > 0) (void) do_bsys_getprop(NULL, "input-device", inputdev); else v_len = 0; inputdev[v_len] = 0; outputdev = inputdev + v_len + 1; v_len = do_bsys_getproplen(NULL, "output-device"); if (v_len > 0) (void) do_bsys_getprop(NULL, "output-device", outputdev); else v_len = 0; outputdev[v_len] = 0; consoledev = outputdev + v_len + 1; v_len = do_bsys_getproplen(NULL, "console"); if (v_len > 0) (void) do_bsys_getprop(NULL, "console", consoledev); else v_len = 0; consoledev[v_len] = 0; bcons_init2(inputdev, outputdev, consoledev); if (strstr((char *)xbootp->bi_cmdline, "prom_debug") || kbm_debug) { value = line; bop_printf(NULL, "\nBoot properties:\n"); name = ""; while ((name = do_bsys_nextprop(NULL, name)) != NULL) { bop_printf(NULL, "\t0x%p %s = ", (void *)name, name); (void) do_bsys_getprop(NULL, name, value); v_len = do_bsys_getproplen(NULL, name); bop_printf(NULL, "len=%d ", v_len); value[v_len] = 0; bop_printf(NULL, "%s\n", value); } } } /* * print formatted output */ /*PRINTFLIKE2*/ /*ARGSUSED*/ void bop_printf(bootops_t *bop, char *fmt, ...) { va_list ap; if (have_console == 0) return; va_start(ap, fmt); (void) vsnprintf(buffer, BUFFERSIZE, fmt, ap); va_end(ap); PUT_STRING(buffer); } /* * Another panic() variant; this one can be used even earlier during boot than * prom_panic(). */ /*PRINTFLIKE1*/ void bop_panic(char *fmt, ...) { va_list ap; va_start(ap, fmt); bop_printf(NULL, fmt, ap); va_end(ap); bop_printf(NULL, "\nPress any key to reboot.\n"); (void) bcons_getchar(); bop_printf(NULL, "Resetting...\n"); pc_reset(); } /* * Do a real mode interrupt BIOS call */ typedef struct bios_regs { unsigned short ax, bx, cx, dx, si, di, bp, es, ds; } bios_regs_t; typedef int (*bios_func_t)(int, bios_regs_t *); /*ARGSUSED*/ static void do_bsys_doint(bootops_t *bop, int intnum, struct bop_regs *rp) { static int firsttime = 1; bios_func_t bios_func = (bios_func_t)(void *)(uintptr_t)0x5000; bios_regs_t br; /* * The first time we do this, we have to copy the pre-packaged * low memory bios call code image into place. */ if (firsttime) { extern char bios_image[]; extern uint32_t bios_size; bcopy(bios_image, (void *)bios_func, bios_size); firsttime = 0; } br.ax = rp->eax.word.ax; br.bx = rp->ebx.word.bx; br.cx = rp->ecx.word.cx; br.dx = rp->edx.word.dx; br.bp = rp->ebp.word.bp; br.si = rp->esi.word.si; br.di = rp->edi.word.di; br.ds = rp->ds; br.es = rp->es; DBG_MSG("Doing BIOS call..."); rp->eflags = bios_func(intnum, &br); DBG_MSG("done\n"); rp->eax.word.ax = br.ax; rp->ebx.word.bx = br.bx; rp->ecx.word.cx = br.cx; rp->edx.word.dx = br.dx; rp->ebp.word.bp = br.bp; rp->esi.word.si = br.si; rp->edi.word.di = br.di; rp->ds = br.ds; rp->es = br.es; } static struct boot_syscalls bop_sysp = { bcons_getchar, bcons_putchar, bcons_ischar, }; static char *whoami; #define BUFLEN 64 static void setup_rarp_props(struct sol_netinfo *sip) { char buf[BUFLEN]; /* to hold ip/mac addrs */ uint8_t *val; val = (uint8_t *)&sip->sn_ciaddr; (void) snprintf(buf, BUFLEN, "%d.%d.%d.%d", val[0], val[1], val[2], val[3]); bsetprops(BP_HOST_IP, buf); val = (uint8_t *)&sip->sn_siaddr; (void) snprintf(buf, BUFLEN, "%d.%d.%d.%d", val[0], val[1], val[2], val[3]); bsetprops(BP_SERVER_IP, buf); if (sip->sn_giaddr != 0) { val = (uint8_t *)&sip->sn_giaddr; (void) snprintf(buf, BUFLEN, "%d.%d.%d.%d", val[0], val[1], val[2], val[3]); bsetprops(BP_ROUTER_IP, buf); } if (sip->sn_netmask != 0) { val = (uint8_t *)&sip->sn_netmask; (void) snprintf(buf, BUFLEN, "%d.%d.%d.%d", val[0], val[1], val[2], val[3]); bsetprops(BP_SUBNET_MASK, buf); } if (sip->sn_mactype != 4 || sip->sn_maclen != 6) { bop_printf(NULL, "unsupported mac type %d, mac len %d\n", sip->sn_mactype, sip->sn_maclen); } else { val = sip->sn_macaddr; (void) snprintf(buf, BUFLEN, "%x:%x:%x:%x:%x:%x", val[0], val[1], val[2], val[3], val[4], val[5]); bsetprops(BP_BOOT_MAC, buf); } } /* * 1st pass at building the table of boot properties. This includes: * - values set on the command line: -B a=x,b=y,c=z .... * - known values we just compute (ie. from xbootp) * - values from /boot/solaris/bootenv.rc (ie. eeprom(1m) values) * * the grub command line looked like: * kernel boot-file [-B prop=value[,prop=value]...] [boot-args] * * whoami is the same as boot-file */ static void build_boot_properties(void) { char *name; int name_len; char *value; int value_len; static int stdout_val = 0; struct boot_modules *bm; char *propbuf; int quoted = 0; int boot_arg_len; uchar_t boot_device; char str[3]; multiboot_info_t *mbi; int netboot; struct sol_netinfo *sip; /* * These have to be done first, so that kobj_mount_root() works */ DBG_MSG("Building boot properties\n"); propbuf = do_bsys_alloc(NULL, NULL, MMU_PAGESIZE, 0); DBG((uintptr_t)propbuf); if (xbootp->bi_module_cnt > 0) { bm = xbootp->bi_modules; bsetprop64("ramdisk_start", (uint64_t)(uintptr_t)bm->bm_addr); bsetprop64("ramdisk_end", (uint64_t)(uintptr_t)bm->bm_addr + bm->bm_size); } DBG_MSG("Parsing command line for boot properties\n"); value = xbootp->bi_cmdline; /* * allocate memory to collect boot_args into */ boot_arg_len = strlen(xbootp->bi_cmdline) + 1; boot_args = do_bsys_alloc(NULL, NULL, boot_arg_len, MMU_PAGESIZE); boot_args[0] = 0; boot_arg_len = 0; while (ISSPACE(*value)) ++value; /* * value now points at the boot-file */ value_len = 0; while (value[value_len] && !ISSPACE(value[value_len])) ++value_len; if (value_len > 0) { whoami = propbuf; bcopy(value, whoami, value_len); whoami[value_len] = 0; bsetprops("boot-file", whoami); /* * strip leading path stuff from whoami, so running from * PXE/miniroot makes sense. */ if (strstr(whoami, "/platform/") != NULL) whoami = strstr(whoami, "/platform/"); bsetprops("whoami", whoami); } /* * Values forcibly set boot properties on the command line via -B. * Allow use of quotes in values. Other stuff goes on kernel * command line. */ name = value + value_len; while (*name != 0) { /* * anything not " -B" is copied to the command line */ if (!ISSPACE(name[0]) || name[1] != '-' || name[2] != 'B') { boot_args[boot_arg_len++] = *name; boot_args[boot_arg_len] = 0; ++name; continue; } /* * skip the " -B" and following white space */ name += 3; while (ISSPACE(*name)) ++name; while (*name && !ISSPACE(*name)) { value = strstr(name, "="); if (value == NULL) break; name_len = value - name; ++value; value_len = 0; quoted = 0; for (; ; ++value_len) { if (!value[value_len]) break; /* * is this value quoted? */ if (value_len == 0 && (value[0] == '\'' || value[0] == '"')) { quoted = value[0]; ++value_len; } /* * In the quote accept any character, * but look for ending quote. */ if (quoted) { if (value[value_len] == quoted) quoted = 0; continue; } /* * a comma or white space ends the value */ if (value[value_len] == ',' || ISSPACE(value[value_len])) break; } if (value_len == 0) { bsetprop(name, name_len, "true", 5); } else { char *v = value; int l = value_len; if (v[0] == v[l - 1] && (v[0] == '\'' || v[0] == '"')) { ++v; l -= 2; } bcopy(v, propbuf, l); propbuf[l] = '\0'; bsetprop(name, name_len, propbuf, l + 1); } name = value + value_len; while (*name == ',') ++name; } } /* * set boot-args property */ bsetprops("boot-args", boot_args); /* * set the BIOS boot device from GRUB */ netboot = 0; mbi = xbootp->bi_mb_info; if (mbi != NULL && mbi->flags & 0x2) { boot_device = mbi->boot_device >> 24; if (boot_device == 0x20) netboot++; str[0] = (boot_device >> 4) + '0'; str[1] = (boot_device & 0xf) + '0'; str[2] = 0; bsetprops("bios-boot-device", str); } else { netboot = 1; } /* * In the netboot case, drives_info is overloaded with the dhcp ack. * This is not multiboot compliant and requires special pxegrub! */ if (netboot && mbi->drives_length != 0) { sip = (struct sol_netinfo *)(uintptr_t)mbi->drives_addr; if (sip->sn_infotype == SN_TYPE_BOOTP) bsetprop("bootp-response", sizeof ("bootp-response"), (void *)(uintptr_t)mbi->drives_addr, mbi->drives_length); else if (sip->sn_infotype == SN_TYPE_BOOTP) setup_rarp_props(sip); } bsetprop("stdout", strlen("stdout"), &stdout_val, sizeof (stdout_val)); /* * more conjured up values for made up things.... */ bsetprops("mfg-name", "i86pc"); bsetprops("impl-arch-name", "i86pc"); /* * Build firmware-provided system properties */ build_firmware_properties(); /* * Find out what these are: * - cpuid_feature_ecx_include * - cpuid_feature_ecx_exclude * - cpuid_feature_edx_include * - cpuid_feature_edx_exclude * * Find out what these are in multiboot: * - bootp-response * - netdev-path * - fstype */ } /* * Install a temporary IDT that lets us catch errors in the boot time code. * We shouldn't get any faults at all while this is installed, so we'll * just generate a traceback and exit. */ #ifdef __amd64 static const int bcode_sel = B64CODE_SEL; #else static const int bcode_sel = B32CODE_SEL; #endif /* * simple description of a stack frame (args are 32 bit only currently) */ typedef struct bop_frame { struct bop_frame *old_frame; pc_t retaddr; long arg[1]; } bop_frame_t; void bop_traceback(bop_frame_t *frame) { pc_t pc; int cnt; int a; char *ksym; ulong_t off; bop_printf(NULL, "Stack traceback:\n"); for (cnt = 0; cnt < 30; ++cnt) { /* up to 30 frames */ pc = frame->retaddr; if (pc == 0) break; ksym = kobj_getsymname(pc, &off); if (ksym) bop_printf(NULL, " %s+%lx", ksym, off); else bop_printf(NULL, " 0x%lx", pc); frame = frame->old_frame; if (frame == 0) { bop_printf(NULL, "\n"); break; } for (a = 0; a < 6; ++a) { /* try for 6 args */ #if defined(__i386) if ((void *)&frame->arg[a] == (void *)frame->old_frame) break; if (a == 0) bop_printf(NULL, "("); else bop_printf(NULL, ","); bop_printf(NULL, "0x%lx", frame->arg[a]); #endif } bop_printf(NULL, ")\n"); } } struct trapframe { ulong_t frame_ptr; /* %[er]bp pushed by our code */ ulong_t error_code; /* optional */ ulong_t inst_ptr; ulong_t code_seg; ulong_t flags_reg; #ifdef __amd64 ulong_t stk_ptr; ulong_t stk_seg; #endif }; void bop_trap(struct trapframe *tf) { bop_frame_t fakeframe; static int depth = 0; /* * Check for an infinite loop of traps. */ if (++depth > 2) bop_panic("Nested trap"); /* * adjust the tf for optional error_code by detecting the code selector */ if (tf->code_seg != bcode_sel) tf = (struct trapframe *)((uintptr_t)tf - sizeof (ulong_t)); bop_printf(NULL, "Unexpected trap\n"); bop_printf(NULL, "instruction pointer 0x%lx\n", tf->inst_ptr); bop_printf(NULL, "error code, optional 0x%lx\n", tf->error_code & 0xffffffff); bop_printf(NULL, "code segment 0x%lx\n", tf->code_seg & 0xffff); bop_printf(NULL, "flags register 0x%lx\n", tf->flags_reg); #ifdef __amd64 bop_printf(NULL, "return %%rsp 0x%lx\n", tf->stk_ptr); bop_printf(NULL, "return %%ss 0x%lx\n", tf->stk_seg & 0xffff); #endif fakeframe.old_frame = (bop_frame_t *)tf->frame_ptr; fakeframe.retaddr = (pc_t)tf->inst_ptr; bop_printf(NULL, "Attempting stack backtrace:\n"); bop_traceback(&fakeframe); bop_panic("unexpected trap in early boot"); } extern void bop_trap_handler(void); static gate_desc_t bop_idt[NIDT]; static desctbr_t bop_idt_info; static void bop_idt_init(void) { int t; bzero(&bop_idt, sizeof (bop_idt)); for (t = 0; t < NIDT; ++t) { set_gatesegd(&bop_idt[t], &bop_trap_handler, bcode_sel, SDT_SYSIGT, SEL_KPL); } bop_idt_info.dtr_limit = sizeof (bop_idt) - 1; bop_idt_info.dtr_base = (uintptr_t)&bop_idt; wr_idtr(&bop_idt_info); } /* * This is where we enter the kernel. It dummies up the boot_ops and * boot_syscalls vectors and jumps off to _kobj_boot() */ void _start(struct xboot_info *xbp) { bootops_t *bops = &bootop; extern void _kobj_boot(); /* * 1st off - initialize the console for any error messages */ xbootp = xbp; bcons_init((void *)xbootp->bi_cmdline); have_console = 1; /* * enable debugging */ if (strstr((char *)xbootp->bi_cmdline, "kbm_debug")) kbm_debug = 1; DBG_MSG("\n\n*** Entered Solaris in _start() cmdline is: "); DBG_MSG((char *)xbootp->bi_cmdline); DBG_MSG("\n\n\n"); /* * Install an IDT to catch early pagefaults (shouldn't have any). * Also needed for kmdb. */ bop_idt_init(); /* * physavail is no longer used by startup */ bm.physinstalled = xbp->bi_phys_install; bm.pcimem = xbp->bi_pcimem; bm.physavail = NULL; /* * initialize the boot time allocator */ next_phys = xbootp->bi_next_paddr; DBG(next_phys); next_virt = (uintptr_t)xbootp->bi_next_vaddr; DBG(next_virt); DBG_MSG("Initializing boot time memory management..."); kbm_init(xbootp); DBG_MSG("done\n"); /* * Fill in the bootops vector */ bops->bsys_version = BO_VERSION; bops->boot_mem = &bm; bops->bsys_alloc = do_bsys_alloc; bops->bsys_free = do_bsys_free; bops->bsys_getproplen = do_bsys_getproplen; bops->bsys_getprop = do_bsys_getprop; bops->bsys_nextprop = do_bsys_nextprop; bops->bsys_printf = bop_printf; bops->bsys_doint = do_bsys_doint; /* * BOP_EALLOC() is no longer needed */ bops->bsys_ealloc = do_bsys_ealloc; /* * */ DBG_MSG("Initializing boot properties:\n"); build_boot_properties(); if (strstr((char *)xbootp->bi_cmdline, "prom_debug") || kbm_debug) { char *name; char *value; int len; value = do_bsys_alloc(NULL, NULL, MMU_PAGESIZE, MMU_PAGESIZE); bop_printf(NULL, "\nBoot properties:\n"); name = ""; while ((name = do_bsys_nextprop(NULL, name)) != NULL) { bop_printf(NULL, "\t0x%p %s = ", (void *)name, name); (void) do_bsys_getprop(NULL, name, value); len = do_bsys_getproplen(NULL, name); bop_printf(NULL, "len=%d ", len); value[len] = 0; bop_printf(NULL, "%s\n", value); } } /* * jump into krtld... */ _kobj_boot(&bop_sysp, NULL, bops, NULL); } /*ARGSUSED*/ static caddr_t no_more_alloc(bootops_t *bop, caddr_t virthint, size_t size, int align) { panic("Attempt to bsys_alloc() too late\n"); return (NULL); } /*ARGSUSED*/ static void no_more_free(bootops_t *bop, caddr_t virt, size_t size) { panic("Attempt to bsys_free() too late\n"); } void bop_no_more_mem(void) { DBG(total_bop_alloc_scratch); DBG(total_bop_alloc_kernel); bootops->bsys_alloc = no_more_alloc; bootops->bsys_free = no_more_free; } /* * Set ACPI firmware properties */ static caddr_t vmap_phys(size_t length, paddr_t pa) { paddr_t start, end; caddr_t va; size_t len, page; start = P2ALIGN(pa, MMU_PAGESIZE); end = P2ROUNDUP(pa + length, MMU_PAGESIZE); len = end - start; va = (caddr_t)alloc_vaddr(len, MMU_PAGESIZE); for (page = 0; page < len; page += MMU_PAGESIZE) kbm_map((uintptr_t)va + page, start + page, 0, 0); return (va + (pa & MMU_PAGEOFFSET)); } static uint8_t checksum_table(uint8_t *tp, size_t len) { uint8_t sum = 0; while (len-- > 0) sum += *tp++; return (sum); } static int valid_rsdp(struct rsdp *rp) { /* validate the V1.x checksum */ if (checksum_table((uint8_t *)&rp->v1, sizeof (struct rsdp_v1)) != 0) return (0); /* If pre-ACPI 2.0, this is a valid RSDP */ if (rp->v1.revision < 2) return (1); /* validate the V2.x checksum */ if (checksum_table((uint8_t *)rp, sizeof (struct rsdp)) != 0) return (0); return (1); } /* * Scan memory range for an RSDP; * see ACPI 3.0 Spec, 5.2.5.1 */ static struct rsdp * scan_rsdp(paddr_t start, paddr_t end) { size_t len = end - start + 1; caddr_t ptr; ptr = vmap_phys(len, start); while (len > 0) { if (strncmp(ptr, ACPI_RSDP_SIG, ACPI_RSDP_SIG_LEN) == 0) if (valid_rsdp((struct rsdp *)ptr)) return ((struct rsdp *)ptr); ptr += 16; len -= 16; } return (NULL); } /* * Refer to ACPI 3.0 Spec, section 5.2.5.1 to understand this function */ static struct rsdp * find_rsdp() { struct rsdp *rsdp; uint16_t *ebda_seg; paddr_t ebda_addr; /* * Get the EBDA segment and scan the first 1K */ ebda_seg = (uint16_t *)vmap_phys(sizeof (uint16_t), ACPI_EBDA_SEG_ADDR); ebda_addr = *ebda_seg << 4; rsdp = scan_rsdp(ebda_addr, ebda_addr + ACPI_EBDA_LEN - 1); if (rsdp == NULL) /* if EBDA doesn't contain RSDP, look in BIOS memory */ rsdp = scan_rsdp(0xe0000, 0xfffff); return (rsdp); } static struct table_header * map_fw_table(paddr_t table_addr) { struct table_header *tp; size_t len = MAX(sizeof (struct table_header), MMU_PAGESIZE); /* * Map at least a page; if the table is larger than this, remap it */ tp = (struct table_header *)vmap_phys(len, table_addr); if (tp->len > len) tp = (struct table_header *)vmap_phys(tp->len, table_addr); return (tp); } static struct table_header * find_fw_table(char *signature) { static int revision = 0; static struct xsdt *xsdt; static int len; paddr_t xsdt_addr; struct rsdp *rsdp; struct table_header *tp; paddr_t table_addr; int n; if (strlen(signature) != ACPI_TABLE_SIG_LEN) return (NULL); /* * Reading the ACPI 3.0 Spec, section 5.2.5.3 will help * understand this code. If we haven't already found the RSDT/XSDT, * revision will be 0. Find the RSDP and check the revision * to find out whether to use the RSDT or XSDT. If revision is * 0 or 1, use the RSDT and set internal revision to 1; if it is 2, * use the XSDT. If the XSDT address is 0, though, fall back to * revision 1 and use the RSDT. */ if (revision == 0) { if ((rsdp = (struct rsdp *)find_rsdp()) != NULL) { revision = rsdp->v1.revision; switch (revision) { case 2: /* * Use the XSDT unless BIOS is buggy and * claims to be rev 2 but has a null XSDT * address */ xsdt_addr = rsdp->xsdt; if (xsdt_addr != 0) break; /* FALLTHROUGH */ case 0: /* treat RSDP rev 0 as revision 1 internally */ revision = 1; /* FALLTHROUGH */ case 1: /* use the RSDT for rev 0/1 */ xsdt_addr = rsdp->v1.rsdt; break; default: /* unknown revision */ revision = 0; break; } } if (revision == 0) return (NULL); /* cache the XSDT info */ xsdt = (struct xsdt *)map_fw_table(xsdt_addr); len = (xsdt->hdr.len - sizeof (xsdt->hdr)) / ((revision == 1) ? sizeof (uint32_t) : sizeof (uint64_t)); } /* * Scan the table headers looking for a signature match */ for (n = 0; n < len; n++) { table_addr = (revision == 1) ? xsdt->p.r[n] : xsdt->p.x[n]; if (table_addr == 0) continue; tp = map_fw_table(table_addr); if (strncmp(tp->sig, signature, ACPI_TABLE_SIG_LEN) == 0) { return (tp); } } return (NULL); } static void process_madt(struct madt *tp) { struct madt_processor *cpu, *end; uint32_t cpu_count = 0; /* * User-set boot-ncpus overrides firmware count */ if (do_bsys_getproplen(NULL, "boot-ncpus") >= 0) return; if (tp != NULL) { end = (struct madt_processor *)(tp->hdr.len + (uintptr_t)tp); cpu = tp->list; while (cpu < end) { if (cpu->type == MADT_PROCESSOR) if (cpu->flags & 1) cpu_count++; cpu = (struct madt_processor *) (cpu->len + (uintptr_t)cpu); } bsetpropsi("boot-ncpus", cpu_count); } } static void process_srat(struct srat *tp) { struct srat_item *item, *end; int i; int proc_num, mem_num; #pragma pack(1) struct { uint32_t domain; uint32_t apic_id; uint32_t sapic_id; } processor; struct { uint32_t domain; uint64_t addr; uint64_t length; uint32_t flags; } memory; #pragma pack() char prop_name[30]; if (tp == NULL) return; proc_num = mem_num = 0; end = (struct srat_item *)(tp->hdr.len + (uintptr_t)tp); item = tp->list; while (item < end) { switch (item->type) { case SRAT_PROCESSOR: if (!(item->i.p.flags & SRAT_ENABLED)) break; processor.domain = item->i.p.domain1; for (i = 0; i < 3; i++) processor.domain += item->i.p.domain2[i] << ((i + 1) * 8); processor.apic_id = item->i.p.apic_id; processor.sapic_id = item->i.p.local_sapic_eid; (void) snprintf(prop_name, 30, "acpi-srat-processor-%d", proc_num); bsetprop(prop_name, strlen(prop_name), &processor, sizeof (processor)); proc_num++; break; case SRAT_MEMORY: if (!(item->i.m.flags & SRAT_ENABLED)) break; memory.domain = item->i.m.domain; memory.addr = item->i.m.base_addr; memory.length = item->i.m.len; memory.flags = item->i.m.flags; (void) snprintf(prop_name, 30, "acpi-srat-memory-%d", mem_num); bsetprop(prop_name, strlen(prop_name), &memory, sizeof (memory)); mem_num++; break; } item = (struct srat_item *) (item->len + (caddr_t)item); } } static void process_slit(struct slit *tp) { /* * Check the number of localities; if it's too huge, we just * return and locality enumeration code will handle this later, * if possible. * * Note that the size of the table is the square of the * number of localities; if the number of localities exceeds * UINT16_MAX, the table size may overflow an int when being * passed to bsetprop() below. */ if (tp->number >= SLIT_LOCALITIES_MAX) return; bsetprop(SLIT_NUM_PROPNAME, strlen(SLIT_NUM_PROPNAME), &tp->number, sizeof (tp->number)); bsetprop(SLIT_PROPNAME, strlen(SLIT_PROPNAME), &tp->entry, tp->number * tp->number); } static void build_firmware_properties(void) { struct table_header *tp; if (tp = find_fw_table("APIC")) process_madt((struct madt *)tp); if (tp = find_fw_table("SRAT")) process_srat((struct srat *)tp); if (tp = find_fw_table("SLIT")) process_slit((struct slit *)tp); } /* * fake up a boot property for USB serial console early boot output */ void * usbser_init(size_t size) { static char *p = NULL; p = do_bsys_alloc(NULL, NULL, size, MMU_PAGESIZE); *p = 0; bsetprop("usb-serial-buf", strlen("usb-serial-buf") + 1, &p, sizeof (p)); return (p); }