iBoot/lib/blockdev/blockdev.c

542 lines
15 KiB
C

/*
* Copyright (C) 2006-2014 Apple Computer, Inc. All rights reserved.
*
* This document is the property of Apple Computer, Inc.
* It is considered confidential and proprietary.
*
* This document may not be reproduced or transmitted in any form,
* in whole or in part, without the express written permission of
* Apple Computer, Inc.
*/
#include <sys.h>
#include <stdlib.h>
#include <string.h>
#include <debug.h>
#include <lib/blockdev.h>
#ifndef HOST_TEST
#include <lib/mib.h>
#endif
#include <lib/libiboot.h>
#include <limits.h>
static struct blockdev *bdev_list = NULL;
#define BLOCKSIZE_MOD(dev, val) ((val) & ((1<<((dev)->block_shift)) - 1))
#define BLOCK_FROM_OFF(dev, off) ((off) >> (dev)->block_shift)
#define BLOCK_TO_OFF(dev, block) ((block) << (dev)->block_shift)
#define ALIGNMENT_MOD(dev, val) ((val) & ((1<<((dev)->alignment_shift)) - 1))
static size_t get_cpu_cache_line_size(void)
{
#ifndef HOST_TEST
return mib_get_size(kMIBPlatformCacheLineSize);
#elif defined CPU_CACHELINE_SIZE
return CPU_CACHELINE_SIZE;
#else
#error "Cache line size is not defined"
#endif
}
struct blockdev *lookup_blockdev(const char *name)
{
struct blockdev *dev;
for (dev = bdev_list; dev; dev = dev->next) {
if (strcmp(dev->name, name) == 0)
return dev;
}
return NULL;
}
struct blockdev *first_blockdev(void)
{
return bdev_list;
}
struct blockdev *next_blockdev(struct blockdev *dev)
{
return dev->next;
}
int register_blockdev(struct blockdev *dev)
{
ASSERT(dev != NULL);
ASSERT(dev->block_size > 0);
dprintf(DEBUG_INFO, "register_blockdev: dev %p '%s'\n", dev, dev->name);
dev->next = bdev_list;
bdev_list = dev;
return 0;
}
static void *get_bounce_buf(struct blockdev *dev)
{
void *result;
uint32_t align = __max(dev->alignment, (uint32_t)get_cpu_cache_line_size());
uint32_t size = __max(dev->alignment, dev->block_size);
posix_memalign(&result, align, size);
return result;
}
/* a reasonable default bytewise read routine in case the block device does not
* choose to override the read_hook. deblocks the device by reading from the read_block hook.
*/
static int blockdev_read_hook(struct blockdev *dev, void *ptr, off_t offset, uint64_t len)
{
int err;
size_t bytes_read;
block_addr block;
size_t buf_offset;
char *buf = NULL;
/* trim the read len */
RELEASE_ASSERT(offset >= 0);
if ((uint64_t)offset >= dev->total_len)
return 0;
if ((offset + len) > dev->total_len)
len = dev->total_len - offset;
buf_offset = 0;
bytes_read = 0;
block = BLOCK_FROM_OFF(dev, offset);
// dprintf(DEBUG_INFO, "read: ptr %p, buf_offset %d, bytes_read %zd, block %u, len %d, offset %lld\n", ptr, buf_offset, bytes_read, block, len, offset);
/* read in partial first block */
if (BLOCKSIZE_MOD(dev, offset) != 0 || ALIGNMENT_MOD(dev, (uintptr_t)ptr) != 0) {
size_t block_off = BLOCKSIZE_MOD(dev, offset);
size_t toread = __min(len, dev->block_size - block_off);
buf = get_bounce_buf(dev);
err = blockdev_read_block(dev, buf, block, 1);
if (err <= 0)
goto done;
/* copy the partial data */
memcpy(ptr, buf + block_off, toread);
block++;
offset += toread;
buf_offset += toread;
bytes_read += toread;
len -= toread;
}
// dprintf(DEBUG_INFO, "read2: ptr %p, buf_offset %d, bytes_read %zd, block %u, len %d, offset %lld, blockshift %d\n", ptr, buf_offset, bytes_read, block, len, offset, dev->block_shift);
ASSERT(len == 0 || BLOCKSIZE_MOD(dev, offset) == 0);
/* handle multiple middle blocks */
while (len >= dev->block_size) {
size_t blocks_to_read;
size_t alignment_offset;
blocks_to_read = BLOCK_FROM_OFF(dev, len);
/* If the buffer we're writing to isn't aligned, shorten the read by a block and
bounce through an aligned address higher up in the buffer. This relies on the block
size being >= the alignment, which is enforced by blockdev_set_buffer_alignment.
For reads of just one block, go through the bounce buffer */
alignment_offset = ALIGNMENT_MOD(dev, (uintptr_t)ptr + buf_offset);
if (alignment_offset != 0) {
if (blocks_to_read > 1) {
uint8_t *dest;
dest = (uint8_t *)ptr + buf_offset + dev->alignment - alignment_offset;
err = blockdev_read_block(dev, dest, block, blocks_to_read - 1);
if (err > 0)
memmove((uint8_t *)ptr + buf_offset, dest, BLOCK_TO_OFF(dev, err));
} else {
if (!buf)
buf = get_bounce_buf(dev);
err = blockdev_read_block(dev, buf, block, 1);
if (err > 0)
memmove((uint8_t *)ptr + buf_offset, buf, BLOCK_TO_OFF(dev, 1));
}
} else {
err = blockdev_read_block(dev, (uint8_t *)ptr + buf_offset, block, blocks_to_read);
}
if (err <= 0)
goto done;
/* account for blocks read */
block += err;
buf_offset += BLOCK_TO_OFF(dev, err);
bytes_read += BLOCK_TO_OFF(dev, err);
len -= BLOCK_TO_OFF(dev, err);
}
// dprintf(DEBUG_INFO, "read3: ptr %p, buf_offset %d, bytes_read %zd, block %ud, len %d, offset %lld\n", ptr, buf_offset, bytes_read, block, len, offset);
/* handle partial last block */
if (len > 0) {
ASSERT(len < dev->block_size);
if (!buf)
buf = get_bounce_buf(dev);
err = blockdev_read_block(dev, buf, block, 1);
if (err <= 0)
goto done;
memcpy((uint8_t *)ptr + buf_offset, buf, len);
bytes_read += len;
}
done:
if (NULL != buf)
free(buf);
return bytes_read;
}
static int blockdev_read_block_hook(struct blockdev *dev, void *ptr, block_addr block, uint32_t count)
{
dprintf(DEBUG_CRITICAL, "no reasonable default block read routine\n");
return -1;
}
static int blockdev_write_hook(struct blockdev *dev, const void *ptr, off_t offset, uint64_t len)
{
int err;
size_t bytes_written;
block_addr block;
size_t buf_offset;
char *buf = NULL;
const void *src;
/* trim the write len */
RELEASE_ASSERT(offset >= 0);
if ((uint64_t)offset >= dev->total_len)
return 0;
if ((offset + len) > dev->total_len)
len = dev->total_len - offset;
buf_offset = 0;
bytes_written = 0;
block = BLOCK_FROM_OFF(dev, offset);
// dprintf(DEBUG_INFO, "write: ptr %p, buf_offset %d, bytes_written %zd, block %u, len %d, offset %lld\n", ptr, buf_offset, bytes_written, block, len, offset);
/* write partial first block */
if (BLOCKSIZE_MOD(dev, offset) != 0 || ALIGNMENT_MOD(dev, offset) != 0) {
size_t block_off = BLOCKSIZE_MOD(dev, offset);
size_t towrite = __min(len, dev->block_size - block_off);
buf = get_bounce_buf(dev);
/* read in a buffer */
err = blockdev_read_block(dev, buf, block, 1);
if (err <= 0)
goto done;
/* copy the partial data */
memcpy(buf + block_off, ptr, towrite);
/* write it back out */
err = blockdev_write_block(dev, buf, block, 1);
if (err <= 0)
goto done;
block++;
offset += towrite;
buf_offset += towrite;
bytes_written += towrite;
len -= towrite;
}
// dprintf(DEBUG_INFO, "write2: ptr %p, buf_offset %d, bytes_written %zd, block %u, len %d, offset %lld, blockshift %d\n", ptr, buf_offset, bytes_written, block, len, offset, dev->block_shift);
ASSERT(len == 0 || BLOCKSIZE_MOD(dev, offset) == 0);
/* handle multiple middle blocks */
while (len >= dev->block_size) {
size_t blocks_to_write;
size_t alignment_offset;
/* If the buffer we're reading from isn't aligned, bounce buffer each block. iBoot
doesn't care about write performance for big writes, so it's ok for this to be slow */
alignment_offset = ALIGNMENT_MOD(dev, (uintptr_t)ptr + buf_offset);
if (alignment_offset != 0) {
if (!buf)
buf = get_bounce_buf(dev);
blocks_to_write = 1;
memcpy(buf, (const uint8_t *)ptr + buf_offset, BLOCK_TO_OFF(dev, blocks_to_write));
src = buf;
} else {
blocks_to_write = BLOCK_FROM_OFF(dev, len);
src = (const uint8_t *)ptr + buf_offset;
}
err = blockdev_write_block(dev, src, block, blocks_to_write);
if (err <= 0)
goto done;
/* account for write */
block += err;
buf_offset += BLOCK_TO_OFF(dev, err);
bytes_written += BLOCK_TO_OFF(dev, err);
len -= BLOCK_TO_OFF(dev, err);
}
// dprintf(DEBUG_INFO, "write3: ptr %p, buf_offset %d, bytes_written %zd, block %ud, len %d, offset %lld\n", ptr, buf_offset, bytes_written, block, len, offset);
/* handle partial last block */
if (len > 0) {
ASSERT(len < dev->block_size);
if (!buf)
buf = get_bounce_buf(dev);
err = blockdev_read_block(dev, buf, block, 1);
if (err <= 0)
goto done;
memcpy(buf, (uint8_t *)ptr + buf_offset, len);
err = blockdev_write_block(dev, buf, block, 1);
if (err <= 0)
goto done;
bytes_written += len;
}
done:
if (NULL != buf)
free(buf);
return bytes_written;
}
static int blockdev_write_block_hook(struct blockdev *dev, const void *ptr, block_addr block, uint32_t count)
{
dprintf(DEBUG_CRITICAL, "no reasonable default block write routine\n");
return -1;
}
static int blockdev_erase_hook(struct blockdev *dev, off_t offset, uint64_t len)
{
dprintf(DEBUG_CRITICAL, "no reasonable default erase routine\n");
return -1;
}
/* compare memory with a range of the block device */
int blockdev_compare(struct blockdev *dev, const void *_ptr, off_t bdev_offset, uint64_t len)
{
int result;
int err;
uint32_t i;
uint8_t *buf;
const uint8_t *ptr = (const uint8_t *)_ptr;
posix_memalign((void **)&buf, get_cpu_cache_line_size(), dev->block_size);
result = 0;
while (len > 0) {
size_t toread = __min(dev->block_size, len);
err = blockdev_read(dev, buf, bdev_offset, toread);
if (err <= 0) {
dprintf(DEBUG_CRITICAL, "bdev_compare: error reading from device at offset %lld\n", bdev_offset);
result = -1;
goto out;
}
if ((uint32_t)err < toread) {
dprintf(DEBUG_CRITICAL, "bdev_compare: short read from device at offset %lld (read %d)\n", bdev_offset, err);
result = -1;
goto out;
}
for (i=0; i < toread; i++) {
if (buf[i] != *ptr) {
dprintf(DEBUG_CRITICAL, "difference at offset %lld: dev 0x%02x mem 0x%02x\n", bdev_offset + i, buf[i], *ptr);
result++;
}
ptr++;
}
len -= toread;
bdev_offset += toread;
}
out:
free(buf);
return result;
}
/* write to the bdev safely, avoiding the protected region */
int blockdev_write_protected(struct blockdev *dev, const void *ptr, off_t offset, uint64_t len)
{
size_t overlap;
off_t protect_start, protect_end;
uint64_t total_length;
int err;
off_t bytes_written = 0;
// printf("write_protected: ptr %p offset 0x%llx length 0x%x\n", ptr, offset, len);
protect_start = dev->protect_start;
protect_end = dev->protect_end;
total_length = (uint64_t)offset + len;
// Test for negative offset and overlap
RELEASE_ASSERT(offset >= 0);
RELEASE_ASSERT(total_length >= len);
/* write the portion before the protected region */
if (offset < protect_start) {
ASSERT(protect_start >= 0);
if ((offset + len) > (size_t)protect_start) {
overlap = (offset + len) - protect_start;
} else {
overlap = 0;
}
// printf("BDEV: writing first part: 0x%llx/0x%x\n", offset, len - overlap);
err = dev->write_hook(dev, ptr, offset, len - overlap);
if (err < 0)
return err;
bytes_written += err;
}
/* and write the portion after */
RELEASE_ASSERT(protect_end >= 0);
if ((offset + len) > (size_t)protect_end) {
if (offset < protect_end) {
overlap = protect_end - offset;
} else {
overlap = 0;
}
// printf("BDEV: writing second part: 0x%llx/0x%x\n", offset + overlap, len - overlap);
err = dev->write_hook(dev, (char *)ptr + overlap, offset + overlap, len - overlap);
if (err < 0)
return err;
bytes_written += err;
}
return bytes_written;
}
int blockdev_write_block_protected(struct blockdev *dev, const void *ptr, block_addr block, uint32_t count)
{
size_t overlap;
size_t protect_start, protect_end;
int err;
uint32_t total_count;
// printf("write_block_protected: ptr %p block 0x%x count 0x%x\n", ptr, block, count);
protect_start = dev->protect_start >> dev->block_shift;
protect_end = dev->protect_end >> dev->block_shift;
total_count = block + count;
// Test for overlap
RELEASE_ASSERT(total_count >= block);
/* write the portion before the protected region */
if (block < protect_start) {
if ((block + count) > protect_start) {
overlap = (block + count) - protect_start;
} else {
overlap = 0;
}
// printf("BDEV: writing first part: 0x%x/0x%x\n", block, count - overlap);
err = dev->write_block_hook(dev, ptr, block, count - overlap);
if (err < 0) /* error */
return(-1);
if ((uint32_t)err < (count - overlap)) /* short write */
return(err);
}
/* and write the portion after */
if ((block + count) > protect_end) {
if (block < protect_end) {
overlap = protect_end - block;
} else {
overlap = 0;
}
// printf("BDEV: writing second part: 0x%x/0x%x\n", block + overlap, count - overlap);
err = dev->write_block_hook(dev, (char *)ptr + (overlap << dev->block_shift), block + overlap, count - overlap);
if (err < 0) /* error */
return(-1);
if ((uint32_t)err < (count - overlap)) /* short write XXX does not handle both ends
* overhanging */
return(err);
}
return(count);
}
/* set the protected region */
int blockdev_set_protection(struct blockdev *dev, off_t offset, uint64_t length)
{
if (dev->protect_end == dev->protect_start) {
dev->protect_start = offset;
dev->protect_end = offset + length;
dprintf(DEBUG_INFO, "BDEV: protecting 0x%llx-0x%llx\n", dev->protect_start, dev->protect_end);
return(0);
}
return(-1);
}
/* set the required buffer alignment for read and write buffers */
void blockdev_set_buffer_alignment(struct blockdev *dev, uint32_t alignment)
{
ASSERT(dev != NULL);
ASSERT(is_pow2(alignment));
/* Standard hooks don't support an alignment requirement bigger than the block size.
Drivers that need this can just make their blocksize equal to the alignment and
break the alignment-sized blocks into their intrinsic blocksize in their read block hook */
ASSERT(alignment <= dev->block_size);
/* can't change the alignment after first setting it */
ASSERT(dev->alignment == 1 && dev->alignment_shift == 0);
dev->alignment = alignment;
dev->alignment_shift = log2_int(dev->alignment);
}
/*
* set up the shared part of the block device structure, and set the default read hooks
* that provide a lot of the base functionality. The specific block device implementation should
* customize the structure further
*/
int construct_blockdev(struct blockdev *dev, const char *name, uint64_t len, uint32_t block_size)
{
ASSERT(dev != NULL);
ASSERT(is_pow2(block_size));
ASSERT(strlen(name) < sizeof(dev->name));
strlcpy(dev->name, name, sizeof(dev->name));
dev->flags = 0;
dev->block_size = block_size;
dev->block_shift = log2_int(dev->block_size);
dev->block_count = len >> dev->block_shift;
dev->total_len = len;
dev->alignment = 1;
dev->alignment_shift = 0;
ASSERT((len >> dev->block_shift) < UINT_MAX);
dev->read_hook = &blockdev_read_hook;
dev->read_block_hook = &blockdev_read_block_hook;
dev->write_hook = &blockdev_write_hook;
dev->write_block_hook = &blockdev_write_block_hook;
dev->erase_hook = &blockdev_erase_hook;
dev->protect_start = 0;
dev->protect_end = 0;
return 0;
}