358 lines
18 KiB
C++
358 lines
18 KiB
C++
// SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
#include <IOKit/IOLib.h>
|
|
#include <IOKit/storage/IOBlockStorageDevice.h>
|
|
#include <IOKit/IODeviceTreeSupport.h>
|
|
#include <libkern/c++/OSData.h>
|
|
#include <libkern/c++/OSDictionary.h>
|
|
#include <libkern/c++/OSIterator.h>
|
|
#include <libkern/c++/OSNumber.h>
|
|
#include <libkern/c++/OSString.h>
|
|
#include <IOKit/IOBSD.h>
|
|
#include <sys/param.h>
|
|
#include <libkern/crypto/sha2.h>
|
|
#include <mach/machine.h>
|
|
#include <machine/machine_routines.h>
|
|
#include <vm/vm_kern.h>
|
|
#include <kern/clock.h>
|
|
#include "SparseDisk.h"
|
|
#include "PersistOverlay.h"
|
|
|
|
extern "C" {
|
|
extern unsigned long gPhysBase, gPhysSize;
|
|
int native_ram_block_volume_device(const char *, dev_t *, char *, size_t);
|
|
volatile uint32_t native_ram_block_ready = 0;
|
|
volatile uint64_t native_ram_block_bytes = 0;
|
|
volatile uint64_t native_ram_block_reads = 0;
|
|
volatile uint64_t native_ram_block_writes = 0;
|
|
volatile uint64_t native_ram_block_failures = 0;
|
|
volatile uint64_t native_ram_block_resident_bytes = 0;
|
|
volatile uint32_t native_ram_block_sparse = 0;
|
|
uint8_t native_ram_block_seed_sha256[32];
|
|
// Optional persistence of the changed pages through a board-provided store window (see PersistOverlay.h). All weak: a build
|
|
// without the provider keeps the volatile disk.
|
|
unsigned native_ufs_store_mode(void) __attribute__((weak_import));
|
|
int native_ufs_store_open(uint64_t, uint64_t *, const char **) __attribute__((weak_import));
|
|
int native_ufs_store_read(uint64_t, uint32_t, void *) __attribute__((weak_import));
|
|
int native_ufs_store_write(uint64_t, uint32_t, const void *) __attribute__((weak_import));
|
|
int native_ufs_store_sync(void) __attribute__((weak_import));
|
|
unsigned native_ufs_store_gpt_check(void) __attribute__((weak_import));
|
|
// Diagnostics. state: 0 off, 1 blank (read-only probe), 2 resumed, 3 fresh, 4 refused/failed, 5 degraded after an I/O error,
|
|
// 6 resumed read-only (probe). header: persist::checkHeader result.
|
|
volatile uint32_t native_data_persist_state = 0, native_data_persist_header = 9, native_data_persist_gpt = 0xff, native_data_persist_gpt_after = 0xff;
|
|
volatile uint64_t native_data_persist_pages = 0, native_data_persist_loaded = 0, native_data_persist_writes = 0,
|
|
native_data_persist_syncs = 0, native_data_persist_errors = 0, native_data_persist_first_lba = 0,
|
|
native_data_persist_present = 0;
|
|
}
|
|
|
|
class NativeRamBlock : public IOBlockStorageDevice {
|
|
OSDeclareDefaultStructors(NativeRamBlock)
|
|
IOLock *gate = nullptr;
|
|
uint8_t *memory = nullptr;
|
|
uint64_t capacity = 0;
|
|
bool online = false;
|
|
RamSparseDisk sparse = {};
|
|
uint8_t seedHash[32] = {};
|
|
persist::Overlay overlay;
|
|
uint8_t *overlayBits = nullptr, *overlayDirty = nullptr;
|
|
bool storeAttached = false;
|
|
uint64_t lastSync = 0;
|
|
struct StoreOps {
|
|
bool read(uint64_t b, uint32_t n, void *d) { return native_ufs_store_read(b, n, d); }
|
|
bool write(uint64_t b, uint32_t n, const void *s) { return native_ufs_store_write(b, n, s); }
|
|
bool sync() { return native_ufs_store_sync(); }
|
|
};
|
|
void persistFailed() { overlay.active = false; native_data_persist_state = 5; __atomic_fetch_add(&native_data_persist_errors, 1, __ATOMIC_RELAXED); }
|
|
void persistSync() {
|
|
if (!overlay.active || overlay.readOnly) return;
|
|
StoreOps ops;
|
|
if (!overlay.sync(ops)) { persistFailed(); return; }
|
|
__atomic_fetch_add(&native_data_persist_syncs, 1, __ATOMIC_RELAXED);
|
|
native_data_persist_present = overlay.present;
|
|
lastSync = mach_absolute_time();
|
|
}
|
|
public:
|
|
// Called once, before the Data volume is mounted (native_ram_block_volume_seed): open the store window, put the pages that
|
|
// earlier boots changed back into the sparse disk, and from then on write changed pages through. The seed identity
|
|
// (seedHash) is not affected. Any refusal or error leaves the disk volatile, exactly as without persistence.
|
|
void attachStore() {
|
|
if (!gate) return;
|
|
IOLockLock(gate);
|
|
if (storeAttached) { IOLockUnlock(gate); return; }
|
|
storeAttached = true;
|
|
unsigned mode = &native_ufs_store_mode ? native_ufs_store_mode() : 0;
|
|
if (mode && sparse.pages && &native_ufs_store_open && &native_ufs_store_read && &native_ufs_store_write &&
|
|
&native_ufs_store_sync && &native_ufs_store_gpt_check) {
|
|
uint64_t pages = sparse.bytes / 4096, first = 0;
|
|
const char *why = "";
|
|
native_data_persist_pages = pages;
|
|
native_data_persist_gpt = native_ufs_store_gpt_check();
|
|
if (!native_ufs_store_open(persist::windowBlocks(pages), &first, &why)) {
|
|
native_data_persist_state = 4;
|
|
IOLog("NativeRamBlock: persistent store refused: %s\n", why);
|
|
} else {
|
|
native_data_persist_first_lba = first;
|
|
uint64_t bb = persist::bitmapBlocks(pages);
|
|
overlayBits = static_cast<uint8_t *>(IOMallocZero(bb * persist::kBlock));
|
|
overlayDirty = static_cast<uint8_t *>(IOMallocZero(bb));
|
|
StoreOps ops;
|
|
persist::Open result = overlayBits && overlayDirty ? overlay.open(ops, pages, seedHash, mode == 1, overlayBits, overlayDirty)
|
|
: persist::Open::Failed;
|
|
native_data_persist_header = static_cast<uint32_t>(overlay.header);
|
|
if (result == persist::Open::Failed) {
|
|
native_data_persist_state = 4;
|
|
overlay.active = false;
|
|
} else if (result == persist::Open::Blank) {
|
|
native_data_persist_state = 1;
|
|
} else if (result == persist::Open::Fresh) {
|
|
native_data_persist_state = 3;
|
|
} else {
|
|
struct Loader {
|
|
NativeRamBlock *self;
|
|
bool operator()(uint64_t i, const uint8_t *page) {
|
|
if (i >= self->sparse.bytes / 4096) return false;
|
|
if (!self->sparse.pages[i]) {
|
|
uint8_t *fresh = static_cast<uint8_t *>(IOMallocZero(4096));
|
|
if (!fresh) return false;
|
|
self->sparse.pages[i] = fresh;
|
|
self->sparse.resident += 4096;
|
|
}
|
|
memcpy(self->sparse.pages[i], page, 4096);
|
|
__atomic_fetch_add(&native_data_persist_loaded, 1, __ATOMIC_RELAXED);
|
|
return true;
|
|
}
|
|
} load = { this };
|
|
if (overlay.forEachPresent(ops, load)) {
|
|
native_data_persist_state = mode == 1 ? 6 : 2;
|
|
native_ram_block_resident_bytes = sparse.resident;
|
|
} else {
|
|
// A partly restored disk would be worse than a volatile one, but the pages already copied are consistent
|
|
// overlay content; stop writing back and let the system decide what it can mount.
|
|
native_data_persist_state = 4;
|
|
overlay.active = false;
|
|
}
|
|
}
|
|
native_data_persist_present = overlay.present;
|
|
native_data_persist_gpt_after = native_ufs_store_gpt_check();
|
|
lastSync = mach_absolute_time();
|
|
}
|
|
}
|
|
IOLockUnlock(gate);
|
|
}
|
|
|
|
bool available() const { return online && (memory || sparse.pages) && capacity; }
|
|
bool seedInfo(uint64_t *base,uint64_t *bytes,uint8_t hash[32]) {
|
|
if(!gate || !base || !bytes || !hash)return false;
|
|
IOLockLock(gate);bool ready=available();
|
|
if(ready){*base=sparse.pages?0:reinterpret_cast<uint64_t>(memory);*bytes=capacity*512;memcpy(hash,seedHash,32);}
|
|
IOLockUnlock(gate);return ready;
|
|
}
|
|
bool start(IOService *) override;
|
|
void stop(IOService *) override;
|
|
void free() override;
|
|
IOReturn doEjectMedia() override { return kIOReturnUnsupported; }
|
|
IOReturn doFormatMedia(UInt64) override { return kIOReturnUnsupported; }
|
|
UInt32 doGetFormatCapacities(UInt64 *, UInt32) const override { return 0; }
|
|
char *getVendorString() override { return const_cast<char *>("Native"); }
|
|
char *getProductString() override { return const_cast<char *>("Owned RAM block storage"); }
|
|
char *getRevisionString() override { return const_cast<char *>("1.0"); }
|
|
char *getAdditionalDeviceInfoString() override { return const_cast<char *>("volatile storage; no persistent trust claim"); }
|
|
IOReturn reportBlockSize(UInt64 *value) override { *value = 512; return kIOReturnSuccess; }
|
|
IOReturn reportEjectability(bool *value) override { *value = false; return kIOReturnSuccess; }
|
|
IOReturn reportMaxValidBlock(UInt64 *value) override { *value = capacity - 1; return kIOReturnSuccess; }
|
|
IOReturn reportMediaState(bool *present, bool *changed) override {
|
|
*present = online; if (changed) *changed = false; return kIOReturnSuccess;
|
|
}
|
|
IOReturn reportRemovability(bool *value) override { *value = false; return kIOReturnSuccess; }
|
|
IOReturn reportWriteProtection(bool *value) override { *value = false; return kIOReturnSuccess; }
|
|
IOReturn doSynchronizeCache() override {
|
|
if (!online) return kIOReturnNotReady;
|
|
if (gate) { IOLockLock(gate); persistSync(); IOLockUnlock(gate); }
|
|
return kIOReturnSuccess;
|
|
}
|
|
IOReturn doSynchronize(UInt64, UInt64, IOOptionBits) override { return doSynchronizeCache(); }
|
|
IOReturn getWriteCacheState(bool *value) override { *value = false; return kIOReturnSuccess; }
|
|
IOReturn setWriteCacheState(bool) override { return kIOReturnUnsupported; }
|
|
IOReturn doAsyncReadWrite(IOMemoryDescriptor *, UInt64, UInt64, IOStorageAttributes *, IOStorageCompletion *) override;
|
|
};
|
|
OSDefineMetaClassAndStructors(NativeRamBlock, IOBlockStorageDevice)
|
|
|
|
bool NativeRamBlock::start(IOService *provider)
|
|
{
|
|
if (!IOBlockStorageDevice::start(provider)) return false;
|
|
IORegistryEntry *map = IORegistryEntry::fromPath("/chosen/memory-map", gIODTPlane);
|
|
if (!map) return false;
|
|
OSData *packed = OSDynamicCast(OSData, map->getProperty("PackedRAMDisk"));
|
|
OSData *range = OSDynamicCast(OSData, map->getProperty("RAMDisk"));
|
|
if(packed && range){map->release();return false;}
|
|
if(packed)range=packed;
|
|
bool packedInput=packed!=nullptr;
|
|
uint64_t physical = 0, bytes = 0;
|
|
if (range && range->getLength() == 16) {
|
|
memcpy(&physical, range->getBytesNoCopy(), 8);
|
|
memcpy(&bytes, static_cast<const uint8_t *>(range->getBytesNoCopy()) + 8, 8);
|
|
}
|
|
map->release();
|
|
// Boot memory belongs to the loader/kernel; never allocate or free it here.
|
|
if (!bytes || bytes > 0x80000000 || (physical & 4095) || (!packedInput && (bytes & 4095)) ||
|
|
physical < gPhysBase || bytes > gPhysSize || physical - gPhysBase > gPhysSize - bytes) return false;
|
|
memory = reinterpret_cast<uint8_t *>(ml_static_ptovirt(physical));
|
|
if (!memory || !(gate = IOLockAlloc())) return false;
|
|
uint64_t logical=bytes;
|
|
if(packedInput){
|
|
if(!ramPackedLayout(memory,bytes,&logical))return false;
|
|
sparse.bytes=logical;
|
|
sparse.pages=static_cast<uint8_t **>(IOMallocZero((logical/4096)*sizeof(uint8_t *)));
|
|
struct ImportOps {uint8_t *allocate(){return static_cast<uint8_t *>(IOMallocZero(4096));}} ops;
|
|
if(!ramSparseImport(sparse,memory,bytes,ops))return false;
|
|
}
|
|
capacity = logical / 512;
|
|
SHA256_CTX context;
|
|
SHA256_Init(&context);
|
|
if(sparse.pages){
|
|
static const uint8_t zero[4096]={};
|
|
for(uint64_t i=0;i<logical/4096;++i)SHA256_Update(&context,sparse.pages[i]?sparse.pages[i]:zero,4096);
|
|
memory=nullptr;
|
|
}else SHA256_Update(&context, memory, bytes);
|
|
SHA256_Final(seedHash, &context);
|
|
memcpy(native_ram_block_seed_sha256,seedHash,32);
|
|
native_ram_block_bytes = logical;
|
|
native_ram_block_sparse=sparse.pages?1:0;
|
|
native_ram_block_resident_bytes=sparse.pages?sparse.resident:logical;
|
|
online = true;
|
|
native_ram_block_ready = 1;
|
|
registerService();
|
|
return true;
|
|
}
|
|
|
|
IOReturn NativeRamBlock::doAsyncReadWrite(IOMemoryDescriptor *buffer, UInt64 block, UInt64 count,
|
|
IOStorageAttributes *, IOStorageCompletion *completion)
|
|
{
|
|
if (!buffer || !completion || !completion->action || !count || block >= capacity ||
|
|
count > capacity - block || buffer->getLength() < count * 512 ||
|
|
(buffer->getDirection() != kIODirectionIn && buffer->getDirection() != kIODirectionOut)) return kIOReturnBadArgument;
|
|
if (!gate) return kIOReturnNotReady;
|
|
const uint64_t bytes = count * 512;
|
|
IOLockLock(gate);
|
|
if (!online) { IOLockUnlock(gate); return kIOReturnNotReady; }
|
|
uint64_t actual;bool noMemory=false;
|
|
if(sparse.pages){
|
|
struct Operations {
|
|
IOMemoryDescriptor *buffer;
|
|
uint8_t *allocate(){return static_cast<uint8_t *>(IOMallocZero(4096));}
|
|
uint64_t read(uint64_t at,void *p,uint64_t n){return buffer->readBytes(at,p,n);}
|
|
uint64_t copy(uint64_t at,const void *p,uint64_t n){return buffer->writeBytes(at,p,n);}
|
|
} ops={buffer};
|
|
bool write=buffer->getDirection()==kIODirectionOut;
|
|
actual=ramSparseIO(sparse,write,block*512,bytes,ops,noMemory);
|
|
if(write && actual==bytes && overlay.active && !overlay.readOnly){
|
|
StoreOps store;
|
|
uint64_t firstPage=block*512/4096,lastPage=(block*512+bytes-1)/4096;
|
|
for(uint64_t i=firstPage;i<=lastPage && overlay.active;++i){
|
|
if(!sparse.pages[i] || !overlay.writePage(store,i,sparse.pages[i]))persistFailed();
|
|
else __atomic_fetch_add(&native_data_persist_writes,1,__ATOMIC_RELAXED);
|
|
}
|
|
uint64_t interval=0;clock_interval_to_absolutetime_interval(2,NSEC_PER_SEC,&interval);
|
|
if(overlay.anyDirty && mach_absolute_time()-lastSync>interval)persistSync();
|
|
}
|
|
if(write)__atomic_fetch_add(&native_ram_block_writes,1,__ATOMIC_RELAXED);
|
|
else __atomic_fetch_add(&native_ram_block_reads,1,__ATOMIC_RELAXED);
|
|
native_ram_block_resident_bytes=sparse.resident;
|
|
}else if (buffer->getDirection() == kIODirectionIn) {
|
|
actual = buffer->writeBytes(0, memory + block * 512, bytes);
|
|
__atomic_fetch_add(&native_ram_block_reads, 1, __ATOMIC_RELAXED);
|
|
} else {
|
|
actual = buffer->readBytes(0, memory + block * 512, bytes);
|
|
__atomic_fetch_add(&native_ram_block_writes, 1, __ATOMIC_RELAXED);
|
|
}
|
|
IOReturn status = actual == bytes ? kIOReturnSuccess : noMemory ? kIOReturnNoMemory : kIOReturnIOError;
|
|
if (status) __atomic_fetch_add(&native_ram_block_failures, 1, __ATOMIC_RELAXED);
|
|
IOLockUnlock(gate);
|
|
completion->action(completion->target, completion->parameter, status, actual);
|
|
return kIOReturnSuccess;
|
|
}
|
|
|
|
void NativeRamBlock::stop(IOService *provider)
|
|
{
|
|
if (gate) { IOLockLock(gate); online = false; IOLockUnlock(gate); }
|
|
IOBlockStorageDevice::stop(provider);
|
|
}
|
|
|
|
void NativeRamBlock::free()
|
|
{
|
|
if (overlayBits) { IOFree(overlayBits, persist::bitmapBlocks(sparse.bytes / 4096) * persist::kBlock); overlayBits = nullptr; }
|
|
if (overlayDirty) { IOFree(overlayDirty, persist::bitmapBlocks(sparse.bytes / 4096)); overlayDirty = nullptr; }
|
|
if (gate) { IOLockFree(gate); gate = nullptr; }
|
|
if(sparse.pages){
|
|
for(uint64_t i=0;i<sparse.bytes/4096;++i)if(sparse.pages[i])IOFree(sparse.pages[i],4096);
|
|
IOFree(sparse.pages,(sparse.bytes/4096)*sizeof(uint8_t *));sparse={};
|
|
}
|
|
// Dense boot allocation and packed input remain owned until reboot.
|
|
IOBlockStorageDevice::free();
|
|
}
|
|
|
|
static int selectVolumeSeed(const char *uuid, dev_t *device, char *path, size_t size,
|
|
uint64_t *seedBase,uint64_t *seedBytes,uint8_t seedDigest[32])
|
|
{
|
|
if (!uuid || strlen(uuid) != 36 || !device || !path || size < 6) return 0;
|
|
*device = NODEV;
|
|
path[0] = 0;
|
|
OSDictionary *matching = IOService::serviceMatching("IOMedia");
|
|
OSDictionary *properties = OSDictionary::withCapacity(1);
|
|
OSString *wanted = OSString::withCString(uuid);
|
|
if (!matching || !properties || !wanted) {
|
|
OSSafeReleaseNULL(wanted); OSSafeReleaseNULL(properties); OSSafeReleaseNULL(matching);
|
|
return 0;
|
|
}
|
|
bool ready = properties->setObject("UUID", wanted) && matching->setObject("IOPropertyMatch", properties);
|
|
wanted->release(); properties->release();
|
|
OSIterator *iterator = ready ? IOService::getMatchingServices(matching) : nullptr;
|
|
matching->release();
|
|
if (!iterator) return 0;
|
|
bool selected = false;
|
|
unsigned matches = 0;
|
|
dev_t candidate = NODEV;
|
|
char candidatePath[64] = {};
|
|
uint64_t base=0,bytes=0;uint8_t hash[32]={};
|
|
while (OSObject *object = iterator->getNextObject()) {
|
|
IOService *media = OSDynamicCast(IOService, object);
|
|
if (!media) continue;
|
|
if (++matches != 1) { selected = false; break; }
|
|
OSString *identity = OSDynamicCast(OSString, media->getProperty("UUID"));
|
|
OSString *name = OSDynamicCast(OSString, media->getProperty(kIOBSDNameKey));
|
|
OSNumber *mjr = OSDynamicCast(OSNumber, media->getProperty(kIOBSDMajorKey));
|
|
OSNumber *mnr = OSDynamicCast(OSNumber, media->getProperty(kIOBSDMinorKey));
|
|
if (!identity || strcmp(identity->getCStringNoCopy(), uuid) || !name || !mjr || !mnr ||
|
|
!name->getLength() || name->getLength() >= sizeof(candidatePath) - 6 || name->getLength() >= size - 6 ||
|
|
mjr->unsigned64BitValue() > 255 || mnr->unsigned64BitValue() > 0xffffff) continue;
|
|
unsigned depth = 0;
|
|
for (IOService *provider = media; provider && depth++ < 64; provider = provider->getProvider()) {
|
|
NativeRamBlock *ram = OSDynamicCast(NativeRamBlock, provider);
|
|
if (!ram) continue;
|
|
ram->attachStore();
|
|
if (!ram->seedInfo(&base,&bytes,hash)) continue;
|
|
candidate = static_cast<dev_t>(makedev(mjr->unsigned32BitValue(), mnr->unsigned32BitValue()));
|
|
snprintf(candidatePath, sizeof(candidatePath), "/dev/%s", name->getCStringNoCopy());
|
|
selected = true;
|
|
break;
|
|
}
|
|
}
|
|
iterator->release();
|
|
if (!selected || matches != 1) return 0;
|
|
*device = candidate;
|
|
strlcpy(path, candidatePath, size);
|
|
if(seedBase)*seedBase=base;
|
|
if(seedBytes)*seedBytes=bytes;
|
|
if(seedDigest)memcpy(seedDigest,hash,32);
|
|
return 1;
|
|
}
|
|
|
|
extern "C" int native_ram_block_volume_device(const char *uuid,dev_t *device,char *path,size_t size) {
|
|
return selectVolumeSeed(uuid,device,path,size,nullptr,nullptr,nullptr);
|
|
}
|
|
extern "C" int native_ram_block_volume_seed(const char *uuid,dev_t *device,char *path,size_t size,
|
|
uint64_t *base,uint64_t *bytes,uint8_t digest[32]) {
|
|
if(!base || !bytes || !digest)return 0;
|
|
*base=0;*bytes=0;memset(digest,0,32);
|
|
return selectVolumeSeed(uuid,device,path,size,base,bytes,digest);
|
|
}
|