| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <assert.h> |
| #include <fcntl.h> |
| #include <inttypes.h> |
| #include <stdatomic.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/param.h> |
| #include <threads.h> |
| |
| #include <ddk/binding.h> |
| #include <ddk/debug.h> |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/protocol/block.h> |
| #include <ddk/protocol/block/partition.h> |
| |
| #include <gpt/c/gpt.h> |
| #include <lib/sync/completion.h> |
| #include <zircon/device/block.h> |
| #include <zircon/threads.h> |
| |
| #define DIV_ROUND_UP(n, d) (((n) + (d)-1) / (d)) |
| #define MBR_SIZE 512 |
| #define MBR_PARTITION_ENTRY_SIZE 16 |
| #define MBR_NUM_PARTITIONS 4 |
| #define MBR_BOOT_SIGNATURE 0xAA55 |
| |
| // ATTN: MBR supports 8 bit partition types instead of GUIDs. Here we define |
| // mappings between partition type and GUIDs that zircon understands. When |
| // the MBR driver receives a request for the type GUID, we lie and return the |
| // a mapping from partition type to type GUID. |
| static const uint8_t data_guid[GPT_GUID_LEN] = GUID_DATA_VALUE; |
| static const uint8_t sys_guid[GPT_GUID_LEN] = GUID_SYSTEM_VALUE; |
| #define PARTITION_TYPE_NONE 0x00 |
| #define PARTITION_TYPE_DATA 0xE9 |
| #define PARTITION_TYPE_SYS 0xEA |
| |
| typedef struct __PACKED mbr_partition_entry { |
| uint8_t status; |
| uint8_t chs_addr_start[3]; |
| uint8_t type; |
| uint8_t chs_addr_end[3]; |
| uint32_t start_sector_lba; |
| uint32_t sector_partition_length; |
| } mbr_partition_entry_t; |
| |
| typedef struct __PACKED mbr { |
| uint8_t bootstrap_code[446]; |
| mbr_partition_entry_t partition[MBR_NUM_PARTITIONS]; |
| uint16_t boot_signature; |
| } mbr_t; |
| |
| typedef struct mbrpart_device { |
| zx_device_t* zxdev; |
| zx_device_t* parent; |
| |
| block_impl_protocol_t bp; |
| |
| mbr_partition_entry_t partition; |
| |
| block_info_t info; |
| size_t block_op_size; |
| |
| sync_completion_t bind_completed; |
| } mbrpart_device_t; |
| |
| static zx_off_t to_parent_offset(mbrpart_device_t* dev, zx_off_t offset) { |
| return offset + (uint64_t)(dev->partition.start_sector_lba) * |
| (uint64_t)dev->info.block_size; |
| } |
| |
| |
| static void mbr_query(void* ctx, block_info_t* bi, size_t* bopsz) { |
| mbrpart_device_t* mbr = ctx; |
| memcpy(bi, &mbr->info, sizeof(block_info_t)); |
| *bopsz = mbr->block_op_size; |
| } |
| |
| static void mbr_queue(void* ctx, block_op_t* bop, block_impl_queue_callback completion_cb, |
| void* cookie) { |
| mbrpart_device_t* mbr = ctx; |
| |
| switch (bop->command & BLOCK_OP_MASK) { |
| case BLOCK_OP_READ: |
| case BLOCK_OP_WRITE: { |
| size_t blocks = bop->rw.length; |
| size_t max = mbr->partition.sector_partition_length; |
| |
| // Ensure that the request is in-bounds |
| if ((bop->rw.offset_dev >= max) || |
| ((max - bop->rw.offset_dev) < blocks)) { |
| completion_cb(cookie, ZX_ERR_OUT_OF_RANGE, bop); |
| return; |
| } |
| |
| // Adjust for partition starting block |
| bop->rw.offset_dev += mbr->partition.start_sector_lba; |
| break; |
| } |
| case BLOCK_OP_FLUSH: |
| break; |
| default: |
| completion_cb(cookie, ZX_ERR_NOT_SUPPORTED, bop); |
| return; |
| } |
| |
| block_impl_queue(&mbr->bp, bop, completion_cb, cookie); |
| } |
| |
| static void mbr_unbind(void* ctx) { |
| mbrpart_device_t* device = ctx; |
| sync_completion_wait(&device->bind_completed, ZX_TIME_INFINITE); |
| device_remove(device->zxdev); |
| } |
| |
| static void mbr_release(void* ctx) { |
| mbrpart_device_t* device = ctx; |
| free(device); |
| } |
| |
| static zx_off_t mbr_get_size(void* ctx) { |
| mbrpart_device_t* dev = ctx; |
| //TODO: use query() results, *but* fvm returns different query and getsize |
| // results, and the latter are dynamic... |
| return device_get_size(dev->parent); |
| } |
| |
| static block_impl_protocol_ops_t block_ops = { |
| .query = mbr_query, |
| .queue = mbr_queue, |
| }; |
| |
| static_assert(GPT_GUID_LEN == GUID_LENGTH, "GUID length mismatch"); |
| |
| static zx_status_t mbr_get_guid(void* ctx, guidtype_t guidtype, guid_t* out_guid) { |
| if (guidtype != GUIDTYPE_TYPE) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| mbrpart_device_t* device = ctx; |
| if (device->partition.type == PARTITION_TYPE_DATA) { |
| memcpy(out_guid, data_guid, GPT_GUID_LEN); |
| return ZX_OK; |
| } else if (device->partition.type == PARTITION_TYPE_SYS) { |
| memcpy(out_guid, sys_guid, GPT_GUID_LEN); |
| return ZX_OK; |
| } else { |
| return ZX_ERR_NOT_FOUND; |
| } |
| } |
| |
| static_assert(GPT_NAME_LEN <= MAX_PARTITION_NAME_LENGTH, "Partition name length mismatch"); |
| |
| static zx_status_t mbr_get_name(void* ctx, char* out_name, size_t capacity) { |
| if (capacity < GPT_NAME_LEN) { |
| return ZX_ERR_BUFFER_TOO_SMALL; |
| } |
| mbrpart_device_t* device = ctx; |
| strlcpy(out_name, device_get_name(device->zxdev), GPT_NAME_LEN); |
| return ZX_OK; |
| } |
| |
| static block_partition_protocol_ops_t partition_ops = { |
| .get_guid = mbr_get_guid, |
| .get_name = mbr_get_name, |
| }; |
| |
| static zx_status_t mbr_get_protocol(void* ctx, uint32_t proto_id, void* out) { |
| mbrpart_device_t* device = ctx; |
| switch (proto_id) { |
| case ZX_PROTOCOL_BLOCK_IMPL: { |
| block_impl_protocol_t* protocol = out; |
| protocol->ctx = device; |
| protocol->ops = &block_ops; |
| return ZX_OK; |
| } |
| case ZX_PROTOCOL_BLOCK_PARTITION: { |
| block_partition_protocol_t* protocol = out; |
| protocol->ctx = device; |
| protocol->ops = &partition_ops; |
| return ZX_OK; |
| } |
| default: |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| } |
| |
| static zx_protocol_device_t mbr_proto = { |
| .version = DEVICE_OPS_VERSION, |
| .get_protocol = mbr_get_protocol, |
| .get_size = mbr_get_size, |
| .unbind = mbr_unbind, |
| .release = mbr_release, |
| }; |
| |
| static void mbr_read_sync_complete(void* cookie, zx_status_t status, block_op_t* bop) { |
| bop->command = status; |
| sync_completion_signal((sync_completion_t*)cookie); |
| } |
| |
| static zx_status_t vmo_read(zx_handle_t vmo, void* data, uint64_t off, size_t len) { |
| return zx_vmo_read(vmo, data, off, len); |
| } |
| |
| static int mbr_bind_thread(void* arg) { |
| mbrpart_device_t* first_dev = (mbrpart_device_t*)arg; |
| zx_device_t* dev = first_dev->parent; |
| |
| // Classic MBR supports 4 partitions. |
| uint8_t partition_count = 0; |
| |
| block_impl_protocol_t bp; |
| memcpy(&bp, &first_dev->bp, sizeof(bp)); |
| |
| block_info_t block_info; |
| size_t block_op_size; |
| block_impl_query(&bp, &block_info, &block_op_size); |
| |
| zx_handle_t vmo = ZX_HANDLE_INVALID; |
| block_op_t* bop = calloc(1, block_op_size); |
| if (bop == NULL) { |
| goto unbind; |
| } |
| |
| // We need to read at least 512B to parse the MBR. Determine if we should |
| // read the device's block size or we should ready exactly 512B. |
| size_t iosize = 0; |
| if (block_info.block_size >= MBR_SIZE) { |
| iosize = block_info.block_size; |
| } else { |
| // Make sure we're reading some multiple of the block size. |
| iosize = DIV_ROUND_UP(MBR_SIZE, block_info.block_size) * block_info.block_size; |
| } |
| |
| if (zx_vmo_create(iosize, 0, &vmo) != ZX_OK) { |
| zxlogf(ERROR, "mbr: cannot allocate vmo\n"); |
| goto unbind; |
| } |
| |
| |
| sync_completion_t cplt = SYNC_COMPLETION_INIT; |
| |
| bop->command = BLOCK_OP_READ; |
| bop->rw.vmo = vmo; |
| bop->rw.length = iosize / block_info.block_size; |
| bop->rw.offset_dev = 0; |
| bop->rw.offset_vmo = 0; |
| |
| bp.ops->queue(bp.ctx, bop, mbr_read_sync_complete, &cplt); |
| sync_completion_wait(&cplt, ZX_TIME_INFINITE); |
| |
| if (bop->command != ZX_OK) { |
| zxlogf(ERROR, "mbr: could not read mbr from device, retcode = %d\n", bop->command); |
| goto unbind; |
| } |
| |
| uint8_t buffer[MBR_SIZE]; |
| mbr_t* mbr = (mbr_t*)buffer; |
| if (vmo_read(vmo, buffer, 0, MBR_SIZE) != ZX_OK) { |
| goto unbind; |
| } |
| |
| // Validate the MBR boot signature. |
| if (mbr->boot_signature != MBR_BOOT_SIGNATURE) { |
| zxlogf(ERROR, "mbr: invalid mbr boot signature, expected 0x%04x got 0x%04x\n", |
| MBR_BOOT_SIGNATURE, mbr->boot_signature); |
| goto unbind; |
| } |
| |
| // Parse the partitions out of the MBR. |
| for (; partition_count < MBR_NUM_PARTITIONS; partition_count++) { |
| mbr_partition_entry_t* entry = &mbr->partition[partition_count]; |
| if (entry->type == PARTITION_TYPE_NONE) { |
| // This partition entry is empty and does not refer to a partition, |
| // skip it. |
| continue; |
| } |
| |
| zxlogf(SPEW, "mbr: found partition, entry = %d, type = 0x%02x, " |
| "start = %u, length = %u\n", |
| partition_count + 1, entry->type, entry->start_sector_lba, |
| entry->sector_partition_length); |
| |
| mbrpart_device_t* pdev; |
| // use first_dev for first partition |
| if (first_dev) { |
| pdev = first_dev; |
| } else { |
| pdev = calloc(1, sizeof(*pdev)); |
| if (!pdev) { |
| zxlogf(ERROR, "mbr: out of memory\n"); |
| goto unbind; |
| } |
| pdev->parent = dev; |
| pdev->bind_completed = SYNC_COMPLETION_INIT; |
| memcpy(&pdev->bp, &bp, sizeof(bp)); |
| } |
| |
| memcpy(&pdev->partition, entry, sizeof(*entry)); |
| block_info.block_count = pdev->partition.sector_partition_length; |
| memcpy(&pdev->info, &block_info, sizeof(block_info)); |
| pdev->block_op_size = block_op_size; |
| |
| if (first_dev) { |
| // make our initial device visible and use if for partition zero |
| device_make_visible(first_dev->zxdev); |
| first_dev = NULL; |
| } else { |
| char name[16]; |
| snprintf(name, sizeof(name), "part-%03u", partition_count); |
| |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = name, |
| .ctx = pdev, |
| .ops = &mbr_proto, |
| .proto_id = ZX_PROTOCOL_BLOCK_IMPL, |
| .proto_ops = &block_ops, |
| }; |
| |
| if (device_add(dev, &args, &pdev->zxdev) != ZX_OK) { |
| free(pdev); |
| continue; |
| } |
| } |
| sync_completion_signal(&pdev->bind_completed); |
| } |
| |
| free(bop); |
| zx_handle_close(vmo); |
| return 0; |
| unbind: |
| free(bop); |
| zx_handle_close(vmo); |
| if (first_dev) { |
| // handle case where no partitions were found |
| sync_completion_signal(&first_dev->bind_completed); |
| device_remove(first_dev->zxdev); |
| } |
| return -1; |
| } |
| |
| static zx_status_t mbr_bind(void* ctx, zx_device_t* parent) { |
| // Make sure the MBR structs are the right size. |
| static_assert(sizeof(mbr_t) == MBR_SIZE, "mbr_t is the wrong size"); |
| static_assert(sizeof(mbr_partition_entry_t) == MBR_PARTITION_ENTRY_SIZE, |
| "mbr_partition_entry_t is the wrong size"); |
| |
| // create an invisible device, which will be used for the first partition |
| mbrpart_device_t* device = calloc(1, sizeof(mbrpart_device_t)); |
| if (!device) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| device->parent = parent; |
| device->bind_completed = SYNC_COMPLETION_INIT; |
| |
| if (device_get_protocol(parent, ZX_PROTOCOL_BLOCK, &device->bp) != ZX_OK) { |
| zxlogf(ERROR, "mbr: ERROR: block device '%s': does not support block protocol\n", |
| device_get_name(parent)); |
| free(device); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| char name[128]; |
| snprintf(name, sizeof(name), "part-%03u", 0); |
| |
| device_add_args_t args = { |
| .version = DEVICE_ADD_ARGS_VERSION, |
| .name = name, |
| .ctx = device, |
| .ops = &mbr_proto, |
| .proto_id = ZX_PROTOCOL_BLOCK_IMPL, |
| .proto_ops = &block_ops, |
| .flags = DEVICE_ADD_INVISIBLE, |
| }; |
| |
| zx_status_t status = device_add(parent, &args, &device->zxdev); |
| if (status != ZX_OK) { |
| free(device); |
| return status; |
| } |
| |
| // Read the partition table asynchronously. |
| thrd_t t; |
| int thrd_rc = thrd_create_with_name(&t, mbr_bind_thread, device, "mbr-init"); |
| if (thrd_rc != thrd_success) { |
| return thrd_status_to_zx_status(thrd_rc); |
| } |
| return ZX_OK; |
| } |
| |
| static zx_driver_ops_t mbr_driver_ops = { |
| .version = DRIVER_OPS_VERSION, |
| .bind = mbr_bind, |
| }; |
| |
| // clang-format off |
| ZIRCON_DRIVER_BEGIN(mbr, mbr_driver_ops, "zircon", "0.1", 2) |
| BI_ABORT_IF_AUTOBIND, |
| BI_MATCH_IF(EQ, BIND_PROTOCOL, ZX_PROTOCOL_BLOCK), |
| ZIRCON_DRIVER_END(mbr) |
| // clang-format on |