| /* asmstub.c - a version of shared_src/asm.S that works under Unix */ |
| /* |
| * GRUB -- GRand Unified Bootloader |
| * Copyright (C) 1999, 2000 Free Software Foundation, Inc. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| */ |
| |
| /* Try to use glibc's transparant LFS support. */ |
| #define _LARGEFILE_SOURCE 1 |
| /* lseek becomes synonymous with lseek64. */ |
| #define _FILE_OFFSET_BITS 64 |
| |
| /* Simulator entry point. */ |
| int grub_stage2 (void); |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <ctype.h> |
| #include <assert.h> |
| #include <stdio.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <time.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <setjmp.h> |
| |
| #ifdef __linux__ |
| # include <sys/ioctl.h> /* ioctl */ |
| # include <linux/hdreg.h> /* HDIO_GETGEO */ |
| # if __GLIBC__ < 2 |
| /* Maybe libc doesn't have large file support. */ |
| # include <linux/unistd.h> /* _llseek */ |
| # include <linux/fs.h> /* BLKFLSBUF */ |
| # endif /* GLIBC < 2 */ |
| # ifndef BLKFLSBUF |
| # define BLKFLSBUF _IO (0x12,97) |
| # endif /* ! BLKFLSBUF */ |
| #endif /* __linux__ */ |
| |
| #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) |
| # include <sys/ioctl.h> /* ioctl */ |
| # include <sys/disklabel.h> |
| #endif /* __FreeBSD__ || __NetBSD__ || __OpenBSD__ */ |
| |
| #ifdef HAVE_OPENDISK |
| # include <util.h> |
| #endif /* HAVE_OPENDISK */ |
| |
| /* We want to prevent any circularararity in our stubs, as well as |
| libc name clashes. */ |
| #define WITHOUT_LIBC_STUBS 1 |
| #include "shared.h" |
| |
| /* Simulated memory sizes. */ |
| #define EXTENDED_MEMSIZE (3 * 1024 * 1024) /* 3MB */ |
| #define CONVENTIONAL_MEMSIZE (640) /* 640kB */ |
| |
| /* Simulated disk sizes. */ |
| #define DEFAULT_FD_CYLINDERS 80 |
| #define DEFAULT_FD_HEADS 2 |
| #define DEFAULT_FD_SECTORS 18 |
| #define DEFAULT_HD_CYLINDERS 620 |
| #define DEFAULT_HD_HEADS 128 |
| #define DEFAULT_HD_SECTORS 63 |
| |
| unsigned long install_partition = 0x20000; |
| unsigned long boot_drive = 0; |
| char version_string[] = VERSION; |
| char config_file[128] = "/boot/grub/menu.lst"; /* FIXME: arbitrary */ |
| |
| /* Emulation requirements. */ |
| char *grub_scratch_mem = 0; |
| |
| #define NUM_DISKS 256 |
| struct geometry *disks = 0; |
| |
| /* The map between BIOS drives and UNIX device file names. */ |
| char **device_map = 0; |
| |
| /* The jump buffer for exiting correctly. */ |
| static jmp_buf env_for_exit; |
| |
| /* Forward declarations. */ |
| static void get_floppy_disk_name (char *name, int unit); |
| static void get_ide_disk_name (char *name, int unit); |
| static void get_scsi_disk_name (char *name, int unit); |
| static void init_device_map (void); |
| |
| /* The main entry point into this mess. */ |
| int |
| grub_stage2 (void) |
| { |
| /* These need to be static, because they survive our stack transitions. */ |
| static int status = 0; |
| static char *realstack; |
| char *scratch, *simstack; |
| int i; |
| |
| /* We need a nested function so that we get a clean stack frame, |
| regardless of how the code is optimized. */ |
| static volatile void doit () |
| { |
| /* Make sure our stack lives in the simulated memory area. */ |
| asm volatile ("movl %%esp, %0\n\tmovl %1, %%esp\n" |
| : "=&r" (realstack) : "r" (simstack)); |
| |
| /* Do a setjmp here for the stop command. */ |
| if (! setjmp (env_for_exit)) |
| { |
| /* Actually enter the generic stage2 code. */ |
| status = 0; |
| init_bios_info (); |
| } |
| else |
| { |
| /* If ERRNUM is non-zero, then set STATUS to non-zero. */ |
| if (errnum) |
| status = 1; |
| } |
| |
| /* Replace our stack before we use any local variables. */ |
| asm volatile ("movl %0, %%esp\n" : : "r" (realstack)); |
| } |
| |
| assert (grub_scratch_mem == 0); |
| scratch = malloc (0x100000 + EXTENDED_MEMSIZE + 15); |
| assert (scratch); |
| grub_scratch_mem = (char *) ((((int) scratch) >> 4) << 4); |
| |
| /* FIXME: simulate the memory holes using mprot, if available. */ |
| |
| assert (disks == 0); |
| disks = malloc (NUM_DISKS * sizeof (*disks)); |
| assert (disks); |
| /* Initialize DISKS. */ |
| for (i = 0; i < NUM_DISKS; i++) |
| disks[i].flags = -1; |
| |
| init_device_map (); |
| |
| /* Check some invariants. */ |
| assert ((SCRATCHSEG << 4) == SCRATCHADDR); |
| assert ((BUFFERSEG << 4) == BUFFERADDR); |
| assert (BUFFERADDR + BUFFERLEN == SCRATCHADDR); |
| assert (FSYS_BUF % 16 == 0); |
| assert (FSYS_BUF + FSYS_BUFLEN == BUFFERADDR); |
| |
| #ifdef HAVE_LIBCURSES |
| /* Get into char-at-a-time mode. */ |
| if (use_curses) |
| { |
| initscr (); |
| cbreak (); |
| noecho (); |
| nonl (); |
| scrollok (stdscr, TRUE); |
| keypad (stdscr, TRUE); |
| wtimeout (stdscr, 100); |
| } |
| #endif |
| |
| /* Make sure that actual writing is done. */ |
| sync (); |
| |
| /* Set our stack, and go for it. */ |
| simstack = (char *) PROTSTACKINIT; |
| doit (); |
| |
| /* I don't know if this is necessary really. */ |
| sync (); |
| |
| #ifdef HAVE_LIBCURSES |
| if (use_curses) |
| endwin (); |
| #endif |
| |
| /* Close off the file descriptors we used. */ |
| for (i = 0; i < NUM_DISKS; i ++) |
| if (disks[i].flags != -1) |
| { |
| #ifdef __linux__ |
| /* In Linux, invalidate the buffer cache. In other OSes, reboot |
| is one of the solutions... */ |
| ioctl (disks[i].flags, BLKFLSBUF, 0); |
| #else |
| # warning "In your operating system, the buffer cache will not be flushed." |
| #endif |
| close (disks[i].flags); |
| } |
| |
| /* Release memory. */ |
| for (i = 0; i < NUM_DISKS; i++) |
| free (device_map[i]); |
| free (device_map[i]); |
| device_map = 0; |
| free (disks); |
| disks = 0; |
| free (scratch); |
| grub_scratch_mem = 0; |
| |
| /* Ahh... at last we're ready to return to caller. */ |
| return status; |
| } |
| |
| static void |
| init_device_map (void) |
| { |
| int i; |
| int num_hd = 0; |
| FILE *fp = 0; |
| |
| static void print_error (int no, const char *msg) |
| { |
| fprintf (stderr, "%s:%d: error: %s\n", device_map_file, no, msg); |
| } |
| |
| assert (device_map == 0); |
| device_map = malloc (NUM_DISKS * sizeof (char *)); |
| assert (device_map); |
| |
| /* Probe devices for creating the device map. */ |
| |
| /* Initialize DEVICE_MAP. */ |
| for (i = 0; i < NUM_DISKS; i++) |
| device_map[i] = 0; |
| |
| if (device_map_file) |
| { |
| /* Open the device map file. */ |
| fp = fopen (device_map_file, "r"); |
| if (fp) |
| { |
| /* If there is the device map file, use the data in it instead of |
| probing devices. */ |
| char buf[1024]; /* XXX */ |
| int line_number = 0; |
| |
| while (fgets (buf, sizeof (buf), fp)) |
| { |
| char *ptr, *eptr; |
| int drive; |
| int is_floppy = 0; |
| |
| /* Increase the number of lines. */ |
| line_number++; |
| |
| /* If the first character is '#', skip it. */ |
| if (buf[0] == '#') |
| continue; |
| |
| ptr = buf; |
| /* Skip leading spaces. */ |
| while (*ptr && isspace (*ptr)) |
| ptr++; |
| |
| if (*ptr != '(') |
| { |
| print_error (line_number, "No open parenthesis found"); |
| stop (); |
| } |
| |
| ptr++; |
| if ((*ptr != 'f' && *ptr != 'h') || *(ptr + 1) != 'd') |
| { |
| print_error (line_number, "Bad drive name"); |
| stop (); |
| } |
| |
| if (*ptr == 'f') |
| is_floppy = 1; |
| |
| ptr += 2; |
| drive = strtoul (ptr, &ptr, 10); |
| if (drive < 0 || drive > 8) |
| { |
| print_error (line_number, "Bad device number"); |
| stop (); |
| } |
| |
| if (! is_floppy) |
| drive += 0x80; |
| |
| if (*ptr != ')') |
| { |
| print_error (line_number, "No close parenthesis found"); |
| stop (); |
| } |
| |
| ptr++; |
| /* Skip spaces. */ |
| while (*ptr && isspace (*ptr)) |
| ptr++; |
| |
| if (! *ptr) |
| { |
| print_error (line_number, "No filename found"); |
| stop (); |
| } |
| |
| /* Terminate the filename. */ |
| eptr = ptr; |
| while (*eptr && ! isspace (*eptr)) |
| eptr++; |
| *eptr = 0; |
| |
| assign_device_name (drive, ptr); |
| } |
| |
| fclose (fp); |
| return; |
| } |
| } |
| |
| /* Print something as the user does not think GRUB has been crashed. */ |
| fprintf (stderr, |
| "Probe devices to guess BIOS drives. This may take a long time.\n"); |
| |
| if (device_map_file) |
| /* Try to open the device map file to write the probed data. */ |
| fp = fopen (device_map_file, "w"); |
| |
| /* Floppies. */ |
| if (! no_floppy) |
| for (i = 0; i < 2; i++) |
| { |
| char name[16]; |
| |
| if (i == 1 && ! probe_second_floppy) |
| break; |
| |
| get_floppy_disk_name (name, i); |
| /* In floppies, write the map, whether check_device succeeds |
| or not, because the user just does not insert floppies. */ |
| if (fp) |
| fprintf (fp, "(fd%d)\t%s\n", i, name); |
| |
| if (check_device (name)) |
| assign_device_name (i, name); |
| } |
| |
| /* IDE disks. */ |
| for (i = 0; i < 8; i++) |
| { |
| char name[16]; |
| |
| get_ide_disk_name (name, i); |
| if (check_device (name)) |
| { |
| assign_device_name (num_hd + 0x80, name); |
| |
| /* If the device map file is opened, write the map. */ |
| if (fp) |
| fprintf (fp, "(hd%d)\t%s\n", num_hd, name); |
| |
| num_hd++; |
| } |
| } |
| |
| /* The rest is SCSI disks. */ |
| for (i = 0; i < 16; i++) |
| { |
| char name[16]; |
| |
| get_scsi_disk_name (name, i); |
| if (check_device (name)) |
| { |
| assign_device_name (num_hd + 0x80, name); |
| |
| /* If the device map file is opened, write the map. */ |
| if (fp) |
| fprintf (fp, "(hd%d)\t%s\n", num_hd, name); |
| |
| num_hd++; |
| } |
| } |
| |
| /* OK, close the device map file if opened. */ |
| if (fp) |
| fclose (fp); |
| } |
| |
| /* These three functions are quite different among OSes. */ |
| static void |
| get_floppy_disk_name (char *name, int unit) |
| { |
| #if defined(__linux__) || defined(__GNU__) |
| /* GNU/Linux and GNU/Hurd */ |
| sprintf (name, "/dev/fd%d", unit); |
| #elif defined(__FreeBSD__) |
| /* FreeBSD */ |
| sprintf (name, "/dev/rfd%d", unit); |
| #elif defined(__NetBSD__) |
| /* NetBSD */ |
| /* opendisk() doesn't work for floppies. */ |
| sprintf (name, "/dev/rfd%da", unit); |
| #elif defined(__OpenBSD__) |
| /* OpenBSD */ |
| sprintf (name, "/dev/rfd%dc", unit); |
| #else |
| # warning "BIOS floppy drives cannot be guessed in your operating system." |
| /* Set NAME to a bogus string. */ |
| *name = 0; |
| #endif |
| } |
| |
| static void |
| get_ide_disk_name (char *name, int unit) |
| { |
| #if defined(__linux__) |
| /* GNU/Linux */ |
| sprintf (name, "/dev/hd%c", unit + 'a'); |
| #elif defined(__GNU__) |
| /* GNU/Hurd */ |
| sprintf (name, "/dev/hd%d", unit); |
| #elif defined(__FreeBSD__) |
| /* FreeBSD */ |
| sprintf (name, "/dev/rwd%d", unit); |
| #elif defined(__NetBSD__) && defined(HAVE_OPENDISK) |
| /* NetBSD */ |
| char shortname[16]; |
| int fd; |
| |
| sprintf (shortname, "wd%d", unit); |
| fd = opendisk (shortname, O_RDONLY, name, |
| 16, /* length of NAME */ |
| 0 /* char device */ |
| ); |
| close (fd); |
| #elif defined(__OpenBSD__) |
| /* OpenBSD */ |
| sprintf (name, "/dev/rwd%dc", unit); |
| #else |
| # warning "BIOS IDE drives cannot be guessed in your operating system." |
| /* Set NAME to a bogus string. */ |
| *name = 0; |
| #endif |
| } |
| |
| static void |
| get_scsi_disk_name (char *name, int unit) |
| { |
| #if defined(__linux__) |
| /* GNU/Linux */ |
| sprintf (name, "/dev/sd%c", unit + 'a'); |
| #elif defined(__GNU__) |
| /* GNU/Hurd */ |
| sprintf (name, "/dev/sd%d", unit); |
| #elif defined(__FreeBSD__) |
| /* FreeBSD */ |
| sprintf (name, "/dev/rda%d", unit); |
| #elif defined(__NetBSD__) && defined(HAVE_OPENDISK) |
| /* NetBSD */ |
| char shortname[16]; |
| int fd; |
| |
| sprintf (shortname, "sd%d", unit); |
| fd = opendisk (shortname, O_RDONLY, name, |
| 16, /* length of NAME */ |
| 0 /* char device */ |
| ); |
| close (fd); |
| #elif defined(__OpenBSD__) |
| /* OpenBSD */ |
| sprintf (name, "/dev/rsd%dc", unit); |
| #else |
| # warning "BIOS SCSI drives cannot be guessed in your operating system." |
| /* Set NAME to a bogus string. */ |
| *name = 0; |
| #endif |
| } |
| |
| /* Check if DEVICE can be read. If an error occurs, return zero, |
| otherwise return non-zero. */ |
| int |
| check_device (const char *device) |
| { |
| char buf[512]; |
| FILE *fp; |
| |
| fp = fopen (device, "r"); |
| if (! fp) |
| { |
| switch (errno) |
| { |
| #ifdef ENOMEDIUM |
| case ENOMEDIUM: |
| # if 0 |
| /* At the moment, this finds only CDROMs, which can't be |
| read anyway, so leave it out. Code should be |
| reactivated if `removable disks' and CDROMs are |
| supported. */ |
| /* Accept it, it may be inserted. */ |
| return 1; |
| # endif |
| break; |
| #endif /* ENOMEDIUM */ |
| default: |
| /* Break case and leave. */ |
| break; |
| } |
| /* Error opening the device. */ |
| return 0; |
| } |
| |
| #ifdef __linux__ |
| /* Check if DEVICE is a CD-ROM drive by the HDIO_GETGEO ioctl. */ |
| { |
| struct hd_geometry hdg; |
| struct stat st; |
| |
| if (fstat (fileno (fp), &st)) |
| return 0; |
| |
| if (S_ISBLK (st.st_mode) && ioctl (fileno (fp), HDIO_GETGEO, &hdg)) |
| return 0; |
| } |
| #endif /* __linux__ */ |
| |
| /* Attempt to read the first sector. */ |
| if (fread (buf, 1, 512, fp) != 512) |
| { |
| fclose (fp); |
| return 0; |
| } |
| |
| fclose (fp); |
| return 1; |
| } |
| |
| /* Assign DRIVE to a device name DEVICE. */ |
| void |
| assign_device_name (int drive, const char *device) |
| { |
| /* If DRIVE is already assigned, free it. */ |
| if (device_map[drive]) |
| free (device_map[drive]); |
| |
| /* If the old one is already opened, close it. */ |
| if (disks[drive].flags != -1) |
| { |
| close (disks[drive].flags); |
| disks[drive].flags = -1; |
| } |
| |
| /* Assign DRIVE to DEVICE. */ |
| if (! device) |
| device_map[drive] = 0; |
| else |
| device_map[drive] = strdup (device); |
| } |
| |
| void |
| stop (void) |
| { |
| #ifdef HAVE_LIBCURSES |
| if (use_curses) |
| endwin (); |
| #endif |
| |
| /* Jump to doit. */ |
| longjmp (env_for_exit, 1); |
| } |
| |
| |
| /* calls for direct boot-loader chaining */ |
| void |
| chain_stage1 (int segment, int offset, int part_table_addr) |
| { |
| stop (); |
| } |
| |
| |
| void |
| chain_stage2 (int segment, int offset) |
| { |
| stop (); |
| } |
| |
| |
| /* do some funky stuff, then boot linux */ |
| void |
| linux_boot (void) |
| { |
| stop (); |
| } |
| |
| |
| /* For bzImage kernels. */ |
| void |
| big_linux_boot (void) |
| { |
| stop (); |
| } |
| |
| |
| /* booting a multiboot executable */ |
| void |
| multi_boot (int start, int mbi) |
| { |
| stop (); |
| } |
| |
| /* sets it to linear or wired A20 operation */ |
| void |
| gateA20 (int linear) |
| { |
| /* Nothing to do in the simulator. */ |
| } |
| |
| /* Set up the int15 handler. */ |
| void |
| set_int15_handler (void) |
| { |
| /* Nothing to do in the simulator. */ |
| } |
| |
| /* Restore the original int15 handler. */ |
| void |
| unset_int15_handler (void) |
| { |
| /* Nothing to do in the simulator. */ |
| } |
| |
| /* The key map. */ |
| unsigned short bios_key_map[KEY_MAP_SIZE + 1]; |
| unsigned short ascii_key_map[KEY_MAP_SIZE + 1]; |
| |
| /* Copy MAP to the drive map and set up the int13 handler. */ |
| void |
| set_int13_handler (unsigned short *map) |
| { |
| /* Nothing to do in the simulator. */ |
| } |
| |
| int |
| get_code_end (void) |
| { |
| /* Just return a little area for simulation. */ |
| return BOOTSEC_LOCATION + (60 * 1024); |
| } |
| |
| |
| /* memory probe routines */ |
| int |
| get_memsize (int type) |
| { |
| if (!type) |
| return CONVENTIONAL_MEMSIZE; |
| else |
| return EXTENDED_MEMSIZE; |
| } |
| |
| |
| /* get_eisamemsize() : return packed EISA memory map, lower 16 bits is |
| * memory between 1M and 16M in 1K parts, upper 16 bits is |
| * memory above 16M in 64K parts. If error, return -1. |
| */ |
| int |
| get_eisamemsize (void) |
| { |
| return (EXTENDED_MEMSIZE / 1024); |
| } |
| |
| |
| #define MMAR_DESC_TYPE_AVAILABLE 1 /* available to OS */ |
| #define MMAR_DESC_TYPE_RESERVED 2 /* not available */ |
| #define MMAR_DESC_TYPE_ACPI_RECLAIM 3 /* usable by OS after reading ACPI */ |
| #define MMAR_DESC_TYPE_ACPI_NVS 4 /* required to save between NVS sessions */ |
| |
| /* Fetch the next entry in the memory map and return the continuation |
| value. DESC is a pointer to the descriptor buffer, and CONT is the |
| previous continuation value (0 to get the first entry in the |
| map). */ |
| int |
| get_mmap_entry (struct mmar_desc *desc, int cont) |
| { |
| if (! cont) |
| { |
| /* First entry, located at 1MB. */ |
| desc->desc_len = sizeof (*desc) - sizeof (desc->desc_len); |
| desc->addr = 1024 * 1024; |
| desc->length = EXTENDED_MEMSIZE; |
| desc->type = MMAR_DESC_TYPE_AVAILABLE; |
| } |
| else |
| { |
| /* No more entries, so give an error. */ |
| desc->desc_len = 0; |
| } |
| return 0; |
| } |
| |
| |
| /* low-level timing info */ |
| int |
| getrtsecs (void) |
| { |
| /* FIXME: exact value is not important, so just return time_t for now. */ |
| return time (0); |
| } |
| |
| |
| /* low-level character I/O */ |
| void |
| cls (void) |
| { |
| #ifdef HAVE_LIBCURSES |
| if (use_curses) |
| clear (); |
| #endif |
| } |
| |
| |
| /* returns packed values, LSB+1 is x, LSB is y */ |
| int |
| getxy (void) |
| { |
| int y, x; |
| #ifdef HAVE_LIBCURSES |
| if (use_curses) |
| getyx (stdscr, y, x); |
| else |
| #endif |
| y = x = 0; |
| return (x << 8) | (y & 0xff); |
| } |
| |
| |
| void |
| gotoxy (int x, int y) |
| { |
| #ifdef HAVE_LIBCURSES |
| if (use_curses) |
| move (y, x); |
| #endif |
| } |
| |
| int |
| grub_setjmp (grub_jmp_buf env) |
| { |
| return setjmp (env); |
| } |
| |
| void |
| grub_longjmp (grub_jmp_buf env, int val) |
| { |
| longjmp (env, val); |
| } |
| |
| /* displays an ASCII character. IBM displays will translate some |
| characters to special graphical ones */ |
| void |
| grub_putchar (int c) |
| { |
| #ifdef HAVE_LIBCURSES |
| if (use_curses) |
| addch (c); |
| else |
| #endif |
| putchar (c); |
| } |
| |
| |
| /* The store for ungetch simulation. This is necessary, because |
| ncurses-1.9.9g is still used in the world and its ungetch is |
| completely broken. */ |
| #ifdef HAVE_LIBCURSES |
| static int save_char = ERR; |
| #endif |
| |
| /* returns packed BIOS/ASCII code */ |
| int |
| getkey (void) |
| { |
| int c; |
| |
| #ifdef HAVE_LIBCURSES |
| if (use_curses) |
| { |
| /* If checkkey has already got a character, then return it. */ |
| if (save_char != ERR) |
| { |
| c = save_char; |
| save_char = ERR; |
| return c; |
| } |
| |
| wtimeout (stdscr, -1); |
| c = getch (); |
| wtimeout (stdscr, 100); |
| } |
| else |
| #endif |
| c = getchar (); |
| |
| /* Quit if we get EOF. */ |
| if (c == -1) |
| stop (); |
| return c; |
| } |
| |
| |
| /* like 'getkey', but doesn't wait, returns -1 if nothing available */ |
| int |
| checkkey (void) |
| { |
| #ifdef HAVE_LIBCURSES |
| if (use_curses) |
| { |
| int c; |
| |
| /* Check for SAVE_CHAR. This should not be true, because this |
| means checkkey is called twice continuously. */ |
| if (save_char != ERR) |
| return save_char; |
| |
| c = getch (); |
| /* If C is not ERR, then put it back in the input queue. */ |
| if (c != ERR) |
| save_char = c; |
| return c; |
| } |
| #endif |
| |
| /* Just pretend they hit the space bar, then read the real key when |
| they call getkey. */ |
| return ' '; |
| } |
| |
| |
| /* sets text mode character attribute at the cursor position */ |
| void |
| set_attrib (int attr) |
| { |
| #ifdef HAVE_LIBCURSES |
| if (use_curses) |
| { |
| /* FIXME: I don't know why, but chgat doesn't work as expected, so |
| use this dirty way... - okuji */ |
| chtype ch = inch (); |
| addch ((ch & A_CHARTEXT) | attr); |
| # if 0 |
| chgat (1, attr, 0, NULL); |
| # endif |
| } |
| #endif |
| } |
| |
| |
| /* Get the geometry of a drive DRIVE. If an error occurs, return zero, |
| otherwise non-zero. */ |
| static int |
| get_drive_geometry (int drive) |
| { |
| struct geometry *geom = &disks[drive]; |
| |
| #if defined(__linux__) |
| /* Linux */ |
| struct hd_geometry hdg; |
| if (ioctl (geom->flags, HDIO_GETGEO, &hdg)) |
| return 0; |
| |
| /* Got the geometry, so save it. */ |
| geom->cylinders = hdg.cylinders; |
| geom->heads = hdg.heads; |
| geom->sectors = hdg.sectors; |
| /* FIXME: Should get the real number of sectors. */ |
| geom->total_sectors = hdg.cylinders * hdg.heads * hdg.sectors; |
| return 1; |
| |
| #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) |
| /* FreeBSD */ |
| struct disklabel hdg; |
| if (ioctl (disks[drive].flags, DIOCGDINFO, &hdg)) |
| return 0; |
| |
| disks[drive].cylinders = hdg.d_ncylinders; |
| disks[drive].heads = hdg.d_ntracks; |
| disks[drive].sectors = hdg.d_nsectors; |
| disks[drive].total_sectors = hdg.d_secperunit; |
| return 1; |
| |
| #else |
| # warning "In your operating system, automatic detection of geometries \ |
| will not be performed." |
| return 0; |
| #endif |
| } |
| |
| |
| /* Low-level disk I/O. Our stubbed version just returns a file |
| descriptor, not the actual geometry. */ |
| int |
| get_diskinfo (int drive, struct geometry *geometry) |
| { |
| /* FIXME: this function is truly horrid. We try opening the device, |
| then severely abuse the GEOMETRY->flags field to pass a file |
| descriptor to biosdisk. Thank God nobody's looking at this comment, |
| or my reputation would be ruined. --Gord */ |
| |
| /* See if we have a cached device. */ |
| if (disks[drive].flags == -1) |
| { |
| /* The unpartitioned device name: /dev/XdX */ |
| char *devname = device_map[drive]; |
| char buf[512]; |
| |
| if (! devname) |
| return -1; |
| |
| if (verbose) |
| grub_printf ("Attempt to open drive 0x%x (%s)\n", |
| drive, devname); |
| |
| /* Open read/write, or read-only if that failed. */ |
| if (! read_only) |
| disks[drive].flags = open (devname, O_RDWR); |
| |
| if (disks[drive].flags == -1) |
| { |
| if (read_only || errno == EACCES || errno == EROFS) |
| { |
| disks[drive].flags = open (devname, O_RDONLY); |
| if (disks[drive].flags == -1) |
| { |
| assign_device_name (drive, 0); |
| return -1; |
| } |
| } |
| else |
| { |
| assign_device_name (drive, 0); |
| return -1; |
| } |
| } |
| |
| /* Attempt to read the first sector. */ |
| if (read (disks[drive].flags, buf, 512) != 512) |
| { |
| close (disks[drive].flags); |
| disks[drive].flags = -1; |
| assign_device_name (drive, 0); |
| return -1; |
| } |
| |
| if (disks[drive].flags != -1) |
| { |
| if (! get_drive_geometry (drive)) |
| { |
| /* Set some arbitrary defaults. */ |
| if (drive & 0x80) |
| { |
| /* Hard drive. */ |
| disks[drive].cylinders = DEFAULT_HD_CYLINDERS; |
| disks[drive].heads = DEFAULT_HD_HEADS; |
| disks[drive].sectors = DEFAULT_HD_SECTORS; |
| disks[drive].total_sectors = (DEFAULT_HD_CYLINDERS |
| * DEFAULT_HD_HEADS |
| * DEFAULT_HD_SECTORS); |
| } |
| else |
| { |
| /* Floppy. */ |
| disks[drive].cylinders = DEFAULT_FD_CYLINDERS; |
| disks[drive].heads = DEFAULT_FD_HEADS; |
| disks[drive].sectors = DEFAULT_FD_SECTORS; |
| disks[drive].total_sectors = (DEFAULT_FD_CYLINDERS |
| * DEFAULT_FD_HEADS |
| * DEFAULT_FD_SECTORS); |
| } |
| } |
| } |
| } |
| |
| if (disks[drive].flags == -1) |
| return -1; |
| |
| #ifdef __linux__ |
| /* In Linux, invalidate the buffer cache, so that left overs |
| from other program in the cache are flushed and seen by us */ |
| ioctl (disks[drive].flags, BLKFLSBUF, 0); |
| #endif |
| |
| *geometry = disks[drive]; |
| return 0; |
| } |
| |
| /* Read LEN bytes from FD in BUF. Return less than or equal to zero if an |
| error occurs, otherwise return LEN. */ |
| static int |
| nread (int fd, char *buf, size_t len) |
| { |
| int size = len; |
| |
| while (len) |
| { |
| int ret = read (fd, buf, len); |
| |
| if (ret <= 0) |
| { |
| if (errno == EINTR) |
| continue; |
| else |
| return ret; |
| } |
| |
| len -= ret; |
| buf += ret; |
| } |
| |
| return size; |
| } |
| |
| /* Write LEN bytes from BUF to FD. Return less than or equal to zero if an |
| error occurs, otherwise return LEN. */ |
| static int |
| nwrite (int fd, char *buf, size_t len) |
| { |
| int size = len; |
| |
| while (len) |
| { |
| int ret = write (fd, buf, len); |
| |
| if (ret <= 0) |
| { |
| if (errno == EINTR) |
| continue; |
| else |
| return ret; |
| } |
| |
| len -= ret; |
| buf += ret; |
| } |
| |
| return size; |
| } |
| |
| /* Dump BUF in the format of hexadecimal numbers. */ |
| static void |
| hex_dump (void *buf, size_t size) |
| { |
| /* FIXME: How to determine which length is readable? */ |
| #define MAX_COLUMN 70 |
| |
| /* use unsigned char for numerical computations */ |
| unsigned char *ptr = buf; |
| /* count the width of the line */ |
| int column = 0; |
| /* how many bytes written */ |
| int count = 0; |
| |
| while (size > 0) |
| { |
| /* high 4 bits */ |
| int hi = *ptr >> 4; |
| /* low 4 bits */ |
| int low = *ptr & 0xf; |
| |
| /* grub_printf does not handle prefix number, such as %2x, so |
| format the number by hand... */ |
| grub_printf ("%x%x", hi, low); |
| column += 2; |
| count++; |
| ptr++; |
| size--; |
| |
| /* Insert space or newline with the interval 4 bytes. */ |
| if (size != 0 && (count % 4) == 0) |
| { |
| if (column < MAX_COLUMN) |
| { |
| grub_printf (" "); |
| column++; |
| } |
| else |
| { |
| grub_printf ("\n"); |
| column = 0; |
| } |
| } |
| } |
| |
| /* Add a newline at the end for readability. */ |
| grub_printf ("\n"); |
| } |
| |
| int |
| biosdisk (int subfunc, int drive, struct geometry *geometry, |
| int sector, int nsec, int segment) |
| { |
| char *buf; |
| int fd = geometry->flags; |
| |
| /* Get the file pointer from the geometry, and make sure it matches. */ |
| if (fd == -1 || fd != disks[drive].flags) |
| return BIOSDISK_ERROR_GEOMETRY; |
| |
| /* Seek to the specified location. */ |
| #if defined(__linux) && (__GLIBC__ < 2) |
| /* Maybe libc doesn't have large file support. */ |
| { |
| loff_t offset, result; |
| static int _llseek (uint fd, ulong hi, ulong lo, loff_t *res, uint wh); |
| _syscall5 (int, _llseek, uint, fd, ulong, hi, ulong, lo, |
| loff_t *, res, uint, wh); |
| |
| offset = (loff_t) sector * (loff_t) SECTOR_SIZE; |
| if (_llseek (fd, offset >> 32, offset & 0xffffffff, &result, SEEK_SET)) |
| return -1; |
| } |
| #else |
| { |
| off_t offset = (off_t) sector * (off_t) SECTOR_SIZE; |
| |
| if (lseek (fd, offset, SEEK_SET) != offset) |
| return -1; |
| } |
| #endif |
| |
| buf = (char *) (segment << 4); |
| |
| switch (subfunc) |
| { |
| case BIOSDISK_READ: |
| if (nread (fd, buf, nsec * SECTOR_SIZE) != nsec * SECTOR_SIZE) |
| return -1; |
| break; |
| |
| case BIOSDISK_WRITE: |
| if (verbose) |
| { |
| grub_printf ("Write %d sectors starting from %d sector" |
| " to drive 0x%x (%s)\n", |
| nsec, sector, drive, device_map[drive]); |
| hex_dump (buf, nsec * SECTOR_SIZE); |
| } |
| if (! read_only) |
| if (nwrite (fd, buf, nsec * SECTOR_SIZE) != nsec * SECTOR_SIZE) |
| return -1; |
| break; |
| |
| default: |
| grub_printf ("unknown subfunc %d\n", subfunc); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| |
| void |
| stop_floppy (void) |
| { |
| /* NOTUSED */ |
| } |