dingusppc/devices/common/ofnvram.cpp
2022-04-13 23:27:53 +02:00

276 lines
8.0 KiB
C++

/*
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 <https://www.gnu.org/licenses/>.
*/
/** @file Utilities for working with the Apple OpenFirmware NVRAM partition. */
#include <devices/common/ofnvram.h>
#include <endianswap.h>
#include <loguru.hpp>
#include <machines/machinebase.h>
#include <memaccess.h>
#include <cinttypes>
#include <iomanip>
#include <iostream>
#include <map>
#include <string>
#include <tuple>
using namespace std;
void OfNvramUtils::init()
{
this->nvram_obj = dynamic_cast<NVram*>(gMachineObj->get_comp_by_name("NVRAM"));
}
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<string, std::tuple<int, uint16_t>> 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 OpenFirmware 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 OpenFirmware 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 stanard 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
}
}
}