/* * 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 #include #include #include #include #ifndef HOST_TEST #include #endif #include #include 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; }