1 | /* |
2 | * Copyright (c) 2021-2022 Apple Inc. All rights reserved. |
3 | * |
4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ |
5 | * |
6 | * This file contains Original Code and/or Modifications of Original Code |
7 | * as defined in and that are subject to the Apple Public Source License |
8 | * Version 2.0 (the 'License'). You may not use this file except in |
9 | * compliance with the License. The rights granted to you under the License |
10 | * may not be used to create, or enable the creation or redistribution of, |
11 | * unlawful or unlicensed copies of an Apple operating system, or to |
12 | * circumvent, violate, or enable the circumvention or violation of, any |
13 | * terms of an Apple operating system software license agreement. |
14 | * |
15 | * Please obtain a copy of the License at |
16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. |
17 | * |
18 | * The Original Code and all software distributed under the License are |
19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER |
20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, |
22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. |
23 | * Please see the License for the specific language governing rights and |
24 | * limitations under the License. |
25 | * |
26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ |
27 | */ |
28 | |
29 | #include <libkern/libkern.h> |
30 | |
31 | #define VARIABLE_STORE_SIGNATURE 'NVV3' |
32 | |
33 | // Variable Store Version |
34 | #define VARIABLE_STORE_VERSION 0x1 |
35 | |
36 | #define VARIABLE_DATA 0x55AA |
37 | #define INVALIDATED_VARIABLE_DATA 0x0000 |
38 | |
39 | // Variable State flags |
40 | #define VAR_IN_DELETED_TRANSITION 0xFE // Variable is in obsolete transistion |
41 | #define VAR_DELETED 0xFD // Variable is obsolete |
42 | #define VAR_INACTIVE 0xFB // Variable is inactive due to failing CRC |
43 | #define VAR_ADDED 0x7F // Variable has been completely added |
44 | |
45 | // No changes needed on save |
46 | #define VAR_NEW_STATE_NONE 0x01 |
47 | // Remove existing entry on save |
48 | #define VAR_NEW_STATE_REMOVE 0x02 |
49 | // Add new value on save, mark previous as inactive |
50 | #define VAR_NEW_STATE_APPEND 0x03 |
51 | |
52 | #pragma pack(1) |
53 | struct { |
54 | uint32_t ; |
55 | uint32_t ; |
56 | uint32_t ; |
57 | uint8_t ; |
58 | uint8_t ; |
59 | uint8_t ; |
60 | uint8_t ; |
61 | uint32_t ; |
62 | uint32_t ; |
63 | }; |
64 | |
65 | struct { |
66 | uint16_t ; |
67 | uint8_t ; |
68 | uint8_t ; |
69 | uint32_t ; |
70 | uint32_t ; |
71 | uint32_t ; |
72 | uuid_t ; |
73 | uint32_t ; |
74 | uint8_t []; |
75 | }; |
76 | #pragma pack() |
77 | |
78 | struct nvram_v3_var_entry { |
79 | uint8_t new_state; |
80 | size_t existing_offset; |
81 | struct v3_var_header ; |
82 | }; |
83 | |
84 | static size_t |
85 | (const struct v3_var_header *) |
86 | { |
87 | return sizeof(struct nvram_v3_var_entry) + header->nameSize + header->dataSize; |
88 | } |
89 | |
90 | static size_t |
91 | (const struct v3_var_header *) |
92 | { |
93 | return sizeof(struct v3_var_header) + header->nameSize + header->dataSize; |
94 | } |
95 | |
96 | static bool |
97 | (const struct v3_store_header *) |
98 | { |
99 | return (header->name == VARIABLE_STORE_SIGNATURE) && (header->version == VARIABLE_STORE_VERSION); |
100 | } |
101 | |
102 | static bool |
103 | (const struct v3_var_header *, size_t buf_len) |
104 | { |
105 | return (buf_len > sizeof(struct v3_var_header)) && |
106 | (header->startId == VARIABLE_DATA) && |
107 | (variable_length(header) <= buf_len); |
108 | } |
109 | |
110 | static uint32_t |
111 | (const struct v3_var_header *var, const uint8_t *image, uint32_t offset, uint32_t len) |
112 | { |
113 | const struct v3_var_header *store_var; |
114 | uint32_t var_offset = 0; |
115 | |
116 | while ((offset + sizeof(struct v3_var_header) < len)) { |
117 | store_var = (const struct v3_var_header *)(image + offset); |
118 | |
119 | if (valid_variable_header(header: store_var, buf_len: len - offset)) { |
120 | if ((store_var->state == VAR_ADDED) && |
121 | (uuid_compare(uu1: var->guid, uu2: store_var->guid) == 0) && |
122 | (var->nameSize == store_var->nameSize) && |
123 | (memcmp(s1: var->name_data_buf, s2: store_var->name_data_buf, n: var->nameSize) == 0)) { |
124 | var_offset = offset; |
125 | break; |
126 | } |
127 | } else { |
128 | break; |
129 | } |
130 | |
131 | offset += variable_length(header: store_var); |
132 | } |
133 | |
134 | return var_offset; |
135 | } |
136 | |
137 | static IOReturn |
138 | find_current_offset_in_image(const uint8_t *image, uint32_t len, uint32_t *newOffset) |
139 | { |
140 | uint32_t offset = 0; |
141 | uint32_t inner_offset = 0; |
142 | |
143 | if (valid_store_header(header: (const struct v3_store_header *)(image + offset))) { |
144 | DEBUG_INFO("valid store header @ %#x\n" , offset); |
145 | offset += sizeof(struct v3_store_header); |
146 | } |
147 | |
148 | while (offset < len) { |
149 | const struct v3_var_header *store_var = (const struct v3_var_header *)(image + offset); |
150 | uuid_string_t uuidString; |
151 | |
152 | if (valid_variable_header(header: store_var, buf_len: len - offset)) { |
153 | uuid_unparse(uu: store_var->guid, out: uuidString); |
154 | DEBUG_INFO("Valid var @ %#08x, state=%#02x, length=%#08zx, %s:%s\n" , offset, store_var->state, |
155 | variable_length(store_var), uuidString, store_var->name_data_buf); |
156 | offset += variable_length(header: store_var); |
157 | } else { |
158 | break; |
159 | } |
160 | } |
161 | |
162 | while (offset < len) { |
163 | if (image[offset] == 0xFF) { |
164 | DEBUG_INFO("scanning for clear memory @ %#x\n" , offset); |
165 | |
166 | inner_offset = offset; |
167 | |
168 | while ((inner_offset < len) && (image[inner_offset] == 0xFF)) { |
169 | inner_offset++; |
170 | } |
171 | |
172 | if (inner_offset == len) { |
173 | DEBUG_INFO("found start of clear mem @ %#x\n" , offset); |
174 | break; |
175 | } else { |
176 | DEBUG_ERROR("ERROR!!!!! found non-clear byte @ %#x\n" , offset); |
177 | return kIOReturnInvalid; |
178 | } |
179 | } |
180 | offset++; |
181 | } |
182 | |
183 | *newOffset = offset; |
184 | |
185 | return kIOReturnSuccess; |
186 | } |
187 | |
188 | class IONVRAMV3Handler : public IODTNVRAMFormatHandler, IOTypedOperatorsMixin<IONVRAMV3Handler> |
189 | { |
190 | private: |
191 | IONVRAMController *_nvramController; |
192 | IODTNVRAM *_provider; |
193 | |
194 | bool _newData; |
195 | bool _resetData; |
196 | bool _reload; |
197 | |
198 | bool _rawController; |
199 | |
200 | uint32_t _generation; |
201 | |
202 | uint8_t *_nvramImage; |
203 | |
204 | OSSharedPtr<OSDictionary> &_varDict; |
205 | |
206 | uint32_t _commonSize; |
207 | uint32_t _systemSize; |
208 | |
209 | uint32_t _commonUsed; |
210 | uint32_t _systemUsed; |
211 | |
212 | uint32_t _currentOffset; |
213 | |
214 | OSSharedPtr<OSArray> _varEntries; |
215 | |
216 | IOReturn unserializeImage(const uint8_t *image, IOByteCount length); |
217 | IOReturn reclaim(void); |
218 | uint32_t findCurrentBank(void); |
219 | size_t getAppendSize(void); |
220 | |
221 | static bool convertObjectToProp(uint8_t *buffer, uint32_t *length, const char *propSymbol, OSObject *propObject); |
222 | static bool convertPropToObject(const uint8_t *propName, uint32_t propNameLength, const uint8_t *propData, uint32_t propDataLength, |
223 | OSSharedPtr<const OSSymbol>& propSymbol, OSSharedPtr<OSObject>& propObject); |
224 | |
225 | IOReturn reloadInternal(void); |
226 | IOReturn setVariableInternal(const uuid_t varGuid, const char *variableName, OSObject *object); |
227 | |
228 | void setEntryForRemove(struct nvram_v3_var_entry *v3Entry, bool system); |
229 | void findExistingEntry(const uuid_t varGuid, const char *varName, struct nvram_v3_var_entry **existing, unsigned int *existingIndex); |
230 | IOReturn syncRaw(void); |
231 | IOReturn syncBlock(void); |
232 | |
233 | public: |
234 | virtual |
235 | ~IONVRAMV3Handler() APPLE_KEXT_OVERRIDE; |
236 | IONVRAMV3Handler(OSSharedPtr<OSDictionary> &varDict); |
237 | |
238 | static bool isValidImage(const uint8_t *image, IOByteCount length); |
239 | |
240 | static IONVRAMV3Handler *init(IODTNVRAM *provider, const uint8_t *image, IOByteCount length, |
241 | OSSharedPtr<OSDictionary> &varDict); |
242 | |
243 | virtual bool getNVRAMProperties(void) APPLE_KEXT_OVERRIDE; |
244 | virtual IOReturn unserializeVariables(void) APPLE_KEXT_OVERRIDE; |
245 | virtual IOReturn setVariable(const uuid_t varGuid, const char *variableName, OSObject *object) APPLE_KEXT_OVERRIDE; |
246 | virtual bool setController(IONVRAMController *controller) APPLE_KEXT_OVERRIDE; |
247 | virtual IOReturn sync(void) APPLE_KEXT_OVERRIDE; |
248 | virtual IOReturn flush(const uuid_t guid, IONVRAMOperation op) APPLE_KEXT_OVERRIDE; |
249 | virtual void reload(void) APPLE_KEXT_OVERRIDE; |
250 | virtual uint32_t getGeneration(void) const APPLE_KEXT_OVERRIDE; |
251 | virtual uint32_t getVersion(void) const APPLE_KEXT_OVERRIDE; |
252 | virtual uint32_t getSystemUsed(void) const APPLE_KEXT_OVERRIDE; |
253 | virtual uint32_t getCommonUsed(void) const APPLE_KEXT_OVERRIDE; |
254 | virtual bool getSystemPartitionActive(void) const APPLE_KEXT_OVERRIDE; |
255 | }; |
256 | |
257 | IONVRAMV3Handler::~IONVRAMV3Handler() |
258 | { |
259 | } |
260 | |
261 | IONVRAMV3Handler::IONVRAMV3Handler(OSSharedPtr<OSDictionary> &varDict) : |
262 | _varDict(varDict) |
263 | { |
264 | } |
265 | |
266 | bool |
267 | IONVRAMV3Handler::isValidImage(const uint8_t *image, IOByteCount length) |
268 | { |
269 | const struct v3_store_header * = (const struct v3_store_header *)image; |
270 | |
271 | if ((header == nullptr) || (length < sizeof(*header))) { |
272 | return false; |
273 | } |
274 | |
275 | return valid_store_header(header); |
276 | } |
277 | |
278 | IONVRAMV3Handler* |
279 | IONVRAMV3Handler::init(IODTNVRAM *provider, const uint8_t *image, IOByteCount length, |
280 | OSSharedPtr<OSDictionary> &varDict) |
281 | { |
282 | OSSharedPtr<IORegistryEntry> entry; |
283 | OSSharedPtr<OSObject> prop; |
284 | bool propertiesOk; |
285 | |
286 | IONVRAMV3Handler *handler = new IONVRAMV3Handler(varDict); |
287 | |
288 | handler->_provider = provider; |
289 | |
290 | propertiesOk = handler->getNVRAMProperties(); |
291 | require_action(propertiesOk, exit, DEBUG_ERROR("Unable to get NVRAM properties\n" )); |
292 | |
293 | require_action(length == handler->_bankSize, exit, DEBUG_ERROR("length %#llx != _bankSize %#x\n" , length, handler->_bankSize)); |
294 | |
295 | if ((image != nullptr) && (length != 0)) { |
296 | if (handler->unserializeImage(image, length) != kIOReturnSuccess) { |
297 | DEBUG_ERROR("Unable to unserialize image, len=%#x\n" , (unsigned int)length); |
298 | } |
299 | } |
300 | |
301 | return handler; |
302 | |
303 | exit: |
304 | delete handler; |
305 | |
306 | return nullptr; |
307 | } |
308 | |
309 | bool |
310 | IONVRAMV3Handler::getNVRAMProperties() |
311 | { |
312 | bool ok = false; |
313 | const char *rawControllerKey = "nvram-raw" ; |
314 | OSSharedPtr<IORegistryEntry> entry; |
315 | OSSharedPtr<OSObject> prop; |
316 | OSData * data; |
317 | |
318 | require_action(IODTNVRAMFormatHandler::getNVRAMProperties(), exit, DEBUG_ERROR("parent getNVRAMProperties failed\n" )); |
319 | |
320 | entry = IORegistryEntry::fromPath(path: "/chosen" , plane: gIODTPlane); |
321 | require_action(entry, exit, DEBUG_ERROR("Unable to find chosen node\n" )); |
322 | |
323 | prop = entry->copyProperty(aKey: rawControllerKey); |
324 | require_action(prop != nullptr, exit, DEBUG_ERROR("No %s entry\n" , rawControllerKey)); |
325 | |
326 | data = OSDynamicCast(OSData, prop.get()); |
327 | require(data != nullptr, exit); |
328 | |
329 | _rawController = *((uint32_t*)data->getBytesNoCopy()); |
330 | DEBUG_INFO("_rawController = %d\n" , _rawController); |
331 | |
332 | ok = true; |
333 | |
334 | exit: |
335 | return ok; |
336 | } |
337 | |
338 | IOReturn |
339 | IONVRAMV3Handler::flush(const uuid_t guid, IONVRAMOperation op) |
340 | { |
341 | IOReturn ret = kIOReturnSuccess; |
342 | bool flushSystem; |
343 | bool flushCommon; |
344 | |
345 | flushSystem = getSystemPartitionActive() && (uuid_compare(uu1: guid, uu2: gAppleSystemVariableGuid) == 0); |
346 | flushCommon = uuid_compare(uu1: guid, uu2: gAppleNVRAMGuid) == 0; |
347 | |
348 | DEBUG_INFO("flushSystem=%d, flushCommon=%d\n" , flushSystem, flushCommon); |
349 | |
350 | if (flushSystem || flushCommon) { |
351 | const OSSymbol *canonicalKey; |
352 | OSSharedPtr<OSDictionary> dictCopy; |
353 | OSSharedPtr<OSCollectionIterator> iter; |
354 | uuid_string_t uuidString; |
355 | |
356 | dictCopy = OSDictionary::withDictionary(dict: _varDict.get()); |
357 | iter = OSCollectionIterator::withCollection(inColl: dictCopy.get()); |
358 | require_action(dictCopy && iter, exit, ret = kIOReturnNoMemory); |
359 | |
360 | while ((canonicalKey = OSDynamicCast(OSSymbol, iter->getNextObject()))) { |
361 | const char *varName; |
362 | uuid_t varGuid; |
363 | bool clear; |
364 | |
365 | parseVariableName(key: canonicalKey->getCStringNoCopy(), guidResult: &varGuid, nameResult: &varName); |
366 | |
367 | uuid_unparse(uu: varGuid, out: uuidString); |
368 | |
369 | clear = ((flushSystem && (uuid_compare(uu1: varGuid, uu2: gAppleSystemVariableGuid) == 0)) || |
370 | (flushCommon && (uuid_compare(uu1: varGuid, uu2: gAppleSystemVariableGuid) != 0))) && |
371 | verifyPermission(op, varGuid, varName, systemActive: getSystemPartitionActive()); |
372 | |
373 | if (clear) { |
374 | DEBUG_INFO("Clearing entry for %s:%s\n" , uuidString, varName); |
375 | setVariableInternal(varGuid, variableName: varName, object: nullptr); |
376 | } else { |
377 | DEBUG_INFO("Keeping entry for %s:%s\n" , uuidString, varName); |
378 | } |
379 | } |
380 | |
381 | _newData = true; |
382 | } |
383 | |
384 | DEBUG_INFO("_commonUsed %#x, _systemUsed %#x\n" , _commonUsed, _systemUsed); |
385 | |
386 | exit: |
387 | return ret; |
388 | } |
389 | |
390 | IOReturn |
391 | IONVRAMV3Handler::reloadInternal(void) |
392 | { |
393 | IOReturn ret; |
394 | uint32_t controllerBank; |
395 | uint8_t *controllerImage; |
396 | struct nvram_v3_var_entry *v3Entry; |
397 | const struct v3_store_header *; |
398 | const struct v3_var_header *storeVar; |
399 | OSData *entryContainer; |
400 | |
401 | controllerBank = findCurrentBank(); |
402 | |
403 | if (_currentBank != controllerBank) { |
404 | DEBUG_ERROR("_currentBank %#x != controllerBank %#x" , _currentBank, controllerBank); |
405 | } |
406 | |
407 | _currentBank = controllerBank; |
408 | |
409 | controllerImage = (uint8_t *)IOMallocData(_bankSize); |
410 | |
411 | _nvramController->select(bank: _currentBank); |
412 | _nvramController->read(offset: 0, buffer: controllerImage, length: _bankSize); |
413 | |
414 | require_action(isValidImage(controllerImage, _bankSize), exit, |
415 | (ret = kIOReturnInvalid, DEBUG_ERROR("Invalid image at bank %d\n" , _currentBank))); |
416 | |
417 | DEBUG_INFO("valid image found\n" ); |
418 | |
419 | storeHeader = (const struct v3_store_header *)controllerImage; |
420 | |
421 | _generation = storeHeader->generation; |
422 | |
423 | // We must sync any existing variables offset on the controller image with our internal representation |
424 | // If we find an existing entry and the data is still the same we record the existing offset and mark it |
425 | // as VAR_NEW_STATE_NONE meaning no action needed |
426 | // Otherwise if the data is different or it is not found on the controller image we mark it as VAR_NEW_STATE_APPEND |
427 | // which will have us invalidate the existing entry if there is one and append it on the next save |
428 | for (unsigned int i = 0; i < _varEntries->getCount(); i++) { |
429 | uint32_t offset = sizeof(struct v3_store_header); |
430 | uint32_t latestOffset; |
431 | uint32_t prevOffset = 0; |
432 | |
433 | entryContainer = (OSDynamicCast(OSData, _varEntries->getObject(i))); |
434 | v3Entry = (struct nvram_v3_var_entry *)entryContainer->getBytesNoCopy(); |
435 | |
436 | DEBUG_INFO("Looking for %s\n" , v3Entry->header.name_data_buf); |
437 | while ((latestOffset = find_active_var_in_image(var: &v3Entry->header, image: controllerImage, offset, len: _bankSize))) { |
438 | DEBUG_INFO("Found offset for %s @ %#08x\n" , v3Entry->header.name_data_buf, latestOffset); |
439 | if (prevOffset) { |
440 | DEBUG_INFO("Marking prev offset for %s at %#08x invalid\n" , v3Entry->header.name_data_buf, offset); |
441 | // Invalidate any previous duplicate entries in the store |
442 | struct v3_var_header * = (struct v3_var_header *)(controllerImage + prevOffset); |
443 | uint8_t state = prevVarHeader->state & VAR_DELETED & VAR_IN_DELETED_TRANSITION; |
444 | |
445 | ret = _nvramController->write(offset: prevOffset + offsetof(struct v3_var_header, state), buffer: &state, length: sizeof(state)); |
446 | require_noerr_action(ret, exit, DEBUG_ERROR("existing state w fail, ret=%#x\n" , ret)); |
447 | } |
448 | |
449 | prevOffset = latestOffset; |
450 | offset += latestOffset; |
451 | } |
452 | |
453 | v3Entry->existing_offset = latestOffset ? latestOffset : prevOffset; |
454 | DEBUG_INFO("Existing offset for %s at %#08zx\n" , v3Entry->header.name_data_buf, v3Entry->existing_offset); |
455 | |
456 | if (v3Entry->existing_offset == 0) { |
457 | DEBUG_ERROR("%s is not in the NOR image\n" , v3Entry->header.name_data_buf); |
458 | if (v3Entry->new_state != VAR_NEW_STATE_REMOVE) { |
459 | DEBUG_INFO("%s marked for append\n" , v3Entry->header.name_data_buf); |
460 | // Doesn't exist in the store, just append it on next sync |
461 | v3Entry->new_state = VAR_NEW_STATE_APPEND; |
462 | } |
463 | } else { |
464 | DEBUG_INFO("Found offset for %s @ %#zx\n" , v3Entry->header.name_data_buf, v3Entry->existing_offset); |
465 | storeVar = (const struct v3_var_header *)&controllerImage[v3Entry->existing_offset]; |
466 | |
467 | if (v3Entry->new_state != VAR_NEW_STATE_REMOVE) { |
468 | // Verify that the existing data matches the store data |
469 | if ((variable_length(header: &v3Entry->header) == variable_length(header: storeVar)) && |
470 | (memcmp(s1: v3Entry->header.name_data_buf, s2: storeVar->name_data_buf, n: storeVar->nameSize + storeVar->dataSize) == 0)) { |
471 | DEBUG_INFO("Store var data for %s matches, marking new state none\n" , v3Entry->header.name_data_buf); |
472 | v3Entry->new_state = VAR_NEW_STATE_NONE; |
473 | } else { |
474 | DEBUG_INFO("Store var data for %s differs, marking new state append\n" , v3Entry->header.name_data_buf); |
475 | v3Entry->new_state = VAR_NEW_STATE_APPEND; |
476 | } |
477 | } else { |
478 | // Store has entry but it has been removed from our collection, keep it marked for delete but with updated |
479 | // existing_offset for coherence |
480 | DEBUG_INFO("Removing entry at %#08zx with next sync\n" , v3Entry->existing_offset); |
481 | } |
482 | } |
483 | } |
484 | |
485 | ret = find_current_offset_in_image(image: controllerImage, len: _bankSize, newOffset: &_currentOffset); |
486 | if (ret != kIOReturnSuccess) { |
487 | DEBUG_ERROR("Unidentified bytes in image, reclaiming\n" ); |
488 | ret = reclaim(); |
489 | require_noerr_action(ret, exit, DEBUG_ERROR("Reclaim byte recovery failed, invalid controller state!!! ret=%#x\n" , ret)); |
490 | } |
491 | DEBUG_INFO("New _currentOffset=%#x\n" , _currentOffset); |
492 | |
493 | exit: |
494 | IOFreeData(address: controllerImage, size: _bankSize); |
495 | return ret; |
496 | } |
497 | |
498 | void |
499 | IONVRAMV3Handler::reload(void) |
500 | { |
501 | _reload = true; |
502 | |
503 | DEBUG_INFO("reload marked\n" ); |
504 | } |
505 | |
506 | void |
507 | IONVRAMV3Handler::setEntryForRemove(struct nvram_v3_var_entry *v3Entry, bool system) |
508 | { |
509 | OSSharedPtr<const OSSymbol> canonicalKey; |
510 | const char *variableName; |
511 | uint32_t variableSize; |
512 | |
513 | require_action(v3Entry != nullptr, exit, DEBUG_INFO("remove with no entry\n" )); |
514 | |
515 | variableName = (const char *)v3Entry->header.name_data_buf; |
516 | variableSize = (uint32_t)variable_length(header: &v3Entry->header); |
517 | canonicalKey = keyWithGuidAndCString(guid: v3Entry->header.guid, cstring: variableName); |
518 | |
519 | if (v3Entry->new_state == VAR_NEW_STATE_REMOVE) { |
520 | DEBUG_INFO("entry %s already marked for remove\n" , variableName); |
521 | } else { |
522 | DEBUG_INFO("marking entry %s for remove\n" , variableName); |
523 | |
524 | v3Entry->new_state = VAR_NEW_STATE_REMOVE; |
525 | |
526 | _provider->_varDict->removeObject(aKey: canonicalKey.get()); |
527 | |
528 | if (system) { |
529 | if (_systemUsed < variableSize) { |
530 | panic("Invalid _systemUsed size\n" ); |
531 | } |
532 | _systemUsed -= variableSize; |
533 | } else { |
534 | if (_commonUsed < variableSize) { |
535 | panic("Invalid _commonUsed size\n" ); |
536 | } |
537 | _commonUsed -= variableSize; |
538 | } |
539 | |
540 | if (_provider->_diags) { |
541 | _provider->_diags->logVariable(region: getPartitionTypeForGUID(guid: v3Entry->header.guid), |
542 | op: kIONVRAMOperationDelete, |
543 | name: variableName, |
544 | data: nullptr); |
545 | } |
546 | } |
547 | |
548 | exit: |
549 | return; |
550 | } |
551 | |
552 | void |
553 | IONVRAMV3Handler::findExistingEntry(const uuid_t varGuid, const char *varName, struct nvram_v3_var_entry **existing, unsigned int *existingIndex) |
554 | { |
555 | struct nvram_v3_var_entry *v3Entry = nullptr; |
556 | OSData *entryContainer = nullptr; |
557 | unsigned int index = 0; |
558 | uint32_t nameLen = (uint32_t)strlen(s: varName) + 1; |
559 | |
560 | for (index = 0; index < _varEntries->getCount(); index++) { |
561 | entryContainer = (OSDynamicCast(OSData, _varEntries->getObject(index))); |
562 | v3Entry = (struct nvram_v3_var_entry *)entryContainer->getBytesNoCopy(); |
563 | |
564 | if ((v3Entry->header.nameSize == nameLen) && |
565 | (memcmp(s1: v3Entry->header.name_data_buf, s2: varName, n: nameLen) == 0)) { |
566 | if (varGuid) { |
567 | if (uuid_compare(uu1: varGuid, uu2: v3Entry->header.guid) == 0) { |
568 | uuid_string_t uuidString; |
569 | uuid_unparse(uu: varGuid, out: uuidString); |
570 | DEBUG_INFO("found existing entry for %s:%s, e_off=%#lx, len=%#lx, new_state=%#x\n" , uuidString, varName, |
571 | v3Entry->existing_offset, variable_length(&v3Entry->header), v3Entry->new_state); |
572 | break; |
573 | } |
574 | } else { |
575 | DEBUG_INFO("found existing entry for %s, e_off=%#lx, len=%#lx\n" , varName, v3Entry->existing_offset, variable_length(&v3Entry->header)); |
576 | break; |
577 | } |
578 | } |
579 | |
580 | v3Entry = nullptr; |
581 | } |
582 | |
583 | if (v3Entry != nullptr) { |
584 | if (existing) { |
585 | *existing = v3Entry; |
586 | } |
587 | |
588 | if (existingIndex) { |
589 | *existingIndex = index; |
590 | } |
591 | } |
592 | } |
593 | |
594 | IOReturn |
595 | IONVRAMV3Handler::unserializeImage(const uint8_t *image, IOByteCount length) |
596 | { |
597 | IOReturn ret = kIOReturnInvalid; |
598 | const struct v3_store_header *; |
599 | |
600 | require(isValidImage(image, length), exit); |
601 | |
602 | storeHeader = (const struct v3_store_header *)image; |
603 | require_action(storeHeader->size == (uint32_t)length, exit, |
604 | DEBUG_ERROR("Image size %#x != header size %#x\n" , (unsigned int)length, storeHeader->size)); |
605 | |
606 | _generation = storeHeader->generation; |
607 | _systemSize = storeHeader->system_size; |
608 | _commonSize = storeHeader->common_size - sizeof(struct v3_store_header); |
609 | |
610 | _systemUsed = 0; |
611 | _commonUsed = 0; |
612 | |
613 | if (_nvramImage) { |
614 | IOFreeData(address: _nvramImage, size: _bankSize); |
615 | } |
616 | |
617 | _varEntries.reset(); |
618 | _varEntries = OSArray::withCapacity(capacity: 40); |
619 | |
620 | _nvramImage = IONewData(uint8_t, length); |
621 | _bankSize = (uint32_t)length; |
622 | bcopy(src: image, dst: _nvramImage, n: _bankSize); |
623 | |
624 | ret = kIOReturnSuccess; |
625 | |
626 | exit: |
627 | return ret; |
628 | } |
629 | |
630 | IOReturn |
631 | IONVRAMV3Handler::unserializeVariables(void) |
632 | { |
633 | IOReturn ret = kIOReturnSuccess; |
634 | OSSharedPtr<const OSSymbol> propSymbol; |
635 | OSSharedPtr<OSObject> propObject; |
636 | OSSharedPtr<OSData> entryContainer; |
637 | struct nvram_v3_var_entry *v3Entry; |
638 | const struct v3_var_header *; |
639 | size_t offset = sizeof(struct v3_store_header); |
640 | uint32_t crc; |
641 | unsigned int i; |
642 | bool system; |
643 | uuid_string_t uuidString; |
644 | size_t existingSize; |
645 | |
646 | if (_systemSize || _commonSize) { |
647 | _varDict = OSDictionary::withCapacity(capacity: 1); |
648 | } |
649 | |
650 | while ((offset + sizeof(struct v3_var_header)) < _bankSize) { |
651 | struct nvram_v3_var_entry *existingEntry = nullptr; |
652 | unsigned int existingIndex = 0; |
653 | |
654 | header = (const struct v3_var_header *)(_nvramImage + offset); |
655 | |
656 | for (i = 0; i < sizeof(struct v3_var_header); i++) { |
657 | if ((_nvramImage[offset + i] != 0) && (_nvramImage[offset + i] != 0xFF)) { |
658 | break; |
659 | } |
660 | } |
661 | |
662 | if (i == sizeof(struct v3_var_header)) { |
663 | DEBUG_INFO("No more variables after offset %#lx\n" , offset); |
664 | break; |
665 | } |
666 | |
667 | if (!valid_variable_header(header, buf_len: _bankSize - offset)) { |
668 | DEBUG_ERROR("invalid header @ %#lx\n" , offset); |
669 | offset += sizeof(struct v3_var_header); |
670 | continue; |
671 | } |
672 | |
673 | uuid_unparse(uu: header->guid, out: uuidString); |
674 | DEBUG_INFO("Valid var @ %#08zx, state=%#02x, length=%#08zx, %s:%s\n" , offset, header->state, |
675 | variable_length(header), uuidString, header->name_data_buf); |
676 | |
677 | if (header->state != VAR_ADDED) { |
678 | goto skip; |
679 | } |
680 | |
681 | crc = crc32(crc: 0, bufp: header->name_data_buf + header->nameSize, len: header->dataSize); |
682 | |
683 | if (crc != header->crc) { |
684 | DEBUG_ERROR("invalid crc @ %#lx, calculated=%#x, read=%#x\n" , offset, crc, header->crc); |
685 | goto skip; |
686 | } |
687 | |
688 | v3Entry = (struct nvram_v3_var_entry *)IOMallocZeroData(nvram_v3_var_container_size(header)); |
689 | __nochk_memcpy(dst: &v3Entry->header, src: _nvramImage + offset, n: variable_length(header)); |
690 | |
691 | // It is assumed that the initial image being unserialized here is going to be the proxy data from EDT and not the image |
692 | // read from the controller, which for various reasons due to the setting of states and saves from iBoot, can be |
693 | // different. We will have an initial existing_offset of 0 and once the controller is set we will read |
694 | // out the image there and update the existing offset with what is present on the NOR image |
695 | v3Entry->existing_offset = 0; |
696 | v3Entry->new_state = VAR_NEW_STATE_NONE; |
697 | |
698 | // safe guard for any strange duplicate entries in the store |
699 | findExistingEntry(varGuid: v3Entry->header.guid, varName: (const char *)v3Entry->header.name_data_buf, existing: &existingEntry, existingIndex: &existingIndex); |
700 | |
701 | if (existingEntry != nullptr) { |
702 | existingSize = variable_length(header: &existingEntry->header); |
703 | |
704 | entryContainer = OSData::withBytes(bytes: v3Entry, numBytes: (uint32_t)nvram_v3_var_container_size(header)); |
705 | _varEntries->replaceObject(index: existingIndex, anObject: entryContainer.get()); |
706 | |
707 | DEBUG_INFO("Found existing for %s, resetting when controller available\n" , v3Entry->header.name_data_buf); |
708 | _resetData = true; |
709 | } else { |
710 | entryContainer = OSData::withBytes(bytes: v3Entry, numBytes: (uint32_t)nvram_v3_var_container_size(header)); |
711 | _varEntries->setObject(entryContainer.get()); |
712 | existingSize = 0; |
713 | } |
714 | |
715 | system = (_systemSize != 0) && (uuid_compare(uu1: v3Entry->header.guid, uu2: gAppleSystemVariableGuid) == 0); |
716 | if (system) { |
717 | _systemUsed = _systemUsed + (uint32_t)variable_length(header) - (uint32_t)existingSize; |
718 | } else { |
719 | _commonUsed = _commonUsed + (uint32_t)variable_length(header) - (uint32_t)existingSize; |
720 | } |
721 | |
722 | if (convertPropToObject(propName: v3Entry->header.name_data_buf, propNameLength: v3Entry->header.nameSize, |
723 | propData: v3Entry->header.name_data_buf + v3Entry->header.nameSize, propDataLength: v3Entry->header.dataSize, |
724 | propSymbol, propObject)) { |
725 | OSSharedPtr<const OSSymbol> canonicalKey = keyWithGuidAndCString(guid: v3Entry->header.guid, cstring: (const char *)v3Entry->header.name_data_buf); |
726 | |
727 | DEBUG_INFO("adding %s, dataLength=%u, system=%d\n" , |
728 | canonicalKey->getCStringNoCopy(), v3Entry->header.dataSize, system); |
729 | |
730 | _varDict->setObject(aKey: canonicalKey.get(), anObject: propObject.get()); |
731 | |
732 | if (_provider->_diags) { |
733 | _provider->_diags->logVariable(region: getPartitionTypeForGUID(guid: v3Entry->header.guid), |
734 | op: kIONVRAMOperationInit, name: propSymbol.get()->getCStringNoCopy(), |
735 | data: (void *)(uintptr_t)(header->name_data_buf + header->nameSize)); |
736 | } |
737 | } |
738 | IOFreeData(address: v3Entry, size: nvram_v3_var_container_size(header)); |
739 | skip: |
740 | offset += variable_length(header); |
741 | } |
742 | |
743 | _currentOffset = (uint32_t)offset; |
744 | |
745 | DEBUG_ALWAYS("_commonSize %#x, _systemSize %#x, _currentOffset %#x\n" , _commonSize, _systemSize, _currentOffset); |
746 | DEBUG_INFO("_commonUsed %#x, _systemUsed %#x\n" , _commonUsed, _systemUsed); |
747 | |
748 | _newData = true; |
749 | |
750 | if (_provider->_diags) { |
751 | OSSharedPtr<OSNumber> val = OSNumber::withNumber(value: getSystemUsed(), numberOfBits: 32); |
752 | _provider->_diags->setProperty(kNVRAMSystemUsedKey, anObject: val.get()); |
753 | DEBUG_INFO("%s=%u\n" , kNVRAMSystemUsedKey, getSystemUsed()); |
754 | |
755 | val = OSNumber::withNumber(value: getCommonUsed(), numberOfBits: 32); |
756 | _provider->_diags->setProperty(kNVRAMCommonUsedKey, anObject: val.get()); |
757 | DEBUG_INFO("%s=%u\n" , kNVRAMCommonUsedKey, getCommonUsed()); |
758 | } |
759 | |
760 | return ret; |
761 | } |
762 | |
763 | IOReturn |
764 | IONVRAMV3Handler::setVariableInternal(const uuid_t varGuid, const char *variableName, OSObject *object) |
765 | { |
766 | struct nvram_v3_var_entry *v3Entry = nullptr; |
767 | struct nvram_v3_var_entry *newV3Entry; |
768 | OSSharedPtr<OSData> newContainer; |
769 | OSSharedPtr<const OSSymbol> canonicalKey; |
770 | bool unset = (object == nullptr); |
771 | bool system = false; |
772 | IOReturn ret = kIOReturnSuccess; |
773 | size_t entryNameLen = strlen(s: variableName) + 1; |
774 | unsigned int existingEntryIndex; |
775 | uint32_t dataSize = 0; |
776 | size_t existingVariableSize = 0; |
777 | size_t newVariableSize = 0; |
778 | size_t newEntrySize; |
779 | uuid_string_t uuidString; |
780 | |
781 | system = (uuid_compare(uu1: varGuid, uu2: gAppleSystemVariableGuid) == 0); |
782 | canonicalKey = keyWithGuidAndCString(guid: varGuid, cstring: variableName); |
783 | |
784 | uuid_unparse(uu: varGuid, out: uuidString); |
785 | DEBUG_INFO("setting %s:%s, system=%d, current var count=%u\n" , uuidString, variableName, system, _varEntries->getCount()); |
786 | |
787 | findExistingEntry(varGuid, varName: variableName, existing: &v3Entry, existingIndex: &existingEntryIndex); |
788 | |
789 | if (unset == true) { |
790 | setEntryForRemove(v3Entry, system); |
791 | } else { |
792 | if ((v3Entry != nullptr) && (v3Entry->new_state != VAR_NEW_STATE_REMOVE)) { |
793 | // Sizing was subtracted in setEntryForRemove |
794 | existingVariableSize = variable_length(header: &v3Entry->header); |
795 | } |
796 | |
797 | convertObjectToProp(buffer: nullptr, length: &dataSize, propSymbol: variableName, propObject: object); |
798 | |
799 | newVariableSize = sizeof(struct v3_var_header) + entryNameLen + dataSize; |
800 | newEntrySize = sizeof(struct nvram_v3_var_entry) + entryNameLen + dataSize; |
801 | |
802 | if (system) { |
803 | if (_systemUsed - existingVariableSize + newVariableSize > _systemSize) { |
804 | DEBUG_ERROR("system region full\n" ); |
805 | ret = kIOReturnNoSpace; |
806 | goto exit; |
807 | } |
808 | } else if (_commonUsed - existingVariableSize + newVariableSize > _commonSize) { |
809 | DEBUG_ERROR("common region full\n" ); |
810 | ret = kIOReturnNoSpace; |
811 | goto exit; |
812 | } |
813 | |
814 | DEBUG_INFO("creating new entry for %s, existingVariableSize=%#zx, newVariableSize=%#zx\n" , variableName, existingVariableSize, newVariableSize); |
815 | newV3Entry = (struct nvram_v3_var_entry *)IOMallocZeroData(newEntrySize); |
816 | |
817 | memcpy(dst: newV3Entry->header.name_data_buf, src: variableName, n: entryNameLen); |
818 | convertObjectToProp(buffer: newV3Entry->header.name_data_buf + entryNameLen, length: &dataSize, propSymbol: variableName, propObject: object); |
819 | |
820 | newV3Entry->header.startId = VARIABLE_DATA; |
821 | newV3Entry->header.nameSize = (uint32_t)entryNameLen; |
822 | newV3Entry->header.dataSize = dataSize; |
823 | newV3Entry->header.crc = crc32(crc: 0, bufp: newV3Entry->header.name_data_buf + entryNameLen, len: dataSize); |
824 | memcpy(dst: newV3Entry->header.guid, src: varGuid, n: sizeof(gAppleNVRAMGuid)); |
825 | newV3Entry->new_state = VAR_NEW_STATE_APPEND; |
826 | |
827 | if (v3Entry) { |
828 | newV3Entry->existing_offset = v3Entry->existing_offset; |
829 | newV3Entry->header.state = v3Entry->header.state; |
830 | newV3Entry->header.attributes = v3Entry->header.attributes; |
831 | |
832 | newContainer = OSData::withBytes(bytes: newV3Entry, numBytes: (uint32_t)newEntrySize); |
833 | _varEntries->replaceObject(index: existingEntryIndex, anObject: newContainer.get()); |
834 | } else { |
835 | newContainer = OSData::withBytes(bytes: newV3Entry, numBytes: (uint32_t)newEntrySize); |
836 | _varEntries->setObject(newContainer.get()); |
837 | } |
838 | |
839 | if (system) { |
840 | _systemUsed = _systemUsed + (uint32_t)newVariableSize - (uint32_t)existingVariableSize; |
841 | } else { |
842 | _commonUsed = _commonUsed + (uint32_t)newVariableSize - (uint32_t)existingVariableSize; |
843 | } |
844 | |
845 | _varDict->setObject(aKey: canonicalKey.get(), anObject: object); |
846 | |
847 | if (_provider->_diags) { |
848 | _provider->_diags->logVariable(region: getPartitionTypeForGUID(guid: varGuid), |
849 | op: kIONVRAMOperationWrite, name: variableName, |
850 | data: (void *)(uintptr_t)dataSize); |
851 | } |
852 | |
853 | IOFreeData(address: newV3Entry, size: newEntrySize); |
854 | } |
855 | |
856 | exit: |
857 | _newData = true; |
858 | |
859 | if (_provider->_diags) { |
860 | OSSharedPtr<OSNumber> val = OSNumber::withNumber(value: getSystemUsed(), numberOfBits: 32); |
861 | _provider->_diags->setProperty(kNVRAMSystemUsedKey, anObject: val.get()); |
862 | |
863 | val = OSNumber::withNumber(value: getCommonUsed(), numberOfBits: 32); |
864 | _provider->_diags->setProperty(kNVRAMCommonUsedKey, anObject: val.get()); |
865 | } |
866 | |
867 | DEBUG_INFO("_commonUsed %#x, _systemUsed %#x\n" , _commonUsed, _systemUsed); |
868 | |
869 | return ret; |
870 | } |
871 | |
872 | IOReturn |
873 | IONVRAMV3Handler::setVariable(const uuid_t varGuid, const char *variableName, OSObject *object) |
874 | { |
875 | uuid_t destGuid; |
876 | |
877 | if (strcmp(s1: variableName, s2: "reclaim-int" ) == 0) { |
878 | return reclaim(); |
879 | } |
880 | |
881 | if (getSystemPartitionActive()) { |
882 | // System region case, if they're using the GUID directly or it's on the system allow list |
883 | // force it to use the System GUID |
884 | if ((uuid_compare(uu1: varGuid, uu2: gAppleSystemVariableGuid) == 0) || variableInAllowList(varName: variableName)) { |
885 | uuid_copy(dst: destGuid, src: gAppleSystemVariableGuid); |
886 | } else { |
887 | uuid_copy(dst: destGuid, src: varGuid); |
888 | } |
889 | } else { |
890 | // No system region, store System GUID as Common GUID |
891 | if ((uuid_compare(uu1: varGuid, uu2: gAppleSystemVariableGuid) == 0) || variableInAllowList(varName: variableName)) { |
892 | uuid_copy(dst: destGuid, src: gAppleNVRAMGuid); |
893 | } else { |
894 | uuid_copy(dst: destGuid, src: varGuid); |
895 | } |
896 | } |
897 | |
898 | return setVariableInternal(varGuid: destGuid, variableName, object); |
899 | } |
900 | |
901 | uint32_t |
902 | IONVRAMV3Handler::findCurrentBank(void) |
903 | { |
904 | struct v3_store_header ; |
905 | uint32_t maxGen = 0; |
906 | uint32_t currentBank = 0; |
907 | |
908 | for (unsigned int i = 0; i < _bankCount; i++) { |
909 | _nvramController->select(bank: i); |
910 | _nvramController->read(offset: 0, buffer: (uint8_t *)&storeHeader, length: sizeof(storeHeader)); |
911 | |
912 | if (valid_store_header(header: &storeHeader) && (storeHeader.generation >= maxGen)) { |
913 | currentBank = i; |
914 | maxGen = storeHeader.generation; |
915 | } |
916 | } |
917 | |
918 | DEBUG_ALWAYS("currentBank=%#x, gen=%#x" , currentBank, maxGen); |
919 | |
920 | return currentBank; |
921 | } |
922 | |
923 | bool |
924 | IONVRAMV3Handler::setController(IONVRAMController *controller) |
925 | { |
926 | IOReturn ret = kIOReturnSuccess; |
927 | |
928 | if (_nvramController == NULL) { |
929 | _nvramController = controller; |
930 | } |
931 | |
932 | DEBUG_INFO("Controller name: %s\n" , _nvramController->getName()); |
933 | |
934 | require(_bankSize != 0, exit); |
935 | |
936 | if (_resetData) { |
937 | _resetData = false; |
938 | DEBUG_ERROR("_resetData set, issuing reclaim recovery\n" ); |
939 | ret = reclaim(); |
940 | require_noerr_action(ret, exit, DEBUG_ERROR("Reclaim recovery failed, invalid controller state!!! ret=%#x\n" , ret)); |
941 | goto exit; |
942 | } |
943 | |
944 | ret = reloadInternal(); |
945 | if (ret != kIOReturnSuccess) { |
946 | DEBUG_ERROR("Invalid image found, issuing reclaim recovery\n" ); |
947 | ret = reclaim(); |
948 | require_noerr_action(ret, exit, DEBUG_ERROR("Reclaim recovery failed, invalid controller state!!! ret=%#x\n" , ret)); |
949 | } |
950 | |
951 | exit: |
952 | return ret == kIOReturnSuccess; |
953 | } |
954 | |
955 | IOReturn |
956 | IONVRAMV3Handler::reclaim(void) |
957 | { |
958 | IOReturn ret; |
959 | struct v3_store_header ; |
960 | struct v3_var_header *; |
961 | struct nvram_v3_var_entry *varEntry; |
962 | OSData *entryContainer; |
963 | size_t new_bank_offset = sizeof(struct v3_store_header); |
964 | uint32_t next_bank = (_currentBank + 1) % _bankCount; |
965 | uint8_t *bankData; |
966 | OSSharedPtr<OSArray> remainingEntries; |
967 | |
968 | DEBUG_INFO("called\n" ); |
969 | |
970 | bankData = (uint8_t *)IOMallocData(_bankSize); |
971 | require_action(bankData != nullptr, exit, ret = kIOReturnNoMemory); |
972 | |
973 | ret = _nvramController->select(bank: next_bank); |
974 | verify_noerr_action(ret, DEBUG_INFO("select of bank %#08x failed\n" , next_bank)); |
975 | |
976 | ret = _nvramController->eraseBank(); |
977 | verify_noerr_action(ret, DEBUG_INFO("eraseBank failed, ret=%#08x\n" , ret)); |
978 | |
979 | _currentBank = next_bank; |
980 | |
981 | remainingEntries = OSArray::withCapacity(capacity: _varEntries->getCapacity()); |
982 | |
983 | for (unsigned int i = 0; i < _varEntries->getCount(); i++) { |
984 | entryContainer = OSDynamicCast(OSData, _varEntries->getObject(i)); |
985 | varEntry = (struct nvram_v3_var_entry *)entryContainer->getBytesNoCopy(); |
986 | varHeader = &varEntry->header; |
987 | |
988 | DEBUG_INFO("entry %u %s, new_state=%#x, e_offset=%#lx, state=%#x\n" , |
989 | i, varEntry->header.name_data_buf, varEntry->new_state, varEntry->existing_offset, varHeader->state); |
990 | |
991 | if ((varEntry->new_state == VAR_NEW_STATE_NONE) || |
992 | (varEntry->new_state == VAR_NEW_STATE_APPEND)) { |
993 | varHeader->state = VAR_ADDED; |
994 | |
995 | memcpy(dst: bankData + new_bank_offset, src: (uint8_t *)varHeader, n: variable_length(header: varHeader)); |
996 | |
997 | varEntry->new_state = VAR_NEW_STATE_NONE; |
998 | varEntry->existing_offset = new_bank_offset; |
999 | new_bank_offset += variable_length(header: varHeader); |
1000 | |
1001 | remainingEntries->setObject(entryContainer); |
1002 | } else { |
1003 | // entryContainer not added to remainingEntries, entry dropped |
1004 | } |
1005 | } |
1006 | |
1007 | memcpy(dst: &newStoreHeader, src: _nvramImage, n: sizeof(newStoreHeader)); |
1008 | |
1009 | _generation += 1; |
1010 | |
1011 | newStoreHeader.generation = _generation; |
1012 | |
1013 | memcpy(dst: bankData, src: (uint8_t *)&newStoreHeader, n: sizeof(newStoreHeader)); |
1014 | |
1015 | ret = _nvramController->write(offset: 0, buffer: bankData, length: new_bank_offset); |
1016 | require_noerr_action(ret, exit, DEBUG_ERROR("reclaim bank write failed, ret=%08x\n" , ret)); |
1017 | |
1018 | _currentOffset = (uint32_t)new_bank_offset; |
1019 | |
1020 | DEBUG_INFO("Reclaim complete, _currentBank=%u _generation=%u, _currentOffset=%#x\n" , _currentBank, _generation, _currentOffset); |
1021 | |
1022 | _newData = false; |
1023 | |
1024 | _varEntries.reset(p: remainingEntries.get(), OSRetain); |
1025 | |
1026 | exit: |
1027 | IOFreeData(address: bankData, size: _bankSize); |
1028 | |
1029 | return ret; |
1030 | } |
1031 | |
1032 | size_t |
1033 | IONVRAMV3Handler::getAppendSize(void) |
1034 | { |
1035 | struct nvram_v3_var_entry *varEntry; |
1036 | struct v3_var_header *; |
1037 | OSData *entryContainer; |
1038 | size_t appendSize = 0; |
1039 | |
1040 | for (unsigned int i = 0; i < _varEntries->getCount(); i++) { |
1041 | entryContainer = OSDynamicCast(OSData, _varEntries->getObject(i)); |
1042 | varEntry = (struct nvram_v3_var_entry *)entryContainer->getBytesNoCopy(); |
1043 | varHeader = &varEntry->header; |
1044 | |
1045 | if (varEntry->new_state == VAR_NEW_STATE_APPEND) { |
1046 | appendSize += variable_length(header: varHeader); |
1047 | } |
1048 | } |
1049 | |
1050 | return appendSize; |
1051 | } |
1052 | |
1053 | IOReturn |
1054 | IONVRAMV3Handler::syncRaw(void) |
1055 | { |
1056 | IOReturn ret = kIOReturnSuccess; |
1057 | struct nvram_v3_var_entry *varEntry; |
1058 | struct v3_var_header *; |
1059 | OSData *entryContainer; |
1060 | OSSharedPtr<OSArray> remainingEntries; |
1061 | uint8_t *appendBuffer = nullptr; |
1062 | size_t appendBufferOffset = 0; |
1063 | size_t *invalidateOffsets = nullptr; |
1064 | size_t invalidateOffsetsCount = 0; |
1065 | size_t invalidateOffsetIndex = 0; |
1066 | size_t invalidatedSize = 0; |
1067 | |
1068 | require_action(_nvramController != nullptr, exit, DEBUG_INFO("No _nvramController\n" )); |
1069 | require_action(_newData == true, exit, DEBUG_INFO("No _newData to sync\n" )); |
1070 | require_action(_bankSize != 0, exit, DEBUG_INFO("No nvram size info\n" )); |
1071 | |
1072 | DEBUG_INFO("_varEntries->getCount()=%#x\n" , _varEntries->getCount()); |
1073 | |
1074 | if (getAppendSize() + _currentOffset < _bankSize) { |
1075 | // No reclaim, build append and invalidate list |
1076 | |
1077 | remainingEntries = OSArray::withCapacity(capacity: _varEntries->getCapacity()); |
1078 | |
1079 | appendBuffer = (uint8_t *)IOMallocData(_bankSize); |
1080 | require_action(appendBuffer, exit, ret = kIOReturnNoMemory); |
1081 | |
1082 | invalidateOffsetsCount = _varEntries->getCount(); |
1083 | invalidateOffsets = (size_t *)IOMallocData(invalidateOffsetsCount * sizeof(size_t)); |
1084 | require_action(invalidateOffsets, exit, ret = kIOReturnNoMemory); |
1085 | |
1086 | for (unsigned int i = 0; i < _varEntries->getCount(); i++) { |
1087 | entryContainer = OSDynamicCast(OSData, _varEntries->getObject(i)); |
1088 | varEntry = (struct nvram_v3_var_entry *)entryContainer->getBytesNoCopy(); |
1089 | varHeader = &varEntry->header; |
1090 | |
1091 | DEBUG_INFO("entry %s, new_state=%#02x state=%#02x, existing_offset=%#zx\n" , |
1092 | varEntry->header.name_data_buf, varEntry->new_state, varEntry->header.state, varEntry->existing_offset); |
1093 | |
1094 | if (varEntry->new_state == VAR_NEW_STATE_APPEND) { |
1095 | size_t varSize = variable_length(header: varHeader); |
1096 | size_t prevOffset = varEntry->existing_offset; |
1097 | |
1098 | varHeader->state = VAR_ADDED; |
1099 | varEntry->existing_offset = _currentOffset + appendBufferOffset; |
1100 | varEntry->new_state = VAR_NEW_STATE_NONE; |
1101 | |
1102 | DEBUG_INFO("Appending %s in append buffer offset %#zx, actual offset %#zx, prevOffset %#zx, varsize=%#zx\n" , |
1103 | varEntry->header.name_data_buf, appendBufferOffset, varEntry->existing_offset, prevOffset, varSize); |
1104 | |
1105 | // Write to append buffer |
1106 | memcpy(dst: appendBuffer + appendBufferOffset, src: (uint8_t *)varHeader, n: varSize); |
1107 | appendBufferOffset += varSize; |
1108 | |
1109 | if (prevOffset) { |
1110 | invalidateOffsets[invalidateOffsetIndex++] = prevOffset; |
1111 | invalidatedSize += variable_length(header: (struct v3_var_header *)prevOffset); |
1112 | } |
1113 | |
1114 | remainingEntries->setObject(entryContainer); |
1115 | } else if (varEntry->new_state == VAR_NEW_STATE_REMOVE) { |
1116 | if (varEntry->existing_offset) { |
1117 | DEBUG_INFO("marking entry at offset %#lx deleted\n" , varEntry->existing_offset); |
1118 | |
1119 | invalidateOffsets[invalidateOffsetIndex++] = varEntry->existing_offset; |
1120 | invalidatedSize += variable_length(header: (struct v3_var_header *)varEntry->existing_offset); |
1121 | } else { |
1122 | DEBUG_INFO("No existing_offset , removing\n" ); |
1123 | } |
1124 | |
1125 | // not re-added to remainingEntries |
1126 | } else { |
1127 | DEBUG_INFO("skipping\n" ); |
1128 | remainingEntries->setObject(entryContainer); |
1129 | } |
1130 | } |
1131 | |
1132 | if (appendBufferOffset > 0) { |
1133 | // Write appendBuffer |
1134 | DEBUG_INFO("Appending append buffer size=%#zx at offset=%#x\n" , appendBufferOffset, _currentOffset); |
1135 | ret = _nvramController->write(offset: _currentOffset, buffer: appendBuffer, length: appendBufferOffset); |
1136 | require_noerr_action(ret, exit, DEBUG_ERROR("could not re-append, ret=%#x\n" , ret)); |
1137 | |
1138 | _currentOffset += appendBufferOffset; |
1139 | } else { |
1140 | DEBUG_INFO("No entries to append\n" ); |
1141 | } |
1142 | |
1143 | if (invalidateOffsetIndex > 0) { |
1144 | // Invalidate Entries |
1145 | for (unsigned int i = 0; i < invalidateOffsetIndex; i++) { |
1146 | uint8_t state = VAR_ADDED & VAR_DELETED & VAR_IN_DELETED_TRANSITION; |
1147 | |
1148 | ret = _nvramController->write(offset: invalidateOffsets[i] + offsetof(struct v3_var_header, state), buffer: &state, length: sizeof(state)); |
1149 | require_noerr_action(ret, exit, DEBUG_ERROR("unable to invalidate at offset %#zx, ret=%#x\n" , invalidateOffsets[i], ret)); |
1150 | DEBUG_INFO("Invalidated entry at offset=%#zx\n" , invalidateOffsets[i]); |
1151 | } |
1152 | } else { |
1153 | DEBUG_INFO("No entries to invalidate\n" ); |
1154 | } |
1155 | |
1156 | _newData = false; |
1157 | |
1158 | _varEntries.reset(p: remainingEntries.get(), OSRetain); |
1159 | } else { |
1160 | // Will need to reclaim, rebuild store and write everything at once |
1161 | ret = reclaim(); |
1162 | } |
1163 | |
1164 | exit: |
1165 | IOFreeData(address: appendBuffer, size: _bankSize); |
1166 | IOFreeData(address: invalidateOffsets, size: invalidateOffsetsCount * sizeof(size_t)); |
1167 | |
1168 | return ret; |
1169 | } |
1170 | |
1171 | IOReturn |
1172 | IONVRAMV3Handler::syncBlock(void) |
1173 | { |
1174 | IOReturn ret = kIOReturnSuccess; |
1175 | struct v3_store_header ; |
1176 | struct v3_var_header *; |
1177 | struct nvram_v3_var_entry *varEntry; |
1178 | OSData *entryContainer; |
1179 | size_t new_bank_offset = sizeof(struct v3_store_header); |
1180 | uint8_t *block; |
1181 | OSSharedPtr<OSArray> remainingEntries; |
1182 | uint32_t next_bank = (_currentBank + 1) % _bankCount; |
1183 | |
1184 | DEBUG_INFO("called\n" ); |
1185 | |
1186 | require_action(_nvramController != nullptr, exit, DEBUG_INFO("No _nvramController\n" )); |
1187 | require_action(_newData == true, exit, DEBUG_INFO("No _newData to sync\n" )); |
1188 | require_action(_bankSize != 0, exit, DEBUG_INFO("No nvram size info\n" )); |
1189 | |
1190 | block = (uint8_t *)IOMallocData(_bankSize); |
1191 | |
1192 | remainingEntries = OSArray::withCapacity(capacity: _varEntries->getCapacity()); |
1193 | |
1194 | ret = _nvramController->select(bank: next_bank); |
1195 | verify_noerr_action(ret, DEBUG_INFO("select of bank %#x failed\n" , next_bank)); |
1196 | |
1197 | ret = _nvramController->eraseBank(); |
1198 | verify_noerr_action(ret, DEBUG_INFO("eraseBank failed, ret=%#08x\n" , ret)); |
1199 | |
1200 | _currentBank = next_bank; |
1201 | |
1202 | memcpy(dst: &newStoreHeader, src: _nvramImage, n: sizeof(newStoreHeader)); |
1203 | |
1204 | _generation += 1; |
1205 | |
1206 | newStoreHeader.generation = _generation; |
1207 | |
1208 | memcpy(dst: block, src: (uint8_t *)&newStoreHeader, n: sizeof(newStoreHeader)); |
1209 | |
1210 | for (unsigned int i = 0; i < _varEntries->getCount(); i++) { |
1211 | entryContainer = OSDynamicCast(OSData, _varEntries->getObject(i)); |
1212 | varEntry = (struct nvram_v3_var_entry *)entryContainer->getBytesNoCopy(); |
1213 | varHeader = &varEntry->header; |
1214 | |
1215 | DEBUG_INFO("entry %u %s, new_state=%#x, e_offset=%#lx, state=%#x\n" , |
1216 | i, varEntry->header.name_data_buf, varEntry->new_state, varEntry->existing_offset, varHeader->state); |
1217 | |
1218 | if (varEntry->new_state != VAR_NEW_STATE_REMOVE) { |
1219 | varHeader->state = VAR_ADDED; |
1220 | |
1221 | memcpy(dst: block + new_bank_offset, src: (uint8_t *)varHeader, n: variable_length(header: varHeader)); |
1222 | |
1223 | varEntry->existing_offset = new_bank_offset; |
1224 | new_bank_offset += variable_length(header: varHeader); |
1225 | varEntry->new_state = VAR_NEW_STATE_NONE; |
1226 | |
1227 | remainingEntries->setObject(entryContainer); |
1228 | } else { |
1229 | DEBUG_INFO("Dropping %s\n" , varEntry->header.name_data_buf); |
1230 | } |
1231 | } |
1232 | |
1233 | ret = _nvramController->write(offset: 0, buffer: block, length: _bankSize); |
1234 | verify_noerr_action(ret, DEBUG_ERROR("w fail, ret=%#x\n" , ret)); |
1235 | |
1236 | _nvramController->sync(); |
1237 | |
1238 | _varEntries.reset(p: remainingEntries.get(), OSRetain); |
1239 | |
1240 | _newData = false; |
1241 | |
1242 | DEBUG_INFO("Save complete, _generation=%u\n" , _generation); |
1243 | |
1244 | IOFreeData(address: block, size: _bankSize); |
1245 | |
1246 | exit: |
1247 | return ret; |
1248 | } |
1249 | |
1250 | IOReturn |
1251 | IONVRAMV3Handler::sync(void) |
1252 | { |
1253 | IOReturn ret; |
1254 | |
1255 | if (_reload) { |
1256 | ret = reloadInternal(); |
1257 | require_noerr_action(ret, exit, DEBUG_ERROR("Reload failed, ret=%#x" , ret)); |
1258 | |
1259 | _reload = false; |
1260 | } |
1261 | |
1262 | if (_rawController == true) { |
1263 | ret = syncRaw(); |
1264 | |
1265 | if (ret != kIOReturnSuccess) { |
1266 | ret = reclaim(); |
1267 | require_noerr_action(ret, exit, DEBUG_ERROR("Reclaim recovery failed, ret=%#x" , ret)); |
1268 | } |
1269 | } else { |
1270 | ret = syncBlock(); |
1271 | } |
1272 | |
1273 | exit: |
1274 | return ret; |
1275 | } |
1276 | |
1277 | uint32_t |
1278 | IONVRAMV3Handler::getGeneration(void) const |
1279 | { |
1280 | return _generation; |
1281 | } |
1282 | |
1283 | uint32_t |
1284 | IONVRAMV3Handler::getVersion(void) const |
1285 | { |
1286 | return kNVRAMVersion3; |
1287 | } |
1288 | |
1289 | uint32_t |
1290 | IONVRAMV3Handler::getSystemUsed(void) const |
1291 | { |
1292 | return _systemUsed; |
1293 | } |
1294 | |
1295 | uint32_t |
1296 | IONVRAMV3Handler::getCommonUsed(void) const |
1297 | { |
1298 | return _commonUsed; |
1299 | } |
1300 | |
1301 | bool |
1302 | IONVRAMV3Handler::getSystemPartitionActive(void) const |
1303 | { |
1304 | return _systemSize != 0; |
1305 | } |
1306 | |
1307 | bool |
1308 | IONVRAMV3Handler::convertObjectToProp(uint8_t *buffer, uint32_t *length, |
1309 | const char *propName, OSObject *propObject) |
1310 | { |
1311 | uint32_t offset; |
1312 | IONVRAMVariableType propType; |
1313 | OSBoolean *tmpBoolean = nullptr; |
1314 | OSNumber *tmpNumber = nullptr; |
1315 | OSString *tmpString = nullptr; |
1316 | OSData *tmpData = nullptr; |
1317 | |
1318 | propType = getVariableType(propName); |
1319 | |
1320 | // Get the size of the data. |
1321 | offset = 0; |
1322 | switch (propType) { |
1323 | case kOFVariableTypeBoolean: |
1324 | tmpBoolean = OSDynamicCast(OSBoolean, propObject); |
1325 | if (tmpBoolean != nullptr) { |
1326 | const char *bool_buf; |
1327 | if (tmpBoolean->getValue()) { |
1328 | bool_buf = "true" ; |
1329 | } else { |
1330 | bool_buf = "false" ; |
1331 | } |
1332 | |
1333 | offset = (uint32_t)strlen(s: bool_buf); |
1334 | |
1335 | if (buffer) { |
1336 | if (*length < offset) { |
1337 | return false; |
1338 | } else { |
1339 | memcpy(dst: buffer, src: bool_buf, n: offset); |
1340 | } |
1341 | } |
1342 | } |
1343 | break; |
1344 | |
1345 | case kOFVariableTypeNumber: |
1346 | tmpNumber = OSDynamicCast(OSNumber, propObject); |
1347 | if (tmpNumber != nullptr) { |
1348 | char num_buf[12]; |
1349 | char *end_buf = num_buf; |
1350 | uint32_t tmpValue = tmpNumber->unsigned32BitValue(); |
1351 | if (tmpValue == 0xFFFFFFFF) { |
1352 | end_buf += snprintf(end_buf, count: sizeof(num_buf), "-1" ); |
1353 | } else if (tmpValue < 1000) { |
1354 | end_buf += snprintf(end_buf, count: sizeof(num_buf), "%d" , (uint32_t)tmpValue); |
1355 | } else { |
1356 | end_buf += snprintf(end_buf, count: sizeof(num_buf), "%#x" , (uint32_t)tmpValue); |
1357 | } |
1358 | |
1359 | offset = (uint32_t)(end_buf - num_buf); |
1360 | if (buffer) { |
1361 | if (*length < offset) { |
1362 | return false; |
1363 | } else { |
1364 | memcpy(dst: buffer, src: num_buf, n: offset); |
1365 | } |
1366 | } |
1367 | } |
1368 | break; |
1369 | |
1370 | case kOFVariableTypeString: |
1371 | tmpString = OSDynamicCast(OSString, propObject); |
1372 | if (tmpString != nullptr) { |
1373 | offset = tmpString->getLength(); |
1374 | |
1375 | if (buffer) { |
1376 | if (*length < offset) { |
1377 | return false; |
1378 | } else { |
1379 | bcopy(src: tmpString->getCStringNoCopy(), dst: buffer, n: offset); |
1380 | } |
1381 | } |
1382 | } |
1383 | break; |
1384 | |
1385 | case kOFVariableTypeData: |
1386 | tmpData = OSDynamicCast(OSData, propObject); |
1387 | if (tmpData != nullptr) { |
1388 | offset = tmpData->getLength(); |
1389 | |
1390 | if (buffer) { |
1391 | if (*length < offset) { |
1392 | return false; |
1393 | } else { |
1394 | bcopy(src: tmpData->getBytesNoCopy(), dst: buffer, n: offset); |
1395 | } |
1396 | } |
1397 | } |
1398 | break; |
1399 | |
1400 | default: |
1401 | return false; |
1402 | } |
1403 | |
1404 | *length = offset; |
1405 | |
1406 | return offset != 0; |
1407 | } |
1408 | |
1409 | |
1410 | bool |
1411 | IONVRAMV3Handler::convertPropToObject(const uint8_t *propName, uint32_t propNameLength, |
1412 | const uint8_t *propData, uint32_t propDataLength, |
1413 | OSSharedPtr<const OSSymbol>& propSymbol, |
1414 | OSSharedPtr<OSObject>& propObject) |
1415 | { |
1416 | OSSharedPtr<const OSSymbol> tmpSymbol; |
1417 | OSSharedPtr<OSNumber> tmpNumber; |
1418 | OSSharedPtr<OSString> tmpString; |
1419 | OSSharedPtr<OSObject> tmpObject = nullptr; |
1420 | |
1421 | tmpSymbol = OSSymbol::withCString(cString: (const char *)propName); |
1422 | |
1423 | if (tmpSymbol == nullptr) { |
1424 | return false; |
1425 | } |
1426 | |
1427 | switch (getVariableType(propSymbol: tmpSymbol.get())) { |
1428 | case kOFVariableTypeBoolean: |
1429 | if (!strncmp(s1: "true" , s2: (const char *)propData, n: propDataLength)) { |
1430 | tmpObject.reset(p: kOSBooleanTrue, OSRetain); |
1431 | } else if (!strncmp(s1: "false" , s2: (const char *)propData, n: propDataLength)) { |
1432 | tmpObject.reset(p: kOSBooleanFalse, OSRetain); |
1433 | } |
1434 | break; |
1435 | |
1436 | case kOFVariableTypeNumber: |
1437 | tmpNumber = OSNumber::withNumber(value: strtol((const char *)propData, nullptr, 0), numberOfBits: 32); |
1438 | if (tmpNumber != nullptr) { |
1439 | tmpObject = tmpNumber; |
1440 | } |
1441 | break; |
1442 | |
1443 | case kOFVariableTypeString: |
1444 | tmpString = OSString::withCString(cString: (const char *)propData, length: propDataLength); |
1445 | if (tmpString != nullptr) { |
1446 | tmpObject = tmpString; |
1447 | } |
1448 | break; |
1449 | |
1450 | case kOFVariableTypeData: |
1451 | tmpObject = OSData::withBytes(bytes: propData, numBytes: propDataLength); |
1452 | break; |
1453 | |
1454 | default: |
1455 | break; |
1456 | } |
1457 | |
1458 | if (tmpObject == nullptr) { |
1459 | tmpSymbol.reset(); |
1460 | return false; |
1461 | } |
1462 | |
1463 | propSymbol = tmpSymbol; |
1464 | propObject = tmpObject; |
1465 | |
1466 | return true; |
1467 | } |
1468 | |