495970a17c
reset (triple fault).
2041 lines
46 KiB
C
2041 lines
46 KiB
C
/* 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 <stddef.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <limits.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <machine/partition.h>
|
|
#include <machine/bios.h>
|
|
#include <minix/config.h>
|
|
#include <minix/type.h>
|
|
#include <minix/dmap.h>
|
|
#include <minix/const.h>
|
|
#include <minix/minlib.h>
|
|
#include <minix/syslib.h>
|
|
#if BIOS
|
|
#include <kernel/const.h>
|
|
#include <sys/video.h>
|
|
#endif
|
|
#if UNIX
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
#include <termios.h>
|
|
#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)))
|
|
|
|
int serial_line = -1;
|
|
|
|
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];
|
|
|
|
static const 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";
|
|
}
|
|
|
|
/* CD's are addressed in 2048-byte sectors.
|
|
* In order to be able to read CD's but maintain the same interface of 512-byte
|
|
* sector addressing, we check if the device is a CD in readsectors() and if so,
|
|
* read it into our own buffer first
|
|
*/
|
|
int readsectors(u32_t bufaddr, u32_t sector, u8_t count)
|
|
{
|
|
#define CDSECTOR_SIZE 2048
|
|
static char cdbuf[CDSECTOR_SIZE];
|
|
static i32_t cdbuf_sec = -1;
|
|
i32_t cdsec;
|
|
|
|
if(device != cddevice) {
|
|
return biosreadsectors(bufaddr, sector, count);
|
|
}
|
|
|
|
while(count > 0) {
|
|
u32_t offset;
|
|
#define FACTOR (CDSECTOR_SIZE/SECTOR_SIZE)
|
|
cdsec = sector / FACTOR;
|
|
offset = (sector % FACTOR) * SECTOR_SIZE;
|
|
if(cdsec != cdbuf_sec) {
|
|
int r;
|
|
if((r=biosreadsectors(mon2abs(cdbuf), cdsec, 1)) != 0) {
|
|
printf("error %d\n", r);
|
|
return r;
|
|
}
|
|
cdbuf_sec = cdsec;
|
|
}
|
|
raw_copy(bufaddr, mon2abs(cdbuf) + offset, SECTOR_SIZE);
|
|
bufaddr += SECTOR_SIZE;
|
|
count--;
|
|
sector++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
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";
|
|
}
|
|
}
|
|
|
|
static void rwerr(const 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;
|
|
|
|
static struct termios termbuf;
|
|
static int istty;
|
|
|
|
void quit(int status)
|
|
{
|
|
if (istty) (void) tcsetattr(0, TCSANOW, &termbuf);
|
|
exit(status);
|
|
}
|
|
|
|
#define exit(s) quit(s)
|
|
|
|
void report(const char *label)
|
|
/* edparams: label: No such file or directory */
|
|
{
|
|
fprintf(stderr, "edparams: %s: %s\n", label, strerror(errno));
|
|
}
|
|
|
|
void fatal(const 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 */
|
|
|
|
static 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;
|
|
}
|
|
|
|
static int sugar(const char *tok)
|
|
/* Recognize special tokens. */
|
|
{
|
|
return strchr("=(){};\n", tok[0]) != nil;
|
|
}
|
|
|
|
static 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;
|
|
|
|
static 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;
|
|
}
|
|
|
|
static token *cmds; /* String of commands to execute. */
|
|
static int err; /* Set on an error. */
|
|
|
|
static char *poptoken(void)
|
|
/* Pop one token off the command chain. */
|
|
{
|
|
token *cmd= cmds;
|
|
char *tok= cmd->token;
|
|
|
|
cmds= cmd->next;
|
|
free(cmd);
|
|
|
|
return tok;
|
|
}
|
|
|
|
static 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);
|
|
}
|
|
|
|
static int interrupt(void)
|
|
/* Clean up after an ESC has been typed. */
|
|
{
|
|
if (escape()) {
|
|
printf("[ESC]\n");
|
|
err= 1;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#if BIOS
|
|
|
|
static int activate;
|
|
|
|
struct biosdev {
|
|
char name[8];
|
|
int device, primary, secondary;
|
|
} bootdev, tmpdev;
|
|
|
|
static 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;
|
|
}
|
|
|
|
static 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
|
|
|
|
/* If we were booted from CD, remember what device it was. */
|
|
if(cdbooted)
|
|
cddevice = device;
|
|
else
|
|
cddevice = 0xff; /* Invalid. */
|
|
|
|
/* 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);
|
|
}
|
|
|
|
/* If we're a CD, we know what we want. */
|
|
if(device == cddevice) {
|
|
p = 1; /* We know this is the root FS. */
|
|
lowsec = table[p]->lowsec;
|
|
bootdev.primary = p;
|
|
break; /* Found! */
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
if(device == cddevice) {
|
|
strcpy(bootdev.name, CDNAME);
|
|
} else {
|
|
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 <vdisk> [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, R_RESET
|
|
};
|
|
|
|
static char resnames[][6] = {
|
|
"", "boot", "ctty", "delay", "echo", "exit", "help",
|
|
"ls", "menu", "off", "save", "set", "trap", "unset", "reset",
|
|
};
|
|
|
|
/* Using this for all null strings saves a lot of memory. */
|
|
#define null (resnames[0])
|
|
|
|
static enum resnames reserved(const char *s)
|
|
/* Recognize reserved strings. */
|
|
{
|
|
enum resnames r;
|
|
|
|
for (r= R_BOOT; r <= R_RESET; r++) {
|
|
if (strcmp(s, resnames[r]) == 0) return r;
|
|
}
|
|
return R_NULL;
|
|
}
|
|
|
|
static void sfree(char *s)
|
|
/* Free a non-null string. */
|
|
{
|
|
if (s != nil && s != null) free(s);
|
|
}
|
|
|
|
static char *copystr(const 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;
|
|
}
|
|
|
|
static int is_default(const environment *e)
|
|
{
|
|
return (e->flags & E_SPECIAL) && e->defval == nil;
|
|
}
|
|
|
|
static environment **searchenv(const 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(const char *name)
|
|
/* The value of a variable. */
|
|
{
|
|
environment *e= b_getenv(name);
|
|
|
|
return e == nil || !(e->flags & E_VAR) ? nil : e->value;
|
|
}
|
|
|
|
static char *b_body(const char *name)
|
|
/* The value of a function. */
|
|
{
|
|
environment *e= b_getenv(name);
|
|
|
|
return e == nil || !(e->flags & E_FUNCTION) ? nil : e->value;
|
|
}
|
|
|
|
static int b_setenv(int flags, const char *name, const char *arg,
|
|
const 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(const 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(const 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(const 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;
|
|
}
|
|
|
|
static void get_parameters(void)
|
|
{
|
|
char params[SECTOR_SIZE + 1];
|
|
token **acmds;
|
|
int r, 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
|
|
}
|
|
|
|
static char *addptr;
|
|
|
|
static void addparm(const char *n)
|
|
{
|
|
while (*n != 0 && *addptr != 0) *addptr++ = *n++;
|
|
}
|
|
|
|
static 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");
|
|
}
|
|
}
|
|
|
|
static 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;
|
|
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 || strcmp(n, CDNAME) == 0) {
|
|
dev= DEV_RAM;
|
|
} 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);
|
|
return 0;
|
|
}
|
|
|
|
static void boot_device(char *devname)
|
|
/* Boot the device named by devname. */
|
|
{
|
|
dev_t dev= name2dev(devname);
|
|
int save_dev= device;
|
|
int r;
|
|
const char *err;
|
|
|
|
if (tmpdev.device < 0) {
|
|
/* FIXME: clearer error message. */
|
|
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();
|
|
}
|
|
|
|
static void ctty(char *line)
|
|
{
|
|
if (line == nil) {
|
|
serial_line = -1;
|
|
} else if (between('0', line[0], '3') && line[1] == 0) {
|
|
serial_line = line[0] - '0';
|
|
} else {
|
|
printf("Bad serial line number: %s\n", line);
|
|
return;
|
|
}
|
|
serial_init(serial_line);
|
|
}
|
|
|
|
#else /* DOS */
|
|
|
|
static void boot_device(char *devname)
|
|
/* No booting of other devices under DOS. */
|
|
{
|
|
printf("Can't boot devices under DOS\n");
|
|
}
|
|
|
|
static 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 */
|
|
|
|
static 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 (r_readdir(name) != 0) printf("%s/%s\n", dir, name);
|
|
}
|
|
|
|
static u32_t milli_time(void)
|
|
{
|
|
return get_tick() * MSEC_PER_TICK;
|
|
}
|
|
|
|
static u32_t milli_since(u32_t base)
|
|
{
|
|
return (milli_time() + (TICKS_PER_DAY*MSEC_PER_TICK) - base)
|
|
% (TICKS_PER_DAY*MSEC_PER_TICK);
|
|
}
|
|
|
|
static char *Thandler;
|
|
static u32_t Tbase, Tcount;
|
|
|
|
static void unschedule(void)
|
|
/* Invalidate a waiting command. */
|
|
{
|
|
alarm(0);
|
|
|
|
if (Thandler != nil) {
|
|
free(Thandler);
|
|
Thandler= nil;
|
|
}
|
|
}
|
|
|
|
static 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(const 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);
|
|
}
|
|
|
|
static enum whatfun { NOFUN, SELECT, DEFFUN, USERFUN } menufun(const 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;
|
|
case NOFUN:
|
|
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);
|
|
}
|
|
}
|
|
|
|
static void execute(void)
|
|
/* Get one command from the command chain and execute it. */
|
|
{
|
|
token *second, *third, *fourth, *sep;
|
|
char *name;
|
|
enum resnames 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;
|
|
case R_RESET: reset(); 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;
|
|
}
|
|
|
|
static 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) {
|
|
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);
|
|
}
|
|
|
|
reset() { }
|
|
|
|
#endif /* UNIX */
|
|
|
|
/*
|
|
* $PchId: boot.c,v 1.14 2002/02/27 19:46:14 philip Exp $
|
|
*/
|
|
|