static int BC_minimum_mem_size = 256;
#define BC_READAHEAD_WAIT_DEFAULT 10
#define BC_READAHEAD_WAIT_CDROM 45
static int BC_wait_for_readahead = BC_READAHEAD_WAIT_DEFAULT;
static int BC_chit_check_start = 500;
static int BC_chit_low_level = 50;
static int BC_cache_timeout = 240 * 100;
#ifndef DBG_BOOTCACHE
#define DBG_BOOTCACHE 7
#endif
#define DBG_BC_TAG 1
#define DBG_BC_BATCH 2
static int dbg_tag_count = 0;
#ifdef DEBUG
# define MACH_DEBUG
# define debug(fmt, args...) printf("**** %s: " fmt "\n", __FUNCTION__ , ##args)
extern void Debugger(char *);
#else
# define debug(fmt, args...)
#endif
#define message(fmt, args...) printf("BootCache: " fmt "\n" , ##args)
#ifdef EXTRA_DEBUG
# define xdebug(fmt, args...) printf("+++ %s: " fmt "\n", __FUNCTION__ , ##args)
#else
# define xdebug(fmt, args...)
#endif
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/kdebug.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/param.h>
#include <sys/proc.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <sys/vnode.h>
#include <sys/ubc.h>
#include <sys/uio.h>
#include <mach/kmod.h>
#include <mach/mach_types.h>
#include <mach/vm_param.h>
#include <vm/vm_kern.h>
#include <libkern/libkern.h>
#include <libkern/OSAtomic.h>
#include <kern/assert.h>
#include <kern/host.h>
#include <kern/kalloc.h>
#include <kern/thread_call.h>
#include <IOKit/storage/IOMediaBSDClient.h>
#include <pexpert/pexpert.h>
#include "BootCache.h"
struct BC_cache_extent {
lck_mtx_t ce_lock;
u_int64_t ce_diskoffset;
u_int64_t ce_length;
off_t ce_cacheoffset;
int ce_flags;
#define CE_ABORTED (1 << 9)
#define CE_IODONE (1 << 10)
u_int32_t *ce_blockmap;
};
struct BC_history_cluster {
struct BC_history_cluster *hc_link;
int hc_entries;
struct BC_history_entry hc_data[0];
};
#ifdef READ_HISTORY_BUFFER
struct BC_read_history_entry {
struct BC_cache_extent *rh_extent;
u_int64_t rh_blkno;
u_int64_t rh_bcount;
int rh_result;
# define BC_RHISTORY_OK 0
# define BC_RHISTORY_FAIL 1
};
#endif
#ifdef STATIC_HISTORY
# define BC_HISTORY_ALLOC 128000
# define BC_HISTORY_ENTRIES \
(((BC_HISTORY_ALLOC - sizeof(struct BC_history_cluster)) / \
sizeof(struct BC_history_entry)) - 1)
# define BC_HISTORY_MAXCLUSTERS 1
#else
# define BC_HISTORY_ALLOC 16000
# define BC_HISTORY_ENTRIES \
(((BC_HISTORY_ALLOC - sizeof(struct BC_history_cluster)) / \
sizeof(struct BC_history_entry)) - 1)
# define BC_HISTORY_MAXCLUSTERS ((BC_MAXENTRIES / BC_HISTORY_ENTRIES) + 1)
#endif
struct BC_cache_control {
dev_t c_dev;
struct vnode *c_devvp;
struct bdevsw *c_devsw;
strategy_fcn_t *c_strategy;
u_int64_t c_blocksize;
u_int64_t c_devsize;
u_int64_t c_cachesize;
u_int64_t c_maxread;
struct BC_cache_extent *c_extents;
int c_extent_count;
int c_batch_count;
lck_grp_t *c_lckgrp;
vnode_t c_vp;
uint32_t c_vid;
struct BC_history_cluster *c_history;
u_int64_t c_history_bytes;
lck_mtx_t c_histlock;
UInt32 c_flags;
#define BC_FLAG_SHUTDOWN (1 << 0)
#define BC_FLAG_CACHEACTIVE (1 << 1)
#define BC_FLAG_HTRUNCATED (1 << 2)
#define BC_FLAG_IOBUSY (1 << 3)
#define BC_FLAG_STARTED (1 << 4)
SInt32 c_strategycalls;
struct BC_statistics c_stats;
struct timeval c_starttime;
struct timeval c_endtime;
struct timeval c_loadtime;
#ifdef READ_HISTORY_BUFFER
int c_rhistory_idx;
struct BC_read_history_entry *c_rhistory;
#endif
};
#define CB_PAGEBLOCKS (PAGE_SIZE / BC_cache->c_blocksize)
#define CB_BLOCK_TO_PAGE(block) ((block) / CB_PAGEBLOCKS)
#define CB_PAGE_TO_BLOCK(page) ((page) * CB_PAGEBLOCKS)
#define CB_BLOCK_TO_BYTE(block) ((block) * BC_cache->c_blocksize)
#define CB_BYTE_TO_BLOCK(byte) ((byte) / BC_cache->c_blocksize)
#define CB_MAPFIELDBITS 32
#define CB_MAPFIELDBYTES 4
#define CB_MAPIDX(x) ((x) / CB_MAPFIELDBITS)
#define CB_MAPBIT(x) (1 << ((x) % CB_MAPFIELDBITS))
#define CB_BLOCK_PRESENT(ce, block) \
((ce)->ce_blockmap[CB_MAPIDX(block)] & CB_MAPBIT(block))
#define CB_MARK_BLOCK_PRESENT(ce, block) \
((ce)->ce_blockmap[CB_MAPIDX(block)] |= CB_MAPBIT(block))
#define CB_MARK_BLOCK_VACANT(ce, block) \
((ce)->ce_blockmap[CB_MAPIDX(block)] &= ~CB_MAPBIT(block))
#define CB_PAGE_VACANT(ce, page) \
(!((ce)->ce_blockmap[CB_MAPIDX(CB_PAGE_TO_BLOCK(page))] & \
(((1 << CB_PAGEBLOCKS) - 1) << (CB_PAGE_TO_BLOCK(page) % \
CB_MAPFIELDBITS))))
#define _FREE_ZERO(p, c) \
do { \
if (p != NULL) { \
_FREE(p, c); \
p = NULL; \
} \
} while (0);
#define LOCK_EXTENT(e) lck_mtx_lock(&(e)->ce_lock)
#define UNLOCK_EXTENT(e) lck_mtx_unlock(&(e)->ce_lock)
#define LOCK_HISTORY() lck_mtx_lock(&BC_cache->c_histlock)
#define UNLOCK_HISTORY() lck_mtx_unlock(&BC_cache->c_histlock)
#define BC_ADD_STAT(stat, inc) OSAddAtomic((inc), ((SInt32 *)&BC_cache->c_stats.ss_##stat))
static struct BC_cache_control BC_cache_softc;
static struct BC_cache_control *BC_cache = &BC_cache_softc;
extern kmod_info_t kmod_info;
static struct BC_playlist_header *BC_preloaded_playlist;
static int BC_preloaded_playlist_size;
static int BC_check_intersection(struct BC_cache_extent *ce, u_int64_t offset, u_int64_t length);
static struct BC_cache_extent *BC_find_extent(u_int64_t offset, u_int64_t length, int contained);
static int BC_discard_blocks(struct BC_cache_extent *ce, u_int64_t offset, u_int64_t length);
static int BC_blocks_present(struct BC_cache_extent *ce, int base, int nblk);
static void BC_reader_thread(void *param0, wait_result_t param1);
static void BC_strategy_bypass(struct buf *bp);
static void BC_strategy(struct buf *bp);
static void BC_handle_write(struct buf *bp);
static int BC_terminate_readahead(void);
static int BC_terminate_cache(void);
static int BC_terminate_history(void);
static void BC_next_valid_range(struct BC_cache_extent *ce, uint32_t from,
uint32_t *nextpage, uint32_t *nextoffset, uint32_t *nextlength);
static u_int64_t BC_setup_extent(struct BC_cache_extent *ce, struct BC_playlist_entry *pce);
static void BC_teardown_extent(struct BC_cache_extent *ce);
static int BC_copyin_playlist(size_t length, user_addr_t uptr);
static int BC_init_cache(size_t length, user_addr_t uptr, u_int64_t blocksize);
static void BC_timeout_cache(void *);
static void BC_add_history(u_int64_t offset, u_int64_t length, int flags);
static int BC_size_history(void);
static int BC_copyout_history(user_addr_t uptr);
static void BC_discard_history(void);
static void BC_auto_start(void);
static int BC_sysctl SYSCTL_HANDLER_ARGS;
SYSCTL_PROC(_kern, OID_AUTO, BootCache, CTLFLAG_RW,
0, 0, BC_sysctl,
"S,BC_command",
"BootCache command interface");
extern int netboot_root(void);
extern void (* mountroot_post_hook)(void);
extern void (* unmountroot_pre_hook)(void);
static int BC_alloc_pagebuffer();
static void BC_free_pagebuffer();
static void BC_free_page(struct BC_cache_extent *ce, int page);
int ubc_range_op(vnode_t, off_t, off_t, int, int *);
#define MACH_KERNEL 1
#include <mach-o/loader.h>
extern void *getsectdatafromheader(struct mach_header *, char *, char *, int *);
static inline UInt32
BC_set_flag(UInt32 flag)
{
return flag & OSBitOrAtomic(flag, &BC_cache->c_flags);
}
static inline UInt32
BC_clear_flag(UInt32 flag)
{
return flag & OSBitAndAtomic(~(flag), &BC_cache->c_flags);
}
static inline int
BC_check_intersection(struct BC_cache_extent *ce, u_int64_t offset, u_int64_t length)
{
if ((offset < (ce->ce_diskoffset + ce->ce_length)) &&
((offset + length) > ce->ce_diskoffset))
return 1;
return 0;
}
static struct BC_cache_extent *
BC_find_extent(u_int64_t offset, u_int64_t length, int contained)
{
struct BC_cache_extent *p, *base, *last;
size_t lim;
assert(length > 0);
base = BC_cache->c_extents;
last = base + BC_cache->c_extent_count - 1;
if (!(BC_cache->c_flags & BC_FLAG_CACHEACTIVE) || (base == NULL))
return(NULL);
if (((offset + length) <= base->ce_diskoffset) ||
(offset >= (last->ce_diskoffset + last->ce_length)))
return(NULL);
for (lim = BC_cache->c_extent_count; lim != 0; lim >>= 1) {
p = base + (lim >> 1);
if (BC_check_intersection(p, offset, length)) {
if ((contained != 0) &&
((offset < p->ce_diskoffset) ||
((offset + length) > (p->ce_diskoffset + p->ce_length))))
return(NULL);
return(p);
}
if (offset > p->ce_diskoffset) {
base = p + 1;
lim--;
}
}
return(NULL);
}
static int
BC_discard_blocks(struct BC_cache_extent *ce, u_int64_t offset, u_int64_t length)
{
u_int64_t estart, eend, dstart, dend, nblk, base, page;
int i, discarded;
assert(length > 0);
#ifdef DEBUG
lck_mtx_assert(&ce->ce_lock, LCK_MTX_ASSERT_OWNED);
#endif
if (ce->ce_flags & CE_ABORTED)
return 0;
dstart = offset;
dend = offset + length;
assert(dend > dstart);
estart = ce->ce_diskoffset;
eend = ce->ce_diskoffset + ce->ce_length;
assert(eend > estart);
if (dend <= estart)
return(0);
if (eend <= dstart)
return(0);
if (dstart < estart)
dstart = estart;
if (dend > eend)
dend = eend;
nblk = CB_BYTE_TO_BLOCK(dend - dstart);
base = CB_BYTE_TO_BLOCK(dstart - ce->ce_diskoffset);
assert(base >= 0);
discarded = 0;
for (i = 0; i < nblk; i++) {
assert((base + i) < howmany(ce->ce_length, BC_cache->c_blocksize));
if (CB_BLOCK_PRESENT(ce, base + i)) {
CB_MARK_BLOCK_VACANT(ce, base + i);
discarded++;
page = CB_BLOCK_TO_PAGE(base + i);
if (CB_PAGE_VACANT(ce, page))
BC_free_page(ce, (int) page);
}
}
return(discarded);
}
static void
BC_next_valid_range(struct BC_cache_extent *ce, uint32_t fromoffset,
uint32_t *nextpage, uint32_t *nextoffset, uint32_t *nextlength)
{
int maxblks, i, nextblk = 0;
int found = 0;
maxblks = (int) howmany(ce->ce_length, BC_cache->c_blocksize);
i = CB_BYTE_TO_BLOCK(fromoffset);
for (; i < maxblks; i++) {
if (CB_BLOCK_PRESENT(ce, i)) {
if (found == 0) {
nextblk = i;
}
found++;
} else {
if (found != 0)
break;
}
}
if (found) {
*nextpage = CB_BLOCK_TO_PAGE(nextblk);
*nextoffset = CB_BLOCK_TO_BYTE(nextblk) % PAGE_SIZE;
*nextlength = MIN(CB_BLOCK_TO_BYTE(found), BC_cache->c_maxread);
} else {
*nextpage = -1;
}
return;
}
static int
BC_blocks_present(struct BC_cache_extent *ce, int base, int nblk)
{
int blk;
assert((base + nblk) < howmany(ce->ce_length, BC_cache->c_blocksize));
for (blk = 0; blk < nblk; blk++) {
if (!CB_BLOCK_PRESENT(ce, base + blk)) {
BC_ADD_STAT(hit_blkmissing, 1);
return(0);
}
}
return(1);
}
static void
BC_reader_thread(void *param0, wait_result_t param1)
{
struct BC_cache_extent *ce = NULL;
struct buf *bp;
int count, batch;
upl_t upl;
kern_return_t kret;
struct timeval batchstart, batchend;
bp = buf_alloc(BC_cache->c_devvp);
debug("reader thread started");
for (batch = 0; batch <= BC_cache->c_batch_count; batch++) {
debug("starting batch %d", batch);
KERNEL_DEBUG_CONSTANT(FSDBG_CODE(DBG_BOOTCACHE, DBG_BC_BATCH) |
DBG_FUNC_START,
batch, 0, 0, 0, 0);
microtime(&batchstart);
for (ce = BC_cache->c_extents;
ce < (BC_cache->c_extents + BC_cache->c_extent_count);
ce++) {
LOCK_EXTENT(ce);
if (BC_cache->c_flags & BC_FLAG_SHUTDOWN)
goto out;
if ((ce->ce_flags & CE_BATCH_MASK) != batch) {
UNLOCK_EXTENT(ce);
continue;
}
if (ce->ce_flags & CE_ABORTED) {
UNLOCK_EXTENT(ce);
continue;
}
buf_setcount(bp, 0);
uint32_t fromoffset = 0;
for (;;) {
uint32_t nextpage;
uint32_t nextoffset;
uint32_t nextlength;
BC_next_valid_range(ce, fromoffset, &nextpage, &nextoffset, &nextlength);
if (nextpage == -1)
break;
fromoffset = (nextpage * PAGE_SIZE) + nextoffset + nextlength;
kret = vnode_getwithvid(BC_cache->c_vp, BC_cache->c_vid);
if (kret != KERN_SUCCESS) {
debug("reader thread: vnode_getwithvid failed - %d\n", kret);
goto out;
}
kret = ubc_create_upl(BC_cache->c_vp,
ce->ce_cacheoffset + (nextpage * PAGE_SIZE),
(int) roundup(nextoffset + nextlength, PAGE_SIZE),
&upl,
NULL,
UPL_SET_LITE|UPL_FILE_IO);
if (kret != KERN_SUCCESS) {
debug("ubc_create_upl returned %d\n", kret);
(void) vnode_put(BC_cache->c_vp);
goto out;
}
buf_setblkno(bp, CB_BYTE_TO_BLOCK(ce->ce_diskoffset + nextoffset) + CB_PAGE_TO_BLOCK(nextpage));
buf_setcount(bp, (unsigned int) nextlength);
buf_setupl(bp, upl, (unsigned int) nextoffset);
buf_setresid(bp, buf_count(bp));
buf_reset(bp, B_READ);
KERNEL_DEBUG_CONSTANT(FSDBG_CODE(DBG_DKRW, DKIO_READ) | DBG_FUNC_NONE,
(uintptr_t) bp, buf_device(bp), (int)buf_blkno(bp), buf_count(bp), 0);
BC_ADD_STAT(initiated_reads, 1);
BC_cache->c_strategy(bp);
buf_biowait(bp);
kret = ubc_upl_commit(upl);
(void) vnode_put(BC_cache->c_vp);
if (kret != KERN_SUCCESS || buf_error(bp) || (buf_resid(bp) != 0)) {
debug("read error: extent %lu %lu/%lu "
"(error buf %ld/%ld flags %08x resid %d)",
(unsigned long) (ce - BC_cache->c_extents),
(unsigned long)ce->ce_diskoffset, (unsigned long)ce->ce_length,
(long)buf_blkno(bp), (long)buf_count(bp),
buf_flags(bp), buf_resid(bp));
count = BC_discard_blocks(ce, CB_BLOCK_TO_BYTE(buf_blkno(bp)),
buf_count(bp));
debug("read error: discarded %d blocks", count);
BC_ADD_STAT(read_errors, 1);
BC_ADD_STAT(error_discards, count);
}
#ifdef READ_HISTORY_BUFFER
if (BC_cache->c_rhistory_idx < READ_HISTORY_BUFFER) {
BC_cache->c_rhistory[BC_cache->c_rhistory_idx].rh_extent = ce;
BC_cache->c_rhistory[BC_cache->c_rhistory_idx].rh_blkno = buf_blkno(bp);
BC_cache->c_rhistory[BC_cache->c_rhistory_idx].rh_bcount = buf_count(bp);
if (buf_error(bp) || (buf_resid(bp) != 0)) {
BC_cache->c_rhistory[BC_cache->c_rhistory_idx].rh_result = BC_RHISTORY_FAIL;
BC_cache->c_rhistory_idx++;
} else {
# ifdef READ_HISTORY_ALL
BC_cache->c_rhistory[BC_cache->c_rhistory_idx].rh_result = BC_RHISTORY_OK;
BC_cache->c_rhistory_idx++;
# endif
}
}
#endif
}
BC_ADD_STAT(read_blocks, (u_int) CB_BYTE_TO_BLOCK(ce->ce_length));
BC_ADD_STAT(batch_size[batch], (u_int) CB_BYTE_TO_BLOCK(ce->ce_length));
ce->ce_flags |= CE_IODONE;
UNLOCK_EXTENT(ce);
wakeup(ce);
}
microtime(&batchend);
timersub(&batchend, &batchstart, &batchend);
BC_cache->c_stats.ss_batch_time[MIN(batch, STAT_BATCHMAX)] =
(u_int) batchend.tv_sec * 1000 +
(u_int) batchend.tv_usec / 1000;
debug("batch %d done", batch);
KERNEL_DEBUG_CONSTANT(FSDBG_CODE(DBG_BOOTCACHE, DBG_BC_BATCH) |
DBG_FUNC_END,
batch, 0, 0, 0, 0);
ce = NULL;
}
out:
if (ce != NULL) {
struct BC_cache_extent *tmp = BC_cache->c_extents;
UNLOCK_EXTENT(ce);
while ((tmp - BC_cache->c_extents) < BC_cache->c_extent_count) {
LOCK_EXTENT(tmp);
if (batch < (tmp->ce_flags & CE_BATCH_MASK)) {
tmp->ce_flags |= CE_ABORTED;
}
if ((tmp >= ce) && (batch == (tmp->ce_flags & CE_BATCH_MASK))) {
tmp->ce_flags |= CE_ABORTED;
}
UNLOCK_EXTENT(tmp);
wakeup(tmp);
tmp++;
}
}
BC_clear_flag(BC_FLAG_IOBUSY);
wakeup(&BC_cache->c_flags);
debug("reader thread done");
buf_free(bp);
}
static void
BC_strategy_bypass(struct buf *bp)
{
int isread = 0;
assert(bp != NULL);
if (buf_flags(bp) & B_THROTTLED_IO) {
BC_ADD_STAT(strategy_throttled, 1);
} else if (buf_flags(bp) & B_READ) {
BC_add_history(CB_BLOCK_TO_BYTE(buf_blkno(bp)), buf_count(bp), BC_HE_MISS);
isread = 1;
}
if (BC_cache->c_flags & BC_FLAG_CACHEACTIVE) {
BC_ADD_STAT(strategy_bypassed, 1);
if (BC_cache->c_flags & BC_FLAG_IOBUSY)
BC_ADD_STAT(strategy_bypass_active, 1);
}
if (isread &&
(BC_cache->c_extents != NULL) &&
(BC_cache->c_flags & BC_FLAG_CACHEACTIVE)) {
if (!(BC_cache->c_flags & BC_FLAG_IOBUSY) &&
(BC_cache->c_stats.ss_extent_lookups > BC_chit_check_start) &&
(((BC_cache->c_stats.ss_extent_hits * 100) /
BC_cache->c_stats.ss_extent_lookups) < BC_chit_low_level)) {
message("hit rate below threshold (%d hits on %d lookups)",
BC_cache->c_stats.ss_extent_hits,
BC_cache->c_stats.ss_extent_lookups);
if (BC_terminate_readahead()) {
message("could not stop readahead on bad cache hitrate");
} else {
OSAddAtomic(-1, &BC_cache->c_strategycalls);
if (BC_terminate_cache()) {
message("could not terminate cache on bad hitrate");
OSAddAtomic(1, &BC_cache->c_strategycalls);
}
}
}
}
BC_cache->c_strategy(bp);
}
static void
BC_strategy(struct buf *bp)
{
struct BC_cache_extent *ce = NULL;
uio_t uio = NULL;
int base, nblk, retry, bcount, resid, discards;
struct timespec timeout;
daddr64_t blkno;
caddr_t p;
off_t offset;
kern_return_t kret;
assert(bp != NULL);
blkno = buf_blkno(bp);
bcount = buf_count(bp);
timeout.tv_sec = 0;
timeout.tv_nsec = 100 * 1000 * 1000;
if (buf_device(bp) != BC_cache->c_dev) {
BC_cache->c_strategy(bp);
return;
}
OSAddAtomic(1, &BC_cache->c_strategycalls);
BC_ADD_STAT(strategy_calls, 1);
if (!(BC_cache->c_flags & BC_FLAG_CACHEACTIVE)) {
goto bypass;
}
if ( !(buf_flags(bp) & B_READ)) {
BC_handle_write(bp);
BC_add_history(CB_BLOCK_TO_BYTE(blkno), bcount, BC_HE_WRITE);
BC_ADD_STAT(strategy_nonread, 1);
goto bypass;
}
if (buf_flags(bp) & B_THROTTLED_IO) {
goto bypass;
}
BC_ADD_STAT(requested_blocks, (u_int) CB_BYTE_TO_BLOCK(bcount));
BC_ADD_STAT(extent_lookups, 1);
if ((ce = BC_find_extent(CB_BLOCK_TO_BYTE(blkno), bcount, 1)) == NULL)
goto bypass;
BC_ADD_STAT(extent_hits, 1);
LOCK_EXTENT(ce);
for (retry = 0; ; retry++) {
if (ce->ce_flags & (CE_ABORTED | CE_IODONE))
break;
if (retry > (BC_wait_for_readahead * 10)) {
debug("timed out waiting for extent %p to be read", ce);
goto bypass;
}
if (retry == 1)
BC_ADD_STAT(strategy_blocked, 1);
msleep(ce, &ce->ce_lock, PRIBIO, "BC_strategy", &timeout);
}
if (ce->ce_flags & CE_ABORTED)
goto bypass;
base = (int) (blkno - CB_BYTE_TO_BLOCK(ce->ce_diskoffset));
nblk = (int) CB_BYTE_TO_BLOCK(bcount);
assert(base >= 0);
if (!BC_blocks_present(ce, base, nblk))
goto bypass;
if (BC_cache->c_flags & BC_FLAG_IOBUSY)
BC_ADD_STAT(strategy_duringio, 1);
#ifdef EMULATE_ONLY
discards = BC_discard_blocks(ce, CB_BLOCK_TO_BYTE(blkno), bcount);
BC_ADD_STAT(hit_blocks, discards);
UNLOCK_EXTENT(ce);
BC_add_history(CB_BLOCK_TO_BYTE(blkno), bcount, BC_HE_HIT);
BC_cache->c_strategy(bp);
#else
if (buf_map(bp, &p)) {
goto bypass;
}
offset = ce->ce_cacheoffset + CB_BLOCK_TO_BYTE(base);
resid = bcount;
uio = uio_create(1, offset, UIO_SYSSPACE, UIO_READ);
if (uio == NULL) {
debug("couldn't allocate uio");
buf_unmap(bp);
goto bypass;
}
kret = uio_addiov(uio, CAST_USER_ADDR_T(p), bcount);
if (kret != KERN_SUCCESS) {
debug("couldn't add iov to uio - %d", kret);
buf_unmap(bp);
goto bypass;
}
kret = vnode_getwithvid(BC_cache->c_vp, BC_cache->c_vid);
if (kret != KERN_SUCCESS) {
debug("vnode_getwithvid failed - %d", kret);
buf_unmap(bp);
goto bypass;
}
kret = cluster_copy_ubc_data(BC_cache->c_vp, uio, &resid, 1);
if (kret != KERN_SUCCESS) {
debug("couldn't copy ubc data - %d", kret);
buf_unmap(bp);
(void) vnode_put(BC_cache->c_vp);
goto bypass;
}
(void) vnode_put(BC_cache->c_vp);
if (resid != 0) {
discards = BC_discard_blocks(ce, CB_BLOCK_TO_BYTE(blkno), bcount);
BC_ADD_STAT(lost_blocks, discards);
buf_unmap(bp);
goto bypass;
}
uio_free(uio);
buf_setresid(bp, 0);
buf_unmap(bp);
buf_biodone(bp);
discards = BC_discard_blocks(ce, CB_BLOCK_TO_BYTE(blkno), bcount);
BC_ADD_STAT(hit_blocks, discards);
UNLOCK_EXTENT(ce);
BC_add_history(CB_BLOCK_TO_BYTE(blkno), bcount, BC_HE_HIT);
#endif
OSAddAtomic(-1, &BC_cache->c_strategycalls);
return;
bypass:
if (ce != NULL)
UNLOCK_EXTENT(ce);
if (uio != NULL)
uio_free(uio);
BC_strategy_bypass(bp);
OSAddAtomic(-1, &BC_cache->c_strategycalls);
return;
}
static void
BC_handle_write(struct buf *bp)
{
struct BC_cache_extent *ce, *p;
int count;
daddr64_t blkno;
u_int64_t offset;
int bcount;
assert(bp != NULL);
blkno = buf_blkno(bp);
bcount = buf_count(bp);
offset = CB_BLOCK_TO_BYTE(blkno);
if ((ce = BC_find_extent(offset, bcount, 0)) == NULL)
return;
LOCK_EXTENT(ce);
count = BC_discard_blocks(ce, offset, bcount);
UNLOCK_EXTENT(ce);
BC_ADD_STAT(write_discards, count);
assert(count != 0);
p = ce - 1;
while (p >= BC_cache->c_extents &&
BC_check_intersection(p, offset, bcount)) {
LOCK_EXTENT(p);
count = BC_discard_blocks(p, offset, bcount);
UNLOCK_EXTENT(p);
if (count == 0)
break;
BC_ADD_STAT(write_discards, count);
p--;
}
p = ce + 1;
while (p < (BC_cache->c_extents + BC_cache->c_extent_count) &&
BC_check_intersection(p, offset, bcount)) {
LOCK_EXTENT(p);
count = BC_discard_blocks(p, offset, bcount);
UNLOCK_EXTENT(p);
if (count == 0)
break;
BC_ADD_STAT(write_discards, count);
p++;
}
}
static int
BC_terminate_readahead(void)
{
int error;
if (BC_cache->c_flags & BC_FLAG_IOBUSY) {
debug("terminating active readahead");
if (!BC_set_flag(BC_FLAG_SHUTDOWN)) {
debug("readahead already shut down");
return 0;
}
error = tsleep(&BC_cache->c_flags, PRIBIO, "BC_terminate_readahead", 1000);
if (error == EWOULDBLOCK) {
debug("timed out waiting for I/O to stop");
if (!(BC_cache->c_flags & BC_FLAG_IOBUSY)) {
debug("but I/O has stopped!");
}
#ifdef DEBUG
Debugger("I/O Kit wedged on us");
#endif
return(EBUSY);
}
}
return(0);
}
static int
BC_terminate_cache(void)
{
int retry, i, j;
if (BC_cache->c_flags & BC_FLAG_IOBUSY) {
debug("cannot terminate cache while readahead is in progress");
return(EBUSY);
}
if (!BC_clear_flag(BC_FLAG_CACHEACTIVE)) {
debug("cannot terminate cache when not active");
return(ENXIO);
}
debug("terminating cache...");
for (i = 0; i < BC_cache->c_extent_count; i++) {
struct BC_cache_extent *ce = BC_cache->c_extents + i;
LOCK_EXTENT(ce);
ce->ce_flags |= CE_ABORTED;
UNLOCK_EXTENT(ce);
wakeup(ce);
for (j = 0; j < howmany(ce->ce_length, BC_cache->c_blocksize); j++) {
if (CB_BLOCK_PRESENT(ce, j))
BC_ADD_STAT(spurious_blocks, 1);
}
BC_teardown_extent(ce);
}
retry = 0;
while (BC_cache->c_strategycalls > 0) {
tsleep(BC_cache, PRIBIO, "BC_terminate_cache", 10);
if (retry++ > 50) {
debug("could not terminate cache, timed out with %d caller%s in BC_strategy",
(int) BC_cache->c_strategycalls,
BC_cache->c_strategycalls == 1 ? "" : "s");
return(EBUSY);
}
}
BC_free_pagebuffer();
_FREE_ZERO(BC_cache->c_extents, M_TEMP);
return(0);
}
static int
BC_terminate_history(void)
{
debug("terminating history collection...");
if ((BC_cache->c_devsw != NULL) &&
(BC_cache->c_strategy != NULL))
BC_cache->c_devsw->d_strategy = BC_cache->c_strategy;
microtime(&BC_cache->c_endtime);
timersub(&BC_cache->c_endtime,
&BC_cache->c_starttime,
&BC_cache->c_endtime);
BC_ADD_STAT(active_time, (u_int) BC_cache->c_endtime.tv_sec * 1000 +
(u_int) BC_cache->c_endtime.tv_usec / 1000);
timersub(&BC_cache->c_starttime,
&BC_cache->c_loadtime,
&BC_cache->c_starttime);
BC_ADD_STAT(preload_time, (u_int) BC_cache->c_starttime.tv_sec * 1000 +
(u_int) BC_cache->c_starttime.tv_usec / 1000);
return(0);
}
static u_int64_t
BC_setup_extent(struct BC_cache_extent *ce, struct BC_playlist_entry *pce)
{
u_int64_t roundsize;
int numblocks;
roundsize = roundup(pce->pce_length, PAGE_SIZE);
numblocks = (int) howmany(pce->pce_length, BC_cache->c_blocksize);
lck_mtx_init(&ce->ce_lock, BC_cache->c_lckgrp,
LCK_ATTR_NULL);
ce->ce_diskoffset = pce->pce_offset;
ce->ce_length = pce->pce_length;
ce->ce_flags = (int) (pce->pce_batch & CE_BATCH_MASK);
#ifdef IGNORE_BATCH
ce->ce_flags &= ~CE_BATCH_MASK;
#endif
ce->ce_cacheoffset = 0;
ce->ce_blockmap = _MALLOC(
howmany(numblocks, (CB_MAPFIELDBITS / CB_MAPFIELDBYTES)),
M_TEMP, M_WAITOK | M_ZERO);
if (!ce->ce_blockmap)
return 0;
if (ce->ce_flags > BC_cache->c_batch_count) {
BC_cache->c_batch_count = ce->ce_flags;
}
return roundsize;
}
static void
BC_teardown_extent(struct BC_cache_extent *ce)
{
ce->ce_flags |= CE_ABORTED;
_FREE_ZERO(ce->ce_blockmap, M_TEMP);
}
static int
BC_copyin_playlist(size_t length, user_addr_t uptr)
{
struct BC_playlist_entry *pce;
struct BC_cache_extent *ce;
int error, idx;
off_t p;
u_int64_t size, esize;
int entries;
int actual;
error = 0;
pce = NULL;
entries = (int) (length / sizeof(*pce));
BC_cache->c_extents = _MALLOC(entries * sizeof(struct BC_cache_extent),
M_TEMP, M_WAITOK | M_ZERO);
if (BC_cache->c_extents == NULL) {
message("can't allocate memory for extents");
error = ENOMEM;
goto out;
}
BC_cache->c_extent_count = entries;
BC_cache->c_stats.ss_total_extents = entries;
ce = BC_cache->c_extents;
size = 0;
if (BC_preloaded_playlist) {
debug("using static playlist with %d entries", entries);
pce = CAST_DOWN(struct BC_playlist_entry *, uptr);
for (idx = 0; idx < entries; idx++) {
esize = BC_setup_extent(ce + idx, pce + idx);
if (esize == 0) {
error = ENOMEM;
goto out;
}
size += esize;
}
} else {
debug("using supplied playlist with %d entries", entries);
pce = _MALLOC(BC_PLC_CHUNK * sizeof(struct BC_playlist_entry),
M_TEMP, M_WAITOK);
if (pce == NULL) {
message("can't allocate unpack buffer");
goto out;
}
while (entries > 0) {
actual = min(entries, BC_PLC_CHUNK);
if ((error = copyin(uptr, pce,
actual * sizeof(struct BC_playlist_entry))) != 0) {
message("copyin from 0x%llx to %p failed", uptr, pce);
_FREE(pce, M_TEMP);
goto out;
}
for (idx = 0; idx < actual; idx++) {
esize = BC_setup_extent(ce + idx, pce + idx);
if (esize == 0) {
error = ENOMEM;
goto out;
}
size += esize;
}
entries -= actual;
uptr += actual * sizeof(struct BC_playlist_entry);
ce += actual;
}
_FREE(pce, M_TEMP);
}
size = roundup(size, PAGE_SIZE * CB_MAPFIELDBITS);
if (size > (max_mem / 2)) {
message("cache size (%lu bytes) too large for physical memory (%llu bytes)",
(unsigned long)size, max_mem);
goto out;
}
BC_cache->c_cachesize = size;
if (BC_alloc_pagebuffer() != 0) {
message("can't allocate %lld bytes for cache buffer", size);
error = ENOMEM;
goto out;
}
p = 0;
for (idx = 0; idx < BC_cache->c_extent_count; idx++) {
u_int64_t length = BC_cache->c_extents[idx].ce_length;
int j;
(BC_cache->c_extents + idx)->ce_cacheoffset = p;
p += roundup(length, PAGE_SIZE);
for (j = 0; j < howmany(length, BC_cache->c_blocksize); j++)
CB_MARK_BLOCK_PRESENT((BC_cache->c_extents + idx), j);
}
out:
if (error != 0) {
debug("cache setup failed, aborting");
if (BC_cache->c_vp != NULL)
BC_free_pagebuffer();
for (idx = 0; idx < BC_cache->c_extent_count; idx++)
BC_teardown_extent(BC_cache->c_extents + idx);
_FREE_ZERO(BC_cache->c_extents, M_TEMP);
BC_cache->c_extent_count = 0;
BC_cache->c_stats.ss_total_extents = 0;
}
return(error);
}
static int
BC_init_cache(size_t length, user_addr_t uptr, u_int64_t blocksize)
{
u_int32_t blksize;
u_int64_t blkcount;
int error;
unsigned int boot_arg;
thread_t rthread;
error = 0;
if (BC_set_flag(BC_FLAG_STARTED)) {
debug("cache already started");
return(EBUSY);
}
if ((max_mem / (1024 * 1024)) < BC_minimum_mem_size) {
debug("insufficient physical memory (%dMB < %dMB required)",
(int)(max_mem / (1024 * 1024)), BC_minimum_mem_size);
return(ENOMEM);
}
if (netboot_root() &&
(!PE_parse_boot_argn("BootCacheOverride", &boot_arg, sizeof(boot_arg)) ||
(boot_arg != 1)))
return(ENXIO);
BC_cache->c_dev = rootdev;
BC_cache->c_devvp = rootvp;
BC_cache->c_devsw = &bdevsw[major(rootdev)];
assert(BC_cache->c_devsw != NULL);
BC_cache->c_strategy = BC_cache->c_devsw->d_strategy;
if (BC_cache->c_strategy == NULL)
return(ENXIO);
BC_cache->c_devsw->d_ioctl(BC_cache->c_dev,
DKIOCGETMAXSEGMENTBYTECOUNTREAD,
(caddr_t)&BC_cache->c_maxread,
0,
kernproc);
if (BC_cache->c_maxread < PAGE_SIZE) {
debug("can't determine device read size; using default");
BC_cache->c_maxread = MAXPHYS;
}
BC_cache->c_devsw->d_ioctl(BC_cache->c_dev,
DKIOCGETBLOCKSIZE,
(caddr_t)&blksize,
0,
kernproc);
if (blksize == 0) {
message("can't determine device block size, defaulting to 512 bytes");
blksize = 512;
}
BC_cache->c_devsw->d_ioctl(BC_cache->c_dev,
DKIOCGETBLOCKCOUNT,
(caddr_t)&blkcount,
0,
kernproc);
if (blkcount == 0) {
message("can't determine device size, not checking");
}
BC_cache->c_blocksize = blksize;
BC_cache->c_devsize = BC_cache->c_blocksize * blkcount;
BC_cache->c_stats.ss_blocksize = (u_int) BC_cache->c_blocksize;
debug("blocksize %lu bytes, filesystem %lu bytes",
(unsigned long)BC_cache->c_blocksize, (unsigned long)BC_cache->c_devsize);
BC_cache->c_lckgrp = lck_grp_alloc_init("BootCache", LCK_GRP_ATTR_NULL);
lck_mtx_init(&BC_cache->c_histlock, BC_cache->c_lckgrp, LCK_ATTR_NULL);
if (length > 0) {
if (BC_cache->c_blocksize > blocksize) {
message("playlist blocksize %lu not compatible with root device's %lu",
(unsigned long)blocksize, (unsigned long)BC_cache->c_blocksize);
return(EINVAL);
}
if ((error = BC_copyin_playlist(length, uptr)) != 0) {
message("can't load playlist");
return(error);
}
} else {
debug("no playlist, recording only");
}
microtime(&BC_cache->c_starttime);
#ifndef NO_HISTORY
# ifdef STATIC_HISTORY
if ((BC_cache->c_history = _MALLOC(BC_HISTORY_ALLOC, M_TEMP, M_WAITOK)) == NULL) {
message("can't allocate %d bytes for static history buffer", BC_HISTORY_ALLOC);
return(ENOMEM);
}
bzero(BC_cache->c_history, BC_HISTORY_ALLOC);
BC_cache->c_stats.ss_history_clusters = 1;
# endif
#endif
#ifdef READ_HISTORY_BUFFER
if ((BC_cache->c_rhistory = _MALLOC(READ_HISTORY_BUFFER * sizeof(struct BC_read_history_entry),
M_TEMP, M_WAITOK)) == NULL) {
message("can't allocate %d bytes for read history buffer",
READ_HISTORY_BUFFER * sizeof(struct BC_read_history_entry));
return(ENOMEM);
}
bzero(BC_cache->c_rhistory, READ_HISTORY_BUFFER * sizeof(struct BC_read_history_entry));
#endif
#ifdef EMULATE_ONLY
{
int i;
for (i = 0; i < BC_cache->c_extent_count; i++)
BC_cache->c_extents[i].ce_flags |= CE_IODONE;
}
#else
if (BC_cache->c_extents != NULL) {
debug("starting readahead");
BC_set_flag(BC_FLAG_IOBUSY);
kernel_thread_start(BC_reader_thread, NULL, &rthread);
thread_deallocate(rthread);
}
#endif
BC_cache->c_devsw->d_strategy = BC_strategy;
BC_set_flag(BC_FLAG_CACHEACTIVE);
return(0);
}
static void
BC_timeout_cache(void *junk)
{
BC_terminate_readahead();
if (BC_terminate_cache())
return;
BC_terminate_history();
}
static void
BC_add_history(u_int64_t offset, u_int64_t length, int flags)
{
struct BC_history_cluster *hc, *tmphc = NULL;
struct BC_history_entry *he;
LOCK_HISTORY();
if (BC_cache->c_flags & BC_FLAG_HTRUNCATED)
goto out;
if ((BC_cache->c_history_bytes + length) > (max_mem / 2))
goto out;
BC_cache->c_history_bytes += length;
#ifdef NO_HISTORY
BC_set_flag(BC_FLAG_HTRUNCATED);
debug("history disabled, truncating");
goto out;
#endif
hc = BC_cache->c_history;
if ((hc == NULL) || (hc->hc_entries >= BC_HISTORY_ENTRIES)) {
BC_ADD_STAT(history_clusters, 1);
if (BC_cache->c_stats.ss_history_clusters >= BC_HISTORY_MAXCLUSTERS) {
message("too many history clusters (%d, limit %ld)",
BC_cache->c_stats.ss_history_clusters,
(long)BC_HISTORY_MAXCLUSTERS);
BC_set_flag(BC_FLAG_HTRUNCATED);
BC_ADD_STAT(history_clusters, -1);
goto out;
}
UNLOCK_HISTORY();
tmphc = kalloc(BC_HISTORY_ALLOC);
LOCK_HISTORY();
if (tmphc == NULL) {
message("could not allocate %d bytes for history cluster",
BC_HISTORY_ALLOC);
BC_set_flag(BC_FLAG_HTRUNCATED);
goto out;
}
hc = BC_cache->c_history;
if ((hc == NULL) || (hc->hc_entries >= BC_HISTORY_ENTRIES)) {
tmphc->hc_entries = 0;
tmphc->hc_link = BC_cache->c_history;
BC_cache->c_history = tmphc;
hc = tmphc;
tmphc = NULL;
}
}
he = &(hc->hc_data[hc->hc_entries]);
assert(he >= &hc->hc_data[0]);
assert(he < &hc->hc_data[BC_HISTORY_ENTRIES]);
he->he_offset = offset;
he->he_length = length;
he->he_flags = flags;
hc->hc_entries++;
out:
UNLOCK_HISTORY();
if (tmphc != NULL)
kfree(tmphc, BC_HISTORY_ALLOC);
return;
}
static int
BC_size_history(void)
{
struct BC_history_cluster *hc;
int nentries;
nentries = 0;
for (hc = BC_cache->c_history; hc != NULL; hc = hc->hc_link)
nentries += hc->hc_entries;
return(nentries * (int) sizeof(struct BC_history_entry));
}
static int
BC_copyout_history(user_addr_t uptr)
{
struct BC_history_cluster *hc, *ohc;
int error, nentries;
assert(uptr != 0);
nentries = 0;
for (hc = BC_cache->c_history; hc != NULL; hc = hc->hc_link)
nentries += hc->hc_entries;
ohc = NULL;
for (;;) {
hc = BC_cache->c_history;
while (hc->hc_link != ohc)
hc = hc->hc_link;
ohc = hc;
if ((error = copyout(hc->hc_data, uptr,
hc->hc_entries * sizeof(struct BC_history_entry))) != 0)
return(error);
uptr += hc->hc_entries * sizeof(struct BC_history_entry);
if (hc == BC_cache->c_history)
break;
}
return(0);
}
static void
BC_discard_history(void)
{
struct BC_history_cluster *hc;
while (BC_cache->c_history != NULL) {
hc = BC_cache->c_history;
BC_cache->c_history = hc->hc_link;
kfree(hc, BC_HISTORY_ALLOC);
}
}
static void
BC_auto_start(void)
{
int error;
error = BC_init_cache(BC_preloaded_playlist->ph_entries * sizeof(struct BC_playlist_entry),
CAST_USER_ADDR_T((uintptr_t)BC_preloaded_playlist + sizeof(struct BC_playlist_header)),
BC_preloaded_playlist->ph_blocksize);
if (error != 0)
debug("BootCache autostart failed: %d", error);
}
static void
BC_unmount_hook(void)
{
untimeout(BC_timeout_cache, NULL);
BC_terminate_readahead();
if (!BC_terminate_cache())
BC_terminate_history();
}
static int
BC_sysctl SYSCTL_HANDLER_ARGS
{
struct BC_command bc;
int error;
if ((error = SYSCTL_IN(req, &bc, sizeof(bc))) != 0) {
debug("couldn't get command");
return(error);
}
if (bc.bc_magic != BC_MAGIC) {
debug("bad command magic");
return(EINVAL);
}
switch (bc.bc_opcode) {
case BC_OP_START:
if (BC_preloaded_playlist) {
error = EINVAL;
break;
}
debug("BC_OP_START(%d)", bc.bc_length);
error = BC_init_cache(bc.bc_length, (user_addr_t) bc.bc_data, (u_int64_t)bc.bc_param);
if (error != 0) {
message("cache init failed");
} else {
timeout(BC_timeout_cache, NULL, BC_cache_timeout);
}
break;
case BC_OP_STOP:
debug("BC_OP_STOP");
unmountroot_pre_hook = NULL;
untimeout(BC_timeout_cache, NULL);
BC_terminate_readahead();
if (!BC_terminate_cache())
BC_terminate_history();
LOCK_HISTORY();
if (BC_cache->c_flags & BC_FLAG_HTRUNCATED) {
bc.bc_length = 0;
} else {
bc.bc_length = BC_size_history();
}
if ((error = SYSCTL_OUT(req, &bc, sizeof(bc))) != 0)
debug("could not return history size");
UNLOCK_HISTORY();
break;
case BC_OP_HISTORY:
debug("BC_OP_HISTORY");
LOCK_HISTORY();
if (bc.bc_data != 0) {
if (bc.bc_length < BC_size_history()) {
debug("supplied history buffer too small");
error = EINVAL;
UNLOCK_HISTORY();
break;
}
if ((error = BC_copyout_history(bc.bc_data)) != 0)
debug("could not copy out history");
}
BC_discard_history();
UNLOCK_HISTORY();
BC_clear_flag(BC_FLAG_STARTED);
break;
case BC_OP_STATS:
debug("BC_OP_STATS");
if (bc.bc_length != sizeof(BC_cache->c_stats)) {
debug("stats structure wrong size");
error = ENOMEM;
} else {
BC_cache->c_stats.ss_cache_flags = BC_cache->c_flags;
if ((error = copyout(&BC_cache->c_stats, bc.bc_data, bc.bc_length)) != 0)
debug("could not copy out statistics");
}
break;
case BC_OP_TAG:
debug("BC_OP_TAG");
KERNEL_DEBUG_CONSTANT(FSDBG_CODE(DBG_BOOTCACHE, DBG_BC_TAG) |
DBG_FUNC_NONE, proc_selfppid(), dbg_tag_count,
0, 0, 0);
BC_add_history((u_int64_t)bc.bc_param, 0, BC_HE_TAG);
dbg_tag_count++;
break;
default:
error = EINVAL;
}
return(error);
}
static int
BC_alloc_pagebuffer()
{
kern_return_t kret;
kret = vnode_open(BC_BOOT_BACKINGFILE, (O_CREAT | O_NOFOLLOW | FREAD),
0400, 0, &BC_cache->c_vp, vfs_context_current());
if (kret != KERN_SUCCESS) {
debug("vnode_open failed - %d", kret);
return -1;
}
BC_cache->c_vid = vnode_vid(BC_cache->c_vp);
assert(BC_cache->c_cachesize != 0);
kret = ubc_setsize(BC_cache->c_vp, BC_cache->c_cachesize);
if (kret != 1) {
debug("ubc_setsize failed - %d", kret);
return -1;
}
(void) vnode_put(BC_cache->c_vp);
return(0);
}
static void
BC_free_pagebuffer(void)
{
kern_return_t kret;
if (BC_cache->c_vp != NULL) {
kret = vnode_getwithref(BC_cache->c_vp);
if (kret != KERN_SUCCESS) {
debug("vnode_getwithref failed - %d", kret);
return;
}
kret = ubc_range_op(BC_cache->c_vp, 0, BC_cache->c_cachesize,
UPL_ROP_DUMP, NULL);
if (kret != KERN_SUCCESS) {
debug("ubc_range_op failed - %d", kret);
}
#if 0
kret = vnode_delete(BC_BOOT_BACKINGFILE, vfs_context_current());
if (kret) {
debug("vnode_delete failed - %d", error);
}
#endif
kret = vnode_close(BC_cache->c_vp, 0, vfs_context_current());
if (kret != KERN_SUCCESS) {
debug("vnode_close failed - %d", kret);
}
BC_cache->c_vp = NULL;
}
}
static void
BC_free_page(struct BC_cache_extent *ce, int page)
{
off_t vpoffset;
kern_return_t kret;
vpoffset = (page * PAGE_SIZE) + ce->ce_cacheoffset;
kret = vnode_getwithvid(BC_cache->c_vp, BC_cache->c_vid);
if (kret != KERN_SUCCESS) {
debug("vnode_getwithvid failed - %d", kret);
return;
}
kret = ubc_range_op(BC_cache->c_vp, vpoffset, vpoffset + PAGE_SIZE,
UPL_ROP_DUMP, NULL);
if (kret != KERN_SUCCESS) {
debug("ubc_range_op failed - %d", kret);
}
(void) vnode_put(BC_cache->c_vp);
}
void
BC_hook(void)
{
microtime(&BC_cache->c_loadtime);
}
void
BC_load(void)
{
struct mach_header *mh;
long xsize;
int has64bitness, error;
size_t sysctlsize = sizeof(int);
char *plsection = "playlist";
sysctl_register_oid(&sysctl__kern_BootCache);
bzero(BC_cache, sizeof(*BC_cache));
if (kmod_info.info_version != KMOD_INFO_VERSION) {
message("incompatible kmod_info versions");
return;
}
mh = (struct mach_header *)kmod_info.address;
error = sysctlbyname("hw.cpu64bit_capable", &has64bitness, &sysctlsize,
NULL, 0);
if (error == 0 && has64bitness == 0) {
plsection = "playlist32";
}
BC_preloaded_playlist = getsectdatafromheader(mh, "BootCache",
plsection, &BC_preloaded_playlist_size);
debug("preload section %s: %d @ %p",
plsection, BC_preloaded_playlist_size, BC_preloaded_playlist);
if (BC_preloaded_playlist != NULL) {
if (BC_preloaded_playlist_size < sizeof(struct BC_playlist_header)) {
message("preloaded playlist too small");
return;
}
if (BC_preloaded_playlist->ph_magic != PH_MAGIC) {
message("preloaded playlist has invalid magic (%x, expected %x)",
BC_preloaded_playlist->ph_magic, PH_MAGIC);
return;
}
xsize = sizeof(struct BC_playlist_header) +
(BC_preloaded_playlist->ph_entries * sizeof(struct BC_playlist_entry));
if (xsize > BC_preloaded_playlist_size) {
message("preloaded playlist too small (%d, expected at least %ld)",
BC_preloaded_playlist_size, xsize);
return;
}
BC_wait_for_readahead = BC_READAHEAD_WAIT_CDROM;
mountroot_post_hook = BC_auto_start;
unmountroot_pre_hook = BC_unmount_hook;
microtime(&BC_cache->c_loadtime);
debug("waiting for root mount...");
} else {
BC_preloaded_playlist = NULL;
mountroot_post_hook = BC_hook;
debug("waiting for playlist...");
}
}
int
BC_unload(void)
{
int error;
error = 1;
debug("preparing to unload...");
unmountroot_pre_hook = NULL;
untimeout(BC_timeout_cache, NULL);
if (BC_terminate_readahead())
goto out;
if (BC_terminate_cache())
goto out;
BC_terminate_history();
LOCK_HISTORY();
BC_discard_history();
UNLOCK_HISTORY();
sysctl_unregister_oid(&sysctl__kern_BootCache);
error = 0;
out:
return(error);
}