/* boot.c - Load and start Minix. Author: Kees J. Bot * 27 Dec 1991 */ char version[]= "2.20"; #define BIOS (!UNIX) /* Either uses BIOS or UNIX syscalls. */ #define nil 0 #define _POSIX_SOURCE 1 #define _MINIX 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if BIOS #include #include #include #endif #if UNIX #include #include #include #include #include #include #endif #include "rawfs.h" #undef EXTERN #define EXTERN /* Empty */ #include "boot.h" #define arraysize(a) (sizeof(a) / sizeof((a)[0])) #define arraylimit(a) ((a) + arraysize(a)) #define between(a, c, z) ((unsigned) ((c) - (a)) <= ((z) - (a))) u16_t vid_port; /* Video i/o port. */ u32_t vid_mem_base; /* Video memory base address. */ u32_t vid_mem_size; /* Video memory size. */ int fsok= -1; /* File system state. Initially unknown. */ static int block_size; #if BIOS /* this data is reserved for BIOS int 0x13 to put the 'specification packet' * in. It has a structure of course, but we don't define a struct because * of compiler padding. We fiddle out the bytes ourselves later. */ unsigned char boot_spec[24]; char *bios_err(int err) /* Translate BIOS error code to a readable string. (This is a rare trait * known as error checking and reporting. Take a good look at it, you won't * see it often.) */ { static struct errlist { int err; char *what; } errlist[] = { #if !DOS { 0x00, "No error" }, { 0x01, "Invalid command" }, { 0x02, "Address mark not found" }, { 0x03, "Disk write-protected" }, { 0x04, "Sector not found" }, { 0x05, "Reset failed" }, { 0x06, "Floppy disk removed" }, { 0x07, "Bad parameter table" }, { 0x08, "DMA overrun" }, { 0x09, "DMA crossed 64 KB boundary" }, { 0x0A, "Bad sector flag" }, { 0x0B, "Bad track flag" }, { 0x0C, "Media type not found" }, { 0x0D, "Invalid number of sectors on format" }, { 0x0E, "Control data address mark detected" }, { 0x0F, "DMA arbitration level out of range" }, { 0x10, "Uncorrectable CRC or ECC data error" }, { 0x11, "ECC corrected data error" }, { 0x20, "Controller failed" }, { 0x40, "Seek failed" }, { 0x80, "Disk timed-out" }, { 0xAA, "Drive not ready" }, { 0xBB, "Undefined error" }, { 0xCC, "Write fault" }, { 0xE0, "Status register error" }, { 0xFF, "Sense operation failed" } #else /* DOS */ { 0x00, "No error" }, { 0x01, "Function number invalid" }, { 0x02, "File not found" }, { 0x03, "Path not found" }, { 0x04, "Too many open files" }, { 0x05, "Access denied" }, { 0x06, "Invalid handle" }, { 0x0C, "Access code invalid" }, #endif /* DOS */ }; struct errlist *errp; for (errp= errlist; errp < arraylimit(errlist); errp++) { if (errp->err == err) return errp->what; } return "Unknown error"; } char *unix_err(int err) /* Translate the few errors rawfs can give. */ { switch (err) { case ENOENT: return "No such file or directory"; case ENOTDIR: return "Not a directory"; default: return "Unknown error"; } } void rwerr(char *rw, off_t sec, int err) { printf("\n%s error 0x%02x (%s) at sector %ld absolute\n", rw, err, bios_err(err), sec); } void readerr(off_t sec, int err) { rwerr("Read", sec, err); } void writerr(off_t sec, int err) { rwerr("Write", sec, err); } void readblock(off_t blk, char *buf, int block_size) /* Read blocks for the rawfs package. */ { int r; u32_t sec= lowsec + blk * RATIO(block_size); if(!block_size) { printf("block_size 0\n"); exit(1); } if ((r= readsectors(mon2abs(buf), sec, 1 * RATIO(block_size))) != 0) { readerr(sec, r); exit(1); } } #define istty (1) #define alarm(n) (0) #endif /* BIOS */ #if UNIX /* The Minix boot block must start with these bytes: */ char boot_magic[] = { 0x31, 0xC0, 0x8E, 0xD8, 0xFA, 0x8E, 0xD0, 0xBC }; struct biosdev { char *name; /* Name of device. */ int device; /* Device to edit parameters. */ } bootdev; struct termios termbuf; int istty; void quit(int status) { if (istty) (void) tcsetattr(0, TCSANOW, &termbuf); exit(status); } #define exit(s) quit(s) void report(char *label) /* edparams: label: No such file or directory */ { fprintf(stderr, "edparams: %s: %s\n", label, strerror(errno)); } void fatal(char *label) { report(label); exit(1); } void *alloc(void *m, size_t n) { m= m == nil ? malloc(n) : realloc(m, n); if (m == nil) fatal(""); return m; } #define malloc(n) alloc(nil, n) #define realloc(m, n) alloc(m, n) #define mon2abs(addr) ((void *) (addr)) int rwsectors(int rw, void *addr, u32_t sec, int nsec) { ssize_t r; size_t len= nsec * SECTOR_SIZE; if (lseek(bootdev.device, sec * SECTOR_SIZE, SEEK_SET) == -1) return errno; if (rw == 0) { r= read(bootdev.device, (char *) addr, len); } else { r= write(bootdev.device, (char *) addr, len); } if (r == -1) return errno; if (r != len) return EIO; return 0; } #define readsectors(a, s, n) rwsectors(0, (a), (s), (n)) #define writesectors(a, s, n) rwsectors(1, (a), (s), (n)) #define readerr(sec, err) (errno= (err), report(bootdev.name)) #define writerr(sec, err) (errno= (err), report(bootdev.name)) #define putch(c) putchar(c) #define unix_err(err) strerror(err) void readblock(off_t blk, char *buf, int block_size) /* Read blocks for the rawfs package. */ { if(!block_size) fatal("block_size 0"); errno= EIO; if (lseek(bootdev.device, blk * block_size, SEEK_SET) == -1 || read(bootdev.device, buf, block_size) != block_size) { fatal(bootdev.name); } } sig_atomic_t trapsig; void trap(int sig) { trapsig= sig; signal(sig, trap); } int escape(void) { if (trapsig == SIGINT) { trapsig= 0; return 1; } return 0; } static unsigned char unchar; int getch(void) { unsigned char c; fflush(stdout); if (unchar != 0) { c= unchar; unchar= 0; return c; } switch (read(0, &c, 1)) { case -1: if (errno != EINTR) fatal(""); return(ESC); case 0: if (istty) putch('\n'); exit(0); default: if (istty && c == termbuf.c_cc[VEOF]) { putch('\n'); exit(0); } return c; } } #define ungetch(c) ((void) (unchar = (c))) #define get_tick() ((u32_t) time(nil)) #define clear_screen() printf("[clear]") #define boot_device(device) printf("[boot %s]\n", device) #define ctty(line) printf("[ctty %s]\n", line) #define bootminix() (run_trailer() && printf("[boot]\n")) #define off() printf("[off]") #endif /* UNIX */ char *readline(void) /* Read a line including a newline with echoing. */ { char *line; size_t i, z; int c; i= 0; z= 20; line= malloc(z * sizeof(char)); do { c= getch(); if (strchr("\b\177\25\30", c) != nil) { /* Backspace, DEL, ctrl-U, or ctrl-X. */ do { if (i == 0) break; printf("\b \b"); i--; } while (c == '\25' || c == '\30'); } else if (c < ' ' && c != '\n') { putch('\7'); } else { putch(c); line[i++]= c; if (i == z) { z*= 2; line= realloc(line, z * sizeof(char)); } } } while (c != '\n'); line[i]= 0; return line; } int sugar(char *tok) /* Recognize special tokens. */ { return strchr("=(){};\n", tok[0]) != nil; } char *onetoken(char **aline) /* Returns a string with one token for tokenize. */ { char *line= *aline; size_t n; char *tok; /* Skip spaces and runs of newlines. */ while (*line == ' ' || (*line == '\n' && line[1] == '\n')) line++; *aline= line; /* Don't do odd junk (nor the terminating 0!). */ if ((unsigned) *line < ' ' && *line != '\n') return nil; if (*line == '(') { /* Function argument, anything goes but () must match. */ int depth= 0; while ((unsigned) *line >= ' ') { if (*line == '(') depth++; if (*line++ == ')' && --depth == 0) break; } } else if (sugar(line)) { /* Single character token. */ line++; } else { /* Multicharacter token. */ do line++; while ((unsigned) *line > ' ' && !sugar(line)); } n= line - *aline; tok= malloc((n + 1) * sizeof(char)); memcpy(tok, *aline, n); tok[n]= 0; if (tok[0] == '\n') tok[0]= ';'; /* ';' same as '\n' */ *aline= line; return tok; } /* Typed commands form strings of tokens. */ typedef struct token { struct token *next; /* Next in a command chain. */ char *token; } token; token **tokenize(token **acmds, char *line) /* Takes a line apart to form tokens. The tokens are inserted into a command * chain at *acmds. Tokenize returns a reference to where another line could * be added. Tokenize looks at spaces as token separators, and recognizes only * ';', '=', '{', '}', and '\n' as single character tokens. One token is * formed from '(' and ')' with anything in between as long as more () match. */ { char *tok; token *newcmd; while ((tok= onetoken(&line)) != nil) { newcmd= malloc(sizeof(*newcmd)); newcmd->token= tok; newcmd->next= *acmds; *acmds= newcmd; acmds= &newcmd->next; } return acmds; } token *cmds; /* String of commands to execute. */ int err; /* Set on an error. */ char *poptoken(void) /* Pop one token off the command chain. */ { token *cmd= cmds; char *tok= cmd->token; cmds= cmd->next; free(cmd); return tok; } void voidtoken(void) /* Remove one token from the command chain. */ { free(poptoken()); } void parse_code(char *code) /* Tokenize a string of monitor code, making sure there is a delimiter. It is * to be executed next. (Prepended to the current input.) */ { if (cmds != nil && cmds->token[0] != ';') (void) tokenize(&cmds, ";"); (void) tokenize(&cmds, code); } int interrupt(void) /* Clean up after an ESC has been typed. */ { if (escape()) { printf("[ESC]\n"); err= 1; return 1; } return 0; } #if BIOS int activate; struct biosdev { char name[8]; int device, primary, secondary; } bootdev, tmpdev; int get_master(char *master, struct part_entry **table, u32_t pos) /* Read a master boot sector and its partition table. */ { int r, n; struct part_entry *pe, **pt; if ((r= readsectors(mon2abs(master), pos, 1)) != 0) return r; pe= (struct part_entry *) (master + PART_TABLE_OFF); for (pt= table; pt < table + NR_PARTITIONS; pt++) *pt= pe++; /* DOS has the misguided idea that partition tables must be sorted. */ if (pos != 0) return 0; /* But only the primary. */ n= NR_PARTITIONS; do { for (pt= table; pt < table + NR_PARTITIONS-1; pt++) { if (pt[0]->sysind == NO_PART || pt[0]->lowsec > pt[1]->lowsec) { pe= pt[0]; pt[0]= pt[1]; pt[1]= pe; } } } while (--n > 0); return 0; } void initialize(void) { char master[SECTOR_SIZE]; struct part_entry *table[NR_PARTITIONS]; int r, p; u32_t masterpos; char *argp; /* Copy the boot program to the far end of low memory, this must be * done to get out of the way of Minix, and to put the data area * cleanly inside a 64K chunk if using BIOS I/O (no DMA problems). */ u32_t oldaddr= caddr; u32_t memend= mem[0].base + mem[0].size; u32_t newaddr= (memend - runsize) & ~0x0000FL; #if !DOS u32_t dma64k= (memend - 1) & ~0x0FFFFL; /* Check if data segment crosses a 64K boundary. */ if (newaddr + (daddr - caddr) < dma64k) { newaddr= (dma64k - runsize) & ~0x0000FL; } #endif /* Set the new caddr for relocate. */ caddr= newaddr; /* Copy code and data. */ raw_copy(newaddr, oldaddr, runsize); /* Make the copy running. */ relocate(); #if !DOS /* Take the monitor out of the memory map if we have memory to spare, * and also keep the BIOS data area safe (1.5K), plus a bit extra for * where we may have to put a.out headers for older kernels. */ if (mon_return = (mem[1].size > 512*1024L)) mem[0].size = newaddr; mem[0].base += 2048; mem[0].size -= 2048; /* Find out what the boot device and partition was. */ bootdev.name[0]= 0; bootdev.device= device; bootdev.primary= -1; bootdev.secondary= -1; if (device < 0x80) { /* Floppy. */ strcpy(bootdev.name, "fd0"); bootdev.name[2] += bootdev.device; return; } /* Disk: Get the partition table from the very first sector, and * determine the partition we booted from using the information from * the booted partition entry as passed on by the bootstrap (rem_part). * All we need from it is the partition offset. */ raw_copy(mon2abs(&lowsec), vec2abs(&rem_part) + offsetof(struct part_entry, lowsec), sizeof(lowsec)); masterpos= 0; /* Master bootsector position. */ for (;;) { /* Extract the partition table from the master boot sector. */ if ((r= get_master(master, table, masterpos)) != 0) { readerr(masterpos, r); exit(1); } /* See if you can find "lowsec" back. */ for (p= 0; p < NR_PARTITIONS; p++) { if (lowsec - table[p]->lowsec < table[p]->size) break; } if (lowsec == table[p]->lowsec) { /* Found! */ if (bootdev.primary < 0) bootdev.primary= p; else bootdev.secondary= p; break; } if (p == NR_PARTITIONS || bootdev.primary >= 0 || table[p]->sysind != MINIX_PART) { /* The boot partition cannot be named, this only means * that "bootdev" doesn't work. */ bootdev.device= -1; return; } /* See if the primary partition is subpartitioned. */ bootdev.primary= p; masterpos= table[p]->lowsec; } strcpy(bootdev.name, "d0p0"); bootdev.name[1] += (device - 0x80); bootdev.name[3] += bootdev.primary; if (bootdev.secondary >= 0) { strcat(bootdev.name, "s0"); bootdev.name[5] += bootdev.secondary; } /* Find out about the video hardware. */ raw_copy(mon2abs(&vid_port), VDU_CRT_BASE_ADDR, sizeof(vid_port)); if(vid_port == C_6845) { vid_mem_base = COLOR_BASE; vid_mem_size = COLOR_SIZE; } else { vid_mem_base = MONO_BASE; vid_mem_size = MONO_SIZE; } if(get_video() >= 3) vid_mem_size = EGA_SIZE; #else /* DOS */ /* Take the monitor out of the memory map if we have memory to spare, * note that only half our PSP is needed at the new place, the first * half is to be kept in its place. */ if (mem[1].size > 0) mem[0].size = newaddr + 0x80 - mem[0].base; /* Parse the command line. */ argp= PSP + 0x81; argp[PSP[0x80]]= 0; while (between('\1', *argp, ' ')) argp++; vdisk= argp; while (!between('\0', *argp, ' ')) argp++; while (between('\1', *argp, ' ')) *argp++= 0; if (*vdisk == 0) { printf("\nUsage: boot [commands ...]\n"); exit(1); } drun= *argp == 0 ? "main" : argp; if ((r= dev_open()) != 0) { printf("\n%s: Error %02x (%s)\n", vdisk, r, bios_err(r)); exit(1); } /* Find the active partition on the virtual disk. */ if ((r= get_master(master, table, 0)) != 0) { readerr(0, r); exit(1); } strcpy(bootdev.name, "d0"); bootdev.primary= -1; for (p= 0; p < NR_PARTITIONS; p++) { if (table[p]->bootind != 0 && table[p]->sysind == MINIX_PART) { bootdev.primary= p; strcat(bootdev.name, "p0"); bootdev.name[3] += p; lowsec= table[p]->lowsec; break; } } #endif /* DOS */ } #endif /* BIOS */ /* Reserved names: */ enum resnames { R_NULL, R_BOOT, R_CTTY, R_DELAY, R_ECHO, R_EXIT, R_HELP, R_LS, R_MENU, R_OFF, R_SAVE, R_SET, R_TRAP, R_UNSET }; char resnames[][6] = { "", "boot", "ctty", "delay", "echo", "exit", "help", "ls", "menu", "off", "save", "set", "trap", "unset", }; /* Using this for all null strings saves a lot of memory. */ #define null (resnames[0]) int reserved(char *s) /* Recognize reserved strings. */ { int r; for (r= R_BOOT; r <= R_UNSET; r++) { if (strcmp(s, resnames[r]) == 0) return r; } return R_NULL; } void sfree(char *s) /* Free a non-null string. */ { if (s != nil && s != null) free(s); } char *copystr(char *s) /* Copy a non-null string using malloc. */ { char *c; if (*s == 0) return null; c= malloc((strlen(s) + 1) * sizeof(char)); strcpy(c, s); return c; } int is_default(environment *e) { return (e->flags & E_SPECIAL) && e->defval == nil; } environment **searchenv(char *name) { environment **aenv= &env; while (*aenv != nil && strcmp((*aenv)->name, name) != 0) { aenv= &(*aenv)->next; } return aenv; } #define b_getenv(name) (*searchenv(name)) /* Return the environment *structure* belonging to name, or nil if not found. */ char *b_value(char *name) /* The value of a variable. */ { environment *e= b_getenv(name); return e == nil || !(e->flags & E_VAR) ? nil : e->value; } char *b_body(char *name) /* The value of a function. */ { environment *e= b_getenv(name); return e == nil || !(e->flags & E_FUNCTION) ? nil : e->value; } int b_setenv(int flags, char *name, char *arg, char *value) /* Change the value of an environment variable. Returns the flags of the * variable if you are not allowed to change it, 0 otherwise. */ { environment **aenv, *e; if (*(aenv= searchenv(name)) == nil) { if (reserved(name)) return E_RESERVED; e= malloc(sizeof(*e)); e->name= copystr(name); e->flags= flags; e->defval= nil; e->next= nil; *aenv= e; } else { e= *aenv; /* Don't change special variables to functions or vv. */ if (e->flags & E_SPECIAL && (e->flags & E_FUNCTION) != (flags & E_FUNCTION) ) return e->flags; e->flags= (e->flags & E_STICKY) | flags; if (is_default(e)) { e->defval= e->value; } else { sfree(e->value); } sfree(e->arg); } e->arg= copystr(arg); e->value= copystr(value); return 0; } int b_setvar(int flags, char *name, char *value) /* Set variable or simple function. */ { int r; if((r=b_setenv(flags, name, null, value))) { return r; } return r; } void b_unset(char *name) /* Remove a variable from the environment. A special variable is reset to * its default value. */ { environment **aenv, *e; if ((e= *(aenv= searchenv(name))) == nil) return; if (e->flags & E_SPECIAL) { if (e->defval != nil) { sfree(e->arg); e->arg= null; sfree(e->value); e->value= e->defval; e->defval= nil; } } else { sfree(e->name); sfree(e->arg); sfree(e->value); *aenv= e->next; free(e); } } long a2l(char *a) /* Cheap atol(). */ { int sign= 1; long n= 0; if (*a == '-') { sign= -1; a++; } while (between('0', *a, '9')) n= n * 10 + (*a++ - '0'); return sign * n; } char *ul2a(u32_t n, unsigned b) /* Transform a long number to ascii at base b, (b >= 8). */ { static char num[(CHAR_BIT * sizeof(n) + 2) / 3 + 1]; char *a= arraylimit(num) - 1; static char hex[16] = "0123456789ABCDEF"; do *--a = hex[(int) (n % b)]; while ((n/= b) > 0); return a; } char *ul2a10(u32_t n) /* Transform a long number to ascii at base 10. */ { return ul2a(n, 10); } unsigned a2x(char *a) /* Ascii to hex. */ { unsigned n= 0; int c; for (;;) { c= *a; if (between('0', c, '9')) c= c - '0' + 0x0; else if (between('A', c, 'F')) c= c - 'A' + 0xA; else if (between('a', c, 'f')) c= c - 'a' + 0xa; else break; n= (n<<4) | c; a++; } return n; } void get_parameters(void) { char params[SECTOR_SIZE + 1]; token **acmds; int r, bus, processor; memory *mp; static char bus_type[][4] = { "xt", "at", "mca" }; static char vid_type[][4] = { "mda", "cga", "ega", "ega", "vga", "vga" }; static char vid_chrome[][6] = { "mono", "color" }; /* Variables that Minix needs: */ b_setvar(E_SPECIAL|E_VAR|E_DEV, "rootdev", "ram"); b_setvar(E_SPECIAL|E_VAR|E_DEV, "ramimagedev", "bootdev"); b_setvar(E_SPECIAL|E_VAR, "ramsize", "0"); #define STRINGIT2(x) #x #define STRINGIT1(x) STRINGIT2(x) b_setvar(E_SPECIAL|E_VAR, "hz", STRINGIT1(DEFAULT_HZ)); #if BIOS processor = getprocessor(); if(processor == 1586) processor = 686; b_setvar(E_SPECIAL|E_VAR, "processor", ul2a10(processor)); b_setvar(E_SPECIAL|E_VAR, "bus", bus_type[get_bus()]); b_setvar(E_SPECIAL|E_VAR, "video", vid_type[get_video()]); b_setvar(E_SPECIAL|E_VAR, "chrome", vid_chrome[get_video() & 1]); params[0]= 0; for (mp= mem; mp < arraylimit(mem); mp++) { if (mp->size == 0) continue; if (params[0] != 0) strcat(params, ","); strcat(params, ul2a(mp->base, 0x10)); strcat(params, ":"); strcat(params, ul2a(mp->size, 0x10)); } b_setvar(E_SPECIAL|E_VAR, "memory", params); #if DOS b_setvar(E_SPECIAL|E_VAR, "dosfile-d0", vdisk); #endif #endif #if UNIX b_setvar(E_SPECIAL|E_VAR, "processor", "?"); b_setvar(E_SPECIAL|E_VAR, "bus", "?"); b_setvar(E_SPECIAL|E_VAR, "video", "?"); b_setvar(E_SPECIAL|E_VAR, "chrome", "?"); b_setvar(E_SPECIAL|E_VAR, "memory", "?"); b_setvar(E_SPECIAL|E_VAR, "c0", "?"); #endif /* Variables boot needs: */ b_setvar(E_SPECIAL|E_VAR, "image", "boot/image"); b_setvar(E_SPECIAL|E_FUNCTION, "leader", "echo --- Welcome to MINIX 3. This is the boot monitor. ---\\n"); b_setvar(E_SPECIAL|E_FUNCTION, "main", "menu"); b_setvar(E_SPECIAL|E_FUNCTION, "trailer", ""); /* Default hidden menu function: */ b_setenv(E_RESERVED|E_FUNCTION, null, "=,Start MINIX", "boot"); /* Tokenize bootparams sector. */ if ((r= readsectors(mon2abs(params), lowsec+PARAMSEC, 1)) != 0) { readerr(lowsec+PARAMSEC, r); exit(1); } params[SECTOR_SIZE]= 0; acmds= tokenize(&cmds, params); /* Stuff the default action into the command chain. */ #if UNIX (void) tokenize(acmds, ":;"); #elif DOS (void) tokenize(tokenize(acmds, ":;leader;"), drun); #else /* BIOS */ (void) tokenize(acmds, ":;leader;main"); #endif } char *addptr; void addparm(char *n) { while (*n != 0 && *addptr != 0) *addptr++ = *n++; } void save_parameters(void) /* Save nondefault environment variables to the bootparams sector. */ { environment *e; char params[SECTOR_SIZE + 1]; int r; /* Default filling: */ memset(params, '\n', SECTOR_SIZE); /* Don't touch the 0! */ params[SECTOR_SIZE]= 0; addptr= params; for (e= env; e != nil; e= e->next) { if (e->flags & E_RESERVED || is_default(e)) continue; addparm(e->name); if (e->flags & E_FUNCTION) { addparm("("); addparm(e->arg); addparm(")"); } else { addparm((e->flags & (E_DEV|E_SPECIAL)) != E_DEV ? "=" : "=d "); } addparm(e->value); if (*addptr == 0) { printf("The environment is too big\n"); return; } *addptr++= '\n'; } /* Save the parameters on disk. */ if ((r= writesectors(mon2abs(params), lowsec+PARAMSEC, 1)) != 0) { writerr(lowsec+PARAMSEC, r); printf("Can't save environment\n"); } } void show_env(void) /* Show the environment settings. */ { environment *e; unsigned more= 0; int c; for (e= env; e != nil; e= e->next) { if (e->flags & E_RESERVED) continue; if (!istty && is_default(e)) continue; if (e->flags & E_FUNCTION) { printf("%s(%s) %s\n", e->name, e->arg, e->value); } else { printf(is_default(e) ? "%s = (%s)\n" : "%s = %s\n", e->name, e->value); } if (e->next != nil && istty && ++more % 20 == 0) { printf("More? "); c= getch(); if (c == ESC || c > ' ') { putch('\n'); if (c > ' ') ungetch(c); break; } printf("\b\b\b\b\b\b"); } } } int numprefix(char *s, char **ps) /* True iff s is a string of digits. *ps will be set to the first nondigit * if non-nil, otherwise the string should end. */ { char *n= s; while (between('0', *n, '9')) n++; if (n == s) return 0; if (ps == nil) return *n == 0; *ps= n; return 1; } int numeric(char *s) { return numprefix(s, (char **) nil); } #if BIOS /* Device numbers of standard MINIX devices. */ #define DEV_FD0 0x0200 static dev_t dev_cNd0[] = { 0x0300, 0x0800, 0x0A00, 0x0C00, 0x1000 }; #define minor_p0s0 128 static int block_size; dev_t name2dev(char *name) /* Translate, say, /dev/c0d0p2 to a device number. If the name can't be * found on the boot device, then do some guesswork. The global structure * "tmpdev" will be filled in based on the name, so that "boot d1p0" knows * what device to boot without interpreting device numbers. */ { dev_t dev; ino_t ino; int drive; struct stat st; char *n, *s; /* "boot *d0p2" means: make partition 2 active before you boot it. */ if ((activate= (name[0] == '*'))) name++; /* The special name "bootdev" must be translated to the boot device. */ if (strcmp(name, "bootdev") == 0) { if (bootdev.device == -1) { printf("The boot device could not be named\n"); errno= 0; return -1; } name= bootdev.name; } /* If our boot device doesn't have a file system, or we want to know * what a name means for the BIOS, then we need to interpret the * device name ourselves: "fd" = floppy, "c0d0" = hard disk, etc. */ tmpdev.device= tmpdev.primary= tmpdev.secondary= -1; dev= -1; n= name; if (strncmp(n, "/dev/", 5) == 0) n+= 5; if (strcmp(n, "ram") == 0) { dev= DEV_RAM; } else if (strcmp(n, "boot") == 0) { dev= DEV_BOOT; } else if (n[0] == 'f' && n[1] == 'd' && numeric(n+2)) { /* Floppy. */ tmpdev.device= a2l(n+2); dev= DEV_FD0 + tmpdev.device; } else if ((n[0] == 'h' || n[0] == 's') && n[1] == 'd' && numprefix(n+2, &s) && (*s == 0 || (between('a', *s, 'd') && s[1] == 0)) ) { /* Old style hard disk (backwards compatibility.) */ dev= a2l(n+2); tmpdev.device= dev / (1 + NR_PARTITIONS); tmpdev.primary= (dev % (1 + NR_PARTITIONS)) - 1; if (*s != 0) { /* Subpartition. */ tmpdev.secondary= *s - 'a'; dev= minor_p0s0 + (tmpdev.device * NR_PARTITIONS + tmpdev.primary) * NR_PARTITIONS + tmpdev.secondary; } tmpdev.device+= 0x80; dev+= n[0] == 'h' ? dev_cNd0[0] : dev_cNd0[2]; } else { /* Hard disk. */ int ctrlr= 0; if (n[0] == 'c' && between('0', n[1], '4')) { ctrlr= (n[1] - '0'); tmpdev.device= 0; n+= 2; } if (n[0] == 'd' && between('0', n[1], '7')) { tmpdev.device= (n[1] - '0'); n+= 2; if (n[0] == 'p' && between('0', n[1], '3')) { tmpdev.primary= (n[1] - '0'); n+= 2; if (n[0] == 's' && between('0', n[1], '3')) { tmpdev.secondary= (n[1] - '0'); n+= 2; } } } if (*n == 0) { dev= dev_cNd0[ctrlr]; if (tmpdev.secondary < 0) { dev += tmpdev.device * (NR_PARTITIONS+1) + (tmpdev.primary + 1); } else { dev += minor_p0s0 + (tmpdev.device * NR_PARTITIONS + tmpdev.primary) * NR_PARTITIONS + tmpdev.secondary; } tmpdev.device+= 0x80; } } /* Look the name up on the boot device for the UNIX device number. */ if (fsok == -1) fsok= r_super(&block_size) != 0; if (fsok) { /* The current working directory is "/dev". */ ino= r_lookup(r_lookup(ROOT_INO, "dev"), name); if (ino != 0) { /* Name has been found, extract the device number. */ r_stat(ino, &st); if (!S_ISBLK(st.st_mode)) { printf("%s is not a block device\n", name); errno= 0; return (dev_t) -1; } dev= st.st_rdev; } } if (tmpdev.primary < 0) activate= 0; /* Careful now! */ if (dev == -1) { printf("Can't recognize '%s' as a device\n", name); errno= 0; } return dev; } #if DEBUG static void apm_perror(char *label, u16_t ax) { unsigned ah; char *str; ah= (ax >> 8); switch(ah) { case 0x01: str= "APM functionality disabled"; break; case 0x03: str= "interface not connected"; break; case 0x09: str= "unrecognized device ID"; break; case 0x0A: str= "parameter value out of range"; break; case 0x0B: str= "interface not engaged"; break; case 0x60: str= "unable to enter requested state"; break; case 0x86: str= "APM not present"; break; default: printf("%s: error 0x%02x\n", label, ah); return; } printf("%s: %s\n", label, str); } #define apm_printf printf #else #define apm_perror(label, ax) ((void)0) #define apm_printf #endif static void off(void) { bios_env_t be; unsigned al, ah; /* Try to switch off the system. Print diagnostic information * that can be useful if the operation fails. */ be.ax= 0x5300; /* APM, Installation check */ be.bx= 0; /* Device, APM BIOS */ int15(&be); if (be.flags & FL_CARRY) { apm_perror("APM installation check failed", be.ax); return; } if (be.bx != (('P' << 8) | 'M')) { apm_printf("APM signature not found (got 0x%04x)\n", be.bx); return; } ah= be.ax >> 8; if (ah > 9) ah= (ah >> 4)*10 + (ah & 0xf); al= be.ax & 0xff; if (al > 9) al= (al >> 4)*10 + (al & 0xf); apm_printf("APM version %u.%u%s%s%s%s%s\n", ah, al, (be.cx & 0x1) ? ", 16-bit PM" : "", (be.cx & 0x2) ? ", 32-bit PM" : "", (be.cx & 0x4) ? ", CPU-Idle" : "", (be.cx & 0x8) ? ", APM-disabled" : "", (be.cx & 0x10) ? ", APM-disengaged" : ""); /* Connect */ be.ax= 0x5301; /* APM, Real mode interface connect */ be.bx= 0x0000; /* APM BIOS */ int15(&be); if (be.flags & FL_CARRY) { apm_perror("APM real mode connect failed", be.ax); return; } /* Ask for a seat upgrade */ be.ax= 0x530e; /* APM, Driver Version */ be.bx= 0x0000; /* BIOS */ be.cx= 0x0102; /* version 1.2 */ int15(&be); if (be.flags & FL_CARRY) { apm_perror("Set driver version failed", be.ax); goto disco; } /* Is this version really worth reporting. Well, if the system * does switch off, you won't see it anyway. */ ah= be.ax >> 8; if (ah > 9) ah= (ah >> 4)*10 + (ah & 0xf); al= be.ax & 0xff; if (al > 9) al= (al >> 4)*10 + (al & 0xf); apm_printf("Got APM connection version %u.%u\n", ah, al); /* Enable */ be.ax= 0x5308; /* APM, Enable/disable power management */ be.bx= 0x0001; /* All device managed by APM BIOS */ #if 0 /* For old APM 1.0 systems, we need 0xffff. Assume that those * systems do not exist. */ be.bx= 0xffff; /* All device managed by APM BIOS (compat) */ #endif be.cx= 0x0001; /* Enable power management */ int15(&be); if (be.flags & FL_CARRY) { apm_perror("Enable power management failed", be.ax); goto disco; } /* Off */ be.ax= 0x5307; /* APM, Set Power State */ be.bx= 0x0001; /* All devices managed by APM */ be.cx= 0x0003; /* Off */ int15(&be); if (be.flags & FL_CARRY) { apm_perror("Set power state failed", be.ax); goto disco; } apm_printf("Power off sequence successfully completed.\n\n"); apm_printf("Ha, ha, just kidding!\n"); disco: /* Disconnect */ be.ax= 0x5304; /* APM, interface disconnect */ be.bx= 0x0000; /* APM BIOS */ int15(&be); if (be.flags & FL_CARRY) { apm_perror("APM interface disconnect failed", be.ax); return; } } #if !DOS #define B_NOSIG -1 /* "No signature" error code. */ int exec_bootstrap(void) /* Load boot sector from the disk or floppy described by tmpdev and execute it. */ { int r, n, dirty= 0; char master[SECTOR_SIZE]; struct part_entry *table[NR_PARTITIONS], dummy, *active= &dummy; u32_t masterpos; active->lowsec= 0; /* Select a partition table entry. */ while (tmpdev.primary >= 0) { masterpos= active->lowsec; if ((r= get_master(master, table, masterpos)) != 0) return r; active= table[tmpdev.primary]; /* How does one check a partition table entry? */ if (active->sysind == NO_PART) return B_NOSIG; tmpdev.primary= tmpdev.secondary; tmpdev.secondary= -1; } if (activate && !active->bootind) { for (n= 0; n < NR_PARTITIONS; n++) table[n]->bootind= 0; active->bootind= ACTIVE_FLAG; dirty= 1; } /* Read the boot sector. */ if ((r= readsectors(BOOTPOS, active->lowsec, 1)) != 0) return r; /* Check signature word. */ if (get_word(BOOTPOS+SIGNATOFF) != SIGNATURE) return B_NOSIG; /* Write the partition table if a member must be made active. */ if (dirty && (r= writesectors(mon2abs(master), masterpos, 1)) != 0) return r; bootstrap(device, active); } void boot_device(char *devname) /* Boot the device named by devname. */ { dev_t dev= name2dev(devname); int save_dev= device; int r; char *err; if (tmpdev.device < 0) { if (dev != -1) printf("Can't boot from %s\n", devname); return; } /* Change current device and try to load and execute its bootstrap. */ device= tmpdev.device; if ((r= dev_open()) == 0) r= exec_bootstrap(); err= r == B_NOSIG ? "Not bootable" : bios_err(r); printf("Can't boot %s: %s\n", devname, err); /* Restore boot device setting. */ device= save_dev; (void) dev_open(); } void ctty(char *line) { if (line == nil) { serial_init(-1); } else if (between('0', line[0], '3') && line[1] == 0) { serial_init(line[0] - '0'); } else { printf("Bad serial line number: %s\n", line); } } #else /* DOS */ void boot_device(char *devname) /* No booting of other devices under DOS. */ { printf("Can't boot devices under DOS\n"); } void ctty(char *line) /* Don't know how to handle serial lines under DOS. */ { printf("No serial line support under DOS\n"); } #endif /* DOS */ #endif /* BIOS */ void ls(char *dir) /* List the contents of a directory. */ { ino_t ino; struct stat st; char name[NAME_MAX+1]; if (fsok == -1) fsok= r_super(&block_size) != 0; if (!fsok) return; /* (,) construct because r_stat returns void */ if ((ino= r_lookup(ROOT_INO, dir)) == 0 || (r_stat(ino, &st), r_readdir(name)) == -1) { printf("ls: %s: %s\n", dir, unix_err(errno)); return; } (void) r_readdir(name); /* Skip ".." too. */ while ((ino= r_readdir(name)) != 0) printf("%s/%s\n", dir, name); } u32_t milli_time(void) { return get_tick() * MSEC_PER_TICK; } u32_t milli_since(u32_t base) { return (milli_time() + (TICKS_PER_DAY*MSEC_PER_TICK) - base) % (TICKS_PER_DAY*MSEC_PER_TICK); } char *Thandler; u32_t Tbase, Tcount; void unschedule(void) /* Invalidate a waiting command. */ { alarm(0); if (Thandler != nil) { free(Thandler); Thandler= nil; } } void schedule(long msec, char *cmd) /* Schedule command at a certain time from now. */ { unschedule(); Thandler= cmd; Tbase= milli_time(); Tcount= msec; alarm(1); } int expired(void) /* Check if the timer expired for getch(). */ { return (Thandler != nil && milli_since(Tbase) >= Tcount); } void delay(char *msec) /* Delay for a given time. */ { u32_t base, count; if ((count= a2l(msec)) == 0) return; base= milli_time(); alarm(1); do { pause(); } while (!interrupt() && !expired() && milli_since(base) < count); } enum whatfun { NOFUN, SELECT, DEFFUN, USERFUN } menufun(environment *e) { if (!(e->flags & E_FUNCTION) || e->arg[0] == 0) return NOFUN; if (e->arg[1] != ',') return SELECT; return e->flags & E_RESERVED ? DEFFUN : USERFUN; } void menu(void) /* By default: Show a simple menu. * Multiple kernels/images: Show extra selection options. * User defined function: Kill the defaults and show these. * Wait for a keypress and execute the given function. */ { int c, def= 1; char *choice= nil; environment *e; /* Just a default menu? */ for (e= env; e != nil; e= e->next) if (menufun(e) == USERFUN) def= 0; printf("\nHit a key as follows:\n\n"); /* Show the choices. */ for (e= env; e != nil; e= e->next) { switch (menufun(e)) { case DEFFUN: if (!def) break; /*FALL THROUGH*/ case USERFUN: printf(" %c %s\n", e->arg[0], e->arg+2); break; case SELECT: printf(" %c Select %s kernel\n", e->arg[0],e->name); break; default:; } } /* Wait for a keypress. */ do { c= getch(); if (interrupt() || expired()) return; unschedule(); for (e= env; e != nil; e= e->next) { switch (menufun(e)) { case DEFFUN: if (!def) break; case USERFUN: case SELECT: if (c == e->arg[0]) choice= e->value; } } } while (choice == nil); /* Execute the chosen function. */ printf("%c\n", c); (void) tokenize(&cmds, choice); } void help(void) /* Not everyone is a rocket scientist. */ { struct help { char *thing; char *help; } *pi; static struct help info[] = { { nil, "Names:" }, { "rootdev", "Root device" }, { "ramimagedev", "Device to use as RAM disk image " }, { "ramsize", "RAM disk size (if no image device) " }, { "bootdev", "Special name for the boot device" }, { "fd0, d0p2, c0d0p1s0", "Devices (as in /dev)" }, { "image", "Name of the boot image to use" }, { "main", "Startup function" }, { "bootdelay", "Delay in msec after loading image" }, { nil, "Commands:" }, { "name = [device] value", "Set environment variable" }, { "name() { ... }", "Define function" }, { "name(key,text) { ... }", "A menu option like: minix(=,Start MINIX) {boot}" }, { "name", "Call function" }, { "boot [device]", "Boot Minix or another O.S." }, { "ctty [line]", "Duplicate to serial line" }, { "delay [msec]", "Delay (500 msec default)" }, { "echo word ...", "Display the words" }, { "ls [directory]", "List contents of directory" }, { "menu", "Show menu and choose menu option" }, { "save / set", "Save or show environment" }, { "trap msec command", "Schedule command " }, { "unset name ...", "Unset variable or set to default" }, { "exit / off", "Exit the Monitor / Power off" }, }; for (pi= info; pi < arraylimit(info); pi++) { if (pi->thing != nil) printf(" %-24s- ", pi->thing); printf("%s\n", pi->help); } } void execute(void) /* Get one command from the command chain and execute it. */ { token *second, *third, *fourth, *sep; char *name; int res; size_t n= 0; if (err) { /* An error occured, stop interpreting. */ while (cmds != nil) voidtoken(); return; } if (expired()) { /* Timer expired? */ parse_code(Thandler); unschedule(); } /* There must be a separator lurking somewhere. */ for (sep= cmds; sep != nil && sep->token[0] != ';'; sep= sep->next) n++; name= cmds->token; res= reserved(name); if ((second= cmds->next) != nil && (third= second->next) != nil) fourth= third->next; /* Null command? */ if (n == 0) { voidtoken(); return; } else /* name = [device] value? */ if ((n == 3 || n == 4) && !sugar(name) && second->token[0] == '=' && !sugar(third->token) && (n == 3 || (n == 4 && third->token[0] == 'd' && !sugar(fourth->token) ))) { char *value= third->token; int flags= E_VAR; if (n == 4) { value= fourth->token; flags|= E_DEV; } if ((flags= b_setvar(flags, name, value)) != 0) { printf("%s is a %s\n", name, flags & E_RESERVED ? "reserved word" : "special function"); err= 1; } while (cmds != sep) voidtoken(); return; } else /* name '(arg)' ... ? */ if (n >= 3 && !sugar(name) && second->token[0] == '(' ) { token *fun; int c, flags, depth; char *body; size_t len; sep= fun= third; depth= 0; len= 1; while (sep != nil) { if ((c= sep->token[0]) == ';' && depth == 0) break; len+= strlen(sep->token) + 1; sep= sep->next; if (c == '{') depth++; if (c == '}' && --depth == 0) break; } body= malloc(len * sizeof(char)); *body= 0; while (fun != sep) { strcat(body, fun->token); if (!sugar(fun->token) && !sugar(fun->next->token) ) strcat(body, " "); fun= fun->next; } second->token[strlen(second->token)-1]= 0; if (depth != 0) { printf("Missing '}'\n"); err= 1; } else if ((flags= b_setenv(E_FUNCTION, name, second->token+1, body)) != 0) { printf("%s is a %s\n", name, flags & E_RESERVED ? "reserved word" : "special variable"); err= 1; } while (cmds != sep) voidtoken(); free(body); return; } else /* Grouping? */ if (name[0] == '{') { token **acmds= &cmds->next; char *t; int depth= 1; /* Find and remove matching '}' */ depth= 1; while (*acmds != nil) { t= (*acmds)->token; if (t[0] == '{') depth++; if (t[0] == '}' && --depth == 0) { t[0]= ';'; break; } acmds= &(*acmds)->next; } voidtoken(); return; } else /* Command coming up, check if ESC typed. */ if (interrupt()) { return; } else /* unset name ..., echo word ...? */ if (n >= 1 && (res == R_UNSET || res == R_ECHO)) { char *arg= poptoken(), *p; for (;;) { free(arg); if (cmds == sep) break; arg= poptoken(); if (res == R_UNSET) { /* unset arg */ b_unset(arg); } else { /* echo arg */ p= arg; while (*p != 0) { if (*p != '\\') { putch(*p); } else switch (*++p) { case 0: if (cmds == sep) return; continue; case 'n': putch('\n'); break; case 'v': printf(version); break; case 'c': clear_screen(); break; case 'w': for (;;) { if (interrupt()) return; if (getch() == '\n') break; } break; default: putch(*p); } p++; } putch(cmds != sep ? ' ' : '\n'); } } return; } else /* boot -opts? */ if (n == 2 && res == R_BOOT && second->token[0] == '-') { static char optsvar[]= "bootopts"; (void) b_setvar(E_VAR, optsvar, second->token); voidtoken(); voidtoken(); bootminix(); b_unset(optsvar); return; } else /* boot device, ls dir, delay msec? */ if (n == 2 && (res == R_BOOT || res == R_CTTY || res == R_DELAY || res == R_LS) ) { if (res == R_BOOT) boot_device(second->token); if (res == R_CTTY) ctty(second->token); if (res == R_DELAY) delay(second->token); if (res == R_LS) ls(second->token); voidtoken(); voidtoken(); return; } else /* trap msec command? */ if (n == 3 && res == R_TRAP && numeric(second->token)) { long msec= a2l(second->token); voidtoken(); voidtoken(); schedule(msec, poptoken()); return; } else /* Simple command. */ if (n == 1) { char *body; int ok= 0; name= poptoken(); switch (res) { case R_BOOT: bootminix(); ok= 1; break; case R_DELAY: delay("500"); ok= 1; break; case R_LS: ls(null); ok= 1; break; case R_MENU: menu(); ok= 1; break; case R_SAVE: save_parameters(); ok= 1;break; case R_SET: show_env(); ok= 1; break; case R_HELP: help(); ok= 1; break; case R_EXIT: exit(0); case R_OFF: off(); ok= 1; break; case R_CTTY: ctty(nil); ok= 1; break; } /* Command to check bootparams: */ if (strcmp(name, ":") == 0) ok= 1; /* User defined function. */ if (!ok && (body= b_body(name)) != nil) { (void) tokenize(&cmds, body); ok= 1; } if (!ok) printf("%s: unknown function", name); free(name); if (ok) return; } else { /* Syntax error. */ printf("Can't parse:"); while (cmds != sep) { printf(" %s", cmds->token); voidtoken(); } } /* Getting here means that the command is not understood. */ printf("\nTry 'help'\n"); err= 1; } int run_trailer(void) /* Run the trailer function between loading Minix and handing control to it. * Return true iff there was no error. */ { token *save_cmds= cmds; cmds= nil; (void) tokenize(&cmds, "trailer"); while (cmds != nil) execute(); cmds= save_cmds; return !err; } void monitor(void) /* Read a line and tokenize it. */ { char *line; unschedule(); /* Kill a trap. */ err= 0; /* Clear error state. */ if (istty) printf("%s>", bootdev.name); line= readline(); (void) tokenize(&cmds, line); free(line); (void) escape(); /* Forget if ESC typed. */ } #if BIOS void boot(void) /* Load Minix and start it, among other things. */ { /* Initialize tables. */ initialize(); /* Get environment variables from the parameter sector. */ get_parameters(); while (1) { /* While there are commands, execute them! */ while (cmds != nil) execute(); /* The "monitor" is just a "read one command" thing. */ monitor(); } } #endif /* BIOS */ #if UNIX void main(int argc, char **argv) /* Do not load or start anything, just edit parameters. */ { int i; char bootcode[SECTOR_SIZE]; struct termios rawterm; istty= (argc <= 2 && tcgetattr(0, &termbuf) == 0); if (argc < 2) { fprintf(stderr, "Usage: edparams device [command ...]\n"); exit(1); } /* Go over the arguments, changing control characters to spaces. */ for (i= 2; i < argc; i++) { char *p; for (p= argv[i]; *p != 0; p++) { if ((unsigned) *p < ' ' && *p != '\n') *p= ' '; } } bootdev.name= argv[1]; if (strncmp(bootdev.name, "/dev/", 5) == 0) bootdev.name+= 5; if ((bootdev.device= open(argv[1], O_RDWR, 0666)) < 0) fatal(bootdev.name); /* Check if it is a bootable Minix device. */ if (readsectors(mon2abs(bootcode), lowsec, 1) != 0 || memcmp(bootcode, boot_magic, sizeof(boot_magic)) != 0) { fprintf(stderr, "edparams: %s: not a bootable Minix device\n", bootdev.name); exit(1); } /* Print greeting message. */ if (istty) printf("Boot parameters editor.\n"); signal(SIGINT, trap); signal(SIGALRM, trap); if (istty) { rawterm= termbuf; rawterm.c_lflag&= ~(ICANON|ECHO|IEXTEN); rawterm.c_cc[VINTR]= ESC; if (tcsetattr(0, TCSANOW, &rawterm) < 0) fatal(""); } /* Get environment variables from the parameter sector. */ get_parameters(); i= 2; for (;;) { /* While there are commands, execute them! */ while (cmds != nil || i < argc) { if (cmds == nil) { /* A command line command. */ parse_code(argv[i++]); } execute(); /* Bail out on errors if not interactive. */ if (err && !istty) exit(1); } /* Commands on the command line? */ if (argc > 2) break; /* The "monitor" is just a "read one command" thing. */ monitor(); } exit(0); } #endif /* UNIX */ /* * $PchId: boot.c,v 1.14 2002/02/27 19:46:14 philip Exp $ */