/* DingusPPC - The Experimental PowerPC Macintosh emulator Copyright (C) 2018-22 divingkatae and maximum (theweirdo) spatium (Contact divingkatae#1017 or powermax#2286 on Discord for more info) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /** @file Utilities for working with the Apple Open Firmware NVRAM partition. */ #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; int OfNvramUtils::init() { this->nvram_obj = dynamic_cast(gMachineObj->get_comp_by_name("NVRAM")); return this->nvram_obj == nullptr; } bool OfNvramUtils::validate() { int i; OfNvramHdr hdr; if (this->nvram_obj == nullptr) return false; // read OF partition header for (i = 0; i < sizeof(OfNvramHdr); i++) { ((uint8_t*)&hdr)[i] = this->nvram_obj->read_byte(OF_NVRAM_OFFSET + i); } // validate partition signature and version if (BYTESWAP_16(hdr.sig) != OF_NVRAM_SIG || hdr.version != 5) return false; this->size = hdr.num_pages * 256; if (this->size != 0x800) return false; // read the entire partition into the local buffer for (i = 0; i < this->size; i++) { this->buf[i] = this->nvram_obj->read_byte(OF_NVRAM_OFFSET + i); } // verify partition checksum if (this->checksum_partition() ^ 0xFFFFU) return false; return true; } uint16_t OfNvramUtils::checksum_partition() { uint32_t acc = 0; for (int i = 0; i < this->size; i += 2) { acc += READ_WORD_BE_A(&this->buf[i]); } return acc + (acc >> 16); } void OfNvramUtils::update_partition() { // set checksum in the header to zero this->buf[4] = 0; this->buf[5] = 0; // calculate new checksum uint16_t checksum = this->checksum_partition(); checksum = checksum ? ~checksum : 0xFFFFU; // stuff it into the header WRITE_WORD_BE_A(&this->buf[4], checksum); // write the entire partition back to NVRAM for (int i = 0; i < this->size; i++) { this->nvram_obj->write_byte(OF_NVRAM_OFFSET + i, this->buf[i]); } } static const string flag_names[8] = { "little-endian?", "real-mode?", "auto-boot?", "diag-switch?", "fcode-debug?", "oem-banner?", "oem-logo?", "use-nvramrc?" }; static const map> of_vars = { // name, type, offset {"real-base", {OF_VAR_TYPE_INT, 0x10}}, {"real-size", {OF_VAR_TYPE_INT, 0x14}}, {"virt-base", {OF_VAR_TYPE_INT, 0x18}}, {"virt-size", {OF_VAR_TYPE_INT, 0x1C}}, {"load-base", {OF_VAR_TYPE_INT, 0x20}}, {"pci-probe-list", {OF_VAR_TYPE_INT, 0x24}}, {"screen-#columns", {OF_VAR_TYPE_INT, 0x28}}, {"screen-#rows", {OF_VAR_TYPE_INT, 0x2C}}, {"selftest-#megs", {OF_VAR_TYPE_INT, 0x30}}, {"boot-device", {OF_VAR_TYPE_STR, 0x34}}, {"boot-file", {OF_VAR_TYPE_STR, 0x38}}, {"diag-device", {OF_VAR_TYPE_STR, 0x3C}}, {"diag-file", {OF_VAR_TYPE_STR, 0x40}}, {"input-device", {OF_VAR_TYPE_STR, 0x44}}, {"output-device", {OF_VAR_TYPE_STR, 0x48}}, {"oem-banner", {OF_VAR_TYPE_STR, 0x4C}}, {"oem-logo", {OF_VAR_TYPE_STR, 0x50}}, {"nvramrc", {OF_VAR_TYPE_STR, 0x54}}, {"boot-command", {OF_VAR_TYPE_STR, 0x58}}, }; void OfNvramUtils::printenv() { int i; if (!this->validate()) { cout << "Invalid Open Firmware partition content!" << endl; return; } uint8_t of_flags = this->buf[12]; cout << endl; // print flags for (i = 0; i < 8; i++) { cout << setw(20) << left << flag_names[i] << (((of_flags << i) & 0x80) ? "true" : "false") << endl; } // print the remaining variables for (auto& var : of_vars) { auto type = std::get<0>(var.second); auto offset = std::get<1>(var.second); cout << setw(20) << left << var.first; switch (type) { case OF_VAR_TYPE_INT: cout << hex << READ_DWORD_BE_A(&this->buf[offset]) << endl; break; case OF_VAR_TYPE_STR: uint16_t str_offset = READ_WORD_BE_A(&this->buf[offset]) - OF_NVRAM_OFFSET; uint16_t str_len = READ_WORD_BE_A(&this->buf[offset+2]); if (str_len) { char value[32] = ""; std::memcpy(value, (char *)&(this->buf[str_offset]), str_len); cout << value; } cout << endl; } } cout << endl; } void OfNvramUtils::setenv(string var_name, string value) { int i, flag; if (!this->validate()) { cout << "Invalid Open Firmware partition content!" << endl; return; } // check if the user tries to change a flag for (i = 0; i < 8; i++) { if (var_name == flag_names[i]) { if (value == "true") flag = 1; else if (value == "false") flag = 0; else { cout << "Invalid property value: " << value << endl; return; } uint8_t flag_bit = 0x80U >> i; uint8_t of_flags = this->buf[12]; if (flag) of_flags |= flag_bit; else of_flags &= ~flag_bit; this->buf[12] = of_flags; this->update_partition(); cout << " ok" << endl; // mimic Forth return; } } // see if one of the standard properties should be changed if (of_vars.find(var_name) == of_vars.end()) { cout << "Attempt to change unknown variable " << var_name << endl; return; } auto type = std::get<0>(of_vars.at(var_name)); auto offset = std::get<1>(of_vars.at(var_name)); if (type == OF_VAR_TYPE_INT) { cout << "Changing of integer variables not supported yet" << endl; } else { uint16_t str_len = READ_WORD_BE_A(&this->buf[offset+2]); if (value.length() > str_len) { // we're going to allocate additional space // for the new string in the heap between here and top. // TODO: implement removal of dead strings and heap compaction OfNvramHdr *hdr = (OfNvramHdr *)&this->buf[0]; uint16_t here = READ_WORD_BE_A(&hdr->here); uint16_t top = READ_WORD_BE_A(&hdr->top); // check if there is enough space in the heap for the new string if ((top - value.length()) < here) { cout << "No room in the heap!" << endl; return; } // allocate required space by lowering top top -= value.length(); i = 0; // copy new string into NVRAM buffer char by char for(char& ch : value) { this->buf[top + i - OF_NVRAM_OFFSET] = ch; i++; } // stuff new values into the variable state WRITE_WORD_BE_A(&this->buf[offset+0], top); WRITE_WORD_BE_A(&this->buf[offset+2], value.length()); // update partition header WRITE_WORD_BE_A(&hdr->top, top); // update physical NVRAM this->update_partition(); cout << " ok" << endl; // mimic Forth return; } else { // TODO: replace existing string } } }