banan-os/kernel/kernel/ACPI/AML/OpRegion.cpp

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 {};
}
}