Files
UniXNU/drivers/NativeRamBlock/NativeRamBlock.cpp
T
2026-10-19 18:49:31 +08:00

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);
}