blob: e025d71ee4782a63d7d136e29c7cb183c69bc442 [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/device.h>
#include <ddk/driver.h>
#include <ddk/protocol/block.h>
#include <zircon/threads.h>
#include <sync/completion.h>
#include <gpt/gpt.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
#define TRACE 0
#if TRACE
#define xprintf(fmt...) printf(fmt)
#else
#define xprintf(fmt...) \
do { \
} while (0)
#endif
// 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* mxdev;
zx_device_t* parent;
mbr_partition_entry_t partition;
block_info_t info;
block_callbacks_t* callbacks;
atomic_int writercount;
} mbrpart_device_t;
static void mbr_read_sync_complete(iotxn_t* txn, void* cookie) {
// Used to synchronize iotxn_calls.
completion_signal((completion_t*)cookie);
}
static inline bool is_writer(uint32_t flags) {
return (flags & O_RDWR || flags & O_WRONLY);
}
static uint64_t getsize(mbrpart_device_t* dev) {
// Returns the size of the partition referred to by dev.
return dev->partition.sector_partition_length * ((uint64_t) dev->info.block_size);
}
static zx_status_t mbr_ioctl(void* ctx, uint32_t op, const void* cmd,
size_t cmdlen, void* reply, size_t max,
size_t* out_actual) {
mbrpart_device_t* device = ctx;
switch (op) {
case IOCTL_BLOCK_GET_INFO: {
block_info_t* info = reply;
if (max < sizeof(*info))
return ZX_ERR_BUFFER_TOO_SMALL;
memcpy(info, &device->info, sizeof(*info));
*out_actual = sizeof(*info);
return ZX_OK;
}
case IOCTL_BLOCK_GET_TYPE_GUID: {
char* guid = reply;
if (max < GPT_GUID_LEN)
return ZX_ERR_BUFFER_TOO_SMALL;
if (device->partition.type == PARTITION_TYPE_DATA) {
memcpy(guid, data_guid, GPT_GUID_LEN);
*out_actual = GPT_GUID_LEN;
return ZX_OK;
} else if (device->partition.type == PARTITION_TYPE_SYS) {
memcpy(guid, sys_guid, GPT_GUID_LEN);
*out_actual = GPT_GUID_LEN;
return ZX_OK;
} else {
return ZX_ERR_NOT_FOUND;
}
}
case IOCTL_BLOCK_GET_PARTITION_GUID: {
return ZX_ERR_NOT_SUPPORTED;
}
case IOCTL_BLOCK_GET_NAME: {
char* name = reply;
memset(name, 0, max);
strncpy(name, device_get_name(device->mxdev), max);
*out_actual = strnlen(name, max);
return ZX_OK;
}
case IOCTL_DEVICE_SYNC: {
// Propagate sync to parent device
return device_ioctl(device->parent, IOCTL_DEVICE_SYNC, NULL, 0, NULL, 0, NULL);
}
default:
return ZX_ERR_NOT_SUPPORTED;
}
}
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_iotxn_queue(void* ctx, iotxn_t* txn) {
mbrpart_device_t* dev = ctx;
if (txn->offset % dev->info.block_size) {
iotxn_complete(txn, ZX_ERR_INVALID_ARGS, 0);
return;
}
if (txn->offset > getsize(dev)) {
iotxn_complete(txn, ZX_ERR_OUT_OF_RANGE, 0);
return;
}
// transactions from read()/write() may be truncated
txn->length = ROUNDDOWN(txn->length, dev->info.block_size);
txn->length = MIN(txn->length, getsize(dev) - txn->offset);
txn->offset = to_parent_offset(dev, txn->offset);
if (txn->length == 0) {
iotxn_complete(txn, ZX_OK, 0);
} else {
iotxn_queue(dev->parent, txn);
}
}
static zx_off_t mbr_getsize(void* ctx) {
return getsize(ctx);
}
static void mbr_unbind(void* ctx) {
mbrpart_device_t* device = ctx;
device_remove(device->mxdev);
}
static void mbr_release(void* ctx) {
mbrpart_device_t* device = ctx;
free(device);
}
static zx_status_t mbr_open(void* ctx, zx_device_t** dev_out,
uint32_t flags) {
mbrpart_device_t* device = ctx;
zx_status_t status = ZX_OK;
if (is_writer(flags) && (atomic_exchange(&device->writercount, 1) == 1)) {
xprintf("Partition cannot be opened as writable (open elsewhere)\n");
status = ZX_ERR_ALREADY_BOUND;
}
return status;
}
static zx_status_t mbr_close(void* ctx, uint32_t flags) {
mbrpart_device_t* device = ctx;
if (is_writer(flags)) {
atomic_fetch_sub(&device->writercount, 1);
}
return ZX_OK;
}
static zx_protocol_device_t mbr_proto = {
.version = DEVICE_OPS_VERSION,
.ioctl = mbr_ioctl,
.iotxn_queue = mbr_iotxn_queue,
.get_size = mbr_getsize,
.unbind = mbr_unbind,
.release = mbr_release,
.open = mbr_open,
.close = mbr_close,
};
static void mbr_block_set_callbacks(void* ctx, block_callbacks_t* cb) {
mbrpart_device_t* device = ctx;
device->callbacks = cb;
}
static void mbr_block_get_info(void* ctx, block_info_t* info) {
mbrpart_device_t* device = ctx;
memcpy(info, &device->info, sizeof(*info));
}
static void mbr_block_complete(iotxn_t* txn, void* cookie) {
mbrpart_device_t* dev;
memcpy(&dev, txn->extra, sizeof(mbrpart_device_t*));
dev->callbacks->complete(cookie, txn->status);
iotxn_release(txn);
}
static void block_do_txn(mbrpart_device_t* dev, uint32_t opcode, zx_handle_t vmo, uint64_t length, uint64_t vmo_offset, uint64_t dev_offset, void* cookie) {
block_info_t* info = &dev->info;
if ((dev_offset % info->block_size) || (length % info->block_size)) {
dev->callbacks->complete(cookie, ZX_ERR_INVALID_ARGS);
return;
}
uint64_t size = getsize(dev);
if ((dev_offset >= size) || (length >= (size - dev_offset))) {
dev->callbacks->complete(cookie, ZX_ERR_OUT_OF_RANGE);
return;
}
zx_status_t status;
iotxn_t* txn;
if ((status = iotxn_alloc_vmo(&txn, IOTXN_ALLOC_POOL, vmo, vmo_offset, length)) != ZX_OK) {
dev->callbacks->complete(cookie, status);
return;
}
txn->opcode = opcode;
txn->length = length;
txn->offset = to_parent_offset(dev, dev_offset);
txn->complete_cb = mbr_block_complete;
txn->cookie = cookie;
memcpy(txn->extra, &dev, sizeof(mbrpart_device_t*));
iotxn_queue(dev->parent, txn);
}
static void mbr_block_read(void* ctx, zx_handle_t vmo, uint64_t length, uint64_t vmo_offset, uint64_t dev_offset, void* cookie) {
block_do_txn(ctx, IOTXN_OP_READ, vmo, length, vmo_offset, dev_offset, cookie);
}
static void mbr_block_write(void* ctx, zx_handle_t vmo, uint64_t length, uint64_t vmo_offset, uint64_t dev_offset, void* cookie) {
block_do_txn(ctx, IOTXN_OP_WRITE, vmo, length, vmo_offset, dev_offset, cookie);
}
static block_protocol_ops_t mbr_block_ops = {
.set_callbacks = mbr_block_set_callbacks,
.get_info = mbr_block_get_info,
.read = mbr_block_read,
.write = mbr_block_write,
};
static int mbr_bind_thread(void* arg) {
zx_device_t* dev = arg;
// Classic MBR supports 4 partitions.
uint8_t partition_count = 0;
iotxn_t* txn = NULL;
block_info_t block_info;
size_t actual;
ssize_t rc = device_ioctl(dev, IOCTL_BLOCK_GET_INFO, NULL, 0,
&block_info, sizeof(block_info), &actual);
if (rc < 0 || actual != sizeof(block_info)) {
xprintf("mbr: Could not get block size for dev=%s, retcode = %zd\n",
dev->name, rc);
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 iotxn_size = 0;
if (block_info.block_size >= MBR_SIZE) {
iotxn_size = block_info.block_size;
} else {
// Make sure we're reading some multiple of the block size.
iotxn_size = DIV_ROUND_UP(MBR_SIZE, block_info.block_size) * block_info.block_size;
}
zx_status_t st = iotxn_alloc(&txn, IOTXN_ALLOC_CONTIGUOUS, iotxn_size);
if (st != ZX_OK) {
xprintf("mbr: failed to allocate iotxn, retcode = %d\n", st);
goto unbind;
}
completion_t cplt = COMPLETION_INIT;
txn->opcode = IOTXN_OP_READ;
txn->offset = 0;
txn->length = iotxn_size;
txn->complete_cb = mbr_read_sync_complete;
txn->cookie = &cplt;
iotxn_queue(dev, txn);
completion_wait(&cplt, ZX_TIME_INFINITE);
if (txn->status != ZX_OK) {
xprintf("mbr: could not read mbr from device, retcode = %d\n",
txn->status);
goto unbind;
}
if (txn->actual < MBR_SIZE) {
xprintf("mbr: expected to read %u bytes but only read %" PRIu64 "\n",
MBR_SIZE, txn->actual);
goto unbind;
}
uint8_t buffer[MBR_SIZE];
iotxn_copyfrom(txn, buffer, MBR_SIZE, 0);
mbr_t* mbr = (mbr_t*)buffer;
// Validate the MBR boot signature.
if (mbr->boot_signature != MBR_BOOT_SIGNATURE) {
xprintf("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;
}
xprintf("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 = calloc(1, sizeof(*pdev));
if (!pdev) {
xprintf("mbr: out of memory\n");
goto unbind;
}
pdev->parent = dev;
memcpy(&pdev->partition, entry, sizeof(*entry));
block_info.block_count = pdev->partition.sector_partition_length;
memcpy(&pdev->info, &block_info, sizeof(block_info));
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_CORE,
.proto_ops = &mbr_block_ops,
};
if ((st = device_add(dev, &args, &pdev->mxdev)) != ZX_OK) {
xprintf("mbr: device_add failed, retcode = %d\n", st);
free(pdev);
continue;
}
}
iotxn_release(txn);
return 0;
unbind:
if (txn)
iotxn_release(txn);
// If we weren't able to bind any subdevices (for partitions), then unbind
// the MBR driver as well.
if (partition_count == 0) {
device_unbind(dev);
}
return -1;
}
static zx_status_t mbr_bind(void* ctx, zx_device_t* dev, void** cookie) {
// 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");
// Read the partition table asyncrhonously.
thrd_t t;
int thrd_rc = thrd_create_with_name(&t, mbr_bind_thread, dev, "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