/* cbfs.c - cbfs and tar filesystem. */ /* * GRUB -- GRand Unified Bootloader * Copyright (C) 2007,2008,2009,2013 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 3 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, see . */ #include #include #include #include #include #include #include #include GRUB_MOD_LICENSE ("GPLv3+"); struct grub_archelp_data { grub_disk_t disk; grub_off_t hofs, next_hofs; grub_off_t dofs; grub_off_t size; grub_off_t cbfs_start; grub_off_t cbfs_end; grub_off_t cbfs_align; }; static grub_err_t grub_cbfs_find_file (struct grub_archelp_data *data, char **name, grub_int32_t *mtime, grub_uint32_t *mode) { grub_size_t offset; for (;; data->dofs = data->hofs + offset, data->next_hofs = ALIGN_UP (data->dofs + data->size, data->cbfs_align)) { struct cbfs_file hd; grub_size_t namesize; data->hofs = data->next_hofs; if (data->hofs >= data->cbfs_end) { *mode = GRUB_ARCHELP_ATTR_END; return GRUB_ERR_NONE; } if (grub_disk_read (data->disk, 0, data->hofs, sizeof (hd), &hd)) return grub_errno; if (grub_memcmp (hd.magic, CBFS_FILE_MAGIC, sizeof (hd.magic)) != 0) { *mode = GRUB_ARCHELP_ATTR_END; return GRUB_ERR_NONE; } data->size = grub_be_to_cpu32 (hd.len); (void) mtime; offset = grub_be_to_cpu32 (hd.offset); *mode = GRUB_ARCHELP_ATTR_FILE | GRUB_ARCHELP_ATTR_NOTIME; namesize = offset; if (namesize >= sizeof (hd)) namesize -= sizeof (hd); if (namesize == 0) continue; *name = grub_malloc (namesize + 1); if (*name == NULL) return grub_errno; if (grub_disk_read (data->disk, 0, data->hofs + sizeof (hd), namesize, *name)) { grub_free (*name); return grub_errno; } if ((*name)[0] == '\0') { grub_free (*name); *name = NULL; continue; } (*name)[namesize] = 0; data->dofs = data->hofs + offset; data->next_hofs = ALIGN_UP (data->dofs + data->size, data->cbfs_align); return GRUB_ERR_NONE; } } static void grub_cbfs_rewind (struct grub_archelp_data *data) { data->next_hofs = data->cbfs_start; } static struct grub_archelp_ops arcops = { .find_file = grub_cbfs_find_file, .rewind = grub_cbfs_rewind }; static int validate_head (struct cbfs_header *head) { return (head->magic == grub_cpu_to_be32_compile_time (CBFS_HEADER_MAGIC) && (head->version == grub_cpu_to_be32_compile_time (CBFS_HEADER_VERSION1) || head->version == grub_cpu_to_be32_compile_time (CBFS_HEADER_VERSION2)) && (grub_be_to_cpu32 (head->bootblocksize) < grub_be_to_cpu32 (head->romsize)) && (grub_be_to_cpu32 (head->offset) < grub_be_to_cpu32 (head->romsize)) && (grub_be_to_cpu32 (head->offset) + grub_be_to_cpu32 (head->bootblocksize) < grub_be_to_cpu32 (head->romsize)) && head->align != 0 && (head->align & (head->align - 1)) == 0 && head->romsize != 0); } static struct grub_archelp_data * grub_cbfs_mount (grub_disk_t disk) { struct cbfs_file hd; struct grub_archelp_data *data = NULL; grub_uint32_t ptr; grub_off_t header_off; struct cbfs_header head; if (grub_disk_get_size (disk) == GRUB_DISK_SIZE_UNKNOWN) goto fail; if (grub_disk_read (disk, grub_disk_get_size (disk) - 1, GRUB_DISK_SECTOR_SIZE - sizeof (ptr), sizeof (ptr), &ptr)) goto fail; ptr = grub_cpu_to_le32 (ptr); header_off = (grub_disk_get_size (disk) << GRUB_DISK_SECTOR_BITS) + (grub_int32_t) ptr; if (grub_disk_read (disk, 0, header_off, sizeof (head), &head)) goto fail; if (!validate_head (&head)) goto fail; data = (struct grub_archelp_data *) grub_zalloc (sizeof (*data)); if (!data) goto fail; data->cbfs_start = (grub_disk_get_size (disk) << GRUB_DISK_SECTOR_BITS) - (grub_be_to_cpu32 (head.romsize) - grub_be_to_cpu32 (head.offset)); data->cbfs_end = (grub_disk_get_size (disk) << GRUB_DISK_SECTOR_BITS) - grub_be_to_cpu32 (head.bootblocksize); data->cbfs_align = grub_be_to_cpu32 (head.align); if (data->cbfs_start >= (grub_disk_get_size (disk) << GRUB_DISK_SECTOR_BITS)) goto fail; if (data->cbfs_end > (grub_disk_get_size (disk) << GRUB_DISK_SECTOR_BITS)) data->cbfs_end = (grub_disk_get_size (disk) << GRUB_DISK_SECTOR_BITS); data->next_hofs = data->cbfs_start; if (grub_disk_read (disk, 0, data->cbfs_start, sizeof (hd), &hd)) goto fail; if (grub_memcmp (hd.magic, CBFS_FILE_MAGIC, sizeof (CBFS_FILE_MAGIC) - 1)) goto fail; data->disk = disk; return data; fail: grub_free (data); grub_error (GRUB_ERR_BAD_FS, "not a cbfs filesystem"); return 0; } static grub_err_t grub_cbfs_dir (grub_device_t device, const char *path_in, grub_fs_dir_hook_t hook, void *hook_data) { struct grub_archelp_data *data; grub_err_t err; data = grub_cbfs_mount (device->disk); if (!data) return grub_errno; err = grub_archelp_dir (data, &arcops, path_in, hook, hook_data); grub_free (data); return err; } static grub_err_t grub_cbfs_open (grub_file_t file, const char *name_in) { struct grub_archelp_data *data; grub_err_t err; data = grub_cbfs_mount (file->device->disk); if (!data) return grub_errno; err = grub_archelp_open (data, &arcops, name_in); if (err) { grub_free (data); } else { file->data = data; file->size = data->size; } return err; } static grub_ssize_t grub_cbfs_read (grub_file_t file, char *buf, grub_size_t len) { struct grub_archelp_data *data; grub_ssize_t ret; data = file->data; data->disk->read_hook = file->read_hook; data->disk->read_hook_data = file->read_hook_data; ret = (grub_disk_read (data->disk, 0, data->dofs + file->offset, len, buf)) ? -1 : (grub_ssize_t) len; data->disk->read_hook = 0; return ret; } static grub_err_t grub_cbfs_close (grub_file_t file) { struct grub_archelp_data *data; data = file->data; grub_free (data); return grub_errno; } #if (defined (__i386__) || defined (__x86_64__)) && !defined (GRUB_UTIL) \ && !defined (GRUB_MACHINE_EMU) && !defined (GRUB_MACHINE_XEN) static char *cbfsdisk_addr; static grub_off_t cbfsdisk_size = 0; static int grub_cbfsdisk_iterate (grub_disk_dev_iterate_hook_t hook, void *hook_data, grub_disk_pull_t pull) { if (pull != GRUB_DISK_PULL_NONE) return 0; return hook ("cbfsdisk", hook_data); } static grub_err_t grub_cbfsdisk_open (const char *name, grub_disk_t disk) { if (grub_strcmp (name, "cbfsdisk")) return grub_error (GRUB_ERR_UNKNOWN_DEVICE, "not a cbfsdisk"); disk->total_sectors = cbfsdisk_size / GRUB_DISK_SECTOR_SIZE; disk->max_agglomerate = GRUB_DISK_MAX_MAX_AGGLOMERATE; disk->id = 0; return GRUB_ERR_NONE; } static void grub_cbfsdisk_close (grub_disk_t disk __attribute((unused))) { } static grub_err_t grub_cbfsdisk_read (grub_disk_t disk __attribute((unused)), grub_disk_addr_t sector, grub_size_t size, char *buf) { grub_memcpy (buf, cbfsdisk_addr + (sector << GRUB_DISK_SECTOR_BITS), size << GRUB_DISK_SECTOR_BITS); return 0; } static grub_err_t grub_cbfsdisk_write (grub_disk_t disk __attribute__ ((unused)), grub_disk_addr_t sector __attribute__ ((unused)), grub_size_t size __attribute__ ((unused)), const char *buf __attribute__ ((unused))) { return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET, "rom flashing isn't implemented yet"); } static struct grub_disk_dev grub_cbfsdisk_dev = { .name = "cbfsdisk", .id = GRUB_DISK_DEVICE_CBFSDISK_ID, .disk_iterate = grub_cbfsdisk_iterate, .disk_open = grub_cbfsdisk_open, .disk_close = grub_cbfsdisk_close, .disk_read = grub_cbfsdisk_read, .disk_write = grub_cbfsdisk_write, .next = 0 }; static void init_cbfsdisk (void) { grub_uint32_t ptr; struct cbfs_header *head; ptr = *(grub_uint32_t *) 0xfffffffc; head = (struct cbfs_header *) (grub_addr_t) ptr; grub_dprintf ("cbfs", "head=%p\n", head); /* coreboot current supports only ROMs <= 16 MiB. Bigger ROMs will have problems as RCBA is 18 MiB below end of 32-bit typically, so either memory map would have to be rearranged or we'd need to support reading ROMs through controller directly. */ if (ptr < 0xff000000 || 0xffffffff - ptr < (grub_uint32_t) sizeof (*head) + 0xf || !validate_head (head)) return; cbfsdisk_size = ALIGN_UP (grub_be_to_cpu32 (head->romsize), GRUB_DISK_SECTOR_SIZE); cbfsdisk_addr = (void *) (grub_addr_t) (0x100000000ULL - cbfsdisk_size); grub_disk_dev_register (&grub_cbfsdisk_dev); } static void fini_cbfsdisk (void) { if (! cbfsdisk_size) return; grub_disk_dev_unregister (&grub_cbfsdisk_dev); } #endif static struct grub_fs grub_cbfs_fs = { .name = "cbfs", .fs_dir = grub_cbfs_dir, .fs_open = grub_cbfs_open, .fs_read = grub_cbfs_read, .fs_close = grub_cbfs_close, #ifdef GRUB_UTIL .reserved_first_sector = 0, .blocklist_install = 0, #endif }; GRUB_MOD_INIT (cbfs) { #if (defined (__i386__) || defined (__x86_64__)) && !defined (GRUB_UTIL) && !defined (GRUB_MACHINE_EMU) && !defined (GRUB_MACHINE_XEN) init_cbfsdisk (); #endif grub_fs_register (&grub_cbfs_fs); } GRUB_MOD_FINI (cbfs) { grub_fs_unregister (&grub_cbfs_fs); #if (defined (__i386__) || defined (__x86_64__)) && !defined (GRUB_UTIL) && !defined (GRUB_MACHINE_EMU) && !defined (GRUB_MACHINE_XEN) fini_cbfsdisk (); #endif }