// SPDX-License-Identifier: BSD-2-Clause-Patent #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(IOMallocZero(bb * persist::kBlock)); overlayDirty = static_cast(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(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(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(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("Native"); } char *getProductString() override { return const_cast("Owned RAM block storage"); } char *getRevisionString() override { return const_cast("1.0"); } char *getAdditionalDeviceInfoString() override { return const_cast("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(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(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(IOMallocZero((logical/4096)*sizeof(uint8_t *))); struct ImportOps {uint8_t *allocate(){return static_cast(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;iaction || !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(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;isetObject("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(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); }