diff --git a/blk/components/blk_components.mk b/blk/components/blk_components.mk index 06b970437..6d4dd1274 100644 --- a/blk/components/blk_components.mk +++ b/blk/components/blk_components.mk @@ -22,18 +22,23 @@ ${CHECK_BLK_FLAGS_MD5}: touch $@ -blk_virt.elf: blk_virt.o +blk_virt.elf: blk_virt.o blk_partitioning.o $(LD) $(LDFLAGS) $^ $(LIBS) -o $@ blk_virt.o: ${CHECK_BLK_FLAGS_MD5} blk_virt.o: ${SDDF}/blk/components/virt.c + ${CC} ${CFLAGS} -I${SDDF}/blk/components ${CFLAGS_blk} -o $@ -c $< + +blk_partitioning.o: ${CHECK_BLK_FLAGS_MD5} +blk_partitioning.o: ${SDDF}/blk/components/partitioning.c ${CC} ${CFLAGS} ${CFLAGS_blk} -o $@ -c $< clean:: - rm -f blk_virt.[od] .blk_cflags-* + rm -f blk_virt.[od] blk_partitioning.[od] .blk_cflags-* clobber:: rm -f ${BLK_IMAGES} -include blk_virt.d +-include blk_partitioning.d diff --git a/blk/components/gpt.h b/blk/components/gpt.h new file mode 100644 index 000000000..2b8e47f37 --- /dev/null +++ b/blk/components/gpt.h @@ -0,0 +1,46 @@ +/* + * Copyright 2024, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +/** + * GUID Partition Table + * + * https://en.wikipedia.org/wiki/GUID_Partition_Table + * */ + +#define GPT_MAX_PARTITIONS 128 +#define GPT_SECTOR_SIZE 512 +#define GPT_PARTITION_INFO_CODE 0x1 +#define GPT_PARTITION_INFO_MIRROR_CODE 0x2 +#define GPT_HEADER_SIGNATURE "EFI PART" + +struct gpt_partition_header { + char signature[8]; + uint32_t revision; + uint32_t header_size; + uint32_t crc32_header; + uint32_t reserved; + uint64_t lba_header; + uint64_t lba_alt_header; + uint64_t block_first; + uint64_t block_last; + uint8_t guid[16]; + uint64_t lba_start; + uint32_t num_entries; + uint32_t entry_size; + uint32_t crc32_entry_array; +} __attribute__((packed)); + +struct gpt_partition_entry { + uint8_t guid_type[16]; + uint8_t guid_unique[16]; + uint64_t lba_start; + uint64_t lba_end; + uint64_t attributes; + char name[72]; +} __attribute__((packed)); diff --git a/include/sddf/blk/msdos_mbr.h b/blk/components/msdos_mbr.h similarity index 94% rename from include/sddf/blk/msdos_mbr.h rename to blk/components/msdos_mbr.h index 7736b0ea8..35ce10b6d 100644 --- a/include/sddf/blk/msdos_mbr.h +++ b/blk/components/msdos_mbr.h @@ -33,3 +33,4 @@ struct msdos_mbr { } __attribute__((packed)); #define MSDOS_MBR_PARTITION_TYPE_EMPTY 0x00 +#define MSDOS_MBR_PARTITION_TYPE_GPT 0xEE diff --git a/blk/components/partitioning.c b/blk/components/partitioning.c new file mode 100644 index 000000000..69cff52f6 --- /dev/null +++ b/blk/components/partitioning.c @@ -0,0 +1,424 @@ +/* + * Copyright 2024, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include "virt.h" +#include "msdos_mbr.h" +#include "gpt.h" + +#include +#include + +/* Client specific info */ +typedef struct client { + uint32_t start_sector; + uint32_t sectors; +} client_t; +static client_t clients[SDDF_BLK_MAX_CLIENTS]; + +/* MS-DOS Master boot record */ +static struct msdos_mbr msdos_mbr; + +static const int mbr_req_count = 1; +static struct { + /* Whether or not we have sent the initial request to the driver for the MBR metadata block. */ + bool sent_request; + /* Request info associated with the MBR metadata block request, valid after sent_request is true */ + uintptr_t req_addr; + uint32_t req_id; +} mbr_state; + +/* GPT metadata */ +/* There are usually 1 (Protective MBR) + 1 (partition header) + 32 (partition table entries) sectors at the + * start of the disk in total, but the size of entry could be different in some cases. */ +static struct { + struct gpt_partition_header *header; + struct gpt_partition_entry *table; + struct gpt_partition_header *mirror_header; + struct gpt_partition_entry *mirror_table; + uint32_t table_size; +} gpt_meta; + +static struct { + /* Whether or not we have started the requests for GPT metadata blocks. */ + bool sent_request; + uintptr_t req_addr; + uint32_t req_id; + uint32_t req_cnt; + uintptr_t mirror_req_addr; + uint32_t mirror_req_id; + uint32_t mirror_req_cnt; + bool partition_table_ready; + bool mirror_partition_table_ready; +} gpt_state; + +static void mbr_partitions_dump() +{ + sddf_dprintf("the following MBR partitions exist:\n"); + for (int i = 0; i < MSDOS_MBR_MAX_PRIMARY_PARTITIONS; i++) { + sddf_dprintf(" partition %d: type: 0x%hhx", i, msdos_mbr.partitions[i].type); + if (msdos_mbr.partitions[i].type == MSDOS_MBR_PARTITION_TYPE_EMPTY) { + sddf_dprintf(" (empty)\n"); + } else { + sddf_dprintf("\n"); + } + } +} + +static void gpt_partitions_dump() +{ + sddf_dprintf("the following GPT partitions exist:\n"); + for (int i = 0; i < GPT_MAX_PARTITIONS; i++) { + /* If no lba_start addr, then do not dump the partition as it is non-existent. */ + if (gpt_meta.table[i].lba_start != 0) { + sddf_dprintf(" partition %d, lba_start: 0x%lx, name: %s\n", i, gpt_meta.table[i].lba_start, + gpt_meta.table[i].name); + } + } +} + +static bool gpt_partitions_init() +{ + for (int i = 0; i < config.num_clients; i++) { + blk_virt_config_client_t *client = &config.clients[i]; + size_t client_partition = client->partition; + if (gpt_meta.table[client_partition].lba_start == 0) { + /* Partition does not exist */ + LOG_BLK_VIRT_ERR( + "Invalid client partition mapping for client %d: partition: %zu, partition does not exist\n", i, + client_partition); + gpt_partitions_dump(); + return false; + } + clients[i].start_sector = gpt_meta.table[client_partition].lba_start; + clients[i].sectors = gpt_meta.table[client_partition].lba_end - gpt_meta.table[client_partition].lba_start + 1; + + blk_storage_info_t *client_storage_info = client->conn.storage_info.vaddr; + blk_storage_info_t *driver_storage_info = config.driver.conn.storage_info.vaddr; + client_storage_info->sector_size = driver_storage_info->sector_size; + client_storage_info->capacity = clients[i].sectors / (BLK_TRANSFER_SIZE / MSDOS_MBR_SECTOR_SIZE); + client_storage_info->read_only = false; + __atomic_store_n(&client_storage_info->ready, true, __ATOMIC_RELEASE); + } + + return true; +} + +uint32_t gpt_calc_crc32(const uint8_t *buffer, uint32_t len) +{ + int i, j; + uint32_t byte, crc, mask; + + i = 0; + crc = 0xFFFFFFFF; + for (i = 0; i < len; i++) { + byte = buffer[i]; // Get next byte. + crc = crc ^ byte; + for (j = 7; j >= 0; j--) { // Do eight times. + mask = -(crc & 1); + crc = (crc >> 1) ^ (0xEDB88320 & mask); + } + } + return ~crc; +} + +static bool gpt_validate_partitions() +{ + int err = 0; + if (blk_queue_empty_resp(&drv_h)) { + LOG_BLK_VIRT("Notified by driver but queue is empty, expecting a response to a BLK_REQ_READ request for GPT " + "partition entries\n"); + return false; + } + + blk_resp_status_t drv_status; + uint16_t drv_success_count; + uint32_t gpt_resp_id; + + /* Two responses could be notified only once */ + while (!blk_queue_empty_resp(&drv_h)) { + err = blk_dequeue_resp(&drv_h, &drv_status, &drv_success_count, &gpt_resp_id); + assert(!err); + + err = ialloc_free(&ialloc, gpt_resp_id); + assert(!err); + + if (drv_status != BLK_RESP_OK) { + LOG_BLK_VIRT_ERR("Failed to read partition table from driver\n"); + return false; + } + + if (gpt_resp_id == gpt_state.req_id) { + // Validate the signature in partition header + if (sddf_strcmp(gpt_meta.header->signature, GPT_HEADER_SIGNATURE)) { + LOG_BLK_VIRT_ERR("Invalid GPT signature\n"); + return false; + } + + // Validate the header checksum + uint32_t reserved_crc32 = gpt_meta.header->crc32_header; + gpt_meta.header->crc32_header = 0; // checksum field is zeroed for calculation + uint32_t crc32_header = gpt_calc_crc32((uint8_t *)gpt_meta.header, 0x5C); + if (crc32_header != reserved_crc32) { + LOG_BLK_VIRT_ERR("CRC32 checksum is incorrect.\n"); + return false; + } + gpt_meta.header->crc32_header = reserved_crc32; // Recover the checksum field + + cache_clean_and_invalidate(gpt_state.req_addr, + gpt_state.req_addr + (BLK_TRANSFER_SIZE * gpt_state.req_cnt)); + + uint32_t crc32_entry_array = gpt_calc_crc32((uint8_t *)gpt_meta.table, gpt_meta.table_size); + if (crc32_entry_array != gpt_meta.header->crc32_entry_array) { + LOG_BLK_VIRT_ERR("CRC32 checksum of partition entry array is incorrect.\n"); + return false; + } + + gpt_state.partition_table_ready = true; + + } else if (gpt_resp_id == gpt_state.mirror_req_id) { + cache_clean_and_invalidate(gpt_state.mirror_req_addr, + gpt_state.mirror_req_addr + (BLK_TRANSFER_SIZE * gpt_state.mirror_req_cnt)); + assert(!err); + + gpt_meta.mirror_header = (struct gpt_partition_header *)(gpt_state.mirror_req_addr + + gpt_state.mirror_req_cnt * BLK_TRANSFER_SIZE + - GPT_SECTOR_SIZE); + // Validate the signature in mirror header + if (sddf_strcmp(gpt_meta.mirror_header->signature, GPT_HEADER_SIGNATURE)) { + LOG_BLK_VIRT_ERR("Invalid GPT signature in mirror partition header\n"); + return false; + } + + // Validate the CRC32 checksum in mirror header + // mirror header is located at the back of the disk + uint32_t reserved_crc32 = gpt_meta.mirror_header->crc32_header; + gpt_meta.mirror_header->crc32_header = 0; // checksum field is zeroed for calculation + uint32_t crc32_header = gpt_calc_crc32((uint8_t *)gpt_meta.mirror_header, 0x5C); + if (crc32_header != reserved_crc32) { + LOG_BLK_VIRT_ERR("mirror CRC32 checksum is incorrect.\n"); + return false; + } + gpt_meta.mirror_header->crc32_header = reserved_crc32; // Recover the checksum field + + // Validate mirror partition table + // mirror table is before the mirror header + uint32_t table_offset = gpt_state.mirror_req_cnt * BLK_TRANSFER_SIZE - gpt_meta.table_size + - GPT_SECTOR_SIZE; + gpt_meta.mirror_table = (struct gpt_partition_entry *)(gpt_state.mirror_req_addr + table_offset); + uint32_t crc32_mirror_entry_array = gpt_calc_crc32((uint8_t *)gpt_meta.mirror_table, gpt_meta.table_size); + if (crc32_mirror_entry_array != gpt_meta.mirror_header->crc32_entry_array) { + LOG_BLK_VIRT_ERR("CRC32 checksum of partition entry array is incorrect.\n"); + return false; + } + + gpt_state.mirror_partition_table_ready = true; + } + } + + if (gpt_state.partition_table_ready && gpt_state.mirror_partition_table_ready) { + // Compare key fields of partition header and backup header + assert(gpt_meta.header->revision == gpt_meta.mirror_header->revision); + assert(gpt_meta.header->header_size == gpt_meta.mirror_header->header_size); + for (int i = 0; i < 16; i++) { + assert(gpt_meta.header->guid[i] == gpt_meta.mirror_header->guid[i]); + } + assert(gpt_meta.header->lba_header == gpt_meta.mirror_header->lba_alt_header); + assert(gpt_meta.header->lba_alt_header == gpt_meta.mirror_header->lba_header); + assert(gpt_meta.header->lba_start = gpt_meta.mirror_header->lba_start); + assert(gpt_meta.header->num_entries = gpt_meta.mirror_header->num_entries); + assert(gpt_meta.header->entry_size = gpt_meta.mirror_header->entry_size); + assert(gpt_meta.header->crc32_entry_array = gpt_meta.mirror_header->crc32_entry_array); + + return true; + } + return false; +} + +static bool mbr_partitions_init(void) +{ + if (msdos_mbr.signature != MSDOS_MBR_SIGNATURE) { + LOG_BLK_VIRT_ERR("Invalid MBR signature\n"); + return false; + } + + /* Validate partition and assign to client */ + for (int i = 0; i < config.num_clients; i++) { + blk_virt_config_client_t *client = &config.clients[i]; + size_t client_partition = client->partition; + + if (client_partition >= MSDOS_MBR_MAX_PRIMARY_PARTITIONS + || msdos_mbr.partitions[client_partition].type == MSDOS_MBR_PARTITION_TYPE_EMPTY) { + /* Partition does not exist */ + LOG_BLK_VIRT_ERR( + "Invalid client partition mapping for client %d: partition: %zu, partition does not exist\n", i, + client_partition); + mbr_partitions_dump(); + return false; + } + + if (msdos_mbr.partitions[client_partition].lba_start % (BLK_TRANSFER_SIZE / MSDOS_MBR_SECTOR_SIZE) != 0) { + /* Partition start sector is not aligned to sDDF transfer size */ + LOG_BLK_VIRT_ERR("Partition %d start sector %d not aligned to sDDF transfer size\n", (int)client_partition, + msdos_mbr.partitions[client_partition].lba_start); + return false; + } + + /* We have a valid partition now. */ + clients[i].start_sector = msdos_mbr.partitions[client_partition].lba_start; + clients[i].sectors = msdos_mbr.partitions[client_partition].sectors; + + blk_storage_info_t *client_storage_info = client->conn.storage_info.vaddr; + blk_storage_info_t *driver_storage_info = config.driver.conn.storage_info.vaddr; + client_storage_info->sector_size = driver_storage_info->sector_size; + client_storage_info->capacity = clients[i].sectors / (BLK_TRANSFER_SIZE / MSDOS_MBR_SECTOR_SIZE); + client_storage_info->read_only = driver_storage_info->read_only; + __atomic_store_n(&client_storage_info->ready, true, __ATOMIC_RELEASE); + } + return true; +} + +static void mbr_request() +{ + int err = 0; + uintptr_t mbr_paddr = config.driver.data.io_addr; + mbr_state.req_addr = (uintptr_t)config.driver.data.region.vaddr; + + err = ialloc_alloc(&ialloc, &mbr_state.req_id); + assert(!err); + + /* Virt-to-driver data region needs to be big enough to transfer MBR data */ + assert(config.driver.data.region.size >= BLK_TRANSFER_SIZE); + err = blk_enqueue_req(&drv_h, BLK_REQ_READ, mbr_paddr, 0, 1, mbr_state.req_id); + assert(!err); + + microkit_deferred_notify(config.driver.conn.id); + + mbr_state.sent_request = true; +} + +/* Process response for MBR metadata. + * Returns true if valid MBR. + * Return false if invalid MBR or GPT disk. + */ +static bool mbr_handle_response() +{ + /* Should not get here without the initial metadata request. */ + assert(mbr_state.sent_request); + + int err = 0; + if (blk_queue_empty_resp(&drv_h)) { + LOG_BLK_VIRT( + "Notified by driver but queue is empty, expecting a response to a BLK_REQ_READ request into sector 0\n"); + return false; + } + + blk_resp_status_t drv_status; + uint16_t drv_success_count; + uint32_t drv_resp_id; + err = blk_dequeue_resp(&drv_h, &drv_status, &drv_success_count, &drv_resp_id); + assert(!err); + + err = ialloc_free(&ialloc, mbr_state.req_id); + assert(!err); + + if (drv_status != BLK_RESP_OK) { + LOG_BLK_VIRT_ERR("Failed to read sector 0 from driver\n"); + return false; + } + + cache_clean_and_invalidate(mbr_state.req_addr, mbr_state.req_addr + (BLK_TRANSFER_SIZE * mbr_req_count)); + sddf_memcpy(&msdos_mbr, (void *)mbr_state.req_addr, sizeof(struct msdos_mbr)); + + /* There is only one partition entry in Protective MBR of the GPT parition schema */ + if (msdos_mbr.partitions[0].type == MSDOS_MBR_PARTITION_TYPE_GPT) { + LOG_BLK_VIRT("Protective MBR of GPT is detected\n"); + + /* Save the primary GPT header, and validate the header in gpt_validate_partitionsk() */ + gpt_meta.header = (struct gpt_partition_header *)(mbr_state.req_addr + GPT_SECTOR_SIZE); + gpt_meta.table_size = gpt_meta.header->num_entries * gpt_meta.header->entry_size; + + /* Copy the first two sectors of partition entries to the table */ + gpt_meta.table = (struct gpt_partition_entry *)(mbr_state.req_addr + GPT_SECTOR_SIZE * 2); + + /* Request for the blocks containing the rest of partition entries */ + uint32_t gpt_req_id = 0; + err = ialloc_alloc(&ialloc, &gpt_req_id); + assert(!err); + uint32_t meta_blk_cnt = (GPT_SECTOR_SIZE * 2 + BLK_TRANSFER_SIZE - 1 + gpt_meta.table_size) / BLK_TRANSFER_SIZE; + gpt_state.req_addr = (uintptr_t)config.driver.data.region.vaddr + BLK_TRANSFER_SIZE; + gpt_state.req_id = gpt_req_id; + gpt_state.req_cnt = meta_blk_cnt - 1; + err = blk_enqueue_req(&drv_h, BLK_REQ_READ, (uintptr_t)config.driver.data.io_addr + BLK_TRANSFER_SIZE, 1, + meta_blk_cnt - 1, gpt_req_id); + assert(!err); + + /* Request for mirror of partition table and header */ + err = ialloc_alloc(&ialloc, &gpt_req_id); + assert(!err); + + uint32_t meta_mirror_blk_cnt = (GPT_SECTOR_SIZE * 1 + BLK_TRANSFER_SIZE - 1 + gpt_meta.table_size) + / BLK_TRANSFER_SIZE; + uint32_t mirror_blk_start = (gpt_meta.header->lba_alt_header - gpt_meta.table_size / GPT_SECTOR_SIZE) + / (BLK_TRANSFER_SIZE / GPT_SECTOR_SIZE); + gpt_state.mirror_req_addr = (uintptr_t)config.driver.data.region.vaddr + BLK_TRANSFER_SIZE * meta_blk_cnt; + gpt_state.mirror_req_id = gpt_req_id; + gpt_state.mirror_req_cnt = meta_mirror_blk_cnt; + err = blk_enqueue_req(&drv_h, BLK_REQ_READ, + (uintptr_t)config.driver.data.io_addr + BLK_TRANSFER_SIZE * meta_blk_cnt, + mirror_blk_start, meta_mirror_blk_cnt, gpt_req_id); + assert(!err); + gpt_state.sent_request = true; + + microkit_deferred_notify(config.driver.conn.id); + return false; + } + + return true; +} + +blk_resp_status_t get_drv_block_number(uint64_t cli_block_number, uint16_t cli_count, int cli_id, + uint64_t *drv_block_number) +{ + uint64_t block_number = cli_block_number + + (clients[cli_id].start_sector / (BLK_TRANSFER_SIZE / MSDOS_MBR_SECTOR_SIZE)); + + unsigned long client_sectors = clients[cli_id].sectors / (BLK_TRANSFER_SIZE / MSDOS_MBR_SECTOR_SIZE); + unsigned long client_start_sector = clients[cli_id].start_sector / (BLK_TRANSFER_SIZE / MSDOS_MBR_SECTOR_SIZE); + if (block_number < client_start_sector || block_number + cli_count > client_start_sector + client_sectors) { + /* Requested block number out of bounds */ + LOG_BLK_VIRT_ERR("client %d request for block %lu is out of bounds\n", cli_id, cli_block_number); + return BLK_RESP_ERR_INVALID_PARAM; + } + + *drv_block_number = block_number; + + return BLK_RESP_OK; +} + +bool virt_partition_init(void) +{ + if (!mbr_state.sent_request) { + mbr_request(); + return false; + } + + bool success = false; + + if (gpt_state.sent_request) { + /* need to validate partition header and table of a GPT disk */ + success = gpt_validate_partitions(); + if (success) { + success = gpt_partitions_init(); + } + return success; + } + + success = mbr_handle_response(); + if (success) { + success = mbr_partitions_init(); + } + + return success; +} diff --git a/blk/components/virt.c b/blk/components/virt.c index 103d8121f..6f6dc0c7a 100644 --- a/blk/components/virt.c +++ b/blk/components/virt.c @@ -6,22 +6,19 @@ #include #include #include -#include -#include -#include -#include #include -#include #include #include #include +#include "virt.h" + #define DRIVER_MAX_NUM_BUFFERS 1024 __attribute__((__section__(".blk_virt_config"))) blk_virt_config_t config; /* Uncomment this to enable debug logging */ -// #define DEBUG_BLK_VIRT +/* #define DEBUG_BLK_VIRT */ #if defined(DEBUG_BLK_VIRT) #define LOG_BLK_VIRT(...) do{ sddf_dprintf("BLK_VIRT|INFO: "); sddf_dprintf(__VA_ARGS__); }while(0) @@ -36,8 +33,7 @@ blk_queue_handle_t drv_h; /* Client specific info */ typedef struct client { blk_queue_handle_t queue_h; - uint32_t start_sector; - uint32_t sectors; + uintptr_t data_paddr; } client_t; client_t clients[SDDF_BLK_MAX_CLIENTS]; @@ -52,119 +48,11 @@ typedef struct reqbk { static reqbk_t reqsbk[DRIVER_MAX_NUM_BUFFERS]; /* Index allocator for driver request id */ -static ialloc_t ialloc; +ialloc_t ialloc; static uint32_t ialloc_idxlist[DRIVER_MAX_NUM_BUFFERS]; -/* MS-DOS Master boot record */ -struct msdos_mbr msdos_mbr; - -/* The virtualiser is not initialised until we can read the MBR and populate the block device configuration. */ bool initialised = false; -static void partitions_dump() -{ - sddf_dprintf("the following partitions exist:\n"); - for (int i = 0; i < MSDOS_MBR_MAX_PRIMARY_PARTITIONS; i++) { - sddf_dprintf("partition %d: type: 0x%hhx", i, msdos_mbr.partitions[i].type); - if (msdos_mbr.partitions[i].type == MSDOS_MBR_PARTITION_TYPE_EMPTY) { - sddf_dprintf(" (empty)\n"); - } else { - sddf_dprintf("\n"); - } - } -} - -static void partitions_init() -{ - if (msdos_mbr.signature != MSDOS_MBR_SIGNATURE) { - LOG_BLK_VIRT_ERR("Invalid MBR signature\n"); - return; - } - - /* Validate partition and assign to client */ - for (int i = 0; i < config.num_clients; i++) { - blk_virt_config_client_t *client = &config.clients[i]; - size_t client_partition = client->partition; - - if (client_partition >= MSDOS_MBR_MAX_PRIMARY_PARTITIONS - || msdos_mbr.partitions[client_partition].type == MSDOS_MBR_PARTITION_TYPE_EMPTY) { - /* Partition does not exist */ - LOG_BLK_VIRT_ERR( - "Invalid client partition mapping for client %d: partition: %zu, partition does not exist\n", i, - client_partition); - partitions_dump(); - return; - } - - if (msdos_mbr.partitions[client_partition].lba_start % (BLK_TRANSFER_SIZE / MSDOS_MBR_SECTOR_SIZE) != 0) { - /* Partition start sector is not aligned to sDDF transfer size */ - LOG_BLK_VIRT_ERR("Partition %d start sector %d not aligned to sDDF transfer size\n", (int)client_partition, - msdos_mbr.partitions[client_partition].lba_start); - return; - } - - /* We have a valid partition now. */ - clients[i].start_sector = msdos_mbr.partitions[client_partition].lba_start; - clients[i].sectors = msdos_mbr.partitions[client_partition].sectors; - - blk_storage_info_t *client_storage_info = client->conn.storage_info.vaddr; - blk_storage_info_t *driver_storage_info = config.driver.conn.storage_info.vaddr; - client_storage_info->sector_size = driver_storage_info->sector_size; - client_storage_info->capacity = clients[i].sectors / (BLK_TRANSFER_SIZE / MSDOS_MBR_SECTOR_SIZE); - client_storage_info->read_only = false; - __atomic_store_n(&client_storage_info->ready, true, __ATOMIC_RELEASE); - } -} - -static void request_mbr() -{ - int err = 0; - uintptr_t mbr_paddr = config.driver.data.io_addr; - uintptr_t mbr_vaddr = (uintptr_t)config.driver.data.region.vaddr; - - uint32_t mbr_req_id = 0; - err = ialloc_alloc(&ialloc, &mbr_req_id); - assert(!err); - reqsbk[mbr_req_id] = (reqbk_t) { 0, 0, mbr_vaddr, 1, 0 }; - - /* Virt-to-driver data region needs to be big enough to transfer MBR data */ - assert(config.driver.data.region.size >= BLK_TRANSFER_SIZE); - err = blk_enqueue_req(&drv_h, BLK_REQ_READ, mbr_paddr, 0, 1, mbr_req_id); - assert(!err); - - microkit_deferred_notify(config.driver.conn.id); -} - -static bool handle_mbr_reply() -{ - int err = 0; - if (blk_queue_empty_resp(&drv_h)) { - LOG_BLK_VIRT( - "Notified by driver but queue is empty, expecting a response to a BLK_REQ_READ request into sector 0\n"); - return false; - } - - blk_resp_status_t drv_status; - uint16_t drv_success_count; - uint32_t drv_resp_id; - err = blk_dequeue_resp(&drv_h, &drv_status, &drv_success_count, &drv_resp_id); - assert(!err); - - reqbk_t mbr_bk = reqsbk[drv_resp_id]; - err = ialloc_free(&ialloc, drv_resp_id); - assert(!err); - - if (drv_status != BLK_RESP_OK) { - LOG_BLK_VIRT_ERR("Failed to read sector 0 from driver\n"); - return false; - } - - cache_clean_and_invalidate(mbr_bk.vaddr, mbr_bk.vaddr + (BLK_TRANSFER_SIZE * mbr_bk.count)); - sddf_memcpy(&msdos_mbr, (void *)mbr_bk.vaddr, sizeof(struct msdos_mbr)); - - return true; -} - void init(void) { assert(blk_config_check_magic(&config)); @@ -189,7 +77,7 @@ void init(void) /* Initialise index allocator */ ialloc_init(&ialloc, ialloc_idxlist, DRIVER_MAX_NUM_BUFFERS); - request_mbr(); + virt_partition_init(); } static void handle_driver() @@ -214,6 +102,8 @@ static void handle_driver() case BLK_REQ_READ: if (drv_status == BLK_RESP_OK) { /* Invalidate cache */ + LOG_BLK_VIRT("start: 0x%lx, end: 0x%lx\n", reqbk.vaddr, + reqbk.vaddr + (BLK_TRANSFER_SIZE * reqbk.count)); cache_clean_and_invalidate(reqbk.vaddr, reqbk.vaddr + (BLK_TRANSFER_SIZE * reqbk.count)); } break; @@ -274,19 +164,14 @@ static bool handle_client(int cli_id) assert(!err); uint64_t drv_block_number = 0; - drv_block_number = cli_block_number + (clients[cli_id].start_sector / (BLK_TRANSFER_SIZE / MSDOS_MBR_SECTOR_SIZE)); blk_resp_status_t resp_status = BLK_RESP_ERR_UNSPEC; switch (cli_code) { case BLK_REQ_READ: case BLK_REQ_WRITE: { - unsigned long client_sectors = clients[cli_id].sectors / (BLK_TRANSFER_SIZE / MSDOS_MBR_SECTOR_SIZE); - unsigned long client_start_sector = clients[cli_id].start_sector / (BLK_TRANSFER_SIZE / MSDOS_MBR_SECTOR_SIZE); - if (drv_block_number < client_start_sector || drv_block_number + cli_count > client_start_sector + client_sectors) { - /* Requested block number out of bounds */ - LOG_BLK_VIRT_ERR("client %d request for block %lu is out of bounds\n", cli_id, cli_block_number); - resp_status = BLK_RESP_ERR_INVALID_PARAM; + resp_status = get_drv_block_number(cli_block_number, cli_count, cli_id, &drv_block_number); + if (resp_status != BLK_RESP_OK) { goto req_fail; } @@ -371,13 +256,9 @@ static void handle_clients() void notified(microkit_channel ch) { - if (initialised == false) { - bool success = handle_mbr_reply(); - if (success) { - partitions_init(); - initialised = true; - }; - return; + if (!initialised) { + /* Continue processing partitions until initialisation has finished. */ + initialised = virt_partition_init(); } if (ch == config.driver.conn.id) { diff --git a/blk/components/virt.h b/blk/components/virt.h new file mode 100644 index 000000000..9649b4dc3 --- /dev/null +++ b/blk/components/virt.h @@ -0,0 +1,56 @@ +/* + * Copyright 2024, UNSW + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_MAX_NUM_BUFFERS 1024 + +extern blk_virt_config_t config; + +/* Uncomment this to enable debug logging */ +// #define DEBUG_BLK_VIRT + +#if defined(DEBUG_BLK_VIRT) +#define LOG_BLK_VIRT(...) do{ sddf_dprintf("BLK_VIRT|INFO: "); sddf_dprintf(__VA_ARGS__); }while(0) +#else +#define LOG_BLK_VIRT(...) do{}while(0) +#endif +#define LOG_BLK_VIRT_ERR(...) do{ sddf_dprintf("BLK_VIRT|ERROR: "); sddf_dprintf(__VA_ARGS__); }while(0) + +extern blk_queue_handle_t drv_h; +extern ialloc_t ialloc; + +/** + * Get the block number for the driver from the client's request information. + * + * @param cli_block_number the block number the client requested + * @param cli_count the number of blocks the client requested + * @param cli_id the client ID number + * @param drv_block_number the address of variable to store the block number in + */ +blk_resp_status_t get_drv_block_number(uint64_t cli_block_number, uint16_t cli_count, int cli_id, + uint64_t *drv_block_number); + +/** + * Initialise the block device partition metadata, either MBR or GPT + * + * This function is intended to be called multiple times until it returns true when + * it has finished all the request/response handling to get the partition metadata. + * The process is as follows: + * 1. Request MBR partition metadata. + * 2. Read MBR partition metadata, see if we are dealing with just MBR or GPT. + * 3. If MBR then we fill client storage info based on partitions, and then we are done. + * 4. If GPT, then we have more requests to send, then validate the partitions, then + * fill the client storage info based on that. + */ +bool virt_partition_init(void); diff --git a/build.zig b/build.zig index 05c652c20..c6698a181 100644 --- a/build.zig +++ b/build.zig @@ -366,8 +366,8 @@ pub fn build(b: *std.Build) void { .optimize = optimize, .strip = false, }); - blk_virt.addCSourceFile(.{ - .file = b.path("blk/components/virt.c"), + blk_virt.addCSourceFiles(.{ + .files = &. { "blk/components/virt.c", "blk/components/partitioning.c" }, }); blk_virt.addIncludePath(b.path("include")); blk_virt.addIncludePath(b.path("include/microkit")); diff --git a/examples/blk/blk.mk b/examples/blk/blk.mk index 7a48ca351..ac2f381b5 100644 --- a/examples/blk/blk.mk +++ b/examples/blk/blk.mk @@ -139,7 +139,7 @@ $(IMAGE_FILE) $(REPORT_FILE): $(IMAGES) $(SYSTEM_FILE) $(MICROKIT_TOOL) $(SYSTEM_FILE) --search-path $(BUILD_DIR) --board $(MICROKIT_BOARD) --config $(MICROKIT_CONFIG) -o $(IMAGE_FILE) -r $(REPORT_FILE) qemu_disk: - $(SDDF)/tools/mkvirtdisk disk 1 512 16777216 + $(SDDF)/tools/mkvirtdisk disk 1 512 16777216 GPT qemu: ${IMAGE_FILE} qemu_disk $(QEMU) $(QEMU_ARCH_ARGS) \ diff --git a/examples/blk/build.zig b/examples/blk/build.zig index 3189ee70d..6febddc7f 100644 --- a/examples/blk/build.zig +++ b/examples/blk/build.zig @@ -251,7 +251,7 @@ pub fn build(b: *std.Build) !void { create_disk_cmd.addFileInput(mkvirtdisk); const disk = create_disk_cmd.addOutputFileArg("disk"); create_disk_cmd.addArgs(&[_][]const u8{ - "1", "512", b.fmt("{}", .{ 1024 * 1024 * 16 }), + "1", "512", b.fmt("{}", .{ 1024 * 1024 * 16 }), "GPT", }); const disk_install = b.addInstallFile(disk, "disk"); disk_install.step.dependOn(&create_disk_cmd.step); diff --git a/tools/mkvirtdisk b/tools/mkvirtdisk index aceb216fa..70d1f2986 100755 --- a/tools/mkvirtdisk +++ b/tools/mkvirtdisk @@ -17,8 +17,8 @@ function cleanup { trap cleanup EXIT # Usage instructions -if [ $# -ne 4 ]; then - echo "Usage: $0 " +if [ $# -ne 5 ]; then + echo "Usage: $0 " exit 1 fi @@ -37,6 +37,7 @@ DISK_IMAGE=$1 NUM_PARTITIONS=$2 LSIZE=$3 MEMSIZE=$4 +PARTITION_SCHEMA=$5 SDDF_TRANSFER_SIZE=4096 FDISK_LSIZE=512 @@ -72,53 +73,94 @@ COUNT=$(( COUNT / MULTIPLE * MULTIPLE)) # Now ensure the real COUNT is a multipl # Create a file to act as a virtual disk dd if=/dev/zero of=$DISK_IMAGE bs=$FDISK_LSIZE count=$COUNT 2> /dev/null -FS_COUNT=$(( (COUNT - POFFSET - 1) / NUM_PARTITIONS )) -FS_COUNT=$(( FS_COUNT / MULTIPLE * MULTIPLE )) # Ensures that both filesystems are a multiple of sDDF transfer size and logical size - # Create MBR partition table -if [[ "$OSTYPE" == "linux"* ]]; then +if [[ "$PARTITION_SCHEMA" == "MBR" ]]; then + FS_COUNT=$(( (COUNT - POFFSET - 1) / NUM_PARTITIONS )) + FS_COUNT=$(( FS_COUNT / MULTIPLE * MULTIPLE )) # Ensures that both filesystems are a multiple of sDDF transfer size and logical size + echo "muliple:" $MULTIPLE + echo "count:" $COUNT + echo "poffset:" $POFFSET + echo "fs_count": $FS_COUNT + + if [[ "$OSTYPE" == "linux"* ]]; then + PREV=$POFFSET + { + echo o # Create a new empty DOS partition table + + # Loop to create each partition + for i in $(seq 1 $NUM_PARTITIONS) + do + echo n # Add a new partition + echo p # Primary partition + if [ $i != 4 ]; then + echo $i # Partition number + fi + echo $PREV # First sector + echo +$(( FS_COUNT - 1 )) # Last sector + PREV=$(( PREV + FS_COUNT )) + done + + echo w # Write changes + } | fdisk $DISK_IMAGE + fdisk -l $DISK_IMAGE # Print the partition table + elif [[ "$OSTYPE" == "darwin"* ]]; then + PREV=$POFFSET + { + echo y # Answer yes to create a new empty DOS partition table + + # Loop to create each partition + for i in $(seq 1 $NUM_PARTITIONS) + do + echo edit $i + echo 01 # Set partition type to FAT12 + echo n # No to editing in CHS mode + echo $PREV # First sector + echo $FS_COUNT # Number of sectors in partition + PREV=$(( PREV + FS_COUNT )) + done + + echo write # Write changes + echo quit # Save and quit fdisk + } | fdisk -e $DISK_IMAGE + fdisk $DISK_IMAGE # Print the partition table + else + echo "This script is not supported on your OS" + exit 1 + fi +elif [[ "$PARTITION_SCHEMA" == "GPT" ]]; then + GPT_SECTOR_ALIGNMENT=2048 + # The last 33 sectors are reserved for the copy of partition header and table + FS_COUNT=$(( (COUNT - POFFSET - 1 - 33) / NUM_PARTITIONS )) + # GPT partitions need to be aligned on 2048-sector boundaries + FS_COUNT=$(( FS_COUNT / GPT_SECTOR_ALIGNMENT * GPT_SECTOR_ALIGNMENT )) # Ensures that both filesystems are a multiple of sDDF transfer size and logical size + echo "muliple:" $MULTIPLE + echo "count:" $COUNT + echo "poffset:" $POFFSET + echo "fs_count": $FS_COUNT + + # The following operations are comptabile with both Linux and MacOSX PREV=$POFFSET { - echo o # Create a new empty DOS partition table + echo o # create a new empty GUID partition table + echo Y # Proceed # Loop to create each partition for i in $(seq 1 $NUM_PARTITIONS) do - echo n # Add a new partition - echo p # Primary partition - if [ $i != 4 ]; then - echo $i # Partition number - fi + echo n # create a new GPT partition + echo $i echo $PREV # First sector - echo +$(( FS_COUNT - 1 )) # Last sector + echo $((PREV + $FS_COUNT - 1)) # Last sector + echo 0700 # Hex code PREV=$(( PREV + FS_COUNT )) done echo w # Write changes - } | fdisk $DISK_IMAGE - fdisk -l $DISK_IMAGE # Print the partition table -elif [[ "$OSTYPE" == "darwin"* ]]; then - PREV=$POFFSET - { - echo y # Answer yes to create a new empty DOS partition table - - # Loop to create each partition - for i in $(seq 1 $NUM_PARTITIONS) - do - echo edit $i - echo 01 # Set partition type to FAT12 - echo n # No to editing in CHS mode - echo $PREV # First sector - echo $FS_COUNT # Number of sectors in partition - PREV=$(( PREV + FS_COUNT )) - done - - echo write # Write changes - echo quit # Save and quit fdisk - } | fdisk -e $DISK_IMAGE - fdisk $DISK_IMAGE # Print the partition table + echo y # Answer yes to proceed writing new GUID partition table to disk + } | gdisk $DISK_IMAGE + gdisk -l $DISK_IMAGE # Print the partition table else - echo "This script is not supported on your OS" + echo "Partition schema $PARTITION_SCHEMA is not supported" exit 1 fi @@ -130,5 +172,6 @@ mkfs.fat -C $BUILD_DIR/fat.img $(( (FS_COUNT * FDISK_LSIZE) / 1024 )) -F 12 -S $ for i in $(seq 0 $(( NUM_PARTITIONS - 1 ))) do echo "Copying FAT filesystem to partition $i, seek=$((POFFSET + i * FS_COUNT)), count=$FS_COUNT, bs=$FDISK_LSIZE" - dd if=$BUILD_DIR/fat.img of=$DISK_IMAGE bs=$FDISK_LSIZE seek="$((POFFSET + i * FS_COUNT))" count=$FS_COUNT 2> /dev/null + # `conv=notrnc` to disable truncating the disk to the size of data written, or it will destroy backup partition table + dd if=$BUILD_DIR/fat.img of=$DISK_IMAGE bs=$FDISK_LSIZE seek="$((POFFSET + i * FS_COUNT))" count=$FS_COUNT conv=notrunc 2> /dev/null done