blob: 1f54462067cfd67ae26fcacee7de6447d4428814 [file] [log] [blame]
// 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