/* cbfs.c - cbfs and tar filesystem. */ /* * VAS_EBOOT -- 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 #include VAS_EBOOT_MOD_LICENSE ("GPLv3+"); struct VasEBoot_archelp_data { VasEBoot_disk_t disk; VasEBoot_off_t hofs, next_hofs; VasEBoot_off_t dofs; VasEBoot_off_t size; VasEBoot_off_t cbfs_start; VasEBoot_off_t cbfs_end; VasEBoot_off_t cbfs_align; }; static VasEBoot_err_t VasEBoot_cbfs_find_file (struct VasEBoot_archelp_data *data, char **name, VasEBoot_int32_t *mtime, VasEBoot_uint32_t *mode) { VasEBoot_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; VasEBoot_size_t namesize; data->hofs = data->next_hofs; if (data->hofs >= data->cbfs_end) { *mode = VAS_EBOOT_ARCHELP_ATTR_END; return VAS_EBOOT_ERR_NONE; } if (VasEBoot_disk_read (data->disk, 0, data->hofs, sizeof (hd), &hd)) return VasEBoot_errno; if (VasEBoot_memcmp (hd.magic, CBFS_FILE_MAGIC, sizeof (hd.magic)) != 0) { *mode = VAS_EBOOT_ARCHELP_ATTR_END; return VAS_EBOOT_ERR_NONE; } data->size = VasEBoot_be_to_cpu32 (hd.len); (void) mtime; offset = VasEBoot_be_to_cpu32 (hd.offset); *mode = VAS_EBOOT_ARCHELP_ATTR_FILE | VAS_EBOOT_ARCHELP_ATTR_NOTIME; namesize = offset; if (namesize >= sizeof (hd)) namesize -= sizeof (hd); if (namesize == 0) continue; *name = VasEBoot_malloc (namesize + 1); if (*name == NULL) return VasEBoot_errno; if (VasEBoot_disk_read (data->disk, 0, data->hofs + sizeof (hd), namesize, *name)) { VasEBoot_free (*name); return VasEBoot_errno; } if ((*name)[0] == '\0') { VasEBoot_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 VAS_EBOOT_ERR_NONE; } } static void VasEBoot_cbfs_rewind (struct VasEBoot_archelp_data *data) { data->next_hofs = data->cbfs_start; } static struct VasEBoot_archelp_ops arcops = { .find_file = VasEBoot_cbfs_find_file, .rewind = VasEBoot_cbfs_rewind }; static int validate_head (struct cbfs_header *head) { return (head->magic == VasEBoot_cpu_to_be32_compile_time (CBFS_HEADER_MAGIC) && (head->version == VasEBoot_cpu_to_be32_compile_time (CBFS_HEADER_VERSION1) || head->version == VasEBoot_cpu_to_be32_compile_time (CBFS_HEADER_VERSION2)) && (VasEBoot_be_to_cpu32 (head->bootblocksize) < VasEBoot_be_to_cpu32 (head->romsize)) && (VasEBoot_be_to_cpu32 (head->offset) < VasEBoot_be_to_cpu32 (head->romsize)) && (VasEBoot_be_to_cpu32 (head->offset) + VasEBoot_be_to_cpu32 (head->bootblocksize) < VasEBoot_be_to_cpu32 (head->romsize)) && head->align != 0 && (head->align & (head->align - 1)) == 0 && head->romsize != 0); } static struct VasEBoot_archelp_data * VasEBoot_cbfs_mount (VasEBoot_disk_t disk) { struct cbfs_file hd; struct VasEBoot_archelp_data *data = NULL; VasEBoot_uint32_t ptr; VasEBoot_off_t header_off; struct cbfs_header head; if (VasEBoot_disk_native_sectors (disk) == VAS_EBOOT_DISK_SIZE_UNKNOWN) goto fail; if (VasEBoot_disk_read (disk, VasEBoot_disk_native_sectors (disk) - 1, VAS_EBOOT_DISK_SECTOR_SIZE - sizeof (ptr), sizeof (ptr), &ptr)) goto fail; ptr = VasEBoot_cpu_to_le32 (ptr); header_off = (VasEBoot_disk_native_sectors (disk) << VAS_EBOOT_DISK_SECTOR_BITS) + (VasEBoot_int32_t) ptr; if (VasEBoot_disk_read (disk, 0, header_off, sizeof (head), &head)) goto fail; if (!validate_head (&head)) goto fail; data = (struct VasEBoot_archelp_data *) VasEBoot_zalloc (sizeof (*data)); if (!data) goto fail; data->cbfs_start = (VasEBoot_disk_native_sectors (disk) << VAS_EBOOT_DISK_SECTOR_BITS) - (VasEBoot_be_to_cpu32 (head.romsize) - VasEBoot_be_to_cpu32 (head.offset)); data->cbfs_end = (VasEBoot_disk_native_sectors (disk) << VAS_EBOOT_DISK_SECTOR_BITS) - VasEBoot_be_to_cpu32 (head.bootblocksize); data->cbfs_align = VasEBoot_be_to_cpu32 (head.align); if (data->cbfs_start >= (VasEBoot_disk_native_sectors (disk) << VAS_EBOOT_DISK_SECTOR_BITS)) goto fail; if (data->cbfs_end > (VasEBoot_disk_native_sectors (disk) << VAS_EBOOT_DISK_SECTOR_BITS)) data->cbfs_end = (VasEBoot_disk_native_sectors (disk) << VAS_EBOOT_DISK_SECTOR_BITS); data->next_hofs = data->cbfs_start; if (VasEBoot_disk_read (disk, 0, data->cbfs_start, sizeof (hd), &hd)) goto fail; if (VasEBoot_memcmp (hd.magic, CBFS_FILE_MAGIC, sizeof (CBFS_FILE_MAGIC) - 1)) goto fail; data->disk = disk; return data; fail: VasEBoot_free (data); VasEBoot_error (VAS_EBOOT_ERR_BAD_FS, "not a cbfs filesystem"); return 0; } static VasEBoot_err_t VasEBoot_cbfs_dir (VasEBoot_device_t device, const char *path_in, VasEBoot_fs_dir_hook_t hook, void *hook_data) { struct VasEBoot_archelp_data *data; VasEBoot_err_t err; data = VasEBoot_cbfs_mount (device->disk); if (!data) return VasEBoot_errno; err = VasEBoot_archelp_dir (data, &arcops, path_in, hook, hook_data); VasEBoot_free (data); return err; } static VasEBoot_err_t VasEBoot_cbfs_open (VasEBoot_file_t file, const char *name_in) { struct VasEBoot_archelp_data *data; VasEBoot_err_t err; data = VasEBoot_cbfs_mount (file->device->disk); if (!data) return VasEBoot_errno; err = VasEBoot_archelp_open (data, &arcops, name_in); if (err) { VasEBoot_free (data); } else { file->data = data; file->size = data->size; } return err; } static VasEBoot_ssize_t VasEBoot_cbfs_read (VasEBoot_file_t file, char *buf, VasEBoot_size_t len) { struct VasEBoot_archelp_data *data; VasEBoot_ssize_t ret; data = file->data; data->disk->read_hook = file->read_hook; data->disk->read_hook_data = file->read_hook_data; ret = (VasEBoot_disk_read (data->disk, 0, data->dofs + file->offset, len, buf)) ? -1 : (VasEBoot_ssize_t) len; data->disk->read_hook = 0; return ret; } static VasEBoot_err_t VasEBoot_cbfs_close (VasEBoot_file_t file) { struct VasEBoot_archelp_data *data; data = file->data; VasEBoot_free (data); return VasEBoot_errno; } #if (defined (__i386__) || defined (__x86_64__)) && !defined (VAS_EBOOT_UTIL) \ && !defined (VAS_EBOOT_MACHINE_EMU) && !defined (VAS_EBOOT_MACHINE_XEN) static char *cbfsdisk_addr; static VasEBoot_off_t cbfsdisk_size = 0; static int VasEBoot_cbfsdisk_iterate (VasEBoot_disk_dev_iterate_hook_t hook, void *hook_data, VasEBoot_disk_pull_t pull) { if (pull != VAS_EBOOT_DISK_PULL_NONE) return 0; return hook ("cbfsdisk", hook_data); } static VasEBoot_err_t VasEBoot_cbfsdisk_open (const char *name, VasEBoot_disk_t disk) { if (VasEBoot_strcmp (name, "cbfsdisk")) return VasEBoot_error (VAS_EBOOT_ERR_UNKNOWN_DEVICE, "not a cbfsdisk"); disk->total_sectors = cbfsdisk_size / VAS_EBOOT_DISK_SECTOR_SIZE; disk->max_agglomerate = VAS_EBOOT_DISK_MAX_MAX_AGGLOMERATE; disk->id = 0; return VAS_EBOOT_ERR_NONE; } static void VasEBoot_cbfsdisk_close (VasEBoot_disk_t disk __attribute((unused))) { } static VasEBoot_err_t VasEBoot_cbfsdisk_read (VasEBoot_disk_t disk __attribute((unused)), VasEBoot_disk_addr_t sector, VasEBoot_size_t size, char *buf) { VasEBoot_memcpy (buf, cbfsdisk_addr + (sector << VAS_EBOOT_DISK_SECTOR_BITS), size << VAS_EBOOT_DISK_SECTOR_BITS); return 0; } static VasEBoot_err_t VasEBoot_cbfsdisk_write (VasEBoot_disk_t disk __attribute__ ((unused)), VasEBoot_disk_addr_t sector __attribute__ ((unused)), VasEBoot_size_t size __attribute__ ((unused)), const char *buf __attribute__ ((unused))) { return VasEBoot_error (VAS_EBOOT_ERR_NOT_IMPLEMENTED_YET, "rom flashing isn't implemented yet"); } static struct VasEBoot_disk_dev VasEBoot_cbfsdisk_dev = { .name = "cbfsdisk", .id = VAS_EBOOT_DISK_DEVICE_CBFSDISK_ID, .disk_iterate = VasEBoot_cbfsdisk_iterate, .disk_open = VasEBoot_cbfsdisk_open, .disk_close = VasEBoot_cbfsdisk_close, .disk_read = VasEBoot_cbfsdisk_read, .disk_write = VasEBoot_cbfsdisk_write, .next = 0 }; static void init_cbfsdisk (void) { VasEBoot_uint32_t ptr; struct cbfs_header *head; ptr = *((VasEBoot_uint32_t *) VasEBoot_absolute_pointer (0xfffffffc)); head = (struct cbfs_header *) (VasEBoot_addr_t) ptr; VasEBoot_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 < (VasEBoot_uint32_t) sizeof (*head) + 0xf || !validate_head (head)) return; cbfsdisk_size = ALIGN_UP (VasEBoot_be_to_cpu32 (head->romsize), VAS_EBOOT_DISK_SECTOR_SIZE); cbfsdisk_addr = (void *) (VasEBoot_addr_t) (0x100000000ULL - cbfsdisk_size); VasEBoot_disk_dev_register (&VasEBoot_cbfsdisk_dev); } static void fini_cbfsdisk (void) { if (! cbfsdisk_size) return; VasEBoot_disk_dev_unregister (&VasEBoot_cbfsdisk_dev); } #endif static struct VasEBoot_fs VasEBoot_cbfs_fs = { .name = "cbfs", .fs_dir = VasEBoot_cbfs_dir, .fs_open = VasEBoot_cbfs_open, .fs_read = VasEBoot_cbfs_read, .fs_close = VasEBoot_cbfs_close, #ifdef VAS_EBOOT_UTIL .reserved_first_sector = 0, .blocklist_install = 0, #endif }; VAS_EBOOT_MOD_INIT (cbfs) { #if (defined (__i386__) || defined (__x86_64__)) && !defined (VAS_EBOOT_UTIL) && !defined (VAS_EBOOT_MACHINE_EMU) && !defined (VAS_EBOOT_MACHINE_XEN) init_cbfsdisk (); #endif if (!VasEBoot_is_lockdown ()) { VasEBoot_cbfs_fs.mod = mod; VasEBoot_fs_register (&VasEBoot_cbfs_fs); } } VAS_EBOOT_MOD_FINI (cbfs) { if (!VasEBoot_is_lockdown ()) VasEBoot_fs_unregister (&VasEBoot_cbfs_fs); #if (defined (__i386__) || defined (__x86_64__)) && !defined (VAS_EBOOT_UTIL) && !defined (VAS_EBOOT_MACHINE_EMU) && !defined (VAS_EBOOT_MACHINE_XEN) fini_cbfsdisk (); #endif }