diff options
Diffstat (limited to 'sys/dev/acpi/uacpi/io.c')
-rw-r--r-- | sys/dev/acpi/uacpi/io.c | 1116 |
1 files changed, 1116 insertions, 0 deletions
diff --git a/sys/dev/acpi/uacpi/io.c b/sys/dev/acpi/uacpi/io.c new file mode 100644 index 0000000..7d10005 --- /dev/null +++ b/sys/dev/acpi/uacpi/io.c @@ -0,0 +1,1116 @@ +#include <uacpi/internal/io.h> +#include <uacpi/internal/stdlib.h> +#include <uacpi/internal/log.h> +#include <uacpi/internal/opregion.h> +#include <uacpi/internal/utilities.h> +#include <uacpi/internal/mutex.h> +#include <uacpi/internal/namespace.h> + +#ifndef UACPI_BAREBONES_MODE + +uacpi_size uacpi_round_up_bits_to_bytes(uacpi_size bit_length) +{ + return UACPI_ALIGN_UP(bit_length, 8, uacpi_size) / 8; +} + +static void cut_misaligned_tail( + uacpi_u8 *data, uacpi_size offset, uacpi_u32 bit_length +) +{ + uacpi_u8 remainder = bit_length & 7; + + if (remainder == 0) + return; + + data[offset] &= ((1ull << remainder) - 1); +} + +struct bit_span +{ + union { + uacpi_u8 *data; + const uacpi_u8 *const_data; + }; + uacpi_u64 index; + uacpi_u64 length; +}; + +static uacpi_size bit_span_offset(struct bit_span *span, uacpi_size bits) +{ + uacpi_size delta = UACPI_MIN(span->length, bits); + + span->index += delta; + span->length -= delta; + + return delta; +} + +static void bit_copy(struct bit_span *dst, struct bit_span *src) +{ + uacpi_u8 src_shift, dst_shift, bits = 0; + uacpi_u16 dst_mask; + uacpi_u8 *dst_ptr, *src_ptr; + uacpi_u64 dst_count, src_count; + + dst_ptr = dst->data + (dst->index / 8); + src_ptr = src->data + (src->index / 8); + + dst_count = dst->length; + dst_shift = dst->index & 7; + + src_count = src->length; + src_shift = src->index & 7; + + while (dst_count) + { + bits = 0; + + if (src_count) { + bits = *src_ptr >> src_shift; + + if (src_shift && src_count > (uacpi_u32)(8 - src_shift)) + bits |= *(src_ptr + 1) << (8 - src_shift); + + if (src_count < 8) { + bits &= (1 << src_count) - 1; + src_count = 0; + } else { + src_count -= 8; + src_ptr++; + } + } + + dst_mask = (dst_count < 8 ? (1 << dst_count) - 1 : 0xFF) << dst_shift; + *dst_ptr = (*dst_ptr & ~dst_mask) | ((bits << dst_shift) & dst_mask); + + if (dst_shift && dst_count > (uacpi_u32)(8 - dst_shift)) { + dst_mask >>= 8; + *(dst_ptr + 1) &= ~dst_mask; + *(dst_ptr + 1) |= (bits >> (8 - dst_shift)) & dst_mask; + } + + dst_count = dst_count > 8 ? dst_count - 8 : 0; + ++dst_ptr; + } +} + +static void do_misaligned_buffer_read( + const uacpi_buffer_field *field, uacpi_u8 *dst +) +{ + struct bit_span src_span = { 0 }; + struct bit_span dst_span = { 0 }; + + src_span.index = field->bit_index; + src_span.length = field->bit_length; + src_span.const_data = field->backing->data; + + dst_span.data = dst; + dst_span.length = uacpi_round_up_bits_to_bytes(field->bit_length) * 8; + bit_copy(&dst_span, &src_span); +} + +void uacpi_read_buffer_field( + const uacpi_buffer_field *field, void *dst +) +{ + if (!(field->bit_index & 7)) { + uacpi_u8 *src = field->backing->data; + uacpi_size count; + + count = uacpi_round_up_bits_to_bytes(field->bit_length); + uacpi_memcpy(dst, src + (field->bit_index / 8), count); + cut_misaligned_tail(dst, count - 1, field->bit_length); + return; + } + + do_misaligned_buffer_read(field, dst); +} + +static void do_write_misaligned_buffer_field( + uacpi_buffer_field *field, + const void *src, uacpi_size size +) +{ + struct bit_span src_span = { 0 }; + struct bit_span dst_span = { 0 }; + + src_span.length = size * 8; + src_span.const_data = src; + + dst_span.index = field->bit_index; + dst_span.length = field->bit_length; + dst_span.data = field->backing->data; + + bit_copy(&dst_span, &src_span); +} + +void uacpi_write_buffer_field( + uacpi_buffer_field *field, + const void *src, uacpi_size size +) +{ + if (!(field->bit_index & 7)) { + uacpi_u8 *dst, last_byte, tail_shift; + uacpi_size count; + + dst = field->backing->data; + dst += field->bit_index / 8; + count = uacpi_round_up_bits_to_bytes(field->bit_length); + + last_byte = dst[count - 1]; + tail_shift = field->bit_length & 7; + + uacpi_memcpy_zerout(dst, src, count, size); + if (tail_shift) { + uacpi_u8 last_shift = 8 - tail_shift; + dst[count - 1] = dst[count - 1] << last_shift; + dst[count - 1] >>= last_shift; + dst[count - 1] |= (last_byte >> tail_shift) << tail_shift; + } + + return; + } + + do_write_misaligned_buffer_field(field, src, size); +} + +static uacpi_status access_field_unit( + uacpi_field_unit *field, uacpi_u32 offset, uacpi_region_op op, + union uacpi_opregion_io_data data +) +{ + uacpi_status ret = UACPI_STATUS_OK; + + if (field->lock_rule) { + ret = uacpi_acquire_aml_mutex( + g_uacpi_rt_ctx.global_lock_mutex, 0xFFFF + ); + if (uacpi_unlikely_error(ret)) + return ret; + } + + switch (field->kind) { + case UACPI_FIELD_UNIT_KIND_BANK: + ret = uacpi_write_field_unit( + field->bank_selection, &field->bank_value, sizeof(field->bank_value), + UACPI_NULL + ); + break; + case UACPI_FIELD_UNIT_KIND_NORMAL: + break; + case UACPI_FIELD_UNIT_KIND_INDEX: + ret = uacpi_write_field_unit( + field->index, &offset, sizeof(offset), + UACPI_NULL + ); + if (uacpi_unlikely_error(ret)) + goto out; + + switch (op) { + case UACPI_REGION_OP_READ: + ret = uacpi_read_field_unit( + field->data, data.integer, field->access_width_bytes, + UACPI_NULL + ); + break; + case UACPI_REGION_OP_WRITE: + ret = uacpi_write_field_unit( + field->data, data.integer, field->access_width_bytes, + UACPI_NULL + ); + break; + default: + ret = UACPI_STATUS_INVALID_ARGUMENT; + break; + } + + goto out; + + default: + uacpi_error("invalid field unit kind %d\n", field->kind); + ret = UACPI_STATUS_INVALID_ARGUMENT; + } + + if (uacpi_unlikely_error(ret)) + goto out; + + ret = uacpi_dispatch_opregion_io(field, offset, op, data); + +out: + if (field->lock_rule) + uacpi_release_aml_mutex(g_uacpi_rt_ctx.global_lock_mutex); + return ret; +} + +#define SERIAL_HEADER_SIZE 2 +#define IPMI_DATA_SIZE 64 + +static uacpi_status wtr_buffer_size( + uacpi_field_unit *field, uacpi_address_space space, + uacpi_size *out_size +) +{ + switch (space) { + case UACPI_ADDRESS_SPACE_IPMI: + *out_size = SERIAL_HEADER_SIZE + IPMI_DATA_SIZE; + break; + case UACPI_ADDRESS_SPACE_PRM: + *out_size = 26; + break; + case UACPI_ADDRESS_SPACE_FFIXEDHW: + *out_size = 256; + break; + case UACPI_ADDRESS_SPACE_GENERIC_SERIAL_BUS: + case UACPI_ADDRESS_SPACE_SMBUS: { + uacpi_size size_for_protocol = SERIAL_HEADER_SIZE; + + switch (field->attributes) { + case UACPI_ACCESS_ATTRIBUTE_QUICK: + break; // + 0 + case UACPI_ACCESS_ATTRIBUTE_SEND_RECEIVE: + case UACPI_ACCESS_ATTRIBUTE_BYTE: + size_for_protocol += 1; + break; + + case UACPI_ACCESS_ATTRIBUTE_WORD: + case UACPI_ACCESS_ATTRIBUTE_PROCESS_CALL: + size_for_protocol += 2; + break; + + case UACPI_ACCESS_ATTRIBUTE_BYTES: + size_for_protocol += field->access_length; + break; + + case UACPI_ACCESS_ATTRIBUTE_BLOCK: + case UACPI_ACCESS_ATTRIBUTE_BLOCK_PROCESS_CALL: + case UACPI_ACCESS_ATTRIBUTE_RAW_BYTES: + case UACPI_ACCESS_ATTRIBUTE_RAW_PROCESS_BYTES: + size_for_protocol += 255; + break; + + default: + uacpi_error( + "unsupported field@%p access attribute %d\n", + field, field->attributes + ); + return UACPI_STATUS_UNIMPLEMENTED; + } + + *out_size = size_for_protocol; + break; + } + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_special_field( + uacpi_field_unit *field, uacpi_data_view buf, + uacpi_region_op op, uacpi_data_view *wtr_response, + uacpi_bool *did_handle +) +{ + uacpi_status ret = UACPI_STATUS_OK; + uacpi_object *obj; + uacpi_operation_region *region; + uacpi_u64 in_out; + uacpi_data_view wtr_buffer; + union uacpi_opregion_io_data data; + + *did_handle = UACPI_FALSE; + + if (field->kind == UACPI_FIELD_UNIT_KIND_INDEX) + return ret; + + obj = uacpi_namespace_node_get_object_typed( + field->region, UACPI_OBJECT_OPERATION_REGION_BIT + ); + if (uacpi_unlikely(obj == UACPI_NULL)) { + ret = UACPI_STATUS_INVALID_ARGUMENT; + uacpi_trace_region_error( + field->region, "attempted access to deleted", ret + ); + goto out_handled; + } + region = obj->op_region; + + switch (region->space) { + case UACPI_ADDRESS_SPACE_GENERAL_PURPOSE_IO: + if (op == UACPI_REGION_OP_WRITE) { + uacpi_memcpy_zerout( + &in_out, buf.const_data, sizeof(in_out), buf.length + ); + } + + data.integer = &in_out; + ret = access_field_unit(field, 0, op, data); + if (uacpi_unlikely_error(ret)) + goto out_handled; + + if (op == UACPI_REGION_OP_READ) + uacpi_memcpy_zerout(buf.data, &in_out, buf.length, sizeof(in_out)); + goto out_handled; + case UACPI_ADDRESS_SPACE_IPMI: + case UACPI_ADDRESS_SPACE_PRM: + if (uacpi_unlikely(op == UACPI_REGION_OP_READ)) { + ret = UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + uacpi_trace_region_error( + field->region, "attempted to read from a write-only", ret + ); + goto out_handled; + } + UACPI_FALLTHROUGH; + case UACPI_ADDRESS_SPACE_FFIXEDHW: + case UACPI_ADDRESS_SPACE_GENERIC_SERIAL_BUS: + case UACPI_ADDRESS_SPACE_SMBUS: + goto do_wtr; + default: + return ret; + } + +do_wtr: + ret = wtr_buffer_size(field, region->space, &wtr_buffer.length); + if (uacpi_unlikely_error(ret)) + goto out_handled; + + wtr_buffer.data = uacpi_kernel_alloc(wtr_buffer.length); + if (uacpi_unlikely(wtr_buffer.data == UACPI_NULL)) { + ret = UACPI_STATUS_OUT_OF_MEMORY; + goto out_handled; + } + + uacpi_memcpy_zerout( + wtr_buffer.data, buf.const_data, wtr_buffer.length, buf.length + ); + data.buffer = wtr_buffer; + ret = access_field_unit( + field, field->byte_offset, + op, data + ); + if (uacpi_unlikely_error(ret)) { + uacpi_free(wtr_buffer.data, wtr_buffer.length); + goto out_handled; + } + + if (wtr_response != UACPI_NULL) + *wtr_response = wtr_buffer; + +out_handled: + *did_handle = UACPI_TRUE; + return ret; +} + +static uacpi_status do_read_misaligned_field_unit( + uacpi_field_unit *field, uacpi_u8 *dst, uacpi_size size +) +{ + uacpi_status ret; + uacpi_size reads_to_do; + uacpi_u64 out; + uacpi_u32 byte_offset = field->byte_offset; + uacpi_u32 bits_left = field->bit_length; + uacpi_u8 width_access_bits = field->access_width_bytes * 8; + + struct bit_span src_span = { 0 }; + struct bit_span dst_span = { 0 }; + + src_span.data = (uacpi_u8*)&out; + src_span.index = field->bit_offset_within_first_byte; + + dst_span.data = dst; + dst_span.index = 0; + dst_span.length = size * 8; + + reads_to_do = UACPI_ALIGN_UP( + field->bit_offset_within_first_byte + field->bit_length, + width_access_bits, + uacpi_u32 + ); + reads_to_do /= width_access_bits; + + while (reads_to_do-- > 0) { + union uacpi_opregion_io_data data; + + src_span.length = UACPI_MIN( + bits_left, width_access_bits - src_span.index + ); + + data.integer = &out; + ret = access_field_unit( + field, byte_offset, UACPI_REGION_OP_READ, + data + ); + if (uacpi_unlikely_error(ret)) + return ret; + + bit_copy(&dst_span, &src_span); + bits_left -= src_span.length; + src_span.index = 0; + + bit_span_offset(&dst_span, src_span.length); + byte_offset += field->access_width_bytes; + } + + return UACPI_STATUS_OK; +} + +uacpi_status uacpi_read_field_unit( + uacpi_field_unit *field, void *dst, uacpi_size size, + uacpi_data_view *wtr_response +) +{ + uacpi_status ret; + uacpi_u32 field_byte_length; + uacpi_bool did_handle; + uacpi_data_view data_view = { 0 }; + + data_view.data = dst; + data_view.length = size; + + ret = handle_special_field( + field, data_view, UACPI_REGION_OP_READ, + wtr_response, &did_handle + ); + if (did_handle) + return ret; + + field_byte_length = uacpi_round_up_bits_to_bytes(field->bit_length); + + /* + * Very simple fast case: + * - Bit offset within first byte is 0 + * AND + * - Field size is <= access width + */ + if (field->bit_offset_within_first_byte == 0 && + field_byte_length <= field->access_width_bytes) + { + uacpi_u64 out; + union uacpi_opregion_io_data data; + + data.integer = &out; + ret = access_field_unit( + field, field->byte_offset, UACPI_REGION_OP_READ, + data + ); + if (uacpi_unlikely_error(ret)) + return ret; + + uacpi_memcpy_zerout(dst, &out, size, field_byte_length); + if (size >= field_byte_length) + cut_misaligned_tail(dst, field_byte_length - 1, field->bit_length); + + return UACPI_STATUS_OK; + } + + // Slow case + return do_read_misaligned_field_unit(field, dst, size); +} + +static uacpi_status write_generic_field_unit( + uacpi_field_unit *field, const void *src, uacpi_size size +) +{ + uacpi_status ret; + uacpi_u32 bits_left, byte_offset = field->byte_offset; + uacpi_u8 width_access_bits = field->access_width_bytes * 8; + uacpi_u64 in; + struct bit_span src_span = { 0 }; + struct bit_span dst_span = { 0 }; + + src_span.const_data = src; + src_span.index = 0; + src_span.length = size * 8; + + dst_span.data = (uacpi_u8 *)∈ + dst_span.index = field->bit_offset_within_first_byte; + + bits_left = field->bit_length; + + while (bits_left) { + union uacpi_opregion_io_data data; + + in = 0; + dst_span.length = UACPI_MIN( + width_access_bits - dst_span.index, bits_left + ); + + if (dst_span.index != 0 || dst_span.length < width_access_bits) { + switch (field->update_rule) { + case UACPI_UPDATE_RULE_PRESERVE: + data.integer = ∈ + ret = access_field_unit( + field, byte_offset, UACPI_REGION_OP_READ, + data + ); + if (uacpi_unlikely_error(ret)) + return ret; + break; + case UACPI_UPDATE_RULE_WRITE_AS_ONES: + in = ~in; + break; + case UACPI_UPDATE_RULE_WRITE_AS_ZEROES: + break; + default: + uacpi_error("invalid field@%p update rule %d\n", + field, field->update_rule); + return UACPI_STATUS_INVALID_ARGUMENT; + } + } + + bit_copy(&dst_span, &src_span); + bit_span_offset(&src_span, dst_span.length); + + data.integer = ∈ + + ret = access_field_unit( + field, byte_offset, UACPI_REGION_OP_WRITE, + data + ); + if (uacpi_unlikely_error(ret)) + return ret; + + bits_left -= dst_span.length; + dst_span.index = 0; + byte_offset += field->access_width_bytes; + } + + return UACPI_STATUS_OK; +} + +uacpi_status uacpi_write_field_unit( + uacpi_field_unit *field, const void *src, uacpi_size size, + uacpi_data_view *wtr_response +) +{ + uacpi_status ret; + uacpi_bool did_handle; + uacpi_data_view data_view = { 0 }; + + data_view.const_data = src; + data_view.length = size; + + ret = handle_special_field( + field, data_view, UACPI_REGION_OP_WRITE, + wtr_response, &did_handle + ); + if (did_handle) + return ret; + + return write_generic_field_unit(field, src, size); +} + +uacpi_status uacpi_field_unit_get_read_type( + struct uacpi_field_unit *field, uacpi_object_type *out_type +) +{ + uacpi_object *obj; + + if (field->kind == UACPI_FIELD_UNIT_KIND_INDEX) + goto out_basic_field; + + obj = uacpi_namespace_node_get_object_typed( + field->region, UACPI_OBJECT_OPERATION_REGION_BIT + ); + if (uacpi_unlikely(obj == UACPI_NULL)) + return UACPI_STATUS_INVALID_ARGUMENT; + + if (uacpi_is_buffer_access_address_space(obj->op_region->space)) { + *out_type = UACPI_OBJECT_BUFFER; + return UACPI_STATUS_OK; + } + +out_basic_field: + if (field->bit_length > (g_uacpi_rt_ctx.is_rev1 ? 32u : 64u)) + *out_type = UACPI_OBJECT_BUFFER; + else + *out_type = UACPI_OBJECT_INTEGER; + + return UACPI_STATUS_OK; +} + +uacpi_status uacpi_field_unit_get_bit_length( + struct uacpi_field_unit *field, uacpi_size *out_length +) +{ + uacpi_object *obj; + + if (field->kind == UACPI_FIELD_UNIT_KIND_INDEX) + goto out_basic_field; + + obj = uacpi_namespace_node_get_object_typed( + field->region, UACPI_OBJECT_OPERATION_REGION_BIT + ); + if (uacpi_unlikely(obj == UACPI_NULL)) + return UACPI_STATUS_INVALID_ARGUMENT; + + if (uacpi_is_buffer_access_address_space(obj->op_region->space)) { + /* + * Bit length is protocol specific, the data will be returned + * via the write-then-read response buffer. + */ + *out_length = 0; + return UACPI_STATUS_OK; + } + +out_basic_field: + *out_length = field->bit_length; + return UACPI_STATUS_OK; +} + +static uacpi_u8 gas_get_access_bit_width(const struct acpi_gas *gas) +{ + /* + * Same algorithm as ACPICA. + * + * The reason we do this is apparently GAS bit offset being non-zero means + * that it's an APEI register, as opposed to FADT, which needs special + * handling. In the case of a FADT register we want to ignore the specified + * access size. + */ + uacpi_u8 access_bit_width; + + if (gas->register_bit_offset == 0 && + UACPI_IS_POWER_OF_TWO(gas->register_bit_width, uacpi_u8) && + UACPI_IS_ALIGNED(gas->register_bit_width, 8, uacpi_u8)) { + access_bit_width = gas->register_bit_width; + } else if (gas->access_size) { + access_bit_width = gas->access_size * 8; + } else { + uacpi_u8 msb; + + msb = uacpi_bit_scan_backward( + (gas->register_bit_offset + gas->register_bit_width) - 1 + ); + access_bit_width = 1 << msb; + + if (access_bit_width <= 8) { + access_bit_width = 8; + } else { + /* + * Keep backing off to previous power of two until we find one + * that is aligned to the address specified in GAS. + */ + while (!UACPI_IS_ALIGNED( + gas->address, access_bit_width / 8, uacpi_u64 + )) + access_bit_width /= 2; + } + } + + return UACPI_MIN( + access_bit_width, + gas->address_space_id == UACPI_ADDRESS_SPACE_SYSTEM_IO ? 32 : 64 + ); +} + +static uacpi_status gas_validate( + const struct acpi_gas *gas, uacpi_u8 *access_bit_width, + uacpi_u8 *bit_width +) +{ + uacpi_size total_width, aligned_width; + + if (uacpi_unlikely(gas == UACPI_NULL)) + return UACPI_STATUS_INVALID_ARGUMENT; + + if (!gas->address) + return UACPI_STATUS_NOT_FOUND; + + if (gas->address_space_id != UACPI_ADDRESS_SPACE_SYSTEM_IO && + gas->address_space_id != UACPI_ADDRESS_SPACE_SYSTEM_MEMORY) { + uacpi_warn("unsupported GAS address space '%s' (%d)\n", + uacpi_address_space_to_string(gas->address_space_id), + gas->address_space_id); + return UACPI_STATUS_UNIMPLEMENTED; + } + + if (gas->access_size > 4) { + uacpi_warn("unsupported GAS access size %d\n", + gas->access_size); + return UACPI_STATUS_UNIMPLEMENTED; + } + + *access_bit_width = gas_get_access_bit_width(gas); + + total_width = gas->register_bit_offset + gas->register_bit_width; + aligned_width = UACPI_ALIGN_UP(total_width, *access_bit_width, uacpi_size); + + if (uacpi_unlikely(aligned_width > 64)) { + uacpi_warn( + "GAS register total width is too large: %zu\n", total_width + ); + return UACPI_STATUS_UNIMPLEMENTED; + } + + *bit_width = total_width; + return UACPI_STATUS_OK; +} + +/* + * Apparently both reading and writing GAS works differently from operation + * region in that bit offsets are not respected when writing the data. + * + * Let's follow ACPICA's approach here so that we don't accidentally + * break any quirky hardware. + */ +uacpi_status uacpi_gas_read_mapped( + const uacpi_mapped_gas *gas, uacpi_u64 *out_value +) +{ + uacpi_status ret; + uacpi_u8 access_byte_width; + uacpi_u8 bit_offset, bits_left, index = 0; + uacpi_u64 data, mask = 0xFFFFFFFFFFFFFFFF; + uacpi_size offset = 0; + + bit_offset = gas->bit_offset; + bits_left = gas->total_bit_width; + + access_byte_width = gas->access_bit_width / 8; + + if (access_byte_width < 8) + mask = ~(mask << gas->access_bit_width); + + *out_value = 0; + + while (bits_left) { + if (bit_offset >= gas->access_bit_width) { + data = 0; + bit_offset -= gas->access_bit_width; + } else { + ret = gas->read(gas->mapping, offset, access_byte_width, &data); + if (uacpi_unlikely_error(ret)) + return ret; + } + + *out_value |= (data & mask) << (index * gas->access_bit_width); + bits_left -= UACPI_MIN(bits_left, gas->access_bit_width); + ++index; + offset += access_byte_width; + } + + return UACPI_STATUS_OK; +} + +uacpi_status uacpi_gas_write_mapped( + const uacpi_mapped_gas *gas, uacpi_u64 in_value +) +{ + uacpi_status ret; + uacpi_u8 access_byte_width; + uacpi_u8 bit_offset, bits_left, index = 0; + uacpi_u64 data, mask = 0xFFFFFFFFFFFFFFFF; + uacpi_size offset = 0; + + bit_offset = gas->bit_offset; + bits_left = gas->total_bit_width; + access_byte_width = gas->access_bit_width / 8; + + if (access_byte_width < 8) + mask = ~(mask << gas->access_bit_width); + + while (bits_left) { + data = (in_value >> (index * gas->access_bit_width)) & mask; + + if (bit_offset >= gas->access_bit_width) { + bit_offset -= gas->access_bit_width; + } else { + ret = gas->write(gas->mapping, offset, access_byte_width, data); + if (uacpi_unlikely_error(ret)) + return ret; + } + + bits_left -= UACPI_MIN(bits_left, gas->access_bit_width); + ++index; + offset += access_byte_width; + } + + return UACPI_STATUS_OK; +} + +static void unmap_gas_io(uacpi_handle io_handle, uacpi_size size) +{ + UACPI_UNUSED(size); + uacpi_kernel_io_unmap(io_handle); +} + +uacpi_status uacpi_map_gas_noalloc( + const struct acpi_gas *gas, uacpi_mapped_gas *out_mapped +) +{ + uacpi_status ret; + uacpi_u8 access_bit_width, total_width; + + ret = gas_validate(gas, &access_bit_width, &total_width); + if (ret != UACPI_STATUS_OK) + return ret; + + if (gas->address_space_id == UACPI_ADDRESS_SPACE_SYSTEM_MEMORY) { + out_mapped->mapping = uacpi_kernel_map(gas->address, total_width / 8); + if (uacpi_unlikely(out_mapped->mapping == UACPI_NULL)) + return UACPI_STATUS_MAPPING_FAILED; + + out_mapped->read = uacpi_system_memory_read; + out_mapped->write = uacpi_system_memory_write; + out_mapped->unmap = uacpi_kernel_unmap; + } else { // IO, validated by gas_validate above + ret = uacpi_kernel_io_map(gas->address, total_width / 8, &out_mapped->mapping); + if (uacpi_unlikely_error(ret)) + return ret; + + out_mapped->read = uacpi_system_io_read; + out_mapped->write = uacpi_system_io_write; + out_mapped->unmap = unmap_gas_io; + } + + out_mapped->access_bit_width = access_bit_width; + out_mapped->total_bit_width = total_width; + out_mapped->bit_offset = gas->register_bit_offset; + + return UACPI_STATUS_OK; +} + +uacpi_status uacpi_map_gas( + const struct acpi_gas *gas, uacpi_mapped_gas **out_mapped +) +{ + uacpi_status ret; + uacpi_mapped_gas *mapping; + + mapping = uacpi_kernel_alloc(sizeof(*mapping)); + if (uacpi_unlikely(mapping == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + ret = uacpi_map_gas_noalloc(gas, mapping); + if (uacpi_unlikely_error(ret)) { + uacpi_free(mapping, sizeof(*mapping)); + return ret; + } + + *out_mapped = mapping; + return ret; +} + +void uacpi_unmap_gas_nofree(uacpi_mapped_gas *gas) +{ + gas->unmap(gas->mapping, gas->access_bit_width / 8); +} + +void uacpi_unmap_gas(uacpi_mapped_gas *gas) +{ + uacpi_unmap_gas_nofree(gas); + uacpi_free(gas, sizeof(*gas)); +} + +uacpi_status uacpi_gas_read(const struct acpi_gas *gas, uacpi_u64 *out_value) +{ + uacpi_status ret; + uacpi_mapped_gas mapping; + + ret = uacpi_map_gas_noalloc(gas, &mapping); + if (uacpi_unlikely_error(ret)) + return ret; + + ret = uacpi_gas_read_mapped(&mapping, out_value); + uacpi_unmap_gas_nofree(&mapping); + + return ret; +} + +uacpi_status uacpi_gas_write(const struct acpi_gas *gas, uacpi_u64 in_value) +{ + uacpi_status ret; + uacpi_mapped_gas mapping; + + ret = uacpi_map_gas_noalloc(gas, &mapping); + if (uacpi_unlikely_error(ret)) + return ret; + + ret = uacpi_gas_write_mapped(&mapping, in_value); + uacpi_unmap_gas_nofree(&mapping); + + return ret; +} + +uacpi_status uacpi_system_memory_read( + void *ptr, uacpi_size offset, uacpi_u8 width, uacpi_u64 *out +) +{ + ptr = UACPI_PTR_ADD(ptr, offset); + + switch (width) { + case 1: + *out = *(volatile uacpi_u8*)ptr; + break; + case 2: + *out = *(volatile uacpi_u16*)ptr; + break; + case 4: + *out = *(volatile uacpi_u32*)ptr; + break; + case 8: + *out = *(volatile uacpi_u64*)ptr; + break; + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + return UACPI_STATUS_OK; +} + +uacpi_status uacpi_system_memory_write( + void *ptr, uacpi_size offset, uacpi_u8 width, uacpi_u64 in +) +{ + ptr = UACPI_PTR_ADD(ptr, offset); + + switch (width) { + case 1: + *(volatile uacpi_u8*)ptr = in; + break; + case 2: + *(volatile uacpi_u16*)ptr = in; + break; + case 4: + *(volatile uacpi_u32*)ptr = in; + break; + case 8: + *(volatile uacpi_u64*)ptr = in; + break; + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + return UACPI_STATUS_OK; +} + +union integer_data { + uacpi_u8 byte; + uacpi_u16 word; + uacpi_u32 dword; + uacpi_u64 qword; +}; + +uacpi_status uacpi_system_io_read( + uacpi_handle handle, uacpi_size offset, uacpi_u8 width, uacpi_u64 *out +) +{ + uacpi_status ret; + union integer_data data = { + .qword = 0, + }; + + switch (width) { + case 1: + ret = uacpi_kernel_io_read8(handle, offset, &data.byte); + break; + case 2: + ret = uacpi_kernel_io_read16(handle, offset, &data.word); + break; + case 4: + ret = uacpi_kernel_io_read32(handle, offset, &data.dword); + break; + default: + uacpi_error( + "invalid SystemIO read %p@%zu width=%d\n", + handle, offset, width + ); + return UACPI_STATUS_INVALID_ARGUMENT; + } + + if (uacpi_likely_success(ret)) + *out = data.qword; + return ret; +} + +uacpi_status uacpi_system_io_write( + uacpi_handle handle, uacpi_size offset, uacpi_u8 width, uacpi_u64 in +) +{ + uacpi_status ret; + + switch (width) { + case 1: + ret = uacpi_kernel_io_write8(handle, offset, in); + break; + case 2: + ret = uacpi_kernel_io_write16(handle, offset, in); + break; + case 4: + ret = uacpi_kernel_io_write32(handle, offset, in); + break; + default: + uacpi_error( + "invalid SystemIO write %p@%zu width=%d\n", + handle, offset, width + ); + return UACPI_STATUS_INVALID_ARGUMENT; + } + + return ret; +} + +uacpi_status uacpi_pci_read( + uacpi_handle handle, uacpi_size offset, uacpi_u8 width, uacpi_u64 *out +) +{ + uacpi_status ret; + union integer_data data = { + .qword = 0, + }; + + switch (width) { + case 1: + ret = uacpi_kernel_pci_read8(handle, offset, &data.byte); + break; + case 2: + ret = uacpi_kernel_pci_read16(handle, offset, &data.word); + break; + case 4: + ret = uacpi_kernel_pci_read32(handle, offset, &data.dword); + break; + default: + uacpi_error( + "invalid PCI_Config read %p@%zu width=%d\n", + handle, offset, width + ); + return UACPI_STATUS_INVALID_ARGUMENT; + } + + if (uacpi_likely_success(ret)) + *out = data.qword; + return ret; +} + +uacpi_status uacpi_pci_write( + uacpi_handle handle, uacpi_size offset, uacpi_u8 width, uacpi_u64 in +) +{ + uacpi_status ret; + + switch (width) { + case 1: + ret = uacpi_kernel_pci_write8(handle, offset, in); + break; + case 2: + ret = uacpi_kernel_pci_write16(handle, offset, in); + break; + case 4: + ret = uacpi_kernel_pci_write32(handle, offset, in); + break; + default: + uacpi_error( + "invalid PCI_Config write %p@%zu width=%d\n", + handle, offset, width + ); + return UACPI_STATUS_INVALID_ARGUMENT; + } + + return ret; +} + +#endif // !UACPI_BAREBONES_MODE |