542 lines
15 KiB
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;
|
||
|
}
|