807 lines
26 KiB
C++
807 lines
26 KiB
C++
#include <kernel/ACPI/AML/Bytes.h>
|
|
#include <kernel/ACPI/AML/Namespace.h>
|
|
#include <kernel/ACPI/AML/OpRegion.h>
|
|
#include <kernel/IO.h>
|
|
#include <kernel/Memory/PageTable.h>
|
|
#include <kernel/PCI.h>
|
|
|
|
namespace Kernel::ACPI::AML
|
|
{
|
|
|
|
static BAN::ErrorOr<size_t> parse_pkg_length(BAN::ConstByteSpan& aml_data)
|
|
{
|
|
if (aml_data.empty())
|
|
return BAN::Error::from_errno(ENODATA);
|
|
|
|
const uint32_t encoding_length = (aml_data[0] >> 6) + 1;
|
|
if (aml_data.size() < encoding_length)
|
|
return BAN::Error::from_errno(ENODATA);
|
|
|
|
uint32_t pkg_length = 0;
|
|
switch (encoding_length)
|
|
{
|
|
case 1:
|
|
pkg_length |= aml_data[0] & 0x3F;
|
|
break;
|
|
case 2:
|
|
pkg_length |= aml_data[0] & 0x0F;
|
|
pkg_length |= aml_data[1] << 4;
|
|
break;
|
|
case 3:
|
|
pkg_length |= aml_data[0] & 0x0F;
|
|
pkg_length |= aml_data[1] << 4;
|
|
pkg_length |= aml_data[2] << 12;
|
|
break;
|
|
case 4:
|
|
pkg_length |= aml_data[0] & 0x0F;
|
|
pkg_length |= aml_data[1] << 4;
|
|
pkg_length |= aml_data[2] << 12;
|
|
pkg_length |= aml_data[3] << 20;
|
|
break;
|
|
}
|
|
|
|
aml_data = aml_data.slice(encoding_length);
|
|
return pkg_length;
|
|
}
|
|
|
|
BAN::ErrorOr<void> parse_opregion_op(ParseContext& context)
|
|
{
|
|
dprintln_if(AML_DUMP_FUNCTION_CALLS, "parse_opregion_op");
|
|
|
|
ASSERT(context.aml_data.size() >= 2);
|
|
ASSERT(static_cast<AML::Byte>(context.aml_data[0]) == AML::Byte::ExtOpPrefix);
|
|
ASSERT(static_cast<AML::ExtOp>(context.aml_data[1]) == AML::ExtOp::OpRegionOp);
|
|
context.aml_data = context.aml_data.slice(2);
|
|
|
|
auto region_name = TRY(parse_name_string(context.aml_data));
|
|
|
|
if (context.aml_data.empty())
|
|
return BAN::Error::from_errno(ENODATA);
|
|
auto region_space = context.aml_data[0];
|
|
context.aml_data = context.aml_data.slice(1);
|
|
|
|
switch (static_cast<GAS::AddressSpaceID>(region_space))
|
|
{
|
|
case GAS::AddressSpaceID::SystemMemory:
|
|
case GAS::AddressSpaceID::SystemIO:
|
|
case GAS::AddressSpaceID::PCIConfig:
|
|
case GAS::AddressSpaceID::EmbeddedController:
|
|
case GAS::AddressSpaceID::SMBus:
|
|
case GAS::AddressSpaceID::SystemCMOS:
|
|
case GAS::AddressSpaceID::PCIBarTarget:
|
|
case GAS::AddressSpaceID::IPMI:
|
|
case GAS::AddressSpaceID::GeneralPurposeIO:
|
|
case GAS::AddressSpaceID::GenericSerialBus:
|
|
case GAS::AddressSpaceID::PlatformCommunicationChannel:
|
|
break;
|
|
default:
|
|
if (region_space < 0x80)
|
|
{
|
|
dwarnln("OpRegion invalid region space {2H}", region_space);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
break;
|
|
}
|
|
|
|
auto region_offset = TRY(convert_node(TRY(parse_node(context)), ConvInteger, sizeof(uint64_t)));
|
|
auto region_length = TRY(convert_node(TRY(parse_node(context)), ConvInteger, sizeof(uint64_t)));
|
|
|
|
Node opregion;
|
|
opregion.type = Node::Type::OpRegion;
|
|
opregion.as.opregion.address_space = static_cast<GAS::AddressSpaceID>(region_space);
|
|
opregion.as.opregion.offset = region_offset.as.integer.value;
|
|
opregion.as.opregion.length = region_length.as.integer.value;
|
|
|
|
TRY(Namespace::root_namespace().add_named_object(context.scope, region_name, BAN::move(opregion)));
|
|
|
|
return {};
|
|
}
|
|
|
|
template<typename F>
|
|
static BAN::ErrorOr<void> parse_field_list(const Scope& scope, BAN::ConstByteSpan field_list_pkg, const F& create_element, uint8_t field_flags)
|
|
{
|
|
uint64_t offset = 0;
|
|
while (!field_list_pkg.empty())
|
|
{
|
|
switch (field_list_pkg[0])
|
|
{
|
|
case 0x00:
|
|
field_list_pkg = field_list_pkg.slice(1);
|
|
offset += TRY(parse_pkg_length(field_list_pkg));
|
|
break;
|
|
case 0x01:
|
|
// FIXME: do something with
|
|
if (field_list_pkg.size() < 3)
|
|
return BAN::Error::from_errno(ENODATA);
|
|
field_flags &= 0xF0;
|
|
field_flags |= field_list_pkg[1] & 0x0F;
|
|
field_list_pkg = field_list_pkg.slice(3);
|
|
break;
|
|
case 0x02:
|
|
dwarnln("TODO: connect field");
|
|
return BAN::Error::from_errno(ENOTSUP);
|
|
case 0x03:
|
|
dwarnln("TODO: extended access field");
|
|
return BAN::Error::from_errno(ENOTSUP);
|
|
default:
|
|
{
|
|
if (field_list_pkg.size() < 4)
|
|
return BAN::Error::from_errno(ENODATA);
|
|
if (!is_lead_name_char(field_list_pkg[0]) || !is_name_char(field_list_pkg[1]) || !is_name_char(field_list_pkg[2]) || !is_name_char(field_list_pkg[3]))
|
|
{
|
|
dwarnln("Invalid NameSeg {2H}, {2H}, {2H}, {2H}",
|
|
field_list_pkg[0], field_list_pkg[1], field_list_pkg[2], field_list_pkg[3]
|
|
);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
const uint32_t name_seg = field_list_pkg.as<const uint32_t>();
|
|
field_list_pkg = field_list_pkg.slice(4);
|
|
const auto field_length = TRY(parse_pkg_length(field_list_pkg));
|
|
|
|
NameString field_name;
|
|
field_name.base = 0;
|
|
TRY(field_name.parts.push_back(name_seg));
|
|
|
|
Node field_node = create_element(offset, field_length, field_flags);
|
|
|
|
TRY(Namespace::root_namespace().add_named_object(scope, field_name, BAN::move(field_node)));
|
|
|
|
offset += field_length;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
BAN::ErrorOr<void> parse_field_op(ParseContext& context)
|
|
{
|
|
dprintln_if(AML_DUMP_FUNCTION_CALLS, "parse_field_op");
|
|
|
|
ASSERT(context.aml_data.size() >= 2);
|
|
ASSERT(static_cast<AML::Byte>(context.aml_data[0]) == AML::Byte::ExtOpPrefix);
|
|
ASSERT(static_cast<AML::ExtOp>(context.aml_data[1]) == AML::ExtOp::FieldOp);
|
|
context.aml_data = context.aml_data.slice(2);
|
|
|
|
auto field_pkg = TRY(parse_pkg(context.aml_data));
|
|
auto opregion_name = TRY(parse_name_string(field_pkg));
|
|
if (field_pkg.empty())
|
|
return BAN::Error::from_errno(ENODATA);
|
|
auto default_flags = field_pkg[0];
|
|
field_pkg = field_pkg.slice(1);
|
|
|
|
auto [_, opregion] = TRY(Namespace::root_namespace().find_named_object(context.scope, opregion_name));
|
|
if (opregion == nullptr)
|
|
{
|
|
dwarnln("could not find '{}'.'{}'", context.scope, opregion_name);
|
|
return BAN::Error::from_errno(ENOENT);
|
|
}
|
|
if (opregion->node.type != Node::Type::OpRegion)
|
|
{
|
|
dwarnln("Field source is {}", opregion->node);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
|
|
const auto create_element =
|
|
[&](uint64_t offset, uint64_t length, uint8_t field_flags) -> Node
|
|
{
|
|
Node field_node {};
|
|
field_node.type = Node::Type::FieldUnit;
|
|
field_node.as.field_unit.type = FieldUnit::Type::Field;
|
|
field_node.as.field_unit.as.field.opregion = opregion->node.as.opregion;
|
|
field_node.as.field_unit.length = length;
|
|
field_node.as.field_unit.offset = offset;
|
|
field_node.as.field_unit.flags = field_flags;
|
|
return field_node;
|
|
};
|
|
|
|
TRY(parse_field_list(context.scope, field_pkg, create_element, default_flags));
|
|
|
|
return {};
|
|
}
|
|
|
|
BAN::ErrorOr<void> parse_index_field_op(ParseContext& context)
|
|
{
|
|
dprintln_if(AML_DUMP_FUNCTION_CALLS, "parse_index_field_op");
|
|
|
|
ASSERT(context.aml_data.size() >= 2);
|
|
ASSERT(static_cast<AML::Byte>(context.aml_data[0]) == AML::Byte::ExtOpPrefix);
|
|
ASSERT(static_cast<AML::ExtOp>(context.aml_data[1]) == AML::ExtOp::IndexFieldOp);
|
|
context.aml_data = context.aml_data.slice(2);
|
|
|
|
auto field_pkg = TRY(parse_pkg(context.aml_data));
|
|
|
|
auto index_name = TRY(parse_name_string(field_pkg));
|
|
auto data_name = TRY(parse_name_string(field_pkg));
|
|
if (field_pkg.empty())
|
|
return BAN::Error::from_errno(ENODATA);
|
|
auto default_flags = field_pkg[0];
|
|
field_pkg = field_pkg.slice(1);
|
|
|
|
auto [_1, index_obj] = TRY(Namespace::root_namespace().find_named_object(context.scope, index_name));
|
|
if (index_obj == nullptr)
|
|
{
|
|
dwarnln("could not find '{}'.'{}'", context.scope, index_name);
|
|
return BAN::Error::from_errno(ENOENT);
|
|
}
|
|
if (index_obj->node.type != Node::Type::FieldUnit)
|
|
{
|
|
dwarnln("IndexField source is {}", index_obj->node);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
|
|
auto [_2, data_obj] = TRY(Namespace::root_namespace().find_named_object(context.scope, data_name));
|
|
if (data_obj == nullptr)
|
|
{
|
|
dwarnln("could not find '{}'.'{}'", context.scope, data_name);
|
|
return BAN::Error::from_errno(ENOENT);
|
|
}
|
|
if (data_obj->node.type != Node::Type::FieldUnit)
|
|
{
|
|
dwarnln("IndexField source is {}", data_obj->node);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
|
|
const auto create_element =
|
|
[&](uint64_t offset, uint64_t length, uint8_t field_flags) -> Node
|
|
{
|
|
Node field_node {};
|
|
field_node.type = Node::Type::FieldUnit;
|
|
field_node.as.field_unit.type = FieldUnit::Type::IndexField;
|
|
field_node.as.field_unit.as.index_field.index = index_obj;
|
|
field_node.as.field_unit.as.index_field.index->ref_count++;
|
|
field_node.as.field_unit.as.index_field.data = data_obj;
|
|
field_node.as.field_unit.as.index_field.data->ref_count++;
|
|
field_node.as.field_unit.length = length;
|
|
field_node.as.field_unit.offset = offset;
|
|
field_node.as.field_unit.flags = field_flags;
|
|
return field_node;
|
|
};
|
|
|
|
TRY(parse_field_list(context.scope, field_pkg, create_element, default_flags));
|
|
|
|
return {};
|
|
}
|
|
|
|
BAN::ErrorOr<void> parse_bank_field_op(ParseContext& context)
|
|
{
|
|
dprintln_if(AML_DUMP_FUNCTION_CALLS, "parse_bank_field_op");
|
|
|
|
ASSERT(context.aml_data.size() >= 2);
|
|
ASSERT(static_cast<AML::Byte>(context.aml_data[0]) == AML::Byte::ExtOpPrefix);
|
|
ASSERT(static_cast<AML::ExtOp>(context.aml_data[1]) == AML::ExtOp::BankFieldOp);
|
|
context.aml_data = context.aml_data.slice(2);
|
|
|
|
auto field_pkg = TRY(parse_pkg(context.aml_data));
|
|
auto opregion_name = TRY(parse_name_string(field_pkg));
|
|
auto bank_selector_name = TRY(parse_name_string(field_pkg));
|
|
|
|
auto temp_aml_data = context.aml_data;
|
|
context.aml_data = field_pkg;
|
|
auto bank_value_node = TRY(parse_node(context));
|
|
field_pkg = context.aml_data;
|
|
context.aml_data = temp_aml_data;
|
|
|
|
if (field_pkg.empty())
|
|
return BAN::Error::from_errno(ENODATA);
|
|
auto default_flags = field_pkg[0];
|
|
field_pkg = field_pkg.slice(1);
|
|
|
|
auto [_1, opregion] = TRY(Namespace::root_namespace().find_named_object(context.scope, opregion_name));
|
|
if (opregion == nullptr)
|
|
{
|
|
dwarnln("could not find '{}'.'{}'", context.scope, opregion_name);
|
|
return BAN::Error::from_errno(ENOENT);
|
|
}
|
|
if (opregion->node.type != Node::Type::OpRegion)
|
|
{
|
|
dwarnln("Field source is {}", opregion->node);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
|
|
auto [_2, bank_selector] = TRY(Namespace::root_namespace().find_named_object(context.scope, bank_selector_name));
|
|
if (bank_selector == nullptr)
|
|
{
|
|
dwarnln("could not find '{}'.'{}'", context.scope, bank_selector_name);
|
|
return BAN::Error::from_errno(ENOENT);
|
|
}
|
|
if (bank_selector->node.type != Node::Type::FieldUnit)
|
|
{
|
|
dwarnln("BankField bank selector is {}", bank_selector->node);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
|
|
const uint64_t bank_value = TRY(convert_node(BAN::move(bank_value_node), ConvInteger, sizeof(uint64_t))).as.integer.value;
|
|
|
|
const auto create_element =
|
|
[&](uint64_t offset, uint64_t length, uint8_t field_flags) -> Node
|
|
{
|
|
Node field_node {};
|
|
field_node.type = Node::Type::FieldUnit;
|
|
field_node.as.field_unit.type = FieldUnit::Type::BankField;
|
|
field_node.as.field_unit.as.bank_field.opregion = opregion->node.as.opregion;
|
|
field_node.as.field_unit.as.bank_field.bank_selector = bank_selector;
|
|
field_node.as.field_unit.as.bank_field.bank_selector->ref_count++;
|
|
field_node.as.field_unit.as.bank_field.bank_value = bank_value;
|
|
field_node.as.field_unit.length = length;
|
|
field_node.as.field_unit.offset = offset;
|
|
field_node.as.field_unit.flags = field_flags;
|
|
return field_node;
|
|
};
|
|
|
|
TRY(parse_field_list(context.scope, field_pkg, create_element, default_flags));
|
|
|
|
return {};
|
|
}
|
|
|
|
struct AccessRule
|
|
{
|
|
uint8_t access_bits;
|
|
bool lock;
|
|
enum {
|
|
Preserve,
|
|
WriteZeros,
|
|
WriteOnes,
|
|
} update_rule;
|
|
};
|
|
|
|
static AccessRule parse_access_rule(uint8_t flags)
|
|
{
|
|
AccessRule rule;
|
|
|
|
switch (flags & 0x0F)
|
|
{
|
|
case 0: rule.access_bits = 8; break;
|
|
case 1: rule.access_bits = 8; break;
|
|
case 2: rule.access_bits = 16; break;
|
|
case 3: rule.access_bits = 32; break;
|
|
case 4: rule.access_bits = 64; break;
|
|
case 5: rule.access_bits = 8; break;
|
|
default: ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
rule.lock = !!(flags & 0x10);
|
|
|
|
switch ((flags >> 5) & 0x03)
|
|
{
|
|
case 0: rule.update_rule = AccessRule::Preserve; break;
|
|
case 1: rule.update_rule = AccessRule::WriteOnes; break;
|
|
case 2: rule.update_rule = AccessRule::WriteZeros; break;
|
|
default: ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
return rule;
|
|
}
|
|
|
|
static BAN::ErrorOr<uint64_t> perform_opregion_read(const OpRegion& opregion, uint8_t access_size, uint64_t offset)
|
|
{
|
|
ASSERT(offset % access_size == 0);
|
|
|
|
const uint64_t byte_offset = opregion.offset + offset;
|
|
|
|
switch (opregion.address_space)
|
|
{
|
|
case GAS::AddressSpaceID::SystemMemory:
|
|
{
|
|
uint64_t result;
|
|
PageTable::with_fast_page(byte_offset & PAGE_ADDR_MASK, [&]() {
|
|
void* addr = PageTable::fast_page_as_ptr(byte_offset % PAGE_SIZE);
|
|
switch (access_size) {
|
|
case 1: result = *static_cast<uint8_t* >(addr); break;
|
|
case 2: result = *static_cast<uint16_t*>(addr); break;
|
|
case 4: result = *static_cast<uint32_t*>(addr); break;
|
|
case 8: result = *static_cast<uint64_t*>(addr); break;
|
|
default: ASSERT_NOT_REACHED();
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
case GAS::AddressSpaceID::SystemIO:
|
|
if (byte_offset + access_size > 0x10000)
|
|
{
|
|
dwarnln("{} byte read from IO port 0x{H}", access_size, byte_offset);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
switch (access_size)
|
|
{
|
|
case 1: return IO::inb(byte_offset);
|
|
case 2: return IO::inw(byte_offset);
|
|
case 4: return IO::inl(byte_offset);
|
|
default:
|
|
dwarnln("{} byte read from IO port", access_size);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
ASSERT_NOT_REACHED();
|
|
case GAS::AddressSpaceID::PCIConfig:
|
|
{
|
|
// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#address-space-format
|
|
// PCI configuration space is confined to segment 0, bus 0
|
|
|
|
const uint16_t device = (byte_offset >> 32) & 0xFFFF;
|
|
const uint16_t function = (byte_offset >> 16) & 0xFFFF;
|
|
const uint16_t offset = byte_offset & 0xFFFF;
|
|
switch (access_size)
|
|
{
|
|
case 1: return PCI::PCIManager::get().read_config_byte (0, device, function, offset);
|
|
case 2: return PCI::PCIManager::get().read_config_word (0, device, function, offset);
|
|
case 4: return PCI::PCIManager::get().read_config_dword(0, device, function, offset);
|
|
default:
|
|
dwarnln("{} byte read from PCI {2H}:{2H}:{2H}", device, function, offset);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
case GAS::AddressSpaceID::EmbeddedController:
|
|
case GAS::AddressSpaceID::SMBus:
|
|
case GAS::AddressSpaceID::SystemCMOS:
|
|
case GAS::AddressSpaceID::PCIBarTarget:
|
|
case GAS::AddressSpaceID::IPMI:
|
|
case GAS::AddressSpaceID::GeneralPurposeIO:
|
|
case GAS::AddressSpaceID::GenericSerialBus:
|
|
case GAS::AddressSpaceID::PlatformCommunicationChannel:
|
|
dwarnln("TODO: Read from address space {}", static_cast<uint8_t>(opregion.address_space));
|
|
return BAN::Error::from_errno(ENOTSUP);
|
|
}
|
|
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
static BAN::ErrorOr<void> perform_opregion_write(const OpRegion& opregion, uint8_t access_size, uint64_t offset, uint64_t value)
|
|
{
|
|
ASSERT(offset % access_size == 0);
|
|
|
|
const uint64_t byte_offset = opregion.offset + offset;
|
|
|
|
switch (opregion.address_space)
|
|
{
|
|
case GAS::AddressSpaceID::SystemMemory:
|
|
PageTable::with_fast_page(byte_offset & PAGE_ADDR_MASK, [&]() {
|
|
void* addr = PageTable::fast_page_as_ptr(byte_offset % PAGE_SIZE);
|
|
switch (access_size) {
|
|
case 1: *static_cast<uint8_t* >(addr) = value; break;
|
|
case 2: *static_cast<uint16_t*>(addr) = value; break;
|
|
case 4: *static_cast<uint32_t*>(addr) = value; break;
|
|
case 8: *static_cast<uint64_t*>(addr) = value; break;
|
|
default: ASSERT_NOT_REACHED();
|
|
}
|
|
});
|
|
return {};
|
|
case GAS::AddressSpaceID::SystemIO:
|
|
if (byte_offset + access_size > 0x10000)
|
|
{
|
|
dwarnln("{} byte write to IO port 0x{H}", access_size, byte_offset);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
switch (access_size)
|
|
{
|
|
case 1: IO::outb(byte_offset, value); break;
|
|
case 2: IO::outw(byte_offset, value); break;
|
|
case 4: IO::outl(byte_offset, value); break;
|
|
default:
|
|
dwarnln("{} byte write to IO port", access_size);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
return {};
|
|
case GAS::AddressSpaceID::PCIConfig:
|
|
{
|
|
// https://uefi.org/htmlspecs/ACPI_Spec_6_4_html/05_ACPI_Software_Programming_Model/ACPI_Software_Programming_Model.html#address-space-format
|
|
// PCI configuration space is confined to segment 0, bus 0
|
|
|
|
const uint16_t device = (byte_offset >> 32) & 0xFFFF;
|
|
const uint16_t function = (byte_offset >> 16) & 0xFFFF;
|
|
const uint16_t offset = byte_offset & 0xFFFF;
|
|
switch (access_size)
|
|
{
|
|
case 1: PCI::PCIManager::get().write_config_byte (0, device, function, offset, value); break;
|
|
case 2: PCI::PCIManager::get().write_config_word (0, device, function, offset, value); break;
|
|
case 4: PCI::PCIManager::get().write_config_dword(0, device, function, offset, value); break;
|
|
default:
|
|
dwarnln("{} byte write to PCI {2H}:{2H}:{2H}", device, function, offset);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
return {};
|
|
}
|
|
case GAS::AddressSpaceID::EmbeddedController:
|
|
case GAS::AddressSpaceID::SMBus:
|
|
case GAS::AddressSpaceID::SystemCMOS:
|
|
case GAS::AddressSpaceID::PCIBarTarget:
|
|
case GAS::AddressSpaceID::IPMI:
|
|
case GAS::AddressSpaceID::GeneralPurposeIO:
|
|
case GAS::AddressSpaceID::GenericSerialBus:
|
|
case GAS::AddressSpaceID::PlatformCommunicationChannel:
|
|
dwarnln("TODO: Write to address space {}", static_cast<uint8_t>(opregion.address_space));
|
|
return BAN::Error::from_errno(ENOTSUP);
|
|
}
|
|
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
struct BufferInfo
|
|
{
|
|
const uint8_t* buffer;
|
|
const uint64_t bytes;
|
|
};
|
|
|
|
static BAN::ErrorOr<BufferInfo> extract_buffer_info(const Node& source)
|
|
{
|
|
switch (source.type)
|
|
{
|
|
case Node::Type::String:
|
|
case Node::Type::Buffer:
|
|
return BufferInfo {
|
|
.buffer = source.as.str_buf->bytes,
|
|
.bytes = source.as.str_buf->size,
|
|
};
|
|
case Node::Type::Integer:
|
|
return BufferInfo {
|
|
.buffer = reinterpret_cast<const uint8_t*>(&source.as.integer.value),
|
|
.bytes = sizeof(uint64_t),
|
|
};
|
|
default:
|
|
dwarnln("Invalid store of {} to FieldUnit", source);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
static BAN::ErrorOr<Node> allocate_destination(const Node& source, Node::Type type, size_t max_bytes)
|
|
{
|
|
ASSERT(source.type == Node::Type::FieldUnit);
|
|
|
|
const uint64_t source_bit_length = source.as.field_unit.length;
|
|
|
|
Node result;
|
|
result.type = type;
|
|
|
|
switch (type)
|
|
{
|
|
case Node::Type::Buffer:
|
|
{
|
|
const size_t dst_bits = BAN::Math::max<size_t>(max_bytes * 8, source_bit_length);
|
|
const size_t bytes = BAN::Math::div_round_up<size_t>(dst_bits, 8);
|
|
|
|
result.as.str_buf = static_cast<Buffer*>(kmalloc(sizeof(Buffer) + bytes));
|
|
if (result.as.str_buf == nullptr)
|
|
return BAN::Error::from_errno(ENOMEM);
|
|
result.as.str_buf->size = bytes;
|
|
result.as.str_buf->ref_count = 1;
|
|
memset(result.as.str_buf->bytes, 0, bytes);
|
|
|
|
break;
|
|
}
|
|
case Node::Type::Integer:
|
|
if (source_bit_length > 64)
|
|
{
|
|
dwarnln("Convert field unit of {} bits to an integer", source_bit_length);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
|
|
result.as.integer.value = 0;
|
|
|
|
break;
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static void write_bits_to_buffer(uint8_t* buffer, size_t bit_offset, uint64_t value, size_t bit_count)
|
|
{
|
|
size_t bits_done = 0;
|
|
while (bits_done < bit_count) {
|
|
const size_t acc_bit_offset = (bit_offset + bits_done) % 8;
|
|
const size_t acc_size = BAN::Math::min<size_t>(bit_count - bits_done, 8 - acc_bit_offset);
|
|
const size_t mask = (1 << acc_size) - 1;
|
|
|
|
buffer[(bit_offset + bits_done) / 8] |= ((value >> bits_done) & mask) << acc_bit_offset;
|
|
|
|
bits_done += acc_size;
|
|
}
|
|
}
|
|
|
|
static uint64_t read_bits_from_buffer(const uint8_t* buffer, size_t bit_offset, size_t bit_count)
|
|
{
|
|
uint64_t result = 0;
|
|
|
|
size_t bits_done = 0;
|
|
while (bits_done < bit_count) {
|
|
const size_t acc_bit_offset = (bit_offset + bits_done) % 8;
|
|
const size_t acc_size = BAN::Math::min<size_t>(bit_count - bits_done, 8 - acc_bit_offset);
|
|
const size_t mask = (1 << acc_size) - 1;
|
|
|
|
result |= static_cast<uint64_t>((buffer[(bit_offset + bits_done) / 8] >> acc_bit_offset) & mask) << bits_done;
|
|
|
|
bits_done += acc_size;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static BAN::ErrorOr<uint64_t> perform_index_field_read(const Node& source, uint64_t acc_byte_offset)
|
|
{
|
|
ASSERT(source.type == Node::Type::FieldUnit);
|
|
ASSERT(source.as.field_unit.type == FieldUnit::Type::IndexField);
|
|
|
|
Node index_node;
|
|
index_node.type = Node::Type::Integer;
|
|
index_node.as.integer.value = acc_byte_offset;
|
|
TRY(store_to_field_unit(index_node, source.as.field_unit.as.index_field.index->node));
|
|
|
|
auto value = TRY(convert_from_field_unit(source.as.field_unit.as.index_field.data->node, ConvInteger, sizeof(uint64_t)));
|
|
return value.as.integer.value;
|
|
}
|
|
|
|
static BAN::ErrorOr<void> perform_index_field_write(const Node& source, uint64_t acc_byte_offset, uint64_t value)
|
|
{
|
|
ASSERT(source.type == Node::Type::FieldUnit);
|
|
ASSERT(source.as.field_unit.type == FieldUnit::Type::IndexField);
|
|
|
|
Node index_node;
|
|
index_node.type = Node::Type::Integer;
|
|
index_node.as.integer.value = acc_byte_offset;
|
|
TRY(store_to_field_unit(index_node, source.as.field_unit.as.index_field.index->node));
|
|
|
|
Node data_node;
|
|
data_node.type = Node::Type::Integer;
|
|
data_node.as.integer.value = value;
|
|
TRY(store_to_field_unit(data_node, source.as.field_unit.as.index_field.data->node));
|
|
return {};
|
|
}
|
|
|
|
BAN::ErrorOr<Node> convert_from_field_unit(const Node& source, uint8_t conversion, size_t max_length)
|
|
{
|
|
ASSERT(source.type == Node::Type::FieldUnit);
|
|
|
|
const bool can_be_integer = source.as.field_unit.length <= 64;
|
|
|
|
auto target_type = Node::Type::Uninitialized;
|
|
if (can_be_integer && (conversion & Conversion::ConvInteger))
|
|
target_type = Node::Type::Integer;
|
|
else if (conversion & Conversion::ConvBuffer)
|
|
target_type = Node::Type::Buffer;
|
|
|
|
if (target_type == Node::Type::Uninitialized)
|
|
{
|
|
dwarnln("Invalid conversion from field unit to 0b{3b}", conversion);
|
|
return BAN::Error::from_errno(EINVAL);
|
|
}
|
|
|
|
if (source.as.field_unit.type == FieldUnit::Type::BankField)
|
|
{
|
|
Node bank_node;
|
|
bank_node.type = Node::Type::Integer;
|
|
bank_node.as.integer.value = source.as.field_unit.as.bank_field.bank_value;
|
|
TRY(store_to_field_unit(bank_node, source.as.field_unit.as.bank_field.bank_selector->node));
|
|
}
|
|
|
|
Node result = TRY(allocate_destination(source, target_type, max_length));
|
|
const auto [dst_buf, dst_bytes] = TRY(extract_buffer_info(result));
|
|
const auto [max_acc_bits, lock, _] = parse_access_rule(source.as.field_unit.flags);
|
|
|
|
const size_t transfer_bits = BAN::Math::min<size_t>(source.as.field_unit.length, dst_bytes * 8);
|
|
|
|
size_t bits_done = 0;
|
|
while (bits_done < transfer_bits)
|
|
{
|
|
const size_t acc_bit_offset = (source.as.field_unit.offset + bits_done) & (max_acc_bits - 1);
|
|
const size_t acc_bit_count = max_acc_bits - acc_bit_offset;
|
|
const size_t acc_byte_offset = ((source.as.field_unit.offset + bits_done) & ~(max_acc_bits - 1)) / 8;
|
|
|
|
uint64_t value = 0;
|
|
switch (source.as.field_unit.type)
|
|
{
|
|
case FieldUnit::Type::Field:
|
|
value = TRY(perform_opregion_read(source.as.field_unit.as.field.opregion, max_acc_bits / 8, acc_byte_offset));
|
|
break;
|
|
case FieldUnit::Type::IndexField:
|
|
value = TRY(perform_index_field_read(source, acc_byte_offset));
|
|
break;
|
|
case FieldUnit::Type::BankField:
|
|
value = TRY(perform_opregion_read(source.as.field_unit.as.bank_field.opregion, max_acc_bits / 8, acc_byte_offset));
|
|
break;
|
|
}
|
|
|
|
write_bits_to_buffer(const_cast<uint8_t*>(dst_buf), bits_done, value >> acc_bit_offset, acc_bit_count);
|
|
|
|
bits_done += acc_bit_count;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
BAN::ErrorOr<void> store_to_field_unit(const Node& source, const Node& target)
|
|
{
|
|
ASSERT(target.type == Node::Type::FieldUnit);
|
|
|
|
switch (source.type)
|
|
{
|
|
case Node::Type::Integer:
|
|
case Node::Type::Buffer:
|
|
case Node::Type::String:
|
|
break;
|
|
default:
|
|
return store_to_field_unit(
|
|
TRY(convert_node(TRY(source.copy()), ConvInteger | ConvBuffer | ConvString, 0xFFFFFFFFFFFFFFFF)),
|
|
target
|
|
);
|
|
}
|
|
|
|
if (target.as.field_unit.type == FieldUnit::Type::BankField)
|
|
{
|
|
Node bank_node;
|
|
bank_node.type = Node::Type::Integer;
|
|
bank_node.as.integer.value = target.as.field_unit.as.bank_field.bank_value;
|
|
TRY(store_to_field_unit(bank_node, target.as.field_unit.as.bank_field.bank_selector->node));
|
|
}
|
|
|
|
const auto [src_buf, src_bytes] = TRY(extract_buffer_info(source));
|
|
const auto [max_acc_bits, lock, update_rule] = parse_access_rule(target.as.field_unit.flags);
|
|
|
|
const size_t transfer_bits = BAN::Math::min<size_t>(target.as.field_unit.length, src_bytes * 8);
|
|
|
|
size_t bits_done = 0;
|
|
while (bits_done < transfer_bits)
|
|
{
|
|
const size_t acc_bit_offset = (target.as.field_unit.offset + bits_done) & (max_acc_bits - 1);
|
|
const size_t acc_bit_count = BAN::Math::min<size_t>(max_acc_bits - acc_bit_offset, transfer_bits - bits_done);
|
|
const size_t acc_byte_offset = ((target.as.field_unit.offset + bits_done) & ~(max_acc_bits - 1)) / 8;
|
|
|
|
uint64_t value;
|
|
switch (update_rule)
|
|
{
|
|
case AccessRule::Preserve:
|
|
switch (target.as.field_unit.type)
|
|
{
|
|
case FieldUnit::Type::Field:
|
|
value = TRY(perform_opregion_read(target.as.field_unit.as.field.opregion, max_acc_bits / 8, acc_byte_offset));
|
|
break;
|
|
case FieldUnit::Type::IndexField:
|
|
value = TRY(perform_index_field_read(target, acc_byte_offset));
|
|
break;
|
|
case FieldUnit::Type::BankField:
|
|
value = TRY(perform_opregion_read(target.as.field_unit.as.bank_field.opregion, max_acc_bits / 8, acc_byte_offset));
|
|
break;
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
break;
|
|
case AccessRule::WriteZeros:
|
|
value = 0x0000000000000000;
|
|
break;
|
|
case AccessRule::WriteOnes:
|
|
value = 0xFFFFFFFFFFFFFFFF;
|
|
break;
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
value &= ~(((static_cast<uint64_t>(1) << acc_bit_count) - 1) << acc_bit_offset);
|
|
value |= read_bits_from_buffer(src_buf, bits_done, acc_bit_count) << acc_bit_offset;
|
|
|
|
switch (target.as.field_unit.type)
|
|
{
|
|
case FieldUnit::Type::Field:
|
|
TRY(perform_opregion_write(target.as.field_unit.as.field.opregion, max_acc_bits / 8, acc_byte_offset, value));
|
|
break;
|
|
case FieldUnit::Type::IndexField:
|
|
TRY(perform_index_field_write(target, acc_byte_offset, value));
|
|
break;
|
|
case FieldUnit::Type::BankField:
|
|
TRY(perform_opregion_write(target.as.field_unit.as.bank_field.opregion, max_acc_bits / 8, acc_byte_offset, value));
|
|
break;
|
|
default:
|
|
ASSERT_NOT_REACHED();
|
|
}
|
|
|
|
bits_done += acc_bit_count;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
}
|