diff options
author | Ian Moffett <ian@osmora.org> | 2025-05-17 21:56:07 -0400 |
---|---|---|
committer | Ian Moffett <ian@osmora.org> | 2025-05-17 21:58:44 -0400 |
commit | 08eeb79db14145d83578025e1f0e7f7af460ee25 (patch) | |
tree | b6af572a4b8dceb4f044f1e0bf5697f5c18dc0fd /sys/dev/acpi/uacpi/interpreter.c | |
parent | 9c64c3e69fa60b3657d33e829a411cb37064a169 (diff) |
kernel: acpi: Add uACPI portexpt
See https://github.com/uACPI/uACPI/
Signed-off-by: Ian Moffett <ian@osmora.org>
Diffstat (limited to 'sys/dev/acpi/uacpi/interpreter.c')
-rw-r--r-- | sys/dev/acpi/uacpi/interpreter.c | 6053 |
1 files changed, 6053 insertions, 0 deletions
diff --git a/sys/dev/acpi/uacpi/interpreter.c b/sys/dev/acpi/uacpi/interpreter.c new file mode 100644 index 0000000..8ffb8d5 --- /dev/null +++ b/sys/dev/acpi/uacpi/interpreter.c @@ -0,0 +1,6053 @@ +#include <uacpi/internal/types.h> +#include <uacpi/internal/interpreter.h> +#include <uacpi/internal/dynamic_array.h> +#include <uacpi/internal/opcodes.h> +#include <uacpi/internal/namespace.h> +#include <uacpi/internal/stdlib.h> +#include <uacpi/internal/context.h> +#include <uacpi/internal/shareable.h> +#include <uacpi/internal/tables.h> +#include <uacpi/internal/helpers.h> +#include <uacpi/kernel_api.h> +#include <uacpi/internal/utilities.h> +#include <uacpi/internal/opregion.h> +#include <uacpi/internal/io.h> +#include <uacpi/internal/notify.h> +#include <uacpi/internal/resources.h> +#include <uacpi/internal/event.h> +#include <uacpi/internal/mutex.h> +#include <uacpi/internal/osi.h> + +#ifndef UACPI_BAREBONES_MODE + +enum item_type { + ITEM_NONE = 0, + ITEM_NAMESPACE_NODE, + ITEM_OBJECT, + ITEM_EMPTY_OBJECT, + ITEM_PACKAGE_LENGTH, + ITEM_IMMEDIATE, +}; + +struct package_length { + uacpi_u32 begin; + uacpi_u32 end; +}; + +struct item { + uacpi_u8 type; + union { + uacpi_handle handle; + uacpi_object *obj; + struct uacpi_namespace_node *node; + struct package_length pkg; + uacpi_u64 immediate; + uacpi_u8 immediate_bytes[8]; + }; +}; + +DYNAMIC_ARRAY_WITH_INLINE_STORAGE(item_array, struct item, 8) +DYNAMIC_ARRAY_WITH_INLINE_STORAGE_IMPL(item_array, struct item, static) + +struct op_context { + uacpi_u8 pc; + uacpi_bool preempted; + + /* + * == 0 -> none + * >= 1 -> item[idx - 1] + */ + uacpi_u8 tracked_pkg_idx; + + uacpi_aml_op switched_from; + + const struct uacpi_op_spec *op; + struct item_array items; +}; + +DYNAMIC_ARRAY_WITH_INLINE_STORAGE(op_context_array, struct op_context, 8) +DYNAMIC_ARRAY_WITH_INLINE_STORAGE_IMPL( + op_context_array, struct op_context, static +) + +static struct op_context *op_context_array_one_before_last( + struct op_context_array *arr +) +{ + uacpi_size size; + + size = op_context_array_size(arr); + + if (size < 2) + return UACPI_NULL; + + return op_context_array_at(arr, size - 2); +} + +enum code_block_type { + CODE_BLOCK_IF = 1, + CODE_BLOCK_ELSE = 2, + CODE_BLOCK_WHILE = 3, + CODE_BLOCK_SCOPE = 4, +}; + +struct code_block { + enum code_block_type type; + uacpi_u32 begin, end; + union { + struct uacpi_namespace_node *node; + uacpi_u64 expiration_point; + }; +}; + +DYNAMIC_ARRAY_WITH_INLINE_STORAGE(code_block_array, struct code_block, 8) +DYNAMIC_ARRAY_WITH_INLINE_STORAGE_IMPL( + code_block_array, struct code_block, static +) + +DYNAMIC_ARRAY_WITH_INLINE_STORAGE(held_mutexes_array, uacpi_mutex*, 8) +DYNAMIC_ARRAY_WITH_INLINE_STORAGE_IMPL( + held_mutexes_array, uacpi_mutex*, static +) + +static uacpi_status held_mutexes_array_push( + struct held_mutexes_array *arr, uacpi_mutex *mutex +) +{ + uacpi_mutex **slot; + + slot = held_mutexes_array_alloc(arr); + if (uacpi_unlikely(slot == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + *slot = mutex; + uacpi_shareable_ref(mutex); + return UACPI_STATUS_OK; +} + +static void held_mutexes_array_remove_idx( + struct held_mutexes_array *arr, uacpi_size i +) +{ + uacpi_size size; + + size = held_mutexes_array_inline_capacity(arr); + + // Only the dynamic array part is affected + if (i >= size) { + i -= size; + size = arr->size_including_inline - size; + size -= i + 1; + + uacpi_memmove( + &arr->dynamic_storage[i], &arr->dynamic_storage[i + 1], + size * sizeof(arr->inline_storage[0]) + ); + + held_mutexes_array_pop(arr); + return; + } + + size = UACPI_MIN(held_mutexes_array_inline_capacity(arr), + arr->size_including_inline); + size -= i + 1; + uacpi_memmove( + &arr->inline_storage[i], &arr->inline_storage[i + 1], + size * sizeof(arr->inline_storage[0]) + ); + + size = held_mutexes_array_size(arr); + i = held_mutexes_array_inline_capacity(arr); + + /* + * This array has dynamic storage as well, now we have to take the first + * dynamic item, move it to the top of inline storage, and then shift all + * dynamic items backward by 1 as well. + */ + if (size > i) { + arr->inline_storage[i - 1] = arr->dynamic_storage[0]; + size -= i + 1; + + uacpi_memmove( + &arr->dynamic_storage[0], &arr->dynamic_storage[1], + size * sizeof(arr->inline_storage[0]) + ); + } + + held_mutexes_array_pop(arr); +} + +enum force_release { + FORCE_RELEASE_NO, + FORCE_RELEASE_YES, +}; + +static uacpi_status held_mutexes_array_remove_and_release( + struct held_mutexes_array *arr, uacpi_mutex *mutex, + enum force_release force +) +{ + uacpi_mutex *item; + uacpi_size i; + + if (uacpi_unlikely(held_mutexes_array_size(arr) == 0)) + return UACPI_STATUS_INVALID_ARGUMENT; + + item = *held_mutexes_array_last(arr); + + if (uacpi_unlikely(item->sync_level != mutex->sync_level && + force != FORCE_RELEASE_YES)) { + uacpi_warn( + "ignoring mutex @%p release due to sync level mismatch: %d vs %d\n", + mutex, mutex->sync_level, item->sync_level + ); + + // We still return OK because we don't want to abort because of this + return UACPI_STATUS_OK; + } + + if (mutex->depth > 1 && force == FORCE_RELEASE_NO) { + uacpi_release_aml_mutex(mutex); + return UACPI_STATUS_OK; + } + + // Fast path for well-behaved AML that releases mutexes in descending order + if (uacpi_likely(item == mutex)) { + held_mutexes_array_pop(arr); + goto do_release; + } + + /* + * The mutex being released is not the last one acquired, although we did + * verify that at least it has the same sync level. Anyway, now we have + * to search for it and then remove it from the array while shifting + * everything backwards. + */ + i = held_mutexes_array_size(arr); + for (;;) { + item = *held_mutexes_array_at(arr, --i); + if (item == mutex) + break; + + if (uacpi_unlikely(i == 0)) + return UACPI_STATUS_INVALID_ARGUMENT; + } + + held_mutexes_array_remove_idx(arr, i); + +do_release: + // This is either a force release, or depth was already 1 to begin with + mutex->depth = 1; + uacpi_release_aml_mutex(mutex); + + uacpi_mutex_unref(mutex); + return UACPI_STATUS_OK; +} + +DYNAMIC_ARRAY_WITH_INLINE_STORAGE( + temp_namespace_node_array, uacpi_namespace_node*, 8) +DYNAMIC_ARRAY_WITH_INLINE_STORAGE_IMPL( + temp_namespace_node_array, uacpi_namespace_node*, static +) + +static uacpi_status temp_namespace_node_array_push( + struct temp_namespace_node_array *arr, uacpi_namespace_node *node +) +{ + uacpi_namespace_node **slot; + + slot = temp_namespace_node_array_alloc(arr); + if (uacpi_unlikely(slot == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + *slot = node; + return UACPI_STATUS_OK; +} + +struct call_frame { + struct uacpi_control_method *method; + + uacpi_object *args[7]; + uacpi_object *locals[8]; + + struct op_context_array pending_ops; + struct code_block_array code_blocks; + struct temp_namespace_node_array temp_nodes; + struct code_block *last_while; + uacpi_u64 prev_while_expiration; + uacpi_u32 prev_while_code_offset; + + uacpi_u32 code_offset; + + struct uacpi_namespace_node *cur_scope; + + // Only used if the method is serialized + uacpi_u8 prev_sync_level; +}; + +static void *call_frame_cursor(struct call_frame *frame) +{ + return frame->method->code + frame->code_offset; +} + +static uacpi_size call_frame_code_bytes_left(struct call_frame *frame) +{ + return frame->method->size - frame->code_offset; +} + +static uacpi_bool call_frame_has_code(struct call_frame *frame) +{ + return call_frame_code_bytes_left(frame) > 0; +} + +DYNAMIC_ARRAY_WITH_INLINE_STORAGE(call_frame_array, struct call_frame, 4) +DYNAMIC_ARRAY_WITH_INLINE_STORAGE_IMPL( + call_frame_array, struct call_frame, static +) + +static struct call_frame *call_frame_array_one_before_last( + struct call_frame_array *arr +) +{ + uacpi_size size; + + size = call_frame_array_size(arr); + + if (size < 2) + return UACPI_NULL; + + return call_frame_array_at(arr, size - 2); +} + +// NOTE: Try to keep size under 2 pages +struct execution_context { + uacpi_object *ret; + struct call_frame_array call_stack; + struct held_mutexes_array held_mutexes; + + struct call_frame *cur_frame; + struct code_block *cur_block; + const struct uacpi_op_spec *cur_op; + struct op_context *prev_op_ctx; + struct op_context *cur_op_ctx; + + uacpi_u8 sync_level; +}; + +#define AML_READ(ptr, offset) (*(((uacpi_u8*)(ptr)) + offset)) + +static uacpi_status parse_nameseg(uacpi_u8 *cursor, + uacpi_object_name *out_name) +{ + if (uacpi_unlikely(!uacpi_is_valid_nameseg(cursor))) + return UACPI_STATUS_AML_INVALID_NAMESTRING; + + uacpi_memcpy(&out_name->id, cursor, 4); + return UACPI_STATUS_OK; +} + +/* + * ------------------------------------------------------------- + * RootChar := ‘\’ + * ParentPrefixChar := ‘^’ + * ‘\’ := 0x5C + * ‘^’ := 0x5E + * ------------------------------------------------------------ + * NameSeg := <leadnamechar namechar namechar namechar> + * NameString := <rootchar namepath> | <prefixpath namepath> + * PrefixPath := Nothing | <’^’ prefixpath> + * NamePath := NameSeg | DualNamePath | MultiNamePath | NullName + * DualNamePath := DualNamePrefix NameSeg NameSeg + * MultiNamePath := MultiNamePrefix SegCount NameSeg(SegCount) + */ + +static uacpi_status name_string_to_path( + struct call_frame *frame, uacpi_size offset, + uacpi_char **out_string, uacpi_size *out_size +) +{ + uacpi_size bytes_left, prefix_bytes, nameseg_bytes = 0, namesegs; + uacpi_char *base_cursor, *cursor; + uacpi_char prev_char; + + bytes_left = frame->method->size - offset; + cursor = (uacpi_char*)frame->method->code + offset; + base_cursor = cursor; + namesegs = 0; + + prefix_bytes = 0; + for (;;) { + if (uacpi_unlikely(bytes_left == 0)) + return UACPI_STATUS_AML_INVALID_NAMESTRING; + + prev_char = *cursor; + + switch (prev_char) { + case '^': + case '\\': + prefix_bytes++; + cursor++; + bytes_left--; + break; + default: + break; + } + + if (prev_char != '^') + break; + } + + // At least a NullName byte is expected here + if (uacpi_unlikely(bytes_left == 0)) + return UACPI_STATUS_AML_INVALID_NAMESTRING; + + namesegs = 0; + bytes_left--; + switch (*cursor++) + { + case UACPI_DUAL_NAME_PREFIX: + namesegs = 2; + break; + case UACPI_MULTI_NAME_PREFIX: + if (uacpi_unlikely(bytes_left == 0)) + return UACPI_STATUS_AML_INVALID_NAMESTRING; + + namesegs = *(uacpi_u8*)cursor; + if (uacpi_unlikely(namesegs == 0)) { + uacpi_error("MultiNamePrefix but SegCount is 0\n"); + return UACPI_STATUS_AML_INVALID_NAMESTRING; + } + + cursor++; + bytes_left--; + break; + case UACPI_NULL_NAME: + break; + default: + /* + * Might be an invalid byte, but assume single nameseg for now, + * the code below will validate it for us. + */ + cursor--; + bytes_left++; + namesegs = 1; + break; + } + + if (uacpi_unlikely((namesegs * 4) > bytes_left)) + return UACPI_STATUS_AML_INVALID_NAMESTRING; + + if (namesegs) { + // 4 chars per nameseg + nameseg_bytes = namesegs * 4; + + // dot separator for every nameseg + nameseg_bytes += namesegs - 1; + } + + *out_size = nameseg_bytes + prefix_bytes + 1; + + *out_string = uacpi_kernel_alloc(*out_size); + if (*out_string == UACPI_NULL) + return UACPI_STATUS_OUT_OF_MEMORY; + + uacpi_memcpy(*out_string, base_cursor, prefix_bytes); + + base_cursor = *out_string; + base_cursor += prefix_bytes; + + while (namesegs-- > 0) { + uacpi_memcpy(base_cursor, cursor, 4); + cursor += 4; + base_cursor += 4; + + if (namesegs) + *base_cursor++ = '.'; + } + + *base_cursor = '\0'; + return UACPI_STATUS_OK; +} + +enum resolve_behavior { + RESOLVE_CREATE_LAST_NAMESEG_FAIL_IF_EXISTS, + RESOLVE_FAIL_IF_DOESNT_EXIST, +}; + +static uacpi_status resolve_name_string( + struct call_frame *frame, + enum resolve_behavior behavior, + struct uacpi_namespace_node **out_node +) +{ + uacpi_status ret = UACPI_STATUS_OK; + uacpi_u8 *cursor; + uacpi_size bytes_left, namesegs = 0; + struct uacpi_namespace_node *parent, *cur_node = frame->cur_scope; + uacpi_char prev_char = 0; + uacpi_bool just_one_nameseg = UACPI_TRUE; + + bytes_left = call_frame_code_bytes_left(frame); + cursor = call_frame_cursor(frame); + + for (;;) { + if (uacpi_unlikely(bytes_left == 0)) + return UACPI_STATUS_AML_INVALID_NAMESTRING; + + switch (*cursor) { + case '\\': + if (prev_char == '^') + return UACPI_STATUS_AML_INVALID_NAMESTRING; + + cur_node = uacpi_namespace_root(); + break; + case '^': + // Tried to go behind root + if (uacpi_unlikely(cur_node == uacpi_namespace_root())) + return UACPI_STATUS_AML_INVALID_NAMESTRING; + + cur_node = cur_node->parent; + break; + default: + break; + } + + prev_char = *cursor; + + switch (prev_char) { + case '^': + case '\\': + just_one_nameseg = UACPI_FALSE; + cursor++; + bytes_left--; + break; + default: + break; + } + + if (prev_char != '^') + break; + } + + // At least a NullName byte is expected here + if (uacpi_unlikely(bytes_left == 0)) + return UACPI_STATUS_AML_INVALID_NAMESTRING; + + bytes_left--; + switch (*cursor++) + { + case UACPI_DUAL_NAME_PREFIX: + namesegs = 2; + just_one_nameseg = UACPI_FALSE; + break; + case UACPI_MULTI_NAME_PREFIX: + if (uacpi_unlikely(bytes_left == 0)) + return UACPI_STATUS_AML_INVALID_NAMESTRING; + + namesegs = *cursor; + if (uacpi_unlikely(namesegs == 0)) { + uacpi_error("MultiNamePrefix but SegCount is 0\n"); + return UACPI_STATUS_AML_INVALID_NAMESTRING; + } + + cursor++; + bytes_left--; + just_one_nameseg = UACPI_FALSE; + break; + case UACPI_NULL_NAME: + if (behavior == RESOLVE_CREATE_LAST_NAMESEG_FAIL_IF_EXISTS || + just_one_nameseg) + return UACPI_STATUS_AML_INVALID_NAMESTRING; + + goto out; + default: + /* + * Might be an invalid byte, but assume single nameseg for now, + * the code below will validate it for us. + */ + cursor--; + bytes_left++; + namesegs = 1; + break; + } + + if (uacpi_unlikely((namesegs * 4) > bytes_left)) + return UACPI_STATUS_AML_INVALID_NAMESTRING; + + for (; namesegs; cursor += 4, namesegs--) { + uacpi_object_name name; + + ret = parse_nameseg(cursor, &name); + if (uacpi_unlikely_error(ret)) + return ret; + + parent = cur_node; + cur_node = uacpi_namespace_node_find_sub_node(parent, name); + + switch (behavior) { + case RESOLVE_CREATE_LAST_NAMESEG_FAIL_IF_EXISTS: + if (namesegs == 1) { + if (cur_node) { + cur_node = UACPI_NULL; + ret = UACPI_STATUS_AML_OBJECT_ALREADY_EXISTS; + goto out; + } + + // Create the node and link to parent but don't install YET + cur_node = uacpi_namespace_node_alloc(name); + if (uacpi_unlikely(cur_node == UACPI_NULL)) { + ret = UACPI_STATUS_OUT_OF_MEMORY; + goto out; + } + + cur_node->parent = parent; + } + break; + case RESOLVE_FAIL_IF_DOESNT_EXIST: + if (just_one_nameseg) { + while (!cur_node && parent != uacpi_namespace_root()) { + cur_node = parent; + parent = cur_node->parent; + + cur_node = uacpi_namespace_node_find_sub_node(parent, name); + } + } + break; + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + if (cur_node == UACPI_NULL) { + ret = UACPI_STATUS_NOT_FOUND; + break; + } + } + +out: + cursor += namesegs * 4; + frame->code_offset = cursor - frame->method->code; + + if (uacpi_likely_success(ret) && behavior == RESOLVE_FAIL_IF_DOESNT_EXIST) + uacpi_shareable_ref(cur_node); + + *out_node = cur_node; + return ret; +} + +static uacpi_status do_install_node_item(struct call_frame *frame, + struct item *item) +{ + uacpi_status ret; + + ret = uacpi_namespace_node_install(item->node->parent, item->node); + if (uacpi_unlikely_error(ret)) + return ret; + + if (!frame->method->named_objects_persist) + ret = temp_namespace_node_array_push(&frame->temp_nodes, item->node); + + if (uacpi_likely_success(ret)) + item->node = UACPI_NULL; + + return ret; +} + +static uacpi_u8 peek_next_op(struct call_frame *frame, uacpi_aml_op *out_op) +{ + uacpi_aml_op op; + uacpi_size bytes_left; + uacpi_u8 length = 0; + uacpi_u8 *cursor; + struct code_block *block; + + block = code_block_array_last(&frame->code_blocks); + bytes_left = block->end - frame->code_offset; + if (bytes_left == 0) + return 0; + + cursor = call_frame_cursor(frame); + + op = AML_READ(cursor, length++); + if (op == UACPI_EXT_PREFIX) { + if (uacpi_unlikely(bytes_left < 2)) + return 0; + + op <<= 8; + op |= AML_READ(cursor, length++); + } + + *out_op = op; + return length; +} + +static uacpi_status get_op(struct execution_context *ctx) +{ + uacpi_aml_op op; + uacpi_u8 length; + + length = peek_next_op(ctx->cur_frame, &op); + if (uacpi_unlikely(length == 0)) + return UACPI_STATUS_AML_BAD_ENCODING; + + ctx->cur_frame->code_offset += length; + g_uacpi_rt_ctx.opcodes_executed++; + + ctx->cur_op = uacpi_get_op_spec(op); + if (uacpi_unlikely(ctx->cur_op->properties & UACPI_OP_PROPERTY_RESERVED)) { + uacpi_error( + "invalid opcode '%s' encountered in bytestream\n", + ctx->cur_op->name + ); + return UACPI_STATUS_AML_INVALID_OPCODE; + } + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_buffer(struct execution_context *ctx) +{ + struct package_length *pkg; + uacpi_u8 *src; + uacpi_object *dst, *declared_size; + uacpi_u32 buffer_size, init_size, aml_offset; + struct op_context *op_ctx = ctx->cur_op_ctx; + + aml_offset = item_array_at(&op_ctx->items, 2)->immediate; + src = ctx->cur_frame->method->code; + src += aml_offset; + + pkg = &item_array_at(&op_ctx->items, 0)->pkg; + init_size = pkg->end - aml_offset; + + // TODO: do package bounds checking at parse time + if (uacpi_unlikely(pkg->end > ctx->cur_frame->method->size)) + return UACPI_STATUS_AML_BAD_ENCODING; + + declared_size = item_array_at(&op_ctx->items, 1)->obj; + + if (uacpi_unlikely(declared_size->integer > 0xE0000000)) { + uacpi_error( + "buffer is too large (%"UACPI_PRIu64"), assuming corrupted " + "bytestream\n", UACPI_FMT64(declared_size->integer) + ); + return UACPI_STATUS_AML_BAD_ENCODING; + } + + if (uacpi_unlikely(declared_size->integer == 0)) { + uacpi_error("attempted to create an empty buffer\n"); + return UACPI_STATUS_AML_BAD_ENCODING; + } + + buffer_size = declared_size->integer; + if (uacpi_unlikely(init_size > buffer_size)) { + uacpi_error( + "too many buffer initializers: %u (size is %u)\n", + init_size, buffer_size + ); + return UACPI_STATUS_AML_BAD_ENCODING; + } + + dst = item_array_at(&op_ctx->items, 3)->obj; + dst->buffer->data = uacpi_kernel_alloc(buffer_size); + if (uacpi_unlikely(dst->buffer->data == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + dst->buffer->size = buffer_size; + + uacpi_memcpy_zerout(dst->buffer->data, src, buffer_size, init_size); + return UACPI_STATUS_OK; +} + +static uacpi_status handle_string(struct execution_context *ctx) +{ + struct call_frame *frame = ctx->cur_frame; + uacpi_object *obj; + + uacpi_char *string; + uacpi_size length, max_bytes; + + obj = item_array_last(&ctx->cur_op_ctx->items)->obj; + string = call_frame_cursor(frame); + + // TODO: sanitize string for valid UTF-8 + max_bytes = call_frame_code_bytes_left(frame); + length = uacpi_strnlen(string, max_bytes); + + if (uacpi_unlikely((length == max_bytes) || (string[length++] != 0x00))) + return UACPI_STATUS_AML_BAD_ENCODING; + + obj->buffer->text = uacpi_kernel_alloc(length); + if (uacpi_unlikely(obj->buffer->text == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + uacpi_memcpy(obj->buffer->text, string, length); + obj->buffer->size = length; + frame->code_offset += length; + return UACPI_STATUS_OK; +} + +static uacpi_status handle_package(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_package *package; + uacpi_u32 num_elements, num_defined_elements, i; + + /* + * Layout of items here: + * [0] -> Package length, not interesting + * [1] -> Immediate or integer object, depending on PackageOp/VarPackageOp + * [2..N-2] -> AML pc+Package element pairs + * [N-1] -> The resulting package object that we're constructing + */ + package = item_array_last(&op_ctx->items)->obj->package; + + // 1. Detect how many elements we have, do sanity checking + if (op_ctx->op->code == UACPI_AML_OP_VarPackageOp) { + uacpi_object *var_num_elements; + + var_num_elements = item_array_at(&op_ctx->items, 1)->obj; + if (uacpi_unlikely(var_num_elements->integer > 0xE0000000)) { + uacpi_error( + "package is too large (%"UACPI_PRIu64"), assuming " + "corrupted bytestream\n", UACPI_FMT64(var_num_elements->integer) + ); + return UACPI_STATUS_AML_BAD_ENCODING; + } + num_elements = var_num_elements->integer; + } else { + num_elements = item_array_at(&op_ctx->items, 1)->immediate; + } + + num_defined_elements = (item_array_size(&op_ctx->items) - 3) / 2; + if (uacpi_unlikely(num_defined_elements > num_elements)) { + uacpi_warn( + "too many package initializers: %u, truncating to %u\n", + num_defined_elements, num_elements + ); + + num_defined_elements = num_elements; + } + + // 2. Create every object in the package, start as uninitialized + if (uacpi_unlikely(!uacpi_package_fill(package, num_elements, + UACPI_PREALLOC_OBJECTS_YES))) + return UACPI_STATUS_OUT_OF_MEMORY; + + // 3. Go through every defined object and copy it into the package + for (i = 0; i < num_defined_elements; ++i) { + uacpi_size base_pkg_index; + uacpi_status ret; + struct item *item; + uacpi_object *obj; + + base_pkg_index = (i * 2) + 2; + item = item_array_at(&op_ctx->items, base_pkg_index + 1); + obj = item->obj; + + if (obj != UACPI_NULL && obj->type == UACPI_OBJECT_REFERENCE) { + /* + * For named objects we don't actually need the object itself, but + * simply the path to it. Often times objects referenced by the + * package are not defined until later so it's not possible to + * resolve them. For uniformity and to follow the behavior of NT, + * simply convert the name string to a path string object to be + * resolved later when actually needed. + */ + if (obj->flags == UACPI_REFERENCE_KIND_NAMED) { + uacpi_object_unref(obj); + item->obj = UACPI_NULL; + obj = UACPI_NULL; + } else { + obj = uacpi_unwrap_internal_reference(obj); + } + } + + if (obj == UACPI_NULL) { + uacpi_size length; + uacpi_char *path; + + obj = uacpi_create_object(UACPI_OBJECT_STRING); + if (uacpi_unlikely(obj == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + ret = name_string_to_path( + ctx->cur_frame, + item_array_at(&op_ctx->items, base_pkg_index)->immediate, + &path, &length + ); + if (uacpi_unlikely_error(ret)) + return ret; + + obj->flags = UACPI_STRING_KIND_PATH; + obj->buffer->text = path; + obj->buffer->size = length; + + item->obj = obj; + item->type = ITEM_OBJECT; + } + + ret = uacpi_object_assign(package->objects[i], obj, + UACPI_ASSIGN_BEHAVIOR_DEEP_COPY); + if (uacpi_unlikely_error(ret)) + return ret; + } + + return UACPI_STATUS_OK; +} + +static uacpi_size sizeof_int(void) +{ + return g_uacpi_rt_ctx.is_rev1 ? 4 : 8; +} + +static uacpi_status get_object_storage( + uacpi_object *obj, uacpi_data_view *out_buf, uacpi_bool include_null +) +{ + switch (obj->type) { + case UACPI_OBJECT_INTEGER: + out_buf->length = sizeof_int(); + out_buf->data = &obj->integer; + break; + case UACPI_OBJECT_STRING: + out_buf->length = obj->buffer->size; + if (out_buf->length && !include_null) + out_buf->length--; + + out_buf->text = obj->buffer->text; + break; + case UACPI_OBJECT_BUFFER: + if (obj->buffer->size == 0) { + out_buf->bytes = UACPI_NULL; + out_buf->length = 0; + break; + } + + out_buf->length = obj->buffer->size; + out_buf->bytes = obj->buffer->data; + break; + case UACPI_OBJECT_REFERENCE: + return UACPI_STATUS_INVALID_ARGUMENT; + default: + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + } + + return UACPI_STATUS_OK; +} + +static uacpi_u8 *buffer_index_cursor(uacpi_buffer_index *buf_idx) +{ + uacpi_u8 *out_cursor; + + out_cursor = buf_idx->buffer->data; + out_cursor += buf_idx->idx; + + return out_cursor; +} + +static void write_buffer_index(uacpi_buffer_index *buf_idx, + uacpi_data_view *src_buf) +{ + uacpi_memcpy_zerout(buffer_index_cursor(buf_idx), src_buf->bytes, + 1, src_buf->length); +} + +/* + * The word "implicit cast" here is only because it's called that in + * the specification. In reality, we just copy one buffer to another + * because that's what NT does. + */ +static uacpi_status object_assign_with_implicit_cast( + uacpi_object *dst, uacpi_object *src, uacpi_data_view *wtr_response +) +{ + uacpi_status ret; + uacpi_data_view src_buf; + + ret = get_object_storage(src, &src_buf, UACPI_FALSE); + if (uacpi_unlikely_error(ret)) + goto out_bad_cast; + + switch (dst->type) { + case UACPI_OBJECT_INTEGER: + case UACPI_OBJECT_STRING: + case UACPI_OBJECT_BUFFER: { + uacpi_data_view dst_buf; + + ret = get_object_storage(dst, &dst_buf, UACPI_FALSE); + if (uacpi_unlikely_error(ret)) + goto out_bad_cast; + + uacpi_memcpy_zerout( + dst_buf.bytes, src_buf.bytes, dst_buf.length, src_buf.length + ); + break; + } + + case UACPI_OBJECT_BUFFER_FIELD: + uacpi_write_buffer_field( + &dst->buffer_field, src_buf.bytes, src_buf.length + ); + break; + + case UACPI_OBJECT_FIELD_UNIT: + return uacpi_write_field_unit( + dst->field_unit, src_buf.bytes, src_buf.length, + wtr_response + ); + + case UACPI_OBJECT_BUFFER_INDEX: + write_buffer_index(&dst->buffer_index, &src_buf); + break; + + default: + ret = UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + goto out_bad_cast; + } + + return ret; + +out_bad_cast: + uacpi_error( + "attempted to perform an invalid implicit cast (%s -> %s)\n", + uacpi_object_type_to_string(src->type), + uacpi_object_type_to_string(dst->type) + ); + return ret; +} + +enum argx_or_localx { + ARGX, + LOCALX, +}; + +static uacpi_status handle_arg_or_local( + struct execution_context *ctx, + uacpi_size idx, enum argx_or_localx type +) +{ + uacpi_object **src; + struct item *dst; + enum uacpi_reference_kind kind; + + if (type == ARGX) { + src = &ctx->cur_frame->args[idx]; + kind = UACPI_REFERENCE_KIND_ARG; + } else { + src = &ctx->cur_frame->locals[idx]; + kind = UACPI_REFERENCE_KIND_LOCAL; + } + + if (*src == UACPI_NULL) { + uacpi_object *default_value; + + default_value = uacpi_create_object(UACPI_OBJECT_UNINITIALIZED); + if (uacpi_unlikely(default_value == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + *src = uacpi_create_internal_reference(kind, default_value); + if (uacpi_unlikely(*src == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + uacpi_object_unref(default_value); + } + + dst = item_array_last(&ctx->cur_op_ctx->items); + dst->obj = *src; + dst->type = ITEM_OBJECT; + uacpi_object_ref(dst->obj); + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_local(struct execution_context *ctx) +{ + uacpi_size idx; + struct op_context *op_ctx = ctx->cur_op_ctx; + + idx = op_ctx->op->code - UACPI_AML_OP_Local0Op; + return handle_arg_or_local(ctx, idx, LOCALX); +} + +static uacpi_status handle_arg(struct execution_context *ctx) +{ + uacpi_size idx; + struct op_context *op_ctx = ctx->cur_op_ctx; + + idx = op_ctx->op->code - UACPI_AML_OP_Arg0Op; + return handle_arg_or_local(ctx, idx, ARGX); +} + +static uacpi_status handle_named_object(struct execution_context *ctx) +{ + struct uacpi_namespace_node *src; + struct item *dst; + + src = item_array_at(&ctx->cur_op_ctx->items, 0)->node; + dst = item_array_at(&ctx->cur_op_ctx->items, 1); + + dst->obj = src->object; + dst->type = ITEM_OBJECT; + uacpi_object_ref(dst->obj); + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_create_alias(struct execution_context *ctx) +{ + uacpi_namespace_node *src, *dst; + + src = item_array_at(&ctx->cur_op_ctx->items, 0)->node; + dst = item_array_at(&ctx->cur_op_ctx->items, 1)->node; + + dst->object = src->object; + dst->flags = UACPI_NAMESPACE_NODE_FLAG_ALIAS; + uacpi_object_ref(dst->object); + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_create_op_region(struct execution_context *ctx) +{ + uacpi_namespace_node *node; + uacpi_object *obj; + uacpi_operation_region *op_region; + uacpi_u64 region_end; + + node = item_array_at(&ctx->cur_op_ctx->items, 0)->node; + obj = item_array_at(&ctx->cur_op_ctx->items, 4)->obj; + op_region = obj->op_region; + + op_region->space = item_array_at(&ctx->cur_op_ctx->items, 1)->immediate; + op_region->offset = item_array_at(&ctx->cur_op_ctx->items, 2)->obj->integer; + op_region->length = item_array_at(&ctx->cur_op_ctx->items, 3)->obj->integer; + region_end = op_region->offset + op_region->length; + + if (uacpi_unlikely(op_region->length == 0)) { + // Don't abort here, as long as it's never accessed we don't care + uacpi_warn("unusable/empty operation region %.4s\n", node->name.text); + } else if (uacpi_unlikely(op_region->offset > region_end)) { + uacpi_error( + "invalid operation region %.4s bounds: offset=0x%"UACPI_PRIX64 + " length=0x%"UACPI_PRIX64"\n", node->name.text, + UACPI_FMT64(op_region->offset), UACPI_FMT64(op_region->length) + ); + return UACPI_STATUS_AML_BAD_ENCODING; + } + + if (op_region->space == UACPI_ADDRESS_SPACE_PCC && op_region->offset > 255) { + uacpi_warn( + "invalid PCC operation region %.4s subspace %"UACPI_PRIX64"\n", + node->name.text, UACPI_FMT64(op_region->offset) + ); + } + + node->object = uacpi_create_internal_reference( + UACPI_REFERENCE_KIND_NAMED, obj + ); + if (uacpi_unlikely(node->object == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + uacpi_initialize_opregion_node(node); + return UACPI_STATUS_OK; +} + +static uacpi_status table_id_error( + const uacpi_char *opcode, const uacpi_char *arg, + uacpi_buffer *str +) +{ + uacpi_error("%s: invalid %s '%s'\n", opcode, arg, str->text); + return UACPI_STATUS_AML_BAD_ENCODING; +} + +static void report_table_id_find_error( + const uacpi_char *opcode, struct uacpi_table_identifiers *id, + uacpi_status ret +) +{ + uacpi_error( + "%s: unable to find table '%.4s' (OEM ID '%.6s', " + "OEM Table ID '%.8s'): %s\n", + opcode, id->signature.text, id->oemid, id->oem_table_id, + uacpi_status_to_string(ret) + ); +} + +static uacpi_status build_table_id( + const uacpi_char *opcode, + struct uacpi_table_identifiers *out_id, + uacpi_buffer *signature, uacpi_buffer *oem_id, + uacpi_buffer *oem_table_id +) +{ + if (uacpi_unlikely(signature->size != (sizeof(uacpi_object_name) + 1))) + return table_id_error(opcode, "SignatureString", signature); + + uacpi_memcpy(out_id->signature.text, signature->text, + sizeof(uacpi_object_name)); + + if (uacpi_unlikely(oem_id->size > (sizeof(out_id->oemid) + 1))) + return table_id_error(opcode, "OemIDString", oem_id); + + uacpi_memcpy_zerout( + out_id->oemid, oem_id->text, + sizeof(out_id->oemid), oem_id->size ? oem_id->size - 1 : 0 + ); + + if (uacpi_unlikely(oem_table_id->size > (sizeof(out_id->oem_table_id) + 1))) + return table_id_error(opcode, "OemTableIDString", oem_table_id); + + uacpi_memcpy_zerout( + out_id->oem_table_id, oem_table_id->text, + sizeof(out_id->oem_table_id), + oem_table_id->size ? oem_table_id->size - 1 : 0 + ); + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_create_data_region(struct execution_context *ctx) +{ + uacpi_status ret; + struct item_array *items = &ctx->cur_op_ctx->items; + struct uacpi_table_identifiers table_id; + uacpi_table table; + uacpi_namespace_node *node; + uacpi_object *obj; + uacpi_operation_region *op_region; + + node = item_array_at(items, 0)->node; + + ret = build_table_id( + "DataTableRegion", &table_id, + item_array_at(items, 1)->obj->buffer, + item_array_at(items, 2)->obj->buffer, + item_array_at(items, 3)->obj->buffer + ); + if (uacpi_unlikely_error(ret)) + return ret; + + ret = uacpi_table_find(&table_id, &table); + if (uacpi_unlikely_error(ret)) { + report_table_id_find_error("DataTableRegion", &table_id, ret); + return ret; + } + + obj = item_array_at(items, 4)->obj; + op_region = obj->op_region; + op_region->space = UACPI_ADDRESS_SPACE_TABLE_DATA; + op_region->offset = table.virt_addr; + op_region->length = table.hdr->length; + op_region->table_idx = table.index; + + node->object = uacpi_create_internal_reference( + UACPI_REFERENCE_KIND_NAMED, obj + ); + if (uacpi_unlikely(node->object == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + uacpi_initialize_opregion_node(node); + return UACPI_STATUS_OK; +} + +static uacpi_bool is_dynamic_table_load(enum uacpi_table_load_cause cause) +{ + return cause != UACPI_TABLE_LOAD_CAUSE_INIT; +} + +static void prepare_table_load( + void *ptr, enum uacpi_table_load_cause cause, uacpi_control_method *in_method +) +{ + struct acpi_dsdt *dsdt = ptr; + enum uacpi_log_level log_level = UACPI_LOG_TRACE; + const uacpi_char *log_prefix = "load of"; + + if (is_dynamic_table_load(cause)) { + log_prefix = cause == UACPI_TABLE_LOAD_CAUSE_HOST ? + "host-invoked load of" : "dynamic load of"; + log_level = UACPI_LOG_INFO; + } + + uacpi_log_lvl( + log_level, "%s "UACPI_PRI_TBL_HDR"\n", + log_prefix, UACPI_FMT_TBL_HDR(&dsdt->hdr) + ); + + in_method->code = dsdt->definition_block; + in_method->size = dsdt->hdr.length - sizeof(dsdt->hdr); + in_method->named_objects_persist = UACPI_TRUE; +} + +static uacpi_status do_load_table( + uacpi_namespace_node *parent, struct acpi_sdt_hdr *tbl, + enum uacpi_table_load_cause cause +) +{ + struct uacpi_control_method method = { 0 }; + uacpi_status ret; + + prepare_table_load(tbl, cause, &method); + + ret = uacpi_execute_control_method(parent, &method, UACPI_NULL, UACPI_NULL); + if (uacpi_unlikely_error(ret)) + return ret; + + if (is_dynamic_table_load(cause)) + uacpi_events_match_post_dynamic_table_load(); + + return ret; +} + +static uacpi_status handle_load_table(struct execution_context *ctx) +{ + uacpi_status ret; + struct item_array *items = &ctx->cur_op_ctx->items; + struct item *root_node_item; + struct uacpi_table_identifiers table_id; + uacpi_table table; + uacpi_buffer *root_path, *param_path; + uacpi_control_method *method; + uacpi_namespace_node *root_node, *param_node = UACPI_NULL; + + /* + * If we already have the last true/false object loaded, this is a second + * invocation of this handler. For the second invocation we want to detect + * new AML GPE handlers that might've been loaded, as well as potentially + * remove the target. + */ + if (item_array_size(items) == 12) { + uacpi_size idx; + struct uacpi_table tmp_table = { 0 }; + + idx = item_array_at(items, 2)->immediate; + tmp_table.index = idx; + uacpi_table_unref(&tmp_table); + + /* + * If this load failed, remove the target that was provided via + * ParameterPathString so that it doesn't get stored to. + */ + if (uacpi_unlikely(item_array_at(items, 11)->obj->integer == 0)) { + uacpi_object *target; + + target = item_array_at(items, 3)->obj; + if (target != UACPI_NULL) { + uacpi_object_unref(target); + item_array_at(items, 3)->obj = UACPI_NULL; + } + + return UACPI_STATUS_OK; + } + + uacpi_events_match_post_dynamic_table_load(); + return UACPI_STATUS_OK; + } + + ret = build_table_id( + "LoadTable", &table_id, + item_array_at(items, 5)->obj->buffer, + item_array_at(items, 6)->obj->buffer, + item_array_at(items, 7)->obj->buffer + ); + if (uacpi_unlikely_error(ret)) + return ret; + + root_path = item_array_at(items, 8)->obj->buffer; + param_path = item_array_at(items, 9)->obj->buffer; + root_node_item = item_array_at(items, 0); + + if (root_path->size > 1) { + ret = uacpi_namespace_node_resolve( + ctx->cur_frame->cur_scope, root_path->text, UACPI_SHOULD_LOCK_NO, + UACPI_MAY_SEARCH_ABOVE_PARENT_YES, UACPI_PERMANENT_ONLY_NO, + &root_node + ); + if (uacpi_unlikely_error(ret)) { + table_id_error("LoadTable", "RootPathString", root_path); + if (ret == UACPI_STATUS_NOT_FOUND) + ret = UACPI_STATUS_AML_UNDEFINED_REFERENCE; + return ret; + } + } else { + root_node = uacpi_namespace_root(); + } + + root_node_item->node = root_node; + root_node_item->type = ITEM_NAMESPACE_NODE; + uacpi_shareable_ref(root_node); + + if (param_path->size > 1) { + struct item *param_item; + + ret = uacpi_namespace_node_resolve( + root_node, param_path->text, UACPI_SHOULD_LOCK_NO, + UACPI_MAY_SEARCH_ABOVE_PARENT_YES, UACPI_PERMANENT_ONLY_NO, + ¶m_node + ); + if (uacpi_unlikely_error(ret)) { + table_id_error("LoadTable", "ParameterPathString", root_path); + if (ret == UACPI_STATUS_NOT_FOUND) + ret = UACPI_STATUS_AML_UNDEFINED_REFERENCE; + return ret; + } + + param_item = item_array_at(items, 3); + param_item->obj = param_node->object; + uacpi_object_ref(param_item->obj); + param_item->type = ITEM_OBJECT; + } + + ret = uacpi_table_find(&table_id, &table); + if (uacpi_unlikely_error(ret)) { + report_table_id_find_error("LoadTable", &table_id, ret); + return ret; + } + uacpi_table_mark_as_loaded(table.index); + + item_array_at(items, 2)->immediate = table.index; + method = item_array_at(items, 1)->obj->method; + prepare_table_load(table.hdr, UACPI_TABLE_LOAD_CAUSE_LOAD_TABLE_OP, method); + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_load(struct execution_context *ctx) +{ + uacpi_status ret; + struct item_array *items = &ctx->cur_op_ctx->items; + uacpi_table table; + uacpi_control_method *method; + uacpi_object *src; + struct acpi_sdt_hdr *src_table = UACPI_NULL; + void *table_buffer; + uacpi_size declared_size; + uacpi_bool unmap_src = UACPI_FALSE; + + /* + * If we already have the last true/false object loaded, this is a second + * invocation of this handler. For the second invocation we simply want to + * detect new AML GPE handlers that might've been loaded. + * We do this only if table load was successful though. + */ + if (item_array_size(items) == 5) { + if (item_array_at(items, 4)->obj->integer != 0) + uacpi_events_match_post_dynamic_table_load(); + return UACPI_STATUS_OK; + } + + src = item_array_at(items, 2)->obj; + + switch (src->type) { + case UACPI_OBJECT_OPERATION_REGION: { + uacpi_operation_region *op_region; + + op_region = src->op_region; + if (uacpi_unlikely( + op_region->space != UACPI_ADDRESS_SPACE_SYSTEM_MEMORY + )) { + uacpi_error("Load: operation region is not SystemMemory\n"); + goto error_out; + } + + if (uacpi_unlikely(op_region->length < sizeof(struct acpi_sdt_hdr))) { + uacpi_error( + "Load: operation region is too small: %"UACPI_PRIu64"\n", + UACPI_FMT64(op_region->length) + ); + goto error_out; + } + + src_table = uacpi_kernel_map(op_region->offset, op_region->length); + if (uacpi_unlikely(src_table == UACPI_NULL)) { + uacpi_error( + "Load: failed to map operation region " + "0x%016"UACPI_PRIX64" -> 0x%016"UACPI_PRIX64"\n", + UACPI_FMT64(op_region->offset), + UACPI_FMT64(op_region->offset + op_region->length) + ); + goto error_out; + } + + unmap_src = UACPI_TRUE; + declared_size = op_region->length; + break; + } + + case UACPI_OBJECT_BUFFER: { + uacpi_buffer *buffer; + + buffer = src->buffer; + if (buffer->size < sizeof(struct acpi_sdt_hdr)) { + uacpi_error( + "Load: buffer is too small: %zu\n", + buffer->size + ); + goto error_out; + } + + src_table = buffer->data; + declared_size = buffer->size; + break; + } + + default: + uacpi_error( + "Load: invalid argument '%s', expected " + "Buffer/Field/OperationRegion\n", + uacpi_object_type_to_string(src->type) + ); + goto error_out; + } + + if (uacpi_unlikely(src_table->length > declared_size)) { + uacpi_error( + "Load: table size %u is larger than the declared size %zu\n", + src_table->length, declared_size + ); + goto error_out; + } + + if (uacpi_unlikely(src_table->length < sizeof(struct acpi_sdt_hdr))) { + uacpi_error("Load: table size %u is too small\n", src_table->length); + goto error_out; + } + + table_buffer = uacpi_kernel_alloc(src_table->length); + if (uacpi_unlikely(table_buffer == UACPI_NULL)) + goto error_out; + + uacpi_memcpy(table_buffer, src_table, src_table->length); + + if (unmap_src) { + uacpi_kernel_unmap(src_table, declared_size); + unmap_src = UACPI_FALSE; + } + + ret = uacpi_table_install_with_origin( + table_buffer, UACPI_TABLE_ORIGIN_FIRMWARE_VIRTUAL, &table + ); + if (uacpi_unlikely_error(ret)) { + uacpi_free(table_buffer, src_table->length); + + if (ret != UACPI_STATUS_OVERRIDDEN) + goto error_out; + } + uacpi_table_mark_as_loaded(table.index); + + item_array_at(items, 0)->node = uacpi_namespace_root(); + + method = item_array_at(items, 1)->obj->method; + prepare_table_load(table.ptr, UACPI_TABLE_LOAD_CAUSE_LOAD_OP, method); + + return UACPI_STATUS_OK; + +error_out: + if (unmap_src && src_table) + uacpi_kernel_unmap(src_table, declared_size); + return UACPI_STATUS_OK; +} + +uacpi_status uacpi_execute_table(void *tbl, enum uacpi_table_load_cause cause) +{ + uacpi_status ret; + + ret = uacpi_namespace_write_lock(); + if (uacpi_unlikely_error(ret)) + return ret; + + ret = do_load_table(uacpi_namespace_root(), tbl, cause); + + uacpi_namespace_write_unlock(); + return ret; +} + +static uacpi_u32 get_field_length(struct item *item) +{ + struct package_length *pkg = &item->pkg; + return pkg->end - pkg->begin; +} + +struct field_specific_data { + uacpi_namespace_node *region; + struct uacpi_field_unit *field0; + struct uacpi_field_unit *field1; + uacpi_u64 value; +}; + +static uacpi_status ensure_is_a_field_unit(uacpi_namespace_node *node, + uacpi_field_unit **out_field) +{ + uacpi_object *obj; + + obj = uacpi_namespace_node_get_object(node); + if (obj->type != UACPI_OBJECT_FIELD_UNIT) { + uacpi_error( + "invalid argument: '%.4s' is not a field unit (%s)\n", + node->name.text, uacpi_object_type_to_string(obj->type) + ); + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + } + + *out_field = obj->field_unit; + return UACPI_STATUS_OK; +} + +static uacpi_status ensure_is_an_op_region(uacpi_namespace_node *node, + uacpi_namespace_node **out_node) +{ + uacpi_object *obj; + + obj = uacpi_namespace_node_get_object(node); + if (obj->type != UACPI_OBJECT_OPERATION_REGION) { + uacpi_error( + "invalid argument: '%.4s' is not an operation region (%s)\n", + node->name.text, uacpi_object_type_to_string(obj->type) + ); + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + } + + *out_node = node; + return UACPI_STATUS_OK; +} + +static uacpi_status handle_create_field(struct execution_context *ctx) +{ + uacpi_status ret; + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_namespace_node *node; + uacpi_object *obj, *connection_obj = UACPI_NULL; + struct field_specific_data field_data = { 0 }; + uacpi_size i = 1, bit_offset = 0; + uacpi_u32 length, pin_offset = 0; + + uacpi_u8 raw_value, access_type, lock_rule, update_rule; + uacpi_u8 access_attrib = 0, access_length = 0; + + switch (op_ctx->op->code) { + case UACPI_AML_OP_FieldOp: + node = item_array_at(&op_ctx->items, i++)->node; + ret = ensure_is_an_op_region(node, &field_data.region); + if (uacpi_unlikely_error(ret)) + return ret; + break; + + case UACPI_AML_OP_BankFieldOp: + node = item_array_at(&op_ctx->items, i++)->node; + ret = ensure_is_an_op_region(node, &field_data.region); + if (uacpi_unlikely_error(ret)) + return ret; + + node = item_array_at(&op_ctx->items, i++)->node; + ret = ensure_is_a_field_unit(node, &field_data.field0); + if (uacpi_unlikely_error(ret)) + return ret; + + field_data.value = item_array_at(&op_ctx->items, i++)->obj->integer; + break; + + case UACPI_AML_OP_IndexFieldOp: + node = item_array_at(&op_ctx->items, i++)->node; + ret = ensure_is_a_field_unit(node, &field_data.field0); + if (uacpi_unlikely_error(ret)) + return ret; + + node = item_array_at(&op_ctx->items, i++)->node; + ret = ensure_is_a_field_unit(node, &field_data.field1); + if (uacpi_unlikely_error(ret)) + return ret; + break; + + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + /* + * ByteData + * bit 0-3: AccessType + * 0 AnyAcc + * 1 ByteAcc + * 2 WordAcc + * 3 DWordAcc + * 4 QWordAcc + * 5 BufferAcc + * 6 Reserved + * 7-15 Reserved + * bit 4: LockRule + * 0 NoLock + * 1 Lock + * bit 5-6: UpdateRule + * 0 Preserve + * 1 WriteAsOnes + * 2 WriteAsZeros + * bit 7: Reserved (must be 0) + */ + raw_value = item_array_at(&op_ctx->items, i++)->immediate; + access_type = (raw_value >> 0) & 0xF; + lock_rule = (raw_value >> 4) & 0x1; + update_rule = (raw_value >> 5) & 0x3; + + while (i < item_array_size(&op_ctx->items)) { + struct item *item; + item = item_array_at(&op_ctx->items, i++); + + // An actual field object + if (item->type == ITEM_NAMESPACE_NODE) { + uacpi_field_unit *field; + + length = get_field_length(item_array_at(&op_ctx->items, i++)); + node = item->node; + + obj = item_array_at(&op_ctx->items, i++)->obj; + field = obj->field_unit; + + field->update_rule = update_rule; + field->lock_rule = lock_rule; + field->attributes = access_attrib; + field->access_length = access_length; + + /* + * 0 AnyAcc + * 1 ByteAcc + * 2 WordAcc + * 3 DWordAcc + * 4 QWordAcc + * 5 BufferAcc + * 6 Reserved + * 7-15 Reserved + */ + switch (access_type) { + case 0: + // TODO: optimize to calculate best access strategy + UACPI_FALLTHROUGH; + case 1: + case 5: + field->access_width_bytes = 1; + break; + case 2: + field->access_width_bytes = 2; + break; + case 3: + field->access_width_bytes = 4; + break; + case 4: + field->access_width_bytes = 8; + break; + default: + uacpi_error("invalid field '%.4s' access type %d\n", + node->name.text, access_type); + return UACPI_STATUS_AML_BAD_ENCODING; + } + + field->bit_length = length; + field->pin_offset = pin_offset; + + // FIXME: overflow, OOB, etc checks + field->byte_offset = UACPI_ALIGN_DOWN( + bit_offset / 8, + field->access_width_bytes, + uacpi_u32 + ); + + field->bit_offset_within_first_byte = bit_offset; + field->bit_offset_within_first_byte = + bit_offset & ((field->access_width_bytes * 8) - 1); + + switch (op_ctx->op->code) { + case UACPI_AML_OP_FieldOp: + field->region = field_data.region; + uacpi_shareable_ref(field->region); + + field->kind = UACPI_FIELD_UNIT_KIND_NORMAL; + break; + + case UACPI_AML_OP_BankFieldOp: + field->bank_region = field_data.region; + uacpi_shareable_ref(field->bank_region); + + field->bank_selection = field_data.field0; + uacpi_shareable_ref(field->bank_selection); + + field->bank_value = field_data.value; + field->kind = UACPI_FIELD_UNIT_KIND_BANK; + break; + + case UACPI_AML_OP_IndexFieldOp: + field->index = field_data.field0; + uacpi_shareable_ref(field->index); + + field->data = field_data.field1; + uacpi_shareable_ref(field->data); + + field->kind = UACPI_FIELD_UNIT_KIND_INDEX; + break; + + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + field->connection = connection_obj; + if (field->connection) + uacpi_object_ref(field->connection); + + node->object = uacpi_create_internal_reference( + UACPI_REFERENCE_KIND_NAMED, obj + ); + if (uacpi_unlikely(node->object == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + ret = do_install_node_item(ctx->cur_frame, item); + if (uacpi_unlikely_error(ret)) + return ret; + + bit_offset += length; + pin_offset += length; + continue; + } + + // All other stuff + switch ((int)item->immediate) { + // ReservedField := 0x00 PkgLength + case 0x00: + length = get_field_length(item_array_at(&op_ctx->items, i++)); + bit_offset += length; + pin_offset += length; + break; + + // AccessField := 0x01 AccessType AccessAttrib + // ExtendedAccessField := 0x03 AccessType ExtendedAccessAttrib AccessLength + case 0x01: + case 0x03: + raw_value = item_array_at(&op_ctx->items, i++)->immediate; + + access_type = raw_value & 0xF; + access_attrib = (raw_value >> 6) & 0x3; + + raw_value = item_array_at(&op_ctx->items, i++)->immediate; + + /* + * Bits 7:6 + * 0 = AccessAttrib = Normal Access Attributes + * 1 = AccessAttrib = AttribBytes (x) + * 2 = AccessAttrib = AttribRawBytes (x) + * 3 = AccessAttrib = AttribRawProcessBytes (x) + * x is encoded as bits 0:7 of the AccessAttrib byte. + */ + if (access_attrib) { + switch (access_attrib) { + case 1: + access_attrib = UACPI_ACCESS_ATTRIBUTE_BYTES; + break; + case 2: + access_attrib = UACPI_ACCESS_ATTRIBUTE_RAW_BYTES; + break; + case 3: + access_attrib = UACPI_ACCESS_ATTRIBUTE_RAW_PROCESS_BYTES; + break; + } + + access_length = raw_value; + } else { // Normal access attributes + access_attrib = raw_value; + } + + if (item->immediate == 3) + access_length = item_array_at(&op_ctx->items, i++)->immediate; + break; + + // ConnectField := <0x02 NameString> | <0x02 BufferData> + case 0x02: + connection_obj = item_array_at(&op_ctx->items, i++)->obj; + pin_offset = 0; + break; + + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + } + + return UACPI_STATUS_OK; +} + +static void truncate_number_if_needed(uacpi_object *obj) +{ + if (!g_uacpi_rt_ctx.is_rev1) + return; + + obj->integer &= 0xFFFFFFFF; +} + +static uacpi_u64 ones(void) +{ + return g_uacpi_rt_ctx.is_rev1 ? 0xFFFFFFFF : 0xFFFFFFFFFFFFFFFF; +} + +static uacpi_status method_get_ret_target(struct execution_context *ctx, + uacpi_object **out_operand) +{ + uacpi_size depth; + + // Check if we're targeting the previous call frame + depth = call_frame_array_size(&ctx->call_stack); + if (depth > 1) { + struct op_context *op_ctx; + struct call_frame *frame; + + frame = call_frame_array_at(&ctx->call_stack, depth - 2); + depth = op_context_array_size(&frame->pending_ops); + + // Ok, no one wants the return value at call site. Discard it. + if (!depth) { + *out_operand = UACPI_NULL; + return UACPI_STATUS_OK; + } + + op_ctx = op_context_array_at(&frame->pending_ops, depth - 1); + + /* + * Prevent the table being dynamically loaded from attempting to return + * a value to the caller. This is unlikely to be ever encountered in the + * wild, but we should still guard against the possibility. + */ + if (uacpi_unlikely(op_ctx->op->code == UACPI_AML_OP_LoadOp || + op_ctx->op->code == UACPI_AML_OP_LoadTableOp)) { + *out_operand = UACPI_NULL; + return UACPI_STATUS_OK; + } + + *out_operand = item_array_last(&op_ctx->items)->obj; + return UACPI_STATUS_OK; + } + + return UACPI_STATUS_NOT_FOUND; +} + +static uacpi_status method_get_ret_object(struct execution_context *ctx, + uacpi_object **out_obj) +{ + uacpi_status ret; + + ret = method_get_ret_target(ctx, out_obj); + if (ret == UACPI_STATUS_NOT_FOUND) { + *out_obj = ctx->ret; + return UACPI_STATUS_OK; + } + if (ret != UACPI_STATUS_OK || *out_obj == UACPI_NULL) + return ret; + + *out_obj = uacpi_unwrap_internal_reference(*out_obj); + return UACPI_STATUS_OK; +} + +static struct code_block *find_last_block(struct code_block_array *blocks, + enum code_block_type type) +{ + uacpi_size i; + + i = code_block_array_size(blocks); + while (i-- > 0) { + struct code_block *block; + + block = code_block_array_at(blocks, i); + if (block->type == type) + return block; + } + + return UACPI_NULL; +} + +static void update_scope(struct call_frame *frame) +{ + struct code_block *block; + + block = find_last_block(&frame->code_blocks, CODE_BLOCK_SCOPE); + if (block == UACPI_NULL) { + frame->cur_scope = uacpi_namespace_root(); + return; + } + + frame->cur_scope = block->node; +} + +static uacpi_status begin_block_execution(struct execution_context *ctx) +{ + struct call_frame *cur_frame = ctx->cur_frame; + struct op_context *op_ctx = ctx->cur_op_ctx; + struct package_length *pkg; + struct code_block *block; + + block = code_block_array_alloc(&cur_frame->code_blocks); + if (uacpi_unlikely(block == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + pkg = &item_array_at(&op_ctx->items, 0)->pkg; + + // Disarm the tracked package so that we don't skip the Scope + op_ctx->tracked_pkg_idx = 0; + + switch (op_ctx->op->code) { + case UACPI_AML_OP_IfOp: + block->type = CODE_BLOCK_IF; + break; + case UACPI_AML_OP_ElseOp: + block->type = CODE_BLOCK_ELSE; + break; + case UACPI_AML_OP_WhileOp: + block->type = CODE_BLOCK_WHILE; + + if (pkg->begin == cur_frame->prev_while_code_offset) { + uacpi_u64 cur_ticks; + + cur_ticks = uacpi_kernel_get_nanoseconds_since_boot(); + + if (uacpi_unlikely(cur_ticks > block->expiration_point)) { + uacpi_error("loop time out after running for %u seconds\n", + g_uacpi_rt_ctx.loop_timeout_seconds); + code_block_array_pop(&cur_frame->code_blocks); + return UACPI_STATUS_AML_LOOP_TIMEOUT; + } + + block->expiration_point = cur_frame->prev_while_expiration; + } else { + /* + * Calculate the expiration point for this loop. + * If a loop is executed past this point, it will get aborted. + */ + block->expiration_point = uacpi_kernel_get_nanoseconds_since_boot(); + block->expiration_point += + g_uacpi_rt_ctx.loop_timeout_seconds * UACPI_NANOSECONDS_PER_SEC; + } + break; + case UACPI_AML_OP_ScopeOp: + case UACPI_AML_OP_DeviceOp: + case UACPI_AML_OP_ProcessorOp: + case UACPI_AML_OP_PowerResOp: + case UACPI_AML_OP_ThermalZoneOp: + block->type = CODE_BLOCK_SCOPE; + block->node = item_array_at(&op_ctx->items, 1)->node; + break; + default: + code_block_array_pop(&cur_frame->code_blocks); + return UACPI_STATUS_INVALID_ARGUMENT; + } + + // -1 because we want to re-evaluate at the start of the op next time + block->begin = pkg->begin - 1; + block->end = pkg->end; + ctx->cur_block = block; + + cur_frame->last_while = find_last_block(&cur_frame->code_blocks, + CODE_BLOCK_WHILE); + update_scope(cur_frame); + return UACPI_STATUS_OK; +} + +static void frame_reset_post_end_block(struct execution_context *ctx, + enum code_block_type type) +{ + struct call_frame *frame = ctx->cur_frame; + + if (type == CODE_BLOCK_WHILE) { + struct code_block *block = ctx->cur_block; + + // + 1 here to skip the WhileOp and get to the PkgLength + frame->prev_while_code_offset = block->begin + 1; + frame->prev_while_expiration = block->expiration_point; + } + + code_block_array_pop(&frame->code_blocks); + ctx->cur_block = code_block_array_last(&frame->code_blocks); + + if (type == CODE_BLOCK_WHILE) { + frame->last_while = find_last_block(&frame->code_blocks, type); + } else if (type == CODE_BLOCK_SCOPE) { + update_scope(frame); + } +} + +static void debug_store_no_recurse(const uacpi_char *prefix, uacpi_object *src) +{ + switch (src->type) { + case UACPI_OBJECT_UNINITIALIZED: + uacpi_trace("%s Uninitialized\n", prefix); + break; + case UACPI_OBJECT_STRING: + uacpi_trace("%s String => \"%s\"\n", prefix, src->buffer->text); + break; + case UACPI_OBJECT_INTEGER: + if (g_uacpi_rt_ctx.is_rev1) { + uacpi_trace( + "%s Integer => 0x%08X\n", prefix, (uacpi_u32)src->integer + ); + } else { + uacpi_trace( + "%s Integer => 0x%016"UACPI_PRIX64"\n", prefix, + UACPI_FMT64(src->integer) + ); + } + break; + case UACPI_OBJECT_REFERENCE: + uacpi_trace("%s Reference @%p => %p\n", prefix, src, src->inner_object); + break; + case UACPI_OBJECT_PACKAGE: + uacpi_trace( + "%s Package @%p (%p) (%zu elements)\n", + prefix, src, src->package, src->package->count + ); + break; + case UACPI_OBJECT_BUFFER: + uacpi_trace( + "%s Buffer @%p (%p) (%zu bytes)\n", + prefix, src, src->buffer, src->buffer->size + ); + break; + case UACPI_OBJECT_OPERATION_REGION: + uacpi_trace( + "%s OperationRegion (ASID %d) 0x%016"UACPI_PRIX64 + " -> 0x%016"UACPI_PRIX64"\n", prefix, + src->op_region->space, UACPI_FMT64(src->op_region->offset), + UACPI_FMT64(src->op_region->offset + src->op_region->length) + ); + break; + case UACPI_OBJECT_POWER_RESOURCE: + uacpi_trace( + "%s Power Resource %d %d\n", + prefix, src->power_resource.system_level, + src->power_resource.resource_order + ); + break; + case UACPI_OBJECT_PROCESSOR: + uacpi_trace( + "%s Processor[%d] 0x%08X (%d)\n", + prefix, src->processor->id, src->processor->block_address, + src->processor->block_length + ); + break; + case UACPI_OBJECT_BUFFER_INDEX: + uacpi_trace( + "%s Buffer Index %p[%zu] => 0x%02X\n", + prefix, src->buffer_index.buffer->data, src->buffer_index.idx, + *buffer_index_cursor(&src->buffer_index) + ); + break; + case UACPI_OBJECT_MUTEX: + uacpi_trace( + "%s Mutex @%p (%p => %p) sync level %d\n", + prefix, src, src->mutex, src->mutex->handle, + src->mutex->sync_level + ); + break; + case UACPI_OBJECT_METHOD: + uacpi_trace("%s Method @%p (%p)\n", prefix, src, src->method); + break; + default: + uacpi_trace( + "%s %s @%p\n", + prefix, uacpi_object_type_to_string(src->type), src + ); + } +} + +static uacpi_status debug_store(uacpi_object *src) +{ + /* + * Don't bother running the body if current log level is not set to trace. + * All DebugOp logging is done as TRACE exclusively. + */ + if (!uacpi_should_log(UACPI_LOG_TRACE)) + return UACPI_STATUS_OK; + + src = uacpi_unwrap_internal_reference(src); + + debug_store_no_recurse("[AML DEBUG]", src); + + if (src->type == UACPI_OBJECT_PACKAGE) { + uacpi_package *pkg = src->package; + uacpi_size i; + + for (i = 0; i < pkg->count; ++i) { + uacpi_object *obj = pkg->objects[i]; + if (obj->type == UACPI_OBJECT_REFERENCE && + obj->flags == UACPI_REFERENCE_KIND_PKG_INDEX) + obj = obj->inner_object; + + debug_store_no_recurse("Element:", obj); + } + } + + return UACPI_STATUS_OK; +} + +/* + * NOTE: this function returns the parent object + */ +static uacpi_object *reference_unwind(uacpi_object *obj) +{ + uacpi_object *parent = obj; + + while (obj) { + if (obj->type != UACPI_OBJECT_REFERENCE) + return parent; + + parent = obj; + obj = parent->inner_object; + } + + // This should be unreachable + return UACPI_NULL; +} + +static uacpi_iteration_decision opregion_try_detach_from_parent( + void *user, uacpi_namespace_node *node, uacpi_u32 node_depth +) +{ + uacpi_object *target_object = user; + UACPI_UNUSED(node_depth); + + if (node->object == target_object) { + uacpi_opregion_uninstall_handler(node); + return UACPI_ITERATION_DECISION_BREAK; + } + + return UACPI_ITERATION_DECISION_CONTINUE; +} + +static void object_replace_child(uacpi_object *parent, uacpi_object *new_child) +{ + if (parent->flags == UACPI_REFERENCE_KIND_NAMED && + uacpi_object_is(parent->inner_object, UACPI_OBJECT_OPERATION_REGION)) { + + /* + * We're doing a CopyObject or similar to a namespace node that is an + * operation region. Try to find the parent node and manually detach + * the handler. + */ + opregion_try_detach_from_parent(parent, uacpi_namespace_root(), 0); + uacpi_namespace_do_for_each_child( + uacpi_namespace_root(), opregion_try_detach_from_parent, UACPI_NULL, + UACPI_OBJECT_OPERATION_REGION_BIT, UACPI_MAX_DEPTH_ANY, + UACPI_SHOULD_LOCK_NO, UACPI_PERMANENT_ONLY_NO, parent + ); + } + + uacpi_object_detach_child(parent); + uacpi_object_attach_child(parent, new_child); +} + +/* + * Breakdown of what happens here: + * + * CopyObject(..., Obj) where Obj is: + * 1. LocalX -> Overwrite LocalX. + * 2. NAME -> Overwrite NAME. + * 3. ArgX -> Overwrite ArgX unless ArgX is a reference, in that case + * overwrite the referenced object. + * 4. RefOf -> Not allowed here. + * 5. Index -> Overwrite Object stored at the index. + */ + static uacpi_status copy_object_to_reference(uacpi_object *dst, + uacpi_object *src) +{ + uacpi_status ret; + uacpi_object *src_obj, *new_obj; + + switch (dst->flags) { + case UACPI_REFERENCE_KIND_ARG: { + uacpi_object *referenced_obj; + + referenced_obj = uacpi_unwrap_internal_reference(dst); + if (referenced_obj->type == UACPI_OBJECT_REFERENCE) { + dst = reference_unwind(referenced_obj); + break; + } + + UACPI_FALLTHROUGH; + } + case UACPI_REFERENCE_KIND_LOCAL: + case UACPI_REFERENCE_KIND_PKG_INDEX: + case UACPI_REFERENCE_KIND_NAMED: + break; + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + src_obj = uacpi_unwrap_internal_reference(src); + + new_obj = uacpi_create_object(UACPI_OBJECT_UNINITIALIZED); + if (uacpi_unlikely(new_obj == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + ret = uacpi_object_assign(new_obj, src_obj, + UACPI_ASSIGN_BEHAVIOR_DEEP_COPY); + if (uacpi_unlikely_error(ret)) + return ret; + + object_replace_child(dst, new_obj); + uacpi_object_unref(new_obj); + + return UACPI_STATUS_OK; +} + +/* + * if Store(..., Obj) where Obj is: + * 1. LocalX/Index -> OVERWRITE unless the object is a reference, in that + * case store to the referenced object _with_ implicit + * cast. + * 2. ArgX -> OVERWRITE unless the object is a reference, in that + * case OVERWRITE the referenced object. + * 3. NAME -> Store with implicit cast. + * 4. RefOf -> Not allowed here. + */ +static uacpi_status store_to_reference( + uacpi_object *dst, uacpi_object *src, uacpi_data_view *wtr_response +) +{ + uacpi_object *src_obj; + uacpi_bool overwrite = UACPI_FALSE; + + switch (dst->flags) { + case UACPI_REFERENCE_KIND_LOCAL: + case UACPI_REFERENCE_KIND_ARG: + case UACPI_REFERENCE_KIND_PKG_INDEX: { + uacpi_object *referenced_obj; + + if (dst->flags == UACPI_REFERENCE_KIND_PKG_INDEX) + referenced_obj = dst->inner_object; + else + referenced_obj = uacpi_unwrap_internal_reference(dst); + + if (referenced_obj->type == UACPI_OBJECT_REFERENCE) { + overwrite = dst->flags == UACPI_REFERENCE_KIND_ARG; + dst = reference_unwind(referenced_obj); + break; + } + + overwrite = UACPI_TRUE; + break; + } + case UACPI_REFERENCE_KIND_NAMED: + dst = reference_unwind(dst); + break; + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + src_obj = uacpi_unwrap_internal_reference(src); + overwrite |= dst->inner_object->type == UACPI_OBJECT_UNINITIALIZED; + + if (overwrite) { + uacpi_status ret; + uacpi_object *new_obj; + + new_obj = uacpi_create_object(UACPI_OBJECT_UNINITIALIZED); + if (uacpi_unlikely(new_obj == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + ret = uacpi_object_assign(new_obj, src_obj, + UACPI_ASSIGN_BEHAVIOR_DEEP_COPY); + if (uacpi_unlikely_error(ret)) { + uacpi_object_unref(new_obj); + return ret; + } + + object_replace_child(dst, new_obj); + uacpi_object_unref(new_obj); + return UACPI_STATUS_OK; + } + + return object_assign_with_implicit_cast( + dst->inner_object, src_obj, wtr_response + ); +} + +static uacpi_status handle_ref_or_deref_of(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_object *dst, *src; + + src = item_array_at(&op_ctx->items, 0)->obj; + + if (op_ctx->op->code == UACPI_AML_OP_CondRefOfOp) + dst = item_array_at(&op_ctx->items, 2)->obj; + else + dst = item_array_at(&op_ctx->items, 1)->obj; + + if (op_ctx->op->code == UACPI_AML_OP_DerefOfOp) { + uacpi_bool was_a_reference = UACPI_FALSE; + + if (src->type == UACPI_OBJECT_REFERENCE) { + was_a_reference = UACPI_TRUE; + + /* + * Explicit dereferencing [DerefOf] behavior: + * Simply grabs the bottom-most object that is not a reference. + * This mimics the behavior of NT Acpi.sys: any DerfOf fetches + * the bottom-most reference. Note that this is different from + * ACPICA where DerefOf dereferences one level. + */ + src = reference_unwind(src)->inner_object; + } + + if (src->type == UACPI_OBJECT_BUFFER_INDEX) { + uacpi_buffer_index *buf_idx = &src->buffer_index; + + dst->type = UACPI_OBJECT_INTEGER; + uacpi_memcpy_zerout( + &dst->integer, buffer_index_cursor(buf_idx), + sizeof(dst->integer), 1 + ); + return UACPI_STATUS_OK; + } + + if (!was_a_reference) { + uacpi_error( + "invalid DerefOf argument: %s, expected a reference\n", + uacpi_object_type_to_string(src->type) + ); + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + } + + return uacpi_object_assign(dst, src, + UACPI_ASSIGN_BEHAVIOR_SHALLOW_COPY); + } + + dst->type = UACPI_OBJECT_REFERENCE; + dst->inner_object = src; + uacpi_object_ref(src); + return UACPI_STATUS_OK; +} + +static uacpi_status do_binary_math( + uacpi_object *arg0, uacpi_object *arg1, + uacpi_object *tgt0, uacpi_object *tgt1, + uacpi_aml_op op +) +{ + uacpi_u64 lhs, rhs, res; + uacpi_bool should_negate = UACPI_FALSE; + + lhs = arg0->integer; + rhs = arg1->integer; + + switch (op) + { + case UACPI_AML_OP_AddOp: + res = lhs + rhs; + break; + case UACPI_AML_OP_SubtractOp: + res = lhs - rhs; + break; + case UACPI_AML_OP_MultiplyOp: + res = lhs * rhs; + break; + case UACPI_AML_OP_ShiftLeftOp: + case UACPI_AML_OP_ShiftRightOp: + if (rhs <= (g_uacpi_rt_ctx.is_rev1 ? 31 : 63)) { + if (op == UACPI_AML_OP_ShiftLeftOp) + res = lhs << rhs; + else + res = lhs >> rhs; + } else { + res = 0; + } + break; + case UACPI_AML_OP_NandOp: + should_negate = UACPI_TRUE; + UACPI_FALLTHROUGH; + case UACPI_AML_OP_AndOp: + res = rhs & lhs; + break; + case UACPI_AML_OP_NorOp: + should_negate = UACPI_TRUE; + UACPI_FALLTHROUGH; + case UACPI_AML_OP_OrOp: + res = rhs | lhs; + break; + case UACPI_AML_OP_XorOp: + res = rhs ^ lhs; + break; + case UACPI_AML_OP_DivideOp: + if (uacpi_unlikely(rhs == 0)) { + uacpi_error("attempted to divide by zero\n"); + return UACPI_STATUS_AML_BAD_ENCODING; + } + tgt1->integer = lhs / rhs; + res = lhs % rhs; + break; + case UACPI_AML_OP_ModOp: + if (uacpi_unlikely(rhs == 0)) { + uacpi_error("attempted to calculate modulo of zero\n"); + return UACPI_STATUS_AML_BAD_ENCODING; + } + res = lhs % rhs; + break; + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + if (should_negate) + res = ~res; + + tgt0->integer = res; + return UACPI_STATUS_OK; +} + +static uacpi_status handle_binary_math(struct execution_context *ctx) +{ + uacpi_object *arg0, *arg1, *tgt0, *tgt1; + struct item_array *items = &ctx->cur_op_ctx->items; + uacpi_aml_op op = ctx->cur_op_ctx->op->code; + + arg0 = item_array_at(items, 0)->obj; + arg1 = item_array_at(items, 1)->obj; + + if (op == UACPI_AML_OP_DivideOp) { + tgt0 = item_array_at(items, 4)->obj; + tgt1 = item_array_at(items, 5)->obj; + } else { + tgt0 = item_array_at(items, 3)->obj; + tgt1 = UACPI_NULL; + } + + return do_binary_math(arg0, arg1, tgt0, tgt1, op); +} + +static uacpi_status handle_unary_math(struct execution_context *ctx) +{ + uacpi_object *arg, *tgt; + struct item_array *items = &ctx->cur_op_ctx->items; + uacpi_aml_op op = ctx->cur_op_ctx->op->code; + + arg = item_array_at(items, 0)->obj; + tgt = item_array_at(items, 2)->obj; + + switch (op) { + case UACPI_AML_OP_NotOp: + tgt->integer = ~arg->integer; + truncate_number_if_needed(tgt); + break; + case UACPI_AML_OP_FindSetRightBitOp: + tgt->integer = uacpi_bit_scan_forward(arg->integer); + break; + case UACPI_AML_OP_FindSetLeftBitOp: + tgt->integer = uacpi_bit_scan_backward(arg->integer); + break; + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + return UACPI_STATUS_OK; +} + +static uacpi_status ensure_valid_idx(uacpi_object *obj, uacpi_size idx, + uacpi_size src_size) +{ + if (uacpi_likely(idx < src_size)) + return UACPI_STATUS_OK; + + uacpi_error( + "invalid index %zu, %s@%p has %zu elements\n", + idx, uacpi_object_type_to_string(obj->type), obj, src_size + ); + return UACPI_STATUS_AML_OUT_OF_BOUNDS_INDEX; +} + +static uacpi_status handle_index(struct execution_context *ctx) +{ + uacpi_status ret; + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_object *src; + struct item *dst; + uacpi_size idx; + + src = item_array_at(&op_ctx->items, 0)->obj; + idx = item_array_at(&op_ctx->items, 1)->obj->integer; + dst = item_array_at(&op_ctx->items, 3); + + switch (src->type) { + case UACPI_OBJECT_BUFFER: + case UACPI_OBJECT_STRING: { + uacpi_buffer_index *buf_idx; + uacpi_data_view buf; + get_object_storage(src, &buf, UACPI_FALSE); + + ret = ensure_valid_idx(src, idx, buf.length); + if (uacpi_unlikely_error(ret)) + return ret; + + dst->type = ITEM_OBJECT; + dst->obj = uacpi_create_object(UACPI_OBJECT_BUFFER_INDEX); + if (uacpi_unlikely(dst->obj == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + buf_idx = &dst->obj->buffer_index; + buf_idx->idx = idx; + buf_idx->buffer = src->buffer; + uacpi_shareable_ref(buf_idx->buffer); + + break; + } + case UACPI_OBJECT_PACKAGE: { + uacpi_package *pkg = src->package; + uacpi_object *obj; + + ret = ensure_valid_idx(src, idx, pkg->count); + if (uacpi_unlikely_error(ret)) + return ret; + + /* + * Lazily transform the package element into an internal reference + * to itself of type PKG_INDEX. This is needed to support stuff like + * CopyObject(..., Index(pkg, X)) where the new object must be + * propagated to anyone else with a currently alive index object. + * + * Sidenote: Yes, IndexOp is not a SimpleName, so technically it is + * illegal to CopyObject to it. However, yet again we fall + * victim to the NT ACPI driver implementation, which allows + * it just fine. + */ + obj = pkg->objects[idx]; + if (obj->type != UACPI_OBJECT_REFERENCE || + obj->flags != UACPI_REFERENCE_KIND_PKG_INDEX) { + + obj = uacpi_create_internal_reference( + UACPI_REFERENCE_KIND_PKG_INDEX, obj + ); + if (uacpi_unlikely(obj == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + pkg->objects[idx] = obj; + uacpi_object_unref(obj->inner_object); + } + + dst->obj = obj; + dst->type = ITEM_OBJECT; + uacpi_object_ref(dst->obj); + break; + } + default: + uacpi_error( + "invalid argument for Index: %s, " + "expected String/Buffer/Package\n", + uacpi_object_type_to_string(src->type) + ); + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + } + + return UACPI_STATUS_OK; +} + +static uacpi_u64 object_to_integer(const uacpi_object *obj, + uacpi_size max_buffer_bytes) +{ + uacpi_u64 dst; + + switch (obj->type) { + case UACPI_OBJECT_INTEGER: + dst = obj->integer; + break; + case UACPI_OBJECT_BUFFER: { + uacpi_size bytes; + bytes = UACPI_MIN(max_buffer_bytes, obj->buffer->size); + uacpi_memcpy_zerout(&dst, obj->buffer->data, sizeof(dst), bytes); + break; + } + case UACPI_OBJECT_STRING: + uacpi_string_to_integer( + obj->buffer->text, obj->buffer->size, UACPI_BASE_AUTO, &dst + ); + break; + default: + dst = 0; + break; + } + + return dst; +} + +static uacpi_status integer_to_string( + uacpi_u64 integer, uacpi_buffer *str, uacpi_bool is_hex +) +{ + int repr_len; + uacpi_char int_buf[21]; + uacpi_size final_size; + + repr_len = uacpi_snprintf( + int_buf, sizeof(int_buf), + is_hex ? "%"UACPI_PRIX64 : "%"UACPI_PRIu64, + UACPI_FMT64(integer) + ); + if (uacpi_unlikely(repr_len < 0)) + return UACPI_STATUS_INVALID_ARGUMENT; + + // 0x prefix + repr + \0 + final_size = (is_hex ? 2 : 0) + repr_len + 1; + + str->data = uacpi_kernel_alloc(final_size); + if (uacpi_unlikely(str->data == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + if (is_hex) { + str->text[0] = '0'; + str->text[1] = 'x'; + } + uacpi_memcpy(str->text + (is_hex ? 2 : 0), int_buf, repr_len + 1); + str->size = final_size; + + return UACPI_STATUS_OK; +} + +static uacpi_status buffer_to_string( + uacpi_buffer *buf, uacpi_buffer *str, uacpi_bool is_hex +) +{ + int repr_len; + uacpi_char int_buf[5]; + uacpi_size i, final_size; + uacpi_char *cursor; + + if (is_hex) { + final_size = 4 * buf->size; + } else { + final_size = 0; + + for (i = 0; i < buf->size; ++i) { + uacpi_u8 value = ((uacpi_u8*)buf->data)[i]; + + if (value < 10) + final_size += 1; + else if (value < 100) + final_size += 2; + else + final_size += 3; + } + } + + // Comma for every value but one + final_size += buf->size - 1; + + // Null terminator + final_size += 1; + + str->data = uacpi_kernel_alloc(final_size); + if (uacpi_unlikely(str->data == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + cursor = str->data; + + for (i = 0; i < buf->size; ++i) { + repr_len = uacpi_snprintf( + int_buf, sizeof(int_buf), + is_hex ? "0x%02X" : "%d", + ((uacpi_u8*)buf->data)[i] + ); + if (uacpi_unlikely(repr_len < 0)) { + uacpi_free(str->data, final_size); + str->data = UACPI_NULL; + return UACPI_STATUS_INVALID_ARGUMENT; + } + + uacpi_memcpy(cursor, int_buf, repr_len + 1); + cursor += repr_len; + + if (i != buf->size - 1) + *cursor++ = ','; + } + + str->size = final_size; + return UACPI_STATUS_OK; +} + +static uacpi_status do_make_empty_object(uacpi_buffer *buf, + uacpi_bool is_string) +{ + buf->text = uacpi_kernel_alloc_zeroed(sizeof(uacpi_char)); + if (uacpi_unlikely(buf->text == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + if (is_string) + buf->size = sizeof(uacpi_char); + + return UACPI_STATUS_OK; +} + +static uacpi_status make_null_string(uacpi_buffer *buf) +{ + return do_make_empty_object(buf, UACPI_TRUE); +} + +static uacpi_status make_null_buffer(uacpi_buffer *buf) +{ + /* + * Allocate at least 1 byte just to be safe, + * even for empty buffers. We still set the + * size to 0 though. + */ + return do_make_empty_object(buf, UACPI_FALSE); +} + +static uacpi_status handle_to(struct execution_context *ctx) +{ + uacpi_status ret = UACPI_STATUS_OK; + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_object *src, *dst; + + src = item_array_at(&op_ctx->items, 0)->obj; + dst = item_array_at(&op_ctx->items, 2)->obj; + + switch (op_ctx->op->code) { + case UACPI_AML_OP_ToIntegerOp: + // NT always takes the first 8 bytes, even for revision 1 + dst->integer = object_to_integer(src, 8); + break; + + case UACPI_AML_OP_ToHexStringOp: + case UACPI_AML_OP_ToDecimalStringOp: { + uacpi_bool is_hex = op_ctx->op->code == UACPI_AML_OP_ToHexStringOp; + + if (src->type == UACPI_OBJECT_INTEGER) { + ret = integer_to_string(src->integer, dst->buffer, is_hex); + break; + } else if (src->type == UACPI_OBJECT_BUFFER) { + if (uacpi_unlikely(src->buffer->size == 0)) + return make_null_string(dst->buffer); + + ret = buffer_to_string(src->buffer, dst->buffer, is_hex); + break; + } + UACPI_FALLTHROUGH; + } + case UACPI_AML_OP_ToBufferOp: { + uacpi_data_view buf; + uacpi_u8 *dst_buf; + + ret = get_object_storage(src, &buf, UACPI_TRUE); + if (uacpi_unlikely_error(ret)) + return ret; + + if (uacpi_unlikely(buf.length == 0)) + return make_null_buffer(dst->buffer); + + dst_buf = uacpi_kernel_alloc(buf.length); + if (uacpi_unlikely(dst_buf == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + uacpi_memcpy(dst_buf, buf.bytes, buf.length); + dst->buffer->data = dst_buf; + dst->buffer->size = buf.length; + break; + } + + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + return ret; +} + +static uacpi_status handle_to_string(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_buffer *src_buf, *dst_buf; + uacpi_size req_len, len; + + src_buf = item_array_at(&op_ctx->items, 0)->obj->buffer; + req_len = item_array_at(&op_ctx->items, 1)->obj->integer; + dst_buf = item_array_at(&op_ctx->items, 3)->obj->buffer; + + len = UACPI_MIN(req_len, src_buf->size); + if (uacpi_unlikely(len == 0)) + return make_null_string(dst_buf); + + len = uacpi_strnlen(src_buf->text, len); + + dst_buf->text = uacpi_kernel_alloc(len + 1); + if (uacpi_unlikely(dst_buf->text == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + uacpi_memcpy(dst_buf->text, src_buf->data, len); + dst_buf->text[len] = '\0'; + dst_buf->size = len + 1; + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_mid(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_object *src, *dst; + uacpi_data_view src_buf; + uacpi_buffer *dst_buf; + uacpi_size idx, len; + uacpi_bool is_string; + + src = item_array_at(&op_ctx->items, 0)->obj; + if (uacpi_unlikely(src->type != UACPI_OBJECT_STRING && + src->type != UACPI_OBJECT_BUFFER)) { + uacpi_error( + "invalid argument for Mid: %s, expected String/Buffer\n", + uacpi_object_type_to_string(src->type) + ); + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + } + + idx = item_array_at(&op_ctx->items, 1)->obj->integer; + len = item_array_at(&op_ctx->items, 2)->obj->integer; + dst = item_array_at(&op_ctx->items, 4)->obj; + dst_buf = dst->buffer; + + is_string = src->type == UACPI_OBJECT_STRING; + get_object_storage(src, &src_buf, UACPI_FALSE); + + if (uacpi_unlikely(src_buf.length == 0 || idx >= src_buf.length || + len == 0)) { + if (src->type == UACPI_OBJECT_STRING) { + dst->type = UACPI_OBJECT_STRING; + return make_null_string(dst_buf); + } + + return make_null_buffer(dst_buf); + } + + // Guaranteed to be at least 1 here + len = UACPI_MIN(len, src_buf.length - idx); + + dst_buf->data = uacpi_kernel_alloc(len + is_string); + if (uacpi_unlikely(dst_buf->data == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + uacpi_memcpy(dst_buf->data, (uacpi_u8*)src_buf.bytes + idx, len); + dst_buf->size = len; + + if (is_string) { + dst_buf->text[dst_buf->size++] = '\0'; + dst->type = UACPI_OBJECT_STRING; + } + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_concatenate(struct execution_context *ctx) +{ + uacpi_status ret = UACPI_STATUS_OK; + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_object *arg0, *arg1, *dst; + uacpi_u8 *dst_buf; + uacpi_size buf_size = 0; + + arg0 = item_array_at(&op_ctx->items, 0)->obj; + arg1 = item_array_at(&op_ctx->items, 1)->obj; + dst = item_array_at(&op_ctx->items, 3)->obj; + + switch (arg0->type) { + case UACPI_OBJECT_INTEGER: { + uacpi_u64 arg1_as_int; + uacpi_size int_size; + + int_size = sizeof_int(); + buf_size = int_size * 2; + + dst_buf = uacpi_kernel_alloc(buf_size); + if (uacpi_unlikely(dst_buf == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + arg1_as_int = object_to_integer(arg1, 8); + + uacpi_memcpy(dst_buf, &arg0->integer, int_size); + uacpi_memcpy(dst_buf+ int_size, &arg1_as_int, int_size); + break; + } + case UACPI_OBJECT_BUFFER: { + uacpi_buffer *arg0_buf = arg0->buffer; + uacpi_data_view arg1_buf = { 0 }; + + get_object_storage(arg1, &arg1_buf, UACPI_TRUE); + buf_size = arg0_buf->size + arg1_buf.length; + + dst_buf = uacpi_kernel_alloc(buf_size); + if (uacpi_unlikely(dst_buf == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + uacpi_memcpy(dst_buf, arg0_buf->data, arg0_buf->size); + uacpi_memcpy(dst_buf + arg0_buf->size, arg1_buf.bytes, arg1_buf.length); + break; + } + case UACPI_OBJECT_STRING: { + uacpi_char int_buf[17]; + void *arg1_ptr; + uacpi_size arg0_size, arg1_size; + uacpi_buffer *arg0_buf = arg0->buffer; + + switch (arg1->type) { + case UACPI_OBJECT_INTEGER: { + int size; + size = uacpi_snprintf(int_buf, sizeof(int_buf), "%"UACPI_PRIx64, + UACPI_FMT64(arg1->integer)); + if (size < 0) + return UACPI_STATUS_INVALID_ARGUMENT; + + arg1_ptr = int_buf; + arg1_size = size + 1; + break; + } + case UACPI_OBJECT_STRING: + arg1_ptr = arg1->buffer->data; + arg1_size = arg1->buffer->size; + break; + case UACPI_OBJECT_BUFFER: { + uacpi_buffer tmp_buf; + + ret = buffer_to_string(arg1->buffer, &tmp_buf, UACPI_TRUE); + if (uacpi_unlikely_error(ret)) + return ret; + + arg1_ptr = tmp_buf.data; + arg1_size = tmp_buf.size; + break; + } + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + arg0_size = arg0_buf->size ? arg0_buf->size - 1 : arg0_buf->size; + buf_size = arg0_size + arg1_size; + + dst_buf = uacpi_kernel_alloc(buf_size); + if (uacpi_unlikely(dst_buf == UACPI_NULL)) { + ret = UACPI_STATUS_OUT_OF_MEMORY; + goto cleanup; + } + + uacpi_memcpy(dst_buf, arg0_buf->data, arg0_size); + uacpi_memcpy(dst_buf + arg0_size, arg1_ptr, arg1_size); + dst->type = UACPI_OBJECT_STRING; + + cleanup: + if (arg1->type == UACPI_OBJECT_BUFFER) + uacpi_free(arg1_ptr, arg1_size); + break; + } + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + if (uacpi_likely_success(ret)) { + dst->buffer->data = dst_buf; + dst->buffer->size = buf_size; + } + return ret; +} + +static uacpi_status handle_concatenate_res(struct execution_context *ctx) +{ + uacpi_status ret; + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_data_view buffer; + uacpi_object *arg0, *arg1, *dst; + uacpi_u8 *dst_buf; + uacpi_size dst_size, arg0_size, arg1_size; + + arg0 = item_array_at(&op_ctx->items, 0)->obj; + arg1 = item_array_at(&op_ctx->items, 1)->obj; + dst = item_array_at(&op_ctx->items, 3)->obj; + + uacpi_buffer_to_view(arg0->buffer, &buffer); + ret = uacpi_find_aml_resource_end_tag(buffer, &arg0_size); + if (uacpi_unlikely_error(ret)) + return ret; + + uacpi_buffer_to_view(arg1->buffer, &buffer); + ret = uacpi_find_aml_resource_end_tag(buffer, &arg1_size); + if (uacpi_unlikely_error(ret)) + return ret; + + dst_size = arg0_size + arg1_size + sizeof(struct acpi_resource_end_tag); + + dst_buf = uacpi_kernel_alloc(dst_size); + if (uacpi_unlikely(dst_buf == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + dst->buffer->data = dst_buf; + dst->buffer->size = dst_size; + + uacpi_memcpy(dst_buf, arg0->buffer->data, arg0_size); + uacpi_memcpy(dst_buf + arg0_size, arg1->buffer->data, arg1_size); + + /* + * Small item (0), End Tag (0x0F), length 1 + * Leave the checksum as 0 + */ + dst_buf[dst_size - 2] = + (ACPI_RESOURCE_END_TAG << ACPI_SMALL_ITEM_NAME_IDX) | + (sizeof(struct acpi_resource_end_tag) - 1); + dst_buf[dst_size - 1] = 0; + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_sizeof(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_object *src, *dst; + + src = item_array_at(&op_ctx->items, 0)->obj; + dst = item_array_at(&op_ctx->items, 1)->obj; + + if (uacpi_likely(src->type == UACPI_OBJECT_REFERENCE)) + src = reference_unwind(src)->inner_object; + + switch (src->type) { + case UACPI_OBJECT_STRING: + case UACPI_OBJECT_BUFFER: { + uacpi_data_view buf; + get_object_storage(src, &buf, UACPI_FALSE); + + dst->integer = buf.length; + break; + } + + case UACPI_OBJECT_PACKAGE: + dst->integer = src->package->count; + break; + + default: + uacpi_error( + "invalid argument for Sizeof: %s, " + "expected String/Buffer/Package\n", + uacpi_object_type_to_string(src->type) + ); + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + } + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_object_type(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_object *src, *dst; + + src = item_array_at(&op_ctx->items, 0)->obj; + dst = item_array_at(&op_ctx->items, 1)->obj; + + if (uacpi_likely(src->type == UACPI_OBJECT_REFERENCE)) + src = reference_unwind(src)->inner_object; + + dst->integer = src->type; + if (dst->integer == UACPI_OBJECT_BUFFER_INDEX) + dst->integer = UACPI_OBJECT_BUFFER_FIELD; + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_timer(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_object *dst; + + dst = item_array_at(&op_ctx->items, 0)->obj; + dst->integer = uacpi_kernel_get_nanoseconds_since_boot() / 100; + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_stall_or_sleep(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_u64 time; + + time = item_array_at(&op_ctx->items, 0)->obj->integer; + + if (op_ctx->op->code == UACPI_AML_OP_SleepOp) { + /* + * ACPICA doesn't allow sleeps longer than 2 seconds, + * so we shouldn't either. + */ + if (time > 2000) + time = 2000; + + uacpi_namespace_write_unlock(); + uacpi_kernel_sleep(time); + uacpi_namespace_write_lock(); + } else { + // Spec says this must evaluate to a ByteData + if (time > 0xFF) + time = 0xFF; + uacpi_kernel_stall(time); + } + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_bcd(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_u64 src, dst = 0; + uacpi_size i; + uacpi_object *dst_obj; + + src = item_array_at(&op_ctx->items, 0)->obj->integer; + dst_obj = item_array_at(&op_ctx->items, 2)->obj; + i = 64; + + /* + * NOTE: ACPICA just errors out for invalid BCD, but NT allows it just fine. + * FromBCD matches NT behavior 1:1 even for invalid BCD, but ToBCD + * produces different results when the input is too large. + */ + if (op_ctx->op->code == UACPI_AML_OP_FromBCDOp) { + do { + i -= 4; + dst *= 10; + dst += (src >> i) & 0xF; + } while (i); + } else { + while (src != 0) { + dst >>= 4; + i -= 4; + dst |= (src % 10) << 60; + src /= 10; + } + + dst >>= (i % 64); + } + + dst_obj->integer = dst; + return UACPI_STATUS_OK; +} + +static uacpi_status handle_unload(struct execution_context *ctx) +{ + UACPI_UNUSED(ctx); + + /* + * Technically this doesn't exist in the wild, from the dumps that I have + * the only user of the Unload opcode is the Surface Pro 3, which triggers + * an unload of some I2C-related table as a response to some event. + * + * This op has been long deprecated by the specification exactly because + * it hasn't really been used by anyone and the fact that it introduces + * an enormous layer of complexity, which no driver is really prepared to + * deal with (aka namespace nodes disappearing under its feet). + * + * Just pretend we have actually unloaded whatever the AML asked for, if it + * ever tries to re-load this table that will just skip opcodes that create + * already existing objects, which should be good enough and mostly + * transparent to the AML. + */ + uacpi_warn("refusing to unload a table from AML\n"); + return UACPI_STATUS_OK; +} + +static uacpi_status handle_logical_not(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_object *src, *dst; + + src = item_array_at(&op_ctx->items, 0)->obj; + dst = item_array_at(&op_ctx->items, 1)->obj; + + dst->type = UACPI_OBJECT_INTEGER; + dst->integer = src->integer ? 0 : ones(); + + return UACPI_STATUS_OK; +} + +static uacpi_bool handle_logical_equality(uacpi_object *lhs, uacpi_object *rhs) +{ + uacpi_bool res = UACPI_FALSE; + + if (lhs->type == UACPI_OBJECT_STRING || lhs->type == UACPI_OBJECT_BUFFER) { + res = lhs->buffer->size == rhs->buffer->size; + + if (res && lhs->buffer->size) { + res = uacpi_memcmp( + lhs->buffer->data, + rhs->buffer->data, + lhs->buffer->size + ) == 0; + } + } else if (lhs->type == UACPI_OBJECT_INTEGER) { + res = lhs->integer == rhs->integer; + } + + return res; +} + +static uacpi_bool handle_logical_less_or_greater( + uacpi_aml_op op, uacpi_object *lhs, uacpi_object *rhs +) +{ + if (lhs->type == UACPI_OBJECT_STRING || lhs->type == UACPI_OBJECT_BUFFER) { + int res; + uacpi_buffer *lhs_buf, *rhs_buf; + + lhs_buf = lhs->buffer; + rhs_buf = rhs->buffer; + + res = uacpi_memcmp(lhs_buf->data, rhs_buf->data, + UACPI_MIN(lhs_buf->size, rhs_buf->size)); + if (res == 0) { + if (lhs_buf->size < rhs_buf->size) + res = -1; + else if (lhs_buf->size > rhs_buf->size) + res = 1; + } + + if (op == UACPI_AML_OP_LLessOp) + return res < 0; + + return res > 0; + } + + if (op == UACPI_AML_OP_LLessOp) + return lhs->integer < rhs->integer; + + return lhs->integer > rhs->integer; +} + +static uacpi_status handle_binary_logic(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_aml_op op = op_ctx->op->code; + uacpi_object *lhs, *rhs, *dst; + uacpi_bool res; + + lhs = item_array_at(&op_ctx->items, 0)->obj; + rhs = item_array_at(&op_ctx->items, 1)->obj; + dst = item_array_at(&op_ctx->items, 2)->obj; + + switch (op) { + case UACPI_AML_OP_LEqualOp: + case UACPI_AML_OP_LLessOp: + case UACPI_AML_OP_LGreaterOp: + // TODO: typecheck at parse time + if (lhs->type != rhs->type) { + uacpi_error( + "don't know how to do a logical comparison of '%s' and '%s'\n", + uacpi_object_type_to_string(lhs->type), + uacpi_object_type_to_string(rhs->type) + ); + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + } + + if (op == UACPI_AML_OP_LEqualOp) + res = handle_logical_equality(lhs, rhs); + else + res = handle_logical_less_or_greater(op, lhs, rhs); + break; + default: { + uacpi_u64 lhs_int, rhs_int; + + // NT only looks at the first 4 bytes of a buffer + lhs_int = object_to_integer(lhs, 4); + rhs_int = object_to_integer(rhs, 4); + + if (op == UACPI_AML_OP_LandOp) + res = lhs_int && rhs_int; + else + res = lhs_int || rhs_int; + break; + } + } + + dst->integer = res ? ones() : 0; + return UACPI_STATUS_OK; +} + +enum match_op { + MTR = 0, + MEQ = 1, + MLE = 2, + MLT = 3, + MGE = 4, + MGT = 5, +}; + +static uacpi_bool match_one(enum match_op op, uacpi_u64 lhs, uacpi_u64 rhs) +{ + switch (op) { + case MTR: + return UACPI_TRUE; + case MEQ: + return lhs == rhs; + case MLE: + return lhs <= rhs; + case MLT: + return lhs < rhs; + case MGE: + return lhs >= rhs; + case MGT: + return lhs > rhs; + default: + return UACPI_FALSE; + } +} + +static uacpi_status handle_match(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_package *pkg; + uacpi_u64 operand0, operand1, start_idx, i; + enum match_op mop0, mop1; + uacpi_object *dst; + + pkg = item_array_at(&op_ctx->items, 0)->obj->package; + mop0 = item_array_at(&op_ctx->items, 1)->immediate; + operand0 = item_array_at(&op_ctx->items, 2)->obj->integer; + mop1 = item_array_at(&op_ctx->items, 3)->immediate; + operand1 = item_array_at(&op_ctx->items, 4)->obj->integer; + start_idx = item_array_at(&op_ctx->items, 5)->obj->integer; + dst = item_array_at(&op_ctx->items, 6)->obj; + + for (i = start_idx; i < pkg->count; ++i) { + uacpi_object *obj = pkg->objects[i]; + + if (obj->type != UACPI_OBJECT_INTEGER) + continue; + + if (match_one(mop0, obj->integer, operand0) && + match_one(mop1, obj->integer, operand1)) + break; + } + + if (i < pkg->count) + dst->integer = i; + else + dst->integer = ones(); + + return UACPI_STATUS_OK; +} + +/* + * PkgLength := + * PkgLeadByte | + * <pkgleadbyte bytedata> | + * <pkgleadbyte bytedata bytedata> | <pkgleadbyte bytedata bytedata bytedata> + * PkgLeadByte := + * <bit 7-6: bytedata count that follows (0-3)> + * <bit 5-4: only used if pkglength < 63> + * <bit 3-0: least significant package length nybble> + */ +static uacpi_status parse_package_length(struct call_frame *frame, + struct package_length *out_pkg) +{ + uacpi_u32 left, size; + uacpi_u8 *data, marker_length; + + out_pkg->begin = frame->code_offset; + marker_length = 1; + + left = call_frame_code_bytes_left(frame); + if (uacpi_unlikely(left < 1)) + return UACPI_STATUS_AML_BAD_ENCODING; + + data = call_frame_cursor(frame); + marker_length += *data >> 6; + + if (uacpi_unlikely(left < marker_length)) + return UACPI_STATUS_AML_BAD_ENCODING; + + switch (marker_length) { + case 1: + size = *data & 0x3F; + break; + case 2: + case 3: + case 4: { + uacpi_u32 temp_byte = 0; + + size = *data & 0xF; + uacpi_memcpy(&temp_byte, data + 1, marker_length - 1); + + // marker_length - 1 is at most 3, so this shift is safe + size |= temp_byte << 4; + break; + } + } + + frame->code_offset += marker_length; + + out_pkg->end = out_pkg->begin + size; + if (uacpi_unlikely(out_pkg->end < out_pkg->begin)) { + uacpi_error( + "PkgLength overflow: start=%u, size=%u\n", out_pkg->begin, size + ); + return UACPI_STATUS_AML_BAD_ENCODING; + } + + return UACPI_STATUS_OK; +} + +/* + * ByteData + * // bit 0-2: ArgCount (0-7) + * // bit 3: SerializeFlag + * // 0 NotSerialized + * // 1 Serialized + * // bit 4-7: SyncLevel (0x00-0x0f) + */ +static void init_method_flags(uacpi_control_method *method, uacpi_u8 flags_byte) +{ + method->args = flags_byte & 0x7; + method->is_serialized = (flags_byte >> 3) & 1; + method->sync_level = flags_byte >> 4; +} + +static uacpi_status handle_create_method(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + struct uacpi_control_method *this_method, *method; + struct package_length *pkg; + struct uacpi_namespace_node *node; + struct uacpi_object *dst; + uacpi_u32 method_begin_offset, method_size; + + this_method = ctx->cur_frame->method; + pkg = &item_array_at(&op_ctx->items, 0)->pkg; + node = item_array_at(&op_ctx->items, 1)->node; + method_begin_offset = item_array_at(&op_ctx->items, 3)->immediate; + + if (uacpi_unlikely(pkg->end < pkg->begin || + pkg->end < method_begin_offset || + pkg->end > this_method->size)) { + uacpi_error( + "invalid method %.4s bounds [%u..%u] (parent size is %u)\n", + node->name.text, method_begin_offset, pkg->end, this_method->size + ); + return UACPI_STATUS_AML_BAD_ENCODING; + } + + dst = item_array_at(&op_ctx->items, 4)->obj; + + method = dst->method; + method_size = pkg->end - method_begin_offset; + + if (method_size) { + method->code = uacpi_kernel_alloc(method_size); + if (uacpi_unlikely(method->code == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + uacpi_memcpy( + method->code, + ctx->cur_frame->method->code + method_begin_offset, + method_size + ); + method->size = method_size; + method->owns_code = 1; + } + + init_method_flags(method, item_array_at(&op_ctx->items, 2)->immediate); + + node->object = uacpi_create_internal_reference(UACPI_REFERENCE_KIND_NAMED, + dst); + if (uacpi_unlikely(node->object == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_create_mutex_or_event(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_namespace_node *node; + uacpi_object *dst; + + node = item_array_at(&op_ctx->items, 0)->node; + + if (op_ctx->op->code == UACPI_AML_OP_MutexOp) { + dst = item_array_at(&op_ctx->items, 2)->obj; + + // bits 0-3: SyncLevel (0x00-0x0f), bits 4-7: Reserved (must be 0) + dst->mutex->sync_level = item_array_at(&op_ctx->items, 1)->immediate; + dst->mutex->sync_level &= 0xF; + } else { + dst = item_array_at(&op_ctx->items, 1)->obj; + } + + node->object = uacpi_create_internal_reference( + UACPI_REFERENCE_KIND_NAMED, + dst + ); + if (uacpi_unlikely(node->object == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_event_ctl(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_object *obj; + + obj = uacpi_unwrap_internal_reference( + item_array_at(&op_ctx->items, 0)->obj + ); + if (uacpi_unlikely(obj->type != UACPI_OBJECT_EVENT)) { + uacpi_error( + "%s: invalid argument '%s', expected an Event object\n", + op_ctx->op->name, uacpi_object_type_to_string(obj->type) + ); + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + } + + switch (op_ctx->op->code) + { + case UACPI_AML_OP_SignalOp: + uacpi_kernel_signal_event(obj->event->handle); + break; + case UACPI_AML_OP_ResetOp: + uacpi_kernel_reset_event(obj->event->handle); + break; + case UACPI_AML_OP_WaitOp: { + uacpi_u64 timeout; + uacpi_bool ret; + + timeout = item_array_at(&op_ctx->items, 1)->obj->integer; + if (timeout > 0xFFFF) + timeout = 0xFFFF; + + uacpi_namespace_write_unlock(); + ret = uacpi_kernel_wait_for_event(obj->event->handle, timeout); + uacpi_namespace_write_lock(); + + /* + * The return value here is inverted, we return 0 for success and Ones + * for timeout and everything else. + */ + if (ret) + item_array_at(&op_ctx->items, 2)->obj->integer = 0; + break; + } + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_mutex_ctl(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_object *obj; + + obj = uacpi_unwrap_internal_reference( + item_array_at(&op_ctx->items, 0)->obj + ); + if (uacpi_unlikely(obj->type != UACPI_OBJECT_MUTEX)) { + uacpi_error( + "%s: invalid argument '%s', expected a Mutex object\n", + op_ctx->op->name, uacpi_object_type_to_string(obj->type) + ); + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + } + + switch (op_ctx->op->code) + { + case UACPI_AML_OP_AcquireOp: { + uacpi_u64 timeout; + uacpi_u64 *return_value; + uacpi_status ret; + + return_value = &item_array_at(&op_ctx->items, 2)->obj->integer; + + if (uacpi_unlikely(ctx->sync_level > obj->mutex->sync_level)) { + uacpi_warn( + "ignoring attempt to acquire mutex @%p with a lower sync level " + "(%d < %d)\n", obj->mutex, obj->mutex->sync_level, + ctx->sync_level + ); + break; + } + + timeout = item_array_at(&op_ctx->items, 1)->immediate; + if (timeout > 0xFFFF) + timeout = 0xFFFF; + + if (uacpi_this_thread_owns_aml_mutex(obj->mutex)) { + ret = uacpi_acquire_aml_mutex(obj->mutex, timeout); + if (uacpi_likely_success(ret)) + *return_value = 0; + break; + } + + ret = uacpi_acquire_aml_mutex(obj->mutex, timeout); + if (uacpi_unlikely_error(ret)) + break; + + ret = held_mutexes_array_push(&ctx->held_mutexes, obj->mutex); + if (uacpi_unlikely_error(ret)) { + uacpi_release_aml_mutex(obj->mutex); + return ret; + } + + ctx->sync_level = obj->mutex->sync_level; + *return_value = 0; + break; + } + + case UACPI_AML_OP_ReleaseOp: { + uacpi_status ret; + + if (!uacpi_this_thread_owns_aml_mutex(obj->mutex)) { + uacpi_warn( + "attempted to release not-previously-acquired mutex object " + "@%p (%p)\n", obj->mutex, obj->mutex->handle + ); + break; + } + + ret = held_mutexes_array_remove_and_release( + &ctx->held_mutexes, obj->mutex, + FORCE_RELEASE_NO + ); + if (uacpi_likely_success(ret)) { + uacpi_mutex **last_mutex; + + last_mutex = held_mutexes_array_last(&ctx->held_mutexes); + if (last_mutex == UACPI_NULL) { + ctx->sync_level = 0; + break; + } + + ctx->sync_level = (*last_mutex)->sync_level; + } + break; + } + + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_notify(struct execution_context *ctx) +{ + uacpi_status ret; + struct op_context *op_ctx = ctx->cur_op_ctx; + struct uacpi_namespace_node *node; + uacpi_u64 value; + + node = item_array_at(&op_ctx->items, 0)->node; + value = item_array_at(&op_ctx->items, 1)->obj->integer; + + ret = uacpi_notify_all(node, value); + if (uacpi_likely_success(ret)) + return ret; + + if (ret == UACPI_STATUS_NO_HANDLER) { + const uacpi_char *path; + + path = uacpi_namespace_node_generate_absolute_path(node); + uacpi_warn( + "ignoring firmware Notify(%s, 0x%"UACPI_PRIX64") request, " + "no listeners\n", path, UACPI_FMT64(value) + ); + uacpi_free_dynamic_string(path); + + return UACPI_STATUS_OK; + } + + if (ret == UACPI_STATUS_INVALID_ARGUMENT) { + uacpi_error("Notify() called on an invalid object %.4s\n", + node->name.text); + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + } + + return ret; +} + +static uacpi_status handle_firmware_request(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_firmware_request req = { 0 }; + + switch (op_ctx->op->code) { + case UACPI_AML_OP_BreakPointOp: + req.type = UACPI_FIRMWARE_REQUEST_TYPE_BREAKPOINT; + req.breakpoint.ctx = ctx; + break; + case UACPI_AML_OP_FatalOp: + req.type = UACPI_FIRMWARE_REQUEST_TYPE_FATAL; + req.fatal.type = item_array_at(&op_ctx->items, 0)->immediate; + req.fatal.code = item_array_at(&op_ctx->items, 1)->immediate; + req.fatal.arg = item_array_at(&op_ctx->items, 2)->obj->integer; + break; + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + uacpi_namespace_write_unlock(); + uacpi_kernel_handle_firmware_request(&req); + uacpi_namespace_write_lock(); + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_create_named(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + struct uacpi_namespace_node *node; + uacpi_object *src; + + node = item_array_at(&op_ctx->items, 0)->node; + src = item_array_at(&op_ctx->items, 1)->obj; + + node->object = uacpi_create_internal_reference(UACPI_REFERENCE_KIND_NAMED, + src); + if (uacpi_unlikely(node->object == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + return UACPI_STATUS_OK; +} + +static uacpi_object_type buffer_field_get_read_type( + struct uacpi_buffer_field *field +) +{ + if (field->bit_length > (g_uacpi_rt_ctx.is_rev1 ? 32u : 64u) || + field->force_buffer) + return UACPI_OBJECT_BUFFER; + + return UACPI_OBJECT_INTEGER; +} + +static uacpi_status field_get_read_type( + uacpi_object *obj, uacpi_object_type *out_type +) +{ + if (obj->type == UACPI_OBJECT_BUFFER_FIELD) { + *out_type = buffer_field_get_read_type(&obj->buffer_field); + return UACPI_STATUS_OK; + } + + return uacpi_field_unit_get_read_type(obj->field_unit, out_type); +} + +static uacpi_status field_byte_size( + uacpi_object *obj, uacpi_size *out_size +) +{ + uacpi_size bit_length; + + if (obj->type == UACPI_OBJECT_BUFFER_FIELD) { + bit_length = obj->buffer_field.bit_length; + } else { + uacpi_status ret; + + ret = uacpi_field_unit_get_bit_length(obj->field_unit, &bit_length); + if (uacpi_unlikely_error(ret)) + return ret; + } + + *out_size = uacpi_round_up_bits_to_bytes(bit_length); + return UACPI_STATUS_OK; +} + +static uacpi_status handle_field_read(struct execution_context *ctx) +{ + uacpi_status ret; + struct op_context *op_ctx = ctx->cur_op_ctx; + struct uacpi_namespace_node *node; + uacpi_object *src_obj, *dst_obj; + uacpi_size dst_size; + void *dst = UACPI_NULL; + uacpi_data_view wtr_response = { 0 }; + + node = item_array_at(&op_ctx->items, 0)->node; + src_obj = uacpi_namespace_node_get_object(node); + dst_obj = item_array_at(&op_ctx->items, 1)->obj; + + if (op_ctx->op->code == UACPI_AML_OP_InternalOpReadFieldAsBuffer) { + uacpi_buffer *buf; + + ret = field_byte_size(src_obj, &dst_size); + if (uacpi_unlikely_error(ret)) + return ret; + + if (dst_size != 0) { + buf = dst_obj->buffer; + + dst = uacpi_kernel_alloc_zeroed(dst_size); + if (dst == UACPI_NULL) + return UACPI_STATUS_OUT_OF_MEMORY; + + buf->data = dst; + buf->size = dst_size; + } + } else { + dst = &dst_obj->integer; + dst_size = sizeof(uacpi_u64); + } + + if (src_obj->type == UACPI_OBJECT_BUFFER_FIELD) { + uacpi_read_buffer_field(&src_obj->buffer_field, dst); + return UACPI_STATUS_OK; + } + + ret = uacpi_read_field_unit( + src_obj->field_unit, dst, dst_size, &wtr_response + ); + if (uacpi_unlikely_error(ret)) + return ret; + + if (wtr_response.data != UACPI_NULL) { + uacpi_buffer *buf; + + buf = dst_obj->buffer; + buf->data = wtr_response.data; + buf->size = wtr_response.length; + } + + return ret; +} + +static uacpi_status handle_create_buffer_field(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + struct uacpi_namespace_node *node; + uacpi_buffer *src_buf; + uacpi_object *field_obj; + uacpi_buffer_field *field; + + /* + * Layout of items here: + * [0] -> Type checked source buffer object + * [1] -> Byte/bit index integer object + * [2] ( if CreateField) -> bit length integer object + * [3] (2 if not CreateField) -> the new namespace node + * [4] (3 if not CreateField) -> the buffer field object we're creating here + */ + src_buf = item_array_at(&op_ctx->items, 0)->obj->buffer; + + if (op_ctx->op->code == UACPI_AML_OP_CreateFieldOp) { + uacpi_object *idx_obj, *len_obj; + + idx_obj = item_array_at(&op_ctx->items, 1)->obj; + len_obj = item_array_at(&op_ctx->items, 2)->obj; + node = item_array_at(&op_ctx->items, 3)->node; + field_obj = item_array_at(&op_ctx->items, 4)->obj; + field = &field_obj->buffer_field; + + field->bit_index = idx_obj->integer; + + if (uacpi_unlikely(!len_obj->integer || + len_obj->integer > 0xFFFFFFFF)) { + uacpi_error("invalid bit field length (%u)\n", field->bit_length); + return UACPI_STATUS_AML_BAD_ENCODING; + } + + field->bit_length = len_obj->integer; + field->force_buffer = UACPI_TRUE; + } else { + uacpi_object *idx_obj; + + idx_obj = item_array_at(&op_ctx->items, 1)->obj; + node = item_array_at(&op_ctx->items, 2)->node; + field_obj = item_array_at(&op_ctx->items, 3)->obj; + field = &field_obj->buffer_field; + + field->bit_index = idx_obj->integer; + switch (op_ctx->op->code) { + case UACPI_AML_OP_CreateBitFieldOp: + field->bit_length = 1; + break; + case UACPI_AML_OP_CreateByteFieldOp: + field->bit_length = 8; + break; + case UACPI_AML_OP_CreateWordFieldOp: + field->bit_length = 16; + break; + case UACPI_AML_OP_CreateDWordFieldOp: + field->bit_length = 32; + break; + case UACPI_AML_OP_CreateQWordFieldOp: + field->bit_length = 64; + break; + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + if (op_ctx->op->code != UACPI_AML_OP_CreateBitFieldOp) + field->bit_index *= 8; + } + + if (uacpi_unlikely((field->bit_index + field->bit_length) > + src_buf->size * 8)) { + uacpi_error( + "invalid buffer field: bits [%zu..%zu], buffer size is %zu bytes\n", + field->bit_index, field->bit_index + field->bit_length, + src_buf->size + ); + return UACPI_STATUS_AML_OUT_OF_BOUNDS_INDEX; + } + + field->backing = src_buf; + uacpi_shareable_ref(field->backing); + node->object = uacpi_create_internal_reference(UACPI_REFERENCE_KIND_NAMED, + field_obj); + if (uacpi_unlikely(node->object == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_control_flow(struct execution_context *ctx) +{ + struct call_frame *frame = ctx->cur_frame; + struct op_context *op_ctx = ctx->cur_op_ctx; + + if (uacpi_unlikely(frame->last_while == UACPI_NULL)) { + uacpi_error( + "attempting to %s outside of a While block\n", + op_ctx->op->code == UACPI_AML_OP_BreakOp ? "Break" : "Continue" + ); + return UACPI_STATUS_AML_BAD_ENCODING; + } + + for (;;) { + if (ctx->cur_block != frame->last_while) { + frame_reset_post_end_block(ctx, ctx->cur_block->type); + continue; + } + + if (op_ctx->op->code == UACPI_AML_OP_BreakOp) + frame->code_offset = ctx->cur_block->end; + else + frame->code_offset = ctx->cur_block->begin; + frame_reset_post_end_block(ctx, ctx->cur_block->type); + break; + } + + return UACPI_STATUS_OK; +} + +static uacpi_status create_named_scope(struct op_context *op_ctx) +{ + uacpi_namespace_node *node; + uacpi_object *obj; + + node = item_array_at(&op_ctx->items, 1)->node; + obj = item_array_last(&op_ctx->items)->obj; + + switch (op_ctx->op->code) { + case UACPI_AML_OP_ProcessorOp: { + uacpi_processor *proc = obj->processor; + proc->id = item_array_at(&op_ctx->items, 2)->immediate; + proc->block_address = item_array_at(&op_ctx->items, 3)->immediate; + proc->block_length = item_array_at(&op_ctx->items, 4)->immediate; + break; + } + + case UACPI_AML_OP_PowerResOp: { + uacpi_power_resource *power_res = &obj->power_resource; + power_res->system_level = item_array_at(&op_ctx->items, 2)->immediate; + power_res->resource_order = item_array_at(&op_ctx->items, 3)->immediate; + break; + } + + default: + break; + } + + node->object = uacpi_create_internal_reference(UACPI_REFERENCE_KIND_NAMED, + obj); + if (uacpi_unlikely(node->object == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + return UACPI_STATUS_OK; +} + +static uacpi_status handle_code_block(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + + switch (op_ctx->op->code) { + case UACPI_AML_OP_ProcessorOp: + case UACPI_AML_OP_PowerResOp: + case UACPI_AML_OP_ThermalZoneOp: + case UACPI_AML_OP_DeviceOp: { + uacpi_status ret; + + ret = create_named_scope(op_ctx); + if (uacpi_unlikely_error(ret)) + return ret; + + UACPI_FALLTHROUGH; + } + case UACPI_AML_OP_ScopeOp: + case UACPI_AML_OP_IfOp: + case UACPI_AML_OP_ElseOp: + case UACPI_AML_OP_WhileOp: { + break; + } + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + return begin_block_execution(ctx); +} + +static uacpi_status handle_return(struct execution_context *ctx) +{ + uacpi_status ret; + uacpi_object *dst = UACPI_NULL; + + ctx->cur_frame->code_offset = ctx->cur_frame->method->size; + ret = method_get_ret_object(ctx, &dst); + + if (uacpi_unlikely_error(ret)) + return ret; + if (dst == UACPI_NULL) + return UACPI_STATUS_OK; + + /* + * Should be possible to move here if method returns a literal + * like Return(Buffer { ... }), otherwise we have to copy just to + * be safe. + */ + return uacpi_object_assign( + dst, + item_array_at(&ctx->cur_op_ctx->items, 0)->obj, + UACPI_ASSIGN_BEHAVIOR_DEEP_COPY + ); +} + +static void refresh_ctx_pointers(struct execution_context *ctx) +{ + struct call_frame *frame = ctx->cur_frame; + + if (frame == UACPI_NULL) { + ctx->cur_op_ctx = UACPI_NULL; + ctx->prev_op_ctx = UACPI_NULL; + ctx->cur_block = UACPI_NULL; + return; + } + + ctx->cur_op_ctx = op_context_array_last(&frame->pending_ops); + ctx->prev_op_ctx = op_context_array_one_before_last(&frame->pending_ops); + ctx->cur_block = code_block_array_last(&frame->code_blocks); +} + +static uacpi_bool ctx_has_non_preempted_op(struct execution_context *ctx) +{ + return ctx->cur_op_ctx && !ctx->cur_op_ctx->preempted; +} + +enum op_trace_action_type { + OP_TRACE_ACTION_BEGIN, + OP_TRACE_ACTION_RESUME, + OP_TRACE_ACTION_END, +}; + +static const uacpi_char *const op_trace_action_types[3] = { + [OP_TRACE_ACTION_BEGIN] = "BEGIN", + [OP_TRACE_ACTION_RESUME] = "RESUME", + [OP_TRACE_ACTION_END] = "END", +}; + +static inline void trace_op( + const struct uacpi_op_spec *op, enum op_trace_action_type action +) +{ + uacpi_debug( + "%s OP '%s' (0x%04X)\n", + op_trace_action_types[action], op->name, op->code + ); +} + +static inline void trace_pop(uacpi_u8 pop) +{ + uacpi_debug(" pOP: %s (0x%02X)\n", uacpi_parse_op_to_string(pop), pop); +} + +static uacpi_status frame_push_args(struct call_frame *frame, + struct op_context *op_ctx) +{ + uacpi_size i; + + /* + * MethodCall items: + * items[0] -> method namespace node + * items[1] -> immediate that was used for parsing the arguments + * items[2...nargs-1] -> method arguments + * items[-1] -> return value object + * + * Here we only care about the arguments though. + */ + for (i = 2; i < item_array_size(&op_ctx->items) - 1; i++) { + uacpi_object *src, *dst; + + src = item_array_at(&op_ctx->items, i)->obj; + + dst = uacpi_create_internal_reference(UACPI_REFERENCE_KIND_ARG, src); + if (uacpi_unlikely(dst == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + frame->args[i - 2] = dst; + } + + return UACPI_STATUS_OK; +} + +static uacpi_status frame_setup_base_scope(struct call_frame *frame, + uacpi_namespace_node *scope, + uacpi_control_method *method) +{ + struct code_block *block; + + block = code_block_array_alloc(&frame->code_blocks); + if (uacpi_unlikely(block == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + block->type = CODE_BLOCK_SCOPE; + block->node = scope; + block->begin = 0; + block->end = method->size; + frame->method = method; + frame->cur_scope = scope; + return UACPI_STATUS_OK; +} + +static uacpi_status push_new_frame(struct execution_context *ctx, + struct call_frame **out_frame) +{ + struct call_frame_array *call_stack = &ctx->call_stack; + struct call_frame *prev_frame; + + *out_frame = call_frame_array_calloc(call_stack); + if (uacpi_unlikely(*out_frame == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + /* + * Allocating a new frame might have reallocated the dynamic buffer so our + * execution_context members might now be pointing to freed memory. + * Refresh them here. + */ + prev_frame = call_frame_array_one_before_last(call_stack); + ctx->cur_frame = prev_frame; + refresh_ctx_pointers(ctx); + + return UACPI_STATUS_OK; +} + +static uacpi_bool maybe_end_block(struct execution_context *ctx) +{ + struct code_block *block = ctx->cur_block; + struct call_frame *cur_frame = ctx->cur_frame; + + if (!block) + return UACPI_FALSE; + if (cur_frame->code_offset != block->end) + return UACPI_FALSE; + + if (block->type == CODE_BLOCK_WHILE) + cur_frame->code_offset = block->begin; + + frame_reset_post_end_block(ctx, block->type); + return UACPI_TRUE; +} + +static uacpi_status store_to_target( + uacpi_object *dst, uacpi_object *src, uacpi_data_view *wtr_response +) +{ + uacpi_status ret; + + switch (dst->type) { + case UACPI_OBJECT_DEBUG: + ret = debug_store(src); + break; + case UACPI_OBJECT_REFERENCE: + ret = store_to_reference(dst, src, wtr_response); + break; + + case UACPI_OBJECT_BUFFER_INDEX: + src = uacpi_unwrap_internal_reference(src); + ret = object_assign_with_implicit_cast(dst, src, wtr_response); + break; + + case UACPI_OBJECT_INTEGER: + // NULL target + if (dst->integer == 0) { + ret = UACPI_STATUS_OK; + break; + } + UACPI_FALLTHROUGH; + default: + uacpi_error("attempted to store to an invalid target: %s\n", + uacpi_object_type_to_string(dst->type)); + ret = UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + } + + return ret; +} + +static uacpi_status handle_copy_object_or_store(struct execution_context *ctx) +{ + uacpi_object *src, *dst; + struct op_context *op_ctx = ctx->cur_op_ctx; + + src = item_array_at(&op_ctx->items, 0)->obj; + dst = item_array_at(&op_ctx->items, 1)->obj; + + if (op_ctx->op->code == UACPI_AML_OP_StoreOp) { + uacpi_status ret; + uacpi_data_view wtr_response = { 0 }; + + ret = store_to_target(dst, src, &wtr_response); + if (uacpi_unlikely_error(ret)) + return ret; + + /* + * This was a write-then-read field access since we got a response + * buffer back from this store. Now we have to return this buffer + * as a prvalue from the StoreOp so that it can be used by AML to + * retrieve the response. + */ + if (wtr_response.data != UACPI_NULL) { + uacpi_object *wtr_response_obj; + + wtr_response_obj = uacpi_create_object(UACPI_OBJECT_BUFFER); + if (uacpi_unlikely(wtr_response_obj == UACPI_NULL)) { + uacpi_free(wtr_response.data, wtr_response.length); + return UACPI_STATUS_OUT_OF_MEMORY; + } + + wtr_response_obj->buffer->data = wtr_response.data; + wtr_response_obj->buffer->size = wtr_response.length; + + uacpi_object_unref(src); + item_array_at(&op_ctx->items, 0)->obj = wtr_response_obj; + } + + return ret; + } + + if (dst->type != UACPI_OBJECT_REFERENCE) + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + + return copy_object_to_reference(dst, src); +} + +static uacpi_status handle_inc_dec(struct execution_context *ctx) +{ + uacpi_object *src, *dst; + struct op_context *op_ctx = ctx->cur_op_ctx; + uacpi_bool field_allowed = UACPI_FALSE; + uacpi_object_type true_src_type; + uacpi_status ret; + + src = item_array_at(&op_ctx->items, 0)->obj; + dst = item_array_at(&op_ctx->items, 1)->obj; + + if (src->type == UACPI_OBJECT_REFERENCE) { + /* + * Increment/Decrement are the only two operators that modify the value + * in-place, thus we need very specific dereference rules here. + * + * Reading buffer fields & field units is only allowed if we were passed + * a namestring directly as opposed to some nested reference chain + * containing a field at the bottom. + */ + if (src->flags == UACPI_REFERENCE_KIND_NAMED) + field_allowed = src->inner_object->type != UACPI_OBJECT_REFERENCE; + + src = reference_unwind(src)->inner_object; + } // else buffer index + + true_src_type = src->type; + + switch (true_src_type) { + case UACPI_OBJECT_INTEGER: + dst->integer = src->integer; + break; + case UACPI_OBJECT_FIELD_UNIT: + case UACPI_OBJECT_BUFFER_FIELD: + if (uacpi_unlikely(!field_allowed)) + goto out_bad_type; + + ret = field_get_read_type(src, &true_src_type); + if (uacpi_unlikely_error(ret)) + goto out_bad_type; + if (true_src_type != UACPI_OBJECT_INTEGER) + goto out_bad_type; + + if (src->type == UACPI_OBJECT_FIELD_UNIT) { + ret = uacpi_read_field_unit( + src->field_unit, &dst->integer, sizeof_int(), + UACPI_NULL + ); + if (uacpi_unlikely_error(ret)) + return ret; + } else { + uacpi_read_buffer_field(&src->buffer_field, &dst->integer); + } + break; + case UACPI_OBJECT_BUFFER_INDEX: + dst->integer = *buffer_index_cursor(&src->buffer_index); + break; + default: + goto out_bad_type; + } + + if (op_ctx->op->code == UACPI_AML_OP_IncrementOp) + dst->integer++; + else + dst->integer--; + + return UACPI_STATUS_OK; + +out_bad_type: + uacpi_error("Increment/Decrement: invalid object type '%s'\n", + uacpi_object_type_to_string(true_src_type)); + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; +} + +static uacpi_status enter_method( + struct execution_context *ctx, struct call_frame *new_frame, + uacpi_control_method *method +) +{ + uacpi_status ret = UACPI_STATUS_OK; + + uacpi_shareable_ref(method); + + if (!method->is_serialized) + return ret; + + if (uacpi_unlikely(ctx->sync_level > method->sync_level)) { + uacpi_error( + "cannot invoke method @%p, sync level %d is too low " + "(current is %d)\n", + method, method->sync_level, ctx->sync_level + ); + return UACPI_STATUS_AML_SYNC_LEVEL_TOO_HIGH; + } + + if (method->mutex == UACPI_NULL) { + method->mutex = uacpi_create_mutex(); + if (uacpi_unlikely(method->mutex == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + method->mutex->sync_level = method->sync_level; + } + + if (!uacpi_this_thread_owns_aml_mutex(method->mutex)) { + ret = uacpi_acquire_aml_mutex(method->mutex, 0xFFFF); + if (uacpi_unlikely_error(ret)) + return ret; + + ret = held_mutexes_array_push(&ctx->held_mutexes, method->mutex); + if (uacpi_unlikely_error(ret)) { + uacpi_release_aml_mutex(method->mutex); + return ret; + } + } + + new_frame->prev_sync_level = ctx->sync_level; + ctx->sync_level = method->sync_level; + return UACPI_STATUS_OK; +} + +static uacpi_status push_op(struct execution_context *ctx) +{ + struct call_frame *frame = ctx->cur_frame; + struct op_context *op_ctx; + + op_ctx = op_context_array_calloc(&frame->pending_ops); + if (op_ctx == UACPI_NULL) + return UACPI_STATUS_OUT_OF_MEMORY; + + op_ctx->op = ctx->cur_op; + refresh_ctx_pointers(ctx); + return UACPI_STATUS_OK; +} + +static uacpi_bool pop_item(struct op_context *op_ctx) +{ + struct item *item; + + if (item_array_size(&op_ctx->items) == 0) + return UACPI_FALSE; + + item = item_array_last(&op_ctx->items); + + if (item->type == ITEM_OBJECT) + uacpi_object_unref(item->obj); + + if (item->type == ITEM_NAMESPACE_NODE) + uacpi_namespace_node_unref(item->node); + + item_array_pop(&op_ctx->items); + return UACPI_TRUE; +} + +static void pop_op(struct execution_context *ctx) +{ + struct call_frame *frame = ctx->cur_frame; + struct op_context *cur_op_ctx = ctx->cur_op_ctx; + + while (pop_item(cur_op_ctx)); + + item_array_clear(&cur_op_ctx->items); + op_context_array_pop(&frame->pending_ops); + refresh_ctx_pointers(ctx); +} + +static void call_frame_clear(struct call_frame *frame) +{ + uacpi_size i; + op_context_array_clear(&frame->pending_ops); + code_block_array_clear(&frame->code_blocks); + + while (temp_namespace_node_array_size(&frame->temp_nodes) != 0) { + uacpi_namespace_node *node; + + node = *temp_namespace_node_array_last(&frame->temp_nodes); + uacpi_namespace_node_uninstall(node); + temp_namespace_node_array_pop(&frame->temp_nodes); + } + temp_namespace_node_array_clear(&frame->temp_nodes); + + for (i = 0; i < 7; ++i) + uacpi_object_unref(frame->args[i]); + for (i = 0; i < 8; ++i) + uacpi_object_unref(frame->locals[i]); + + uacpi_method_unref(frame->method); +} + +static uacpi_u8 parse_op_generates_item[0x100] = { + [UACPI_PARSE_OP_SIMPLE_NAME] = ITEM_EMPTY_OBJECT, + [UACPI_PARSE_OP_SUPERNAME] = ITEM_EMPTY_OBJECT, + [UACPI_PARSE_OP_SUPERNAME_OR_UNRESOLVED] = ITEM_EMPTY_OBJECT, + [UACPI_PARSE_OP_TERM_ARG] = ITEM_EMPTY_OBJECT, + [UACPI_PARSE_OP_TERM_ARG_UNWRAP_INTERNAL] = ITEM_EMPTY_OBJECT, + [UACPI_PARSE_OP_TERM_ARG_OR_NAMED_OBJECT] = ITEM_EMPTY_OBJECT, + [UACPI_PARSE_OP_TERM_ARG_OR_NAMED_OBJECT_OR_UNRESOLVED] = ITEM_EMPTY_OBJECT, + [UACPI_PARSE_OP_OPERAND] = ITEM_EMPTY_OBJECT, + [UACPI_PARSE_OP_STRING] = ITEM_EMPTY_OBJECT, + [UACPI_PARSE_OP_COMPUTATIONAL_DATA] = ITEM_EMPTY_OBJECT, + [UACPI_PARSE_OP_TARGET] = ITEM_EMPTY_OBJECT, + [UACPI_PARSE_OP_PKGLEN] = ITEM_PACKAGE_LENGTH, + [UACPI_PARSE_OP_TRACKED_PKGLEN] = ITEM_PACKAGE_LENGTH, + [UACPI_PARSE_OP_CREATE_NAMESTRING] = ITEM_NAMESPACE_NODE, + [UACPI_PARSE_OP_CREATE_NAMESTRING_OR_NULL_IF_LOAD] = ITEM_NAMESPACE_NODE, + [UACPI_PARSE_OP_EXISTING_NAMESTRING] = ITEM_NAMESPACE_NODE, + [UACPI_PARSE_OP_EXISTING_NAMESTRING_OR_NULL] = ITEM_NAMESPACE_NODE, + [UACPI_PARSE_OP_EXISTING_NAMESTRING_OR_NULL_IF_LOAD] = ITEM_NAMESPACE_NODE, + [UACPI_PARSE_OP_LOAD_INLINE_IMM_AS_OBJECT] = ITEM_OBJECT, + [UACPI_PARSE_OP_LOAD_INLINE_IMM] = ITEM_IMMEDIATE, + [UACPI_PARSE_OP_LOAD_ZERO_IMM] = ITEM_IMMEDIATE, + [UACPI_PARSE_OP_LOAD_IMM] = ITEM_IMMEDIATE, + [UACPI_PARSE_OP_LOAD_IMM_AS_OBJECT] = ITEM_OBJECT, + [UACPI_PARSE_OP_LOAD_FALSE_OBJECT] = ITEM_OBJECT, + [UACPI_PARSE_OP_LOAD_TRUE_OBJECT] = ITEM_OBJECT, + [UACPI_PARSE_OP_OBJECT_ALLOC] = ITEM_OBJECT, + [UACPI_PARSE_OP_OBJECT_ALLOC_TYPED] = ITEM_OBJECT, + [UACPI_PARSE_OP_EMPTY_OBJECT_ALLOC] = ITEM_EMPTY_OBJECT, + [UACPI_PARSE_OP_OBJECT_CONVERT_TO_SHALLOW_COPY] = ITEM_OBJECT, + [UACPI_PARSE_OP_OBJECT_CONVERT_TO_DEEP_COPY] = ITEM_OBJECT, + [UACPI_PARSE_OP_RECORD_AML_PC] = ITEM_IMMEDIATE, +}; + +static const uacpi_u8 *op_decode_cursor(const struct op_context *ctx) +{ + const struct uacpi_op_spec *spec = ctx->op; + + if (spec->properties & UACPI_OP_PROPERTY_OUT_OF_LINE) + return &spec->indirect_decode_ops[ctx->pc]; + + return &spec->decode_ops[ctx->pc]; +} + +static uacpi_u8 op_decode_byte(struct op_context *ctx) +{ + uacpi_u8 byte; + + byte = *op_decode_cursor(ctx); + ctx->pc++; + + return byte; +} + +static uacpi_aml_op op_decode_aml_op(struct op_context *op_ctx) +{ + uacpi_aml_op op = 0; + + op |= op_decode_byte(op_ctx); + op |= op_decode_byte(op_ctx) << 8; + + return op; +} + +// MSVC doesn't support __VA_OPT__ so we do this weirdness +#define EXEC_OP_DO_LVL(lvl, reason, ...) \ + uacpi_##lvl("Op 0x%04X ('%s'): "reason"\n", \ + op_ctx->op->code, op_ctx->op->name __VA_ARGS__) + +#define EXEC_OP_DO_ERR(reason, ...) EXEC_OP_DO_LVL(error, reason, __VA_ARGS__) +#define EXEC_OP_DO_WARN(reason, ...) EXEC_OP_DO_LVL(warn, reason, __VA_ARGS__) + +#define EXEC_OP_ERR_2(reason, arg0, arg1) EXEC_OP_DO_ERR(reason, ,arg0, arg1) +#define EXEC_OP_ERR_1(reason, arg0) EXEC_OP_DO_ERR(reason, ,arg0) +#define EXEC_OP_ERR(reason) EXEC_OP_DO_ERR(reason) + +#define EXEC_OP_WARN(reason) EXEC_OP_DO_WARN(reason) + +#define SPEC_SIMPLE_NAME "SimpleName := NameString | ArgObj | LocalObj" +#define SPEC_SUPER_NAME \ + "SuperName := SimpleName | DebugObj | ReferenceTypeOpcode" +#define SPEC_TERM_ARG \ + "TermArg := ExpressionOpcode | DataObject | ArgObj | LocalObj" +#define SPEC_OPERAND "Operand := TermArg => Integer" +#define SPEC_STRING "String := TermArg => String" +#define SPEC_TARGET "Target := SuperName | NullName" + +#define SPEC_COMPUTATIONAL_DATA \ + "ComputationalData := ByteConst | WordConst | DWordConst | QWordConst " \ + "| String | ConstObj | RevisionOp | DefBuffer" + +static uacpi_bool op_wants_supername(enum uacpi_parse_op op) +{ + switch (op) { + case UACPI_PARSE_OP_SIMPLE_NAME: + case UACPI_PARSE_OP_SUPERNAME: + case UACPI_PARSE_OP_SUPERNAME_OR_UNRESOLVED: + case UACPI_PARSE_OP_TARGET: + return UACPI_TRUE; + default: + return UACPI_FALSE; + } +} + +static uacpi_bool op_wants_term_arg_or_operand(enum uacpi_parse_op op) +{ + switch (op) { + case UACPI_PARSE_OP_TERM_ARG: + case UACPI_PARSE_OP_TERM_ARG_UNWRAP_INTERNAL: + case UACPI_PARSE_OP_OPERAND: + case UACPI_PARSE_OP_STRING: + case UACPI_PARSE_OP_COMPUTATIONAL_DATA: + return UACPI_TRUE; + default: + return UACPI_FALSE; + } +} + +static uacpi_bool op_allows_unresolved(enum uacpi_parse_op op) +{ + switch (op) { + case UACPI_PARSE_OP_SUPERNAME_OR_UNRESOLVED: + case UACPI_PARSE_OP_TERM_ARG_OR_NAMED_OBJECT_OR_UNRESOLVED: + case UACPI_PARSE_OP_EXISTING_NAMESTRING_OR_NULL: + return UACPI_TRUE; + default: + return UACPI_FALSE; + } +} + +static uacpi_bool op_allows_unresolved_if_load(enum uacpi_parse_op op) +{ + switch (op) { + case UACPI_PARSE_OP_CREATE_NAMESTRING_OR_NULL_IF_LOAD: + case UACPI_PARSE_OP_EXISTING_NAMESTRING_OR_NULL_IF_LOAD: + return UACPI_TRUE; + default: + return UACPI_FALSE; + } +} + +static uacpi_status op_typecheck(const struct op_context *op_ctx, + const struct op_context *cur_op_ctx) +{ + const uacpi_char *expected_type_str; + uacpi_u8 ok_mask = 0; + uacpi_u8 props = cur_op_ctx->op->properties; + + switch (*op_decode_cursor(op_ctx)) { + // SimpleName := NameString | ArgObj | LocalObj + case UACPI_PARSE_OP_SIMPLE_NAME: + expected_type_str = SPEC_SIMPLE_NAME; + ok_mask |= UACPI_OP_PROPERTY_SIMPLE_NAME; + break; + + // Target := SuperName | NullName + case UACPI_PARSE_OP_TARGET: + expected_type_str = SPEC_TARGET; + ok_mask |= UACPI_OP_PROPERTY_TARGET | UACPI_OP_PROPERTY_SUPERNAME; + break; + + // SuperName := SimpleName | DebugObj | ReferenceTypeOpcode + case UACPI_PARSE_OP_SUPERNAME: + case UACPI_PARSE_OP_SUPERNAME_OR_UNRESOLVED: + expected_type_str = SPEC_SUPER_NAME; + ok_mask |= UACPI_OP_PROPERTY_SUPERNAME; + break; + + // TermArg := ExpressionOpcode | DataObject | ArgObj | LocalObj + case UACPI_PARSE_OP_TERM_ARG: + case UACPI_PARSE_OP_TERM_ARG_UNWRAP_INTERNAL: + case UACPI_PARSE_OP_TERM_ARG_OR_NAMED_OBJECT: + case UACPI_PARSE_OP_TERM_ARG_OR_NAMED_OBJECT_OR_UNRESOLVED: + case UACPI_PARSE_OP_OPERAND: + case UACPI_PARSE_OP_STRING: + case UACPI_PARSE_OP_COMPUTATIONAL_DATA: + expected_type_str = SPEC_TERM_ARG; + ok_mask |= UACPI_OP_PROPERTY_TERM_ARG; + break; + default: + return UACPI_STATUS_INVALID_ARGUMENT; + } + + if (!(props & ok_mask)) { + EXEC_OP_ERR_2("invalid argument: '%s', expected a %s", + cur_op_ctx->op->name, expected_type_str); + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + } + + return UACPI_STATUS_OK; +} + +static uacpi_status typecheck_obj( + const struct op_context *op_ctx, + const uacpi_object *obj, + enum uacpi_object_type expected_type, + const uacpi_char *spec_desc +) +{ + if (uacpi_likely(obj->type == expected_type)) + return UACPI_STATUS_OK; + + EXEC_OP_ERR_2("invalid argument type: %s, expected a %s", + uacpi_object_type_to_string(obj->type), spec_desc); + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; +} + +static uacpi_status typecheck_operand( + const struct op_context *op_ctx, + const uacpi_object *obj +) +{ + return typecheck_obj(op_ctx, obj, UACPI_OBJECT_INTEGER, SPEC_OPERAND); +} + +static uacpi_status typecheck_string( + const struct op_context *op_ctx, + const uacpi_object *obj +) +{ + return typecheck_obj(op_ctx, obj, UACPI_OBJECT_STRING, SPEC_STRING); +} + +static uacpi_status typecheck_computational_data( + const struct op_context *op_ctx, + const uacpi_object *obj +) +{ + switch (obj->type) { + case UACPI_OBJECT_STRING: + case UACPI_OBJECT_BUFFER: + case UACPI_OBJECT_INTEGER: + return UACPI_STATUS_OK; + default: + EXEC_OP_ERR_2( + "invalid argument type: %s, expected a %s", + uacpi_object_type_to_string(obj->type), + SPEC_COMPUTATIONAL_DATA + ); + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + } +} + +static void emit_op_skip_warn(const struct op_context *op_ctx) +{ + EXEC_OP_WARN("skipping due to previous errors"); +} + +static void trace_named_object_lookup_or_creation_failure( + struct call_frame *frame, uacpi_size offset, enum uacpi_parse_op op, + uacpi_status ret, enum uacpi_log_level level +) +{ + static const uacpi_char *oom_prefix = "<...>"; + static const uacpi_char *empty_string = ""; + static const uacpi_char *unknown_path = "<unknown-path>"; + static const uacpi_char *invalid_path = "<invalid-path>"; + + uacpi_status conv_ret; + const uacpi_char *action; + const uacpi_char *requested_path_to_print; + const uacpi_char *middle_part = UACPI_NULL; + const uacpi_char *prefix_path = UACPI_NULL; + uacpi_char *requested_path = UACPI_NULL; + uacpi_size length; + uacpi_bool is_create; + + is_create = op == UACPI_PARSE_OP_CREATE_NAMESTRING || + op == UACPI_PARSE_OP_CREATE_NAMESTRING_OR_NULL_IF_LOAD; + + if (is_create) + action = "create"; + else + action = "lookup"; + + conv_ret = name_string_to_path( + frame, offset, &requested_path, &length + ); + if (uacpi_unlikely_error(conv_ret)) { + if (conv_ret == UACPI_STATUS_OUT_OF_MEMORY) + requested_path_to_print = unknown_path; + else + requested_path_to_print = invalid_path; + } else { + requested_path_to_print = requested_path; + } + + if (requested_path && requested_path[0] != '\\') { + prefix_path = uacpi_namespace_node_generate_absolute_path( + frame->cur_scope + ); + if (uacpi_unlikely(prefix_path == UACPI_NULL)) + prefix_path = oom_prefix; + + if (prefix_path[1] != '\0') + middle_part = "."; + } else { + prefix_path = empty_string; + } + + if (middle_part == UACPI_NULL) + middle_part = empty_string; + + if (length == 5 && !is_create) { + uacpi_log_lvl( + level, + "unable to %s named object '%s' within (or above) " + "scope '%s': %s\n", action, requested_path_to_print, + prefix_path, uacpi_status_to_string(ret) + ); + } else { + uacpi_log_lvl( + level, + "unable to %s named object '%s%s%s': %s\n", + action, prefix_path, middle_part, + requested_path_to_print, uacpi_status_to_string(ret) + ); + } + + uacpi_free(requested_path, length); + if (prefix_path != oom_prefix && prefix_path != empty_string) + uacpi_free_dynamic_string(prefix_path); +} + +static uacpi_status uninstalled_op_handler(struct execution_context *ctx) +{ + struct op_context *op_ctx = ctx->cur_op_ctx; + + EXEC_OP_ERR("no dedicated handler installed"); + return UACPI_STATUS_UNIMPLEMENTED; +} + +enum op_handler { + OP_HANDLER_UNINSTALLED = 0, + OP_HANDLER_LOCAL, + OP_HANDLER_ARG, + OP_HANDLER_STRING, + OP_HANDLER_BINARY_MATH, + OP_HANDLER_CONTROL_FLOW, + OP_HANDLER_CODE_BLOCK, + OP_HANDLER_RETURN, + OP_HANDLER_CREATE_METHOD, + OP_HANDLER_COPY_OBJECT_OR_STORE, + OP_HANDLER_INC_DEC, + OP_HANDLER_REF_OR_DEREF_OF, + OP_HANDLER_LOGICAL_NOT, + OP_HANDLER_BINARY_LOGIC, + OP_HANDLER_NAMED_OBJECT, + OP_HANDLER_BUFFER, + OP_HANDLER_PACKAGE, + OP_HANDLER_CREATE_NAMED, + OP_HANDLER_CREATE_BUFFER_FIELD, + OP_HANDLER_READ_FIELD, + OP_HANDLER_ALIAS, + OP_HANDLER_CONCATENATE, + OP_HANDLER_CONCATENATE_RES, + OP_HANDLER_SIZEOF, + OP_HANDLER_UNARY_MATH, + OP_HANDLER_INDEX, + OP_HANDLER_OBJECT_TYPE, + OP_HANDLER_CREATE_OP_REGION, + OP_HANDLER_CREATE_DATA_REGION, + OP_HANDLER_CREATE_FIELD, + OP_HANDLER_TO, + OP_HANDLER_TO_STRING, + OP_HANDLER_TIMER, + OP_HANDLER_MID, + OP_HANDLER_MATCH, + OP_HANDLER_CREATE_MUTEX_OR_EVENT, + OP_HANDLER_BCD, + OP_HANDLER_UNLOAD, + OP_HANDLER_LOAD_TABLE, + OP_HANDLER_LOAD, + OP_HANDLER_STALL_OR_SLEEP, + OP_HANDLER_EVENT_CTL, + OP_HANDLER_MUTEX_CTL, + OP_HANDLER_NOTIFY, + OP_HANDLER_FIRMWARE_REQUEST, +}; + +static uacpi_status (*op_handlers[])(struct execution_context *ctx) = { + /* + * All OPs that don't have a handler dispatch to here if + * UACPI_PARSE_OP_INVOKE_HANDLER is reached. + */ + [OP_HANDLER_UNINSTALLED] = uninstalled_op_handler, + [OP_HANDLER_LOCAL] = handle_local, + [OP_HANDLER_ARG] = handle_arg, + [OP_HANDLER_NAMED_OBJECT] = handle_named_object, + [OP_HANDLER_STRING] = handle_string, + [OP_HANDLER_BINARY_MATH] = handle_binary_math, + [OP_HANDLER_CONTROL_FLOW] = handle_control_flow, + [OP_HANDLER_CODE_BLOCK] = handle_code_block, + [OP_HANDLER_RETURN] = handle_return, + [OP_HANDLER_CREATE_METHOD] = handle_create_method, + [OP_HANDLER_CREATE_MUTEX_OR_EVENT] = handle_create_mutex_or_event, + [OP_HANDLER_COPY_OBJECT_OR_STORE] = handle_copy_object_or_store, + [OP_HANDLER_INC_DEC] = handle_inc_dec, + [OP_HANDLER_REF_OR_DEREF_OF] = handle_ref_or_deref_of, + [OP_HANDLER_LOGICAL_NOT] = handle_logical_not, + [OP_HANDLER_BINARY_LOGIC] = handle_binary_logic, + [OP_HANDLER_BUFFER] = handle_buffer, + [OP_HANDLER_PACKAGE] = handle_package, + [OP_HANDLER_CREATE_NAMED] = handle_create_named, + [OP_HANDLER_CREATE_BUFFER_FIELD] = handle_create_buffer_field, + [OP_HANDLER_READ_FIELD] = handle_field_read, + [OP_HANDLER_TO] = handle_to, + [OP_HANDLER_ALIAS] = handle_create_alias, + [OP_HANDLER_CONCATENATE] = handle_concatenate, + [OP_HANDLER_CONCATENATE_RES] = handle_concatenate_res, + [OP_HANDLER_SIZEOF] = handle_sizeof, + [OP_HANDLER_UNARY_MATH] = handle_unary_math, + [OP_HANDLER_INDEX] = handle_index, + [OP_HANDLER_OBJECT_TYPE] = handle_object_type, + [OP_HANDLER_CREATE_OP_REGION] = handle_create_op_region, + [OP_HANDLER_CREATE_DATA_REGION] = handle_create_data_region, + [OP_HANDLER_CREATE_FIELD] = handle_create_field, + [OP_HANDLER_TIMER] = handle_timer, + [OP_HANDLER_TO_STRING] = handle_to_string, + [OP_HANDLER_MID] = handle_mid, + [OP_HANDLER_MATCH] = handle_match, + [OP_HANDLER_BCD] = handle_bcd, + [OP_HANDLER_UNLOAD] = handle_unload, + [OP_HANDLER_LOAD_TABLE] = handle_load_table, + [OP_HANDLER_LOAD] = handle_load, + [OP_HANDLER_STALL_OR_SLEEP] = handle_stall_or_sleep, + [OP_HANDLER_EVENT_CTL] = handle_event_ctl, + [OP_HANDLER_MUTEX_CTL] = handle_mutex_ctl, + [OP_HANDLER_NOTIFY] = handle_notify, + [OP_HANDLER_FIRMWARE_REQUEST] = handle_firmware_request, +}; + +static uacpi_u8 handler_idx_of_op[0x100] = { + [UACPI_AML_OP_Local0Op] = OP_HANDLER_LOCAL, + [UACPI_AML_OP_Local1Op] = OP_HANDLER_LOCAL, + [UACPI_AML_OP_Local2Op] = OP_HANDLER_LOCAL, + [UACPI_AML_OP_Local3Op] = OP_HANDLER_LOCAL, + [UACPI_AML_OP_Local4Op] = OP_HANDLER_LOCAL, + [UACPI_AML_OP_Local5Op] = OP_HANDLER_LOCAL, + [UACPI_AML_OP_Local6Op] = OP_HANDLER_LOCAL, + [UACPI_AML_OP_Local7Op] = OP_HANDLER_LOCAL, + + [UACPI_AML_OP_Arg0Op] = OP_HANDLER_ARG, + [UACPI_AML_OP_Arg1Op] = OP_HANDLER_ARG, + [UACPI_AML_OP_Arg2Op] = OP_HANDLER_ARG, + [UACPI_AML_OP_Arg3Op] = OP_HANDLER_ARG, + [UACPI_AML_OP_Arg4Op] = OP_HANDLER_ARG, + [UACPI_AML_OP_Arg5Op] = OP_HANDLER_ARG, + [UACPI_AML_OP_Arg6Op] = OP_HANDLER_ARG, + + [UACPI_AML_OP_StringPrefix] = OP_HANDLER_STRING, + + [UACPI_AML_OP_AddOp] = OP_HANDLER_BINARY_MATH, + [UACPI_AML_OP_SubtractOp] = OP_HANDLER_BINARY_MATH, + [UACPI_AML_OP_MultiplyOp] = OP_HANDLER_BINARY_MATH, + [UACPI_AML_OP_DivideOp] = OP_HANDLER_BINARY_MATH, + [UACPI_AML_OP_ShiftLeftOp] = OP_HANDLER_BINARY_MATH, + [UACPI_AML_OP_ShiftRightOp] = OP_HANDLER_BINARY_MATH, + [UACPI_AML_OP_AndOp] = OP_HANDLER_BINARY_MATH, + [UACPI_AML_OP_NandOp] = OP_HANDLER_BINARY_MATH, + [UACPI_AML_OP_OrOp] = OP_HANDLER_BINARY_MATH, + [UACPI_AML_OP_NorOp] = OP_HANDLER_BINARY_MATH, + [UACPI_AML_OP_XorOp] = OP_HANDLER_BINARY_MATH, + [UACPI_AML_OP_ModOp] = OP_HANDLER_BINARY_MATH, + + [UACPI_AML_OP_IfOp] = OP_HANDLER_CODE_BLOCK, + [UACPI_AML_OP_ElseOp] = OP_HANDLER_CODE_BLOCK, + [UACPI_AML_OP_WhileOp] = OP_HANDLER_CODE_BLOCK, + [UACPI_AML_OP_ScopeOp] = OP_HANDLER_CODE_BLOCK, + + [UACPI_AML_OP_ContinueOp] = OP_HANDLER_CONTROL_FLOW, + [UACPI_AML_OP_BreakOp] = OP_HANDLER_CONTROL_FLOW, + + [UACPI_AML_OP_ReturnOp] = OP_HANDLER_RETURN, + + [UACPI_AML_OP_MethodOp] = OP_HANDLER_CREATE_METHOD, + + [UACPI_AML_OP_StoreOp] = OP_HANDLER_COPY_OBJECT_OR_STORE, + [UACPI_AML_OP_CopyObjectOp] = OP_HANDLER_COPY_OBJECT_OR_STORE, + + [UACPI_AML_OP_IncrementOp] = OP_HANDLER_INC_DEC, + [UACPI_AML_OP_DecrementOp] = OP_HANDLER_INC_DEC, + + [UACPI_AML_OP_RefOfOp] = OP_HANDLER_REF_OR_DEREF_OF, + [UACPI_AML_OP_DerefOfOp] = OP_HANDLER_REF_OR_DEREF_OF, + + [UACPI_AML_OP_LnotOp] = OP_HANDLER_LOGICAL_NOT, + + [UACPI_AML_OP_LEqualOp] = OP_HANDLER_BINARY_LOGIC, + [UACPI_AML_OP_LandOp] = OP_HANDLER_BINARY_LOGIC, + [UACPI_AML_OP_LorOp] = OP_HANDLER_BINARY_LOGIC, + [UACPI_AML_OP_LGreaterOp] = OP_HANDLER_BINARY_LOGIC, + [UACPI_AML_OP_LLessOp] = OP_HANDLER_BINARY_LOGIC, + + [UACPI_AML_OP_InternalOpNamedObject] = OP_HANDLER_NAMED_OBJECT, + + [UACPI_AML_OP_BufferOp] = OP_HANDLER_BUFFER, + + [UACPI_AML_OP_PackageOp] = OP_HANDLER_PACKAGE, + [UACPI_AML_OP_VarPackageOp] = OP_HANDLER_PACKAGE, + + [UACPI_AML_OP_NameOp] = OP_HANDLER_CREATE_NAMED, + + [UACPI_AML_OP_CreateBitFieldOp] = OP_HANDLER_CREATE_BUFFER_FIELD, + [UACPI_AML_OP_CreateByteFieldOp] = OP_HANDLER_CREATE_BUFFER_FIELD, + [UACPI_AML_OP_CreateWordFieldOp] = OP_HANDLER_CREATE_BUFFER_FIELD, + [UACPI_AML_OP_CreateDWordFieldOp] = OP_HANDLER_CREATE_BUFFER_FIELD, + [UACPI_AML_OP_CreateQWordFieldOp] = OP_HANDLER_CREATE_BUFFER_FIELD, + + [UACPI_AML_OP_InternalOpReadFieldAsBuffer] = OP_HANDLER_READ_FIELD, + [UACPI_AML_OP_InternalOpReadFieldAsInteger] = OP_HANDLER_READ_FIELD, + + [UACPI_AML_OP_ToIntegerOp] = OP_HANDLER_TO, + [UACPI_AML_OP_ToBufferOp] = OP_HANDLER_TO, + [UACPI_AML_OP_ToDecimalStringOp] = OP_HANDLER_TO, + [UACPI_AML_OP_ToHexStringOp] = OP_HANDLER_TO, + [UACPI_AML_OP_ToStringOp] = OP_HANDLER_TO_STRING, + + [UACPI_AML_OP_AliasOp] = OP_HANDLER_ALIAS, + + [UACPI_AML_OP_ConcatOp] = OP_HANDLER_CONCATENATE, + [UACPI_AML_OP_ConcatResOp] = OP_HANDLER_CONCATENATE_RES, + + [UACPI_AML_OP_SizeOfOp] = OP_HANDLER_SIZEOF, + + [UACPI_AML_OP_NotOp] = OP_HANDLER_UNARY_MATH, + [UACPI_AML_OP_FindSetLeftBitOp] = OP_HANDLER_UNARY_MATH, + [UACPI_AML_OP_FindSetRightBitOp] = OP_HANDLER_UNARY_MATH, + + [UACPI_AML_OP_IndexOp] = OP_HANDLER_INDEX, + + [UACPI_AML_OP_ObjectTypeOp] = OP_HANDLER_OBJECT_TYPE, + + [UACPI_AML_OP_MidOp] = OP_HANDLER_MID, + + [UACPI_AML_OP_MatchOp] = OP_HANDLER_MATCH, + + [UACPI_AML_OP_NotifyOp] = OP_HANDLER_NOTIFY, + + [UACPI_AML_OP_BreakPointOp] = OP_HANDLER_FIRMWARE_REQUEST, +}; + +#define EXT_OP_IDX(op) (op & 0xFF) + +static uacpi_u8 handler_idx_of_ext_op[0x100] = { + [EXT_OP_IDX(UACPI_AML_OP_CreateFieldOp)] = OP_HANDLER_CREATE_BUFFER_FIELD, + [EXT_OP_IDX(UACPI_AML_OP_CondRefOfOp)] = OP_HANDLER_REF_OR_DEREF_OF, + [EXT_OP_IDX(UACPI_AML_OP_OpRegionOp)] = OP_HANDLER_CREATE_OP_REGION, + [EXT_OP_IDX(UACPI_AML_OP_DeviceOp)] = OP_HANDLER_CODE_BLOCK, + [EXT_OP_IDX(UACPI_AML_OP_ProcessorOp)] = OP_HANDLER_CODE_BLOCK, + [EXT_OP_IDX(UACPI_AML_OP_PowerResOp)] = OP_HANDLER_CODE_BLOCK, + [EXT_OP_IDX(UACPI_AML_OP_ThermalZoneOp)] = OP_HANDLER_CODE_BLOCK, + [EXT_OP_IDX(UACPI_AML_OP_TimerOp)] = OP_HANDLER_TIMER, + [EXT_OP_IDX(UACPI_AML_OP_MutexOp)] = OP_HANDLER_CREATE_MUTEX_OR_EVENT, + [EXT_OP_IDX(UACPI_AML_OP_EventOp)] = OP_HANDLER_CREATE_MUTEX_OR_EVENT, + + [EXT_OP_IDX(UACPI_AML_OP_FieldOp)] = OP_HANDLER_CREATE_FIELD, + [EXT_OP_IDX(UACPI_AML_OP_IndexFieldOp)] = OP_HANDLER_CREATE_FIELD, + [EXT_OP_IDX(UACPI_AML_OP_BankFieldOp)] = OP_HANDLER_CREATE_FIELD, + + [EXT_OP_IDX(UACPI_AML_OP_FromBCDOp)] = OP_HANDLER_BCD, + [EXT_OP_IDX(UACPI_AML_OP_ToBCDOp)] = OP_HANDLER_BCD, + + [EXT_OP_IDX(UACPI_AML_OP_DataRegionOp)] = OP_HANDLER_CREATE_DATA_REGION, + + [EXT_OP_IDX(UACPI_AML_OP_LoadTableOp)] = OP_HANDLER_LOAD_TABLE, + [EXT_OP_IDX(UACPI_AML_OP_LoadOp)] = OP_HANDLER_LOAD, + [EXT_OP_IDX(UACPI_AML_OP_UnloadOp)] = OP_HANDLER_UNLOAD, + + [EXT_OP_IDX(UACPI_AML_OP_StallOp)] = OP_HANDLER_STALL_OR_SLEEP, + [EXT_OP_IDX(UACPI_AML_OP_SleepOp)] = OP_HANDLER_STALL_OR_SLEEP, + + [EXT_OP_IDX(UACPI_AML_OP_SignalOp)] = OP_HANDLER_EVENT_CTL, + [EXT_OP_IDX(UACPI_AML_OP_ResetOp)] = OP_HANDLER_EVENT_CTL, + [EXT_OP_IDX(UACPI_AML_OP_WaitOp)] = OP_HANDLER_EVENT_CTL, + + [EXT_OP_IDX(UACPI_AML_OP_AcquireOp)] = OP_HANDLER_MUTEX_CTL, + [EXT_OP_IDX(UACPI_AML_OP_ReleaseOp)] = OP_HANDLER_MUTEX_CTL, + + [EXT_OP_IDX(UACPI_AML_OP_FatalOp)] = OP_HANDLER_FIRMWARE_REQUEST, +}; + +enum method_call_type { + METHOD_CALL_NATIVE, + METHOD_CALL_AML, + METHOD_CALL_TABLE_LOAD, +}; + +static uacpi_status prepare_method_call( + struct execution_context *ctx, uacpi_namespace_node *node, + uacpi_control_method *method, enum method_call_type type, + const uacpi_object_array *args +) +{ + uacpi_status ret; + struct call_frame *frame; + + if (uacpi_unlikely(call_frame_array_size(&ctx->call_stack) >= + g_uacpi_rt_ctx.max_call_stack_depth)) + return UACPI_STATUS_AML_CALL_STACK_DEPTH_LIMIT; + + ret = push_new_frame(ctx, &frame); + if (uacpi_unlikely_error(ret)) + return ret; + + ret = enter_method(ctx, frame, method); + if (uacpi_unlikely_error(ret)) + goto method_dispatch_error; + + if (type == METHOD_CALL_NATIVE) { + uacpi_u8 arg_count; + + arg_count = args ? args->count : 0; + if (uacpi_unlikely(arg_count != method->args)) { + uacpi_error( + "invalid number of arguments %zu to call %.4s, expected %d\n", + args ? args->count : 0, node->name.text, method->args + ); + + ret = UACPI_STATUS_INVALID_ARGUMENT; + goto method_dispatch_error; + } + + if (args != UACPI_NULL) { + uacpi_u8 i; + + for (i = 0; i < method->args; ++i) { + frame->args[i] = args->objects[i]; + uacpi_object_ref(args->objects[i]); + } + } + } else if (type == METHOD_CALL_AML) { + ret = frame_push_args(frame, ctx->cur_op_ctx); + if (uacpi_unlikely_error(ret)) + goto method_dispatch_error; + } + + ret = frame_setup_base_scope(frame, node, method); + if (uacpi_unlikely_error(ret)) + goto method_dispatch_error; + + ctx->cur_frame = frame; + ctx->cur_op_ctx = UACPI_NULL; + ctx->prev_op_ctx = UACPI_NULL; + ctx->cur_block = code_block_array_last(&ctx->cur_frame->code_blocks); + + if (method->native_call) { + uacpi_object *retval; + + ret = method_get_ret_object(ctx, &retval); + if (uacpi_unlikely_error(ret)) + goto method_dispatch_error; + + return method->handler(ctx, retval); + } + + return UACPI_STATUS_OK; + +method_dispatch_error: + call_frame_clear(frame); + call_frame_array_pop(&ctx->call_stack); + return ret; +} + +static void apply_tracked_pkg( + struct call_frame *frame, struct op_context *op_ctx +) +{ + struct item *item; + + if (op_ctx->tracked_pkg_idx == 0) + return; + + item = item_array_at(&op_ctx->items, op_ctx->tracked_pkg_idx - 1); + frame->code_offset = item->pkg.end; +} + +static uacpi_status exec_op(struct execution_context *ctx) +{ + uacpi_status ret = UACPI_STATUS_OK; + struct call_frame *frame = ctx->cur_frame; + struct op_context *op_ctx; + struct item *item = UACPI_NULL; + enum uacpi_parse_op prev_op = 0, op; + + /* + * Allocate a new op context if previous is preempted (looking for a + * dynamic argument), or doesn't exist at all. + */ + if (!ctx_has_non_preempted_op(ctx)) { + ret = push_op(ctx); + if (uacpi_unlikely_error(ret)) + return ret; + } else { + trace_op(ctx->cur_op_ctx->op, OP_TRACE_ACTION_RESUME); + } + + if (ctx->prev_op_ctx) + prev_op = *op_decode_cursor(ctx->prev_op_ctx); + + for (;;) { + if (uacpi_unlikely_error(ret)) + return ret; + + op_ctx = ctx->cur_op_ctx; + frame = ctx->cur_frame; + + if (op_ctx->pc == 0 && ctx->prev_op_ctx) { + /* + * Type check the current arg type against what is expected by the + * preempted op. This check is able to catch most type violations + * with the only exception being Operand as we only know whether + * that evaluates to an integer after the fact. + */ + ret = op_typecheck(ctx->prev_op_ctx, ctx->cur_op_ctx); + if (uacpi_unlikely_error(ret)) + return ret; + } + + op = op_decode_byte(op_ctx); + trace_pop(op); + + if (parse_op_generates_item[op] != ITEM_NONE) { + item = item_array_alloc(&op_ctx->items); + if (uacpi_unlikely(item == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + item->type = parse_op_generates_item[op]; + if (item->type == ITEM_OBJECT) { + enum uacpi_object_type type = UACPI_OBJECT_UNINITIALIZED; + + if (op == UACPI_PARSE_OP_OBJECT_ALLOC_TYPED) + type = op_decode_byte(op_ctx); + + item->obj = uacpi_create_object(type); + if (uacpi_unlikely(item->obj == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + } else { + uacpi_memzero(&item->immediate, sizeof(item->immediate)); + } + } else if (item == UACPI_NULL) { + item = item_array_last(&op_ctx->items); + } + + switch (op) { + case UACPI_PARSE_OP_END: + case UACPI_PARSE_OP_SKIP_WITH_WARN_IF_NULL: { + trace_op(ctx->cur_op_ctx->op, OP_TRACE_ACTION_END); + + if (op == UACPI_PARSE_OP_SKIP_WITH_WARN_IF_NULL) { + uacpi_u8 idx; + + idx = op_decode_byte(op_ctx); + if (item_array_at(&op_ctx->items, idx)->handle != UACPI_NULL) + break; + + emit_op_skip_warn(op_ctx); + } + + apply_tracked_pkg(frame, op_ctx); + + pop_op(ctx); + if (ctx->cur_op_ctx) { + ctx->cur_op_ctx->preempted = UACPI_FALSE; + ctx->cur_op_ctx->pc++; + } + + return UACPI_STATUS_OK; + } + + case UACPI_PARSE_OP_EMIT_SKIP_WARN: + emit_op_skip_warn(op_ctx); + break; + + case UACPI_PARSE_OP_SIMPLE_NAME: + case UACPI_PARSE_OP_SUPERNAME: + case UACPI_PARSE_OP_SUPERNAME_OR_UNRESOLVED: + case UACPI_PARSE_OP_TERM_ARG: + case UACPI_PARSE_OP_TERM_ARG_UNWRAP_INTERNAL: + case UACPI_PARSE_OP_TERM_ARG_OR_NAMED_OBJECT: + case UACPI_PARSE_OP_TERM_ARG_OR_NAMED_OBJECT_OR_UNRESOLVED: + case UACPI_PARSE_OP_OPERAND: + case UACPI_PARSE_OP_STRING: + case UACPI_PARSE_OP_COMPUTATIONAL_DATA: + case UACPI_PARSE_OP_TARGET: + /* + * Preempt this op parsing for now as we wait for the dynamic arg + * to be parsed. + */ + op_ctx->preempted = UACPI_TRUE; + op_ctx->pc--; + return UACPI_STATUS_OK; + + case UACPI_PARSE_OP_TRACKED_PKGLEN: + op_ctx->tracked_pkg_idx = item_array_size(&op_ctx->items); + UACPI_FALLTHROUGH; + case UACPI_PARSE_OP_PKGLEN: + ret = parse_package_length(frame, &item->pkg); + break; + + case UACPI_PARSE_OP_LOAD_INLINE_IMM: + case UACPI_PARSE_OP_LOAD_INLINE_IMM_AS_OBJECT: { + void *dst; + uacpi_u8 src_width; + + if (op == UACPI_PARSE_OP_LOAD_INLINE_IMM_AS_OBJECT) { + item->obj->type = UACPI_OBJECT_INTEGER; + dst = &item->obj->integer; + src_width = 8; + } else { + dst = &item->immediate; + src_width = op_decode_byte(op_ctx); + } + + uacpi_memcpy_zerout( + dst, op_decode_cursor(op_ctx), + sizeof(uacpi_u64), src_width + ); + op_ctx->pc += src_width; + break; + } + + case UACPI_PARSE_OP_LOAD_ZERO_IMM: + break; + + case UACPI_PARSE_OP_LOAD_IMM: + case UACPI_PARSE_OP_LOAD_IMM_AS_OBJECT: { + uacpi_u8 width; + void *dst; + + width = op_decode_byte(op_ctx); + if (uacpi_unlikely(call_frame_code_bytes_left(frame) < width)) + return UACPI_STATUS_AML_BAD_ENCODING; + + if (op == UACPI_PARSE_OP_LOAD_IMM_AS_OBJECT) { + item->obj->type = UACPI_OBJECT_INTEGER; + item->obj->integer = 0; + dst = &item->obj->integer; + } else { + dst = item->immediate_bytes; + } + + uacpi_memcpy(dst, call_frame_cursor(frame), width); + frame->code_offset += width; + break; + } + + case UACPI_PARSE_OP_LOAD_FALSE_OBJECT: + case UACPI_PARSE_OP_LOAD_TRUE_OBJECT: { + uacpi_object *obj = item->obj; + obj->type = UACPI_OBJECT_INTEGER; + obj->integer = op == UACPI_PARSE_OP_LOAD_FALSE_OBJECT ? 0 : ones(); + break; + } + + case UACPI_PARSE_OP_RECORD_AML_PC: + item->immediate = frame->code_offset; + break; + + case UACPI_PARSE_OP_TRUNCATE_NUMBER: + truncate_number_if_needed(item->obj); + break; + + case UACPI_PARSE_OP_TYPECHECK: { + enum uacpi_object_type expected_type; + + expected_type = op_decode_byte(op_ctx); + + if (uacpi_unlikely(item->obj->type != expected_type)) { + EXEC_OP_ERR_2("bad object type: expected %s, got %s!", + uacpi_object_type_to_string(expected_type), + uacpi_object_type_to_string(item->obj->type)); + ret = UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + } + + break; + } + + case UACPI_PARSE_OP_BAD_OPCODE: + case UACPI_PARSE_OP_UNREACHABLE: + EXEC_OP_ERR("invalid/unexpected opcode"); + ret = UACPI_STATUS_AML_INVALID_OPCODE; + break; + + case UACPI_PARSE_OP_AML_PC_DECREMENT: + frame->code_offset--; + break; + + case UACPI_PARSE_OP_IMM_DECREMENT: + item_array_at(&op_ctx->items, op_decode_byte(op_ctx))->immediate--; + break; + + case UACPI_PARSE_OP_ITEM_POP: + pop_item(op_ctx); + item = item_array_last(&op_ctx->items); + break; + + case UACPI_PARSE_OP_IF_HAS_DATA: { + uacpi_size pkg_idx = op_ctx->tracked_pkg_idx - 1; + struct package_length *pkg; + uacpi_u8 bytes_skip; + + bytes_skip = op_decode_byte(op_ctx); + pkg = &item_array_at(&op_ctx->items, pkg_idx)->pkg; + + if (frame->code_offset >= pkg->end) + op_ctx->pc += bytes_skip; + + break; + } + + case UACPI_PARSE_OP_IF_NOT_NULL: + case UACPI_PARSE_OP_IF_NULL: + case UACPI_PARSE_OP_IF_LAST_NULL: + case UACPI_PARSE_OP_IF_LAST_NOT_NULL: { + uacpi_u8 idx, bytes_skip; + uacpi_bool is_null, skip_if_null; + + if (op == UACPI_PARSE_OP_IF_LAST_NULL || + op == UACPI_PARSE_OP_IF_LAST_NOT_NULL) { + is_null = item->handle == UACPI_NULL; + } else { + idx = op_decode_byte(op_ctx); + is_null = item_array_at(&op_ctx->items, idx)->handle == UACPI_NULL; + } + + bytes_skip = op_decode_byte(op_ctx); + skip_if_null = op == UACPI_PARSE_OP_IF_NOT_NULL || + op == UACPI_PARSE_OP_IF_LAST_NOT_NULL; + + if (is_null == skip_if_null) + op_ctx->pc += bytes_skip; + + break; + } + + case UACPI_PARSE_OP_IF_LAST_EQUALS: { + uacpi_u8 value, bytes_skip; + + value = op_decode_byte(op_ctx); + bytes_skip = op_decode_byte(op_ctx); + + if (item->immediate != value) + op_ctx->pc += bytes_skip; + + break; + } + + case UACPI_PARSE_OP_IF_LAST_FALSE: + case UACPI_PARSE_OP_IF_LAST_TRUE: { + uacpi_u8 bytes_skip; + uacpi_bool is_false, skip_if_false; + + bytes_skip = op_decode_byte(op_ctx); + is_false = item->obj->integer == 0; + skip_if_false = op == UACPI_PARSE_OP_IF_LAST_TRUE; + + if (is_false == skip_if_false) + op_ctx->pc += bytes_skip; + + break; + } + + case UACPI_PARSE_OP_JMP: { + op_ctx->pc = op_decode_byte(op_ctx); + break; + } + + case UACPI_PARSE_OP_CREATE_NAMESTRING: + case UACPI_PARSE_OP_CREATE_NAMESTRING_OR_NULL_IF_LOAD: + case UACPI_PARSE_OP_EXISTING_NAMESTRING: + case UACPI_PARSE_OP_EXISTING_NAMESTRING_OR_NULL: + case UACPI_PARSE_OP_EXISTING_NAMESTRING_OR_NULL_IF_LOAD: { + uacpi_size offset = frame->code_offset; + enum resolve_behavior behavior; + + if (op == UACPI_PARSE_OP_CREATE_NAMESTRING || + op == UACPI_PARSE_OP_CREATE_NAMESTRING_OR_NULL_IF_LOAD) + behavior = RESOLVE_CREATE_LAST_NAMESEG_FAIL_IF_EXISTS; + else + behavior = RESOLVE_FAIL_IF_DOESNT_EXIST; + + ret = resolve_name_string(frame, behavior, &item->node); + + if (ret == UACPI_STATUS_NOT_FOUND) { + uacpi_bool is_ok; + + if (prev_op) { + is_ok = op_allows_unresolved(prev_op); + is_ok &= op_allows_unresolved(op); + } else { + // This is the only standalone op where we allow unresolved + is_ok = op_ctx->op->code == UACPI_AML_OP_ExternalOp; + } + + if (is_ok) + ret = UACPI_STATUS_OK; + } + + if (uacpi_unlikely_error(ret)) { + enum uacpi_log_level lvl = UACPI_LOG_ERROR; + uacpi_status trace_ret = ret; + uacpi_bool abort_whileif = UACPI_FALSE; + + if (frame->method->named_objects_persist && + (ret == UACPI_STATUS_AML_OBJECT_ALREADY_EXISTS || + ret == UACPI_STATUS_NOT_FOUND)) { + struct op_context *first_ctx; + + first_ctx = op_context_array_at(&frame->pending_ops, 0); + abort_whileif = first_ctx->op->code == UACPI_AML_OP_WhileOp || + first_ctx->op->code == UACPI_AML_OP_IfOp; + + if (op_allows_unresolved_if_load(op) || abort_whileif) { + lvl = UACPI_LOG_WARN; + ret = UACPI_STATUS_OK; + } + } + + trace_named_object_lookup_or_creation_failure( + frame, offset, op, trace_ret, lvl + ); + + if (abort_whileif) { + while (op_context_array_size(&frame->pending_ops) != 1) + pop_op(ctx); + + op_ctx = op_context_array_at(&frame->pending_ops, 0); + op_ctx->pc++; + op_ctx->preempted = UACPI_FALSE; + break; + } + + if (ret == UACPI_STATUS_NOT_FOUND) + ret = UACPI_STATUS_AML_UNDEFINED_REFERENCE; + } + + if (behavior == RESOLVE_CREATE_LAST_NAMESEG_FAIL_IF_EXISTS && + !frame->method->named_objects_persist) + item->node->flags |= UACPI_NAMESPACE_NODE_FLAG_TEMPORARY; + + break; + } + + case UACPI_PARSE_OP_INVOKE_HANDLER: { + uacpi_aml_op code = op_ctx->op->code; + uacpi_u8 idx; + + if (code <= 0xFF) + idx = handler_idx_of_op[code]; + else + idx = handler_idx_of_ext_op[EXT_OP_IDX(code)]; + + ret = op_handlers[idx](ctx); + break; + } + + case UACPI_PARSE_OP_INSTALL_NAMESPACE_NODE: + item = item_array_at(&op_ctx->items, op_decode_byte(op_ctx)); + ret = do_install_node_item(frame, item); + break; + + case UACPI_PARSE_OP_OBJECT_TRANSFER_TO_PREV: + case UACPI_PARSE_OP_OBJECT_COPY_TO_PREV: { + uacpi_object *src; + struct item *dst; + + if (!ctx->prev_op_ctx) + break; + + switch (prev_op) { + case UACPI_PARSE_OP_TERM_ARG_UNWRAP_INTERNAL: + case UACPI_PARSE_OP_COMPUTATIONAL_DATA: + case UACPI_PARSE_OP_OPERAND: + case UACPI_PARSE_OP_STRING: + src = uacpi_unwrap_internal_reference(item->obj); + + if (prev_op == UACPI_PARSE_OP_OPERAND) + ret = typecheck_operand(ctx->prev_op_ctx, src); + else if (prev_op == UACPI_PARSE_OP_STRING) + ret = typecheck_string(ctx->prev_op_ctx, src); + else if (prev_op == UACPI_PARSE_OP_COMPUTATIONAL_DATA) + ret = typecheck_computational_data(ctx->prev_op_ctx, src); + + break; + case UACPI_PARSE_OP_SUPERNAME: + case UACPI_PARSE_OP_SUPERNAME_OR_UNRESOLVED: + src = item->obj; + break; + + case UACPI_PARSE_OP_SIMPLE_NAME: + case UACPI_PARSE_OP_TERM_ARG: + case UACPI_PARSE_OP_TERM_ARG_OR_NAMED_OBJECT: + case UACPI_PARSE_OP_TERM_ARG_OR_NAMED_OBJECT_OR_UNRESOLVED: + case UACPI_PARSE_OP_TARGET: + src = item->obj; + break; + + default: + EXEC_OP_ERR_1("don't know how to copy/transfer object to %d", + prev_op); + ret = UACPI_STATUS_INVALID_ARGUMENT; + break; + } + + if (uacpi_likely_success(ret)) { + dst = item_array_last(&ctx->prev_op_ctx->items); + dst->type = ITEM_OBJECT; + + if (op == UACPI_PARSE_OP_OBJECT_TRANSFER_TO_PREV) { + dst->obj = src; + uacpi_object_ref(dst->obj); + } else { + dst->obj = uacpi_create_object(UACPI_OBJECT_UNINITIALIZED); + if (uacpi_unlikely(dst->obj == UACPI_NULL)) { + ret = UACPI_STATUS_OUT_OF_MEMORY; + break; + } + + ret = uacpi_object_assign(dst->obj, src, + UACPI_ASSIGN_BEHAVIOR_DEEP_COPY); + } + } + break; + } + + case UACPI_PARSE_OP_STORE_TO_TARGET: + case UACPI_PARSE_OP_STORE_TO_TARGET_INDIRECT: { + uacpi_object *dst, *src; + + dst = item_array_at(&op_ctx->items, op_decode_byte(op_ctx))->obj; + + if (op == UACPI_PARSE_OP_STORE_TO_TARGET_INDIRECT) { + src = item_array_at(&op_ctx->items, + op_decode_byte(op_ctx))->obj; + } else { + src = item->obj; + } + + ret = store_to_target(dst, src, UACPI_NULL); + break; + } + + // Nothing to do here, object is allocated automatically + case UACPI_PARSE_OP_OBJECT_ALLOC: + case UACPI_PARSE_OP_OBJECT_ALLOC_TYPED: + case UACPI_PARSE_OP_EMPTY_OBJECT_ALLOC: + break; + + case UACPI_PARSE_OP_OBJECT_CONVERT_TO_SHALLOW_COPY: + case UACPI_PARSE_OP_OBJECT_CONVERT_TO_DEEP_COPY: { + uacpi_object *temp = item->obj; + enum uacpi_assign_behavior behavior; + + item_array_pop(&op_ctx->items); + item = item_array_last(&op_ctx->items); + + if (op == UACPI_PARSE_OP_OBJECT_CONVERT_TO_SHALLOW_COPY) + behavior = UACPI_ASSIGN_BEHAVIOR_SHALLOW_COPY; + else + behavior = UACPI_ASSIGN_BEHAVIOR_DEEP_COPY; + + ret = uacpi_object_assign(temp, item->obj, behavior); + if (uacpi_unlikely_error(ret)) + break; + + uacpi_object_unref(item->obj); + item->obj = temp; + break; + } + + case UACPI_PARSE_OP_DISPATCH_METHOD_CALL: { + struct uacpi_namespace_node *node; + struct uacpi_control_method *method; + + node = item_array_at(&op_ctx->items, 0)->node; + method = uacpi_namespace_node_get_object(node)->method; + + ret = prepare_method_call( + ctx, node, method, METHOD_CALL_AML, UACPI_NULL + ); + return ret; + } + + case UACPI_PARSE_OP_DISPATCH_TABLE_LOAD: { + struct uacpi_namespace_node *node; + struct uacpi_control_method *method; + + node = item_array_at(&op_ctx->items, 0)->node; + method = item_array_at(&op_ctx->items, 1)->obj->method; + + ret = prepare_method_call( + ctx, node, method, METHOD_CALL_TABLE_LOAD, UACPI_NULL + ); + return ret; + } + + case UACPI_PARSE_OP_CONVERT_NAMESTRING: { + uacpi_aml_op new_op = UACPI_AML_OP_InternalOpNamedObject; + uacpi_object *obj; + + if (item->node == UACPI_NULL) { + if (!op_allows_unresolved(prev_op)) + ret = UACPI_STATUS_NOT_FOUND; + break; + } + + obj = uacpi_namespace_node_get_object(item->node); + + switch (obj->type) { + case UACPI_OBJECT_METHOD: { + uacpi_bool should_invoke; + + switch (prev_op) { + case UACPI_PARSE_OP_TERM_ARG_OR_NAMED_OBJECT: + case UACPI_PARSE_OP_TERM_ARG_OR_NAMED_OBJECT_OR_UNRESOLVED: + should_invoke = UACPI_FALSE; + break; + default: + should_invoke = !op_wants_supername(prev_op); + } + + if (!should_invoke) + break; + + new_op = UACPI_AML_OP_InternalOpMethodCall0Args; + new_op += obj->method->args; + break; + } + + case UACPI_OBJECT_BUFFER_FIELD: + case UACPI_OBJECT_FIELD_UNIT: { + uacpi_object_type type; + + if (!op_wants_term_arg_or_operand(prev_op)) + break; + + ret = field_get_read_type(obj, &type); + if (uacpi_unlikely_error(ret)) { + const uacpi_char *field_path; + + field_path = uacpi_namespace_node_generate_absolute_path( + item->node + ); + + uacpi_error( + "unable to perform a read from field %s: " + "parent opregion gone\n", field_path + ); + uacpi_free_absolute_path(field_path); + } + + switch (type) { + case UACPI_OBJECT_BUFFER: + new_op = UACPI_AML_OP_InternalOpReadFieldAsBuffer; + break; + case UACPI_OBJECT_INTEGER: + new_op = UACPI_AML_OP_InternalOpReadFieldAsInteger; + break; + default: + ret = UACPI_STATUS_INVALID_ARGUMENT; + continue; + } + break; + } + default: + break; + } + + op_ctx->pc = 0; + op_ctx->op = uacpi_get_op_spec(new_op); + break; + } + + case UACPI_PARSE_OP_SWITCH_TO_NEXT_IF_EQUALS: { + uacpi_aml_op op, target_op; + uacpi_u32 cur_offset; + uacpi_u8 op_length; + + cur_offset = frame->code_offset; + apply_tracked_pkg(frame, op_ctx); + op_length = peek_next_op(frame, &op); + + target_op = op_decode_aml_op(op_ctx); + if (op_length == 0 || op != target_op) { + // Revert tracked package + frame->code_offset = cur_offset; + break; + } + + frame->code_offset += op_length; + op_ctx->switched_from = op_ctx->op->code; + op_ctx->op = uacpi_get_op_spec(target_op); + op_ctx->pc = 0; + break; + } + + case UACPI_PARSE_OP_IF_SWITCHED_FROM: { + uacpi_aml_op target_op; + uacpi_u8 skip_bytes; + + target_op = op_decode_aml_op(op_ctx); + skip_bytes = op_decode_byte(op_ctx); + + if (op_ctx->switched_from != target_op) + op_ctx->pc += skip_bytes; + break; + } + + default: + EXEC_OP_ERR_1("unhandled parser op '%d'", op); + ret = UACPI_STATUS_UNIMPLEMENTED; + break; + } + } +} + +static void ctx_reload_post_ret(struct execution_context *ctx) +{ + uacpi_control_method *method = ctx->cur_frame->method; + + if (method->is_serialized) { + held_mutexes_array_remove_and_release( + &ctx->held_mutexes, method->mutex, FORCE_RELEASE_YES + ); + ctx->sync_level = ctx->cur_frame->prev_sync_level; + } + + call_frame_clear(ctx->cur_frame); + call_frame_array_pop(&ctx->call_stack); + + ctx->cur_frame = call_frame_array_last(&ctx->call_stack); + refresh_ctx_pointers(ctx); +} + +static void trace_method_abort(struct code_block *block, uacpi_size depth) +{ + static const uacpi_char *unknown_path = "<unknown>"; + uacpi_char oom_absolute_path[9] = "<?>."; + + const uacpi_char *absolute_path; + + if (block != UACPI_NULL && block->type == CODE_BLOCK_SCOPE) { + absolute_path = uacpi_namespace_node_generate_absolute_path(block->node); + if (uacpi_unlikely(absolute_path == UACPI_NULL)) + uacpi_memcpy(oom_absolute_path + 4, block->node->name.text, 4); + } else { + absolute_path = unknown_path; + } + + uacpi_error(" #%zu in %s()\n", depth, absolute_path); + + if (absolute_path != oom_absolute_path && absolute_path != unknown_path) + uacpi_free_dynamic_string(absolute_path); +} + +static void stack_unwind(struct execution_context *ctx) +{ + uacpi_size depth; + uacpi_bool should_stop; + + /* + * Non-empty call stack here means the execution was aborted at some point, + * probably due to a bytecode error. + */ + depth = call_frame_array_size(&ctx->call_stack); + + if (depth != 0) { + uacpi_size idx = 0; + uacpi_bool table_level_code; + + do { + table_level_code = ctx->cur_frame->method->named_objects_persist; + + if (table_level_code && idx != 0) + /* + * This isn't the first frame that we are aborting. + * If this is table-level code, we have just unwound a call + * chain that had triggered an abort. Stop here, no need to + * abort table load because of it. + */ + break; + + while (op_context_array_size(&ctx->cur_frame->pending_ops) != 0) + pop_op(ctx); + + trace_method_abort( + code_block_array_at(&ctx->cur_frame->code_blocks, 0), idx + ); + + should_stop = idx++ == 0 && table_level_code; + ctx_reload_post_ret(ctx); + } while (--depth && !should_stop); + } +} + +static void execution_context_release(struct execution_context *ctx) +{ + if (ctx->ret) + uacpi_object_unref(ctx->ret); + + while (held_mutexes_array_size(&ctx->held_mutexes) != 0) { + held_mutexes_array_remove_and_release( + &ctx->held_mutexes, + *held_mutexes_array_last(&ctx->held_mutexes), + FORCE_RELEASE_YES + ); + } + + call_frame_array_clear(&ctx->call_stack); + held_mutexes_array_clear(&ctx->held_mutexes); + uacpi_free(ctx, sizeof(*ctx)); +} + +uacpi_status uacpi_execute_control_method( + uacpi_namespace_node *scope, uacpi_control_method *method, + const uacpi_object_array *args, uacpi_object **out_obj +) +{ + uacpi_status ret = UACPI_STATUS_OK; + struct execution_context *ctx; + + ctx = uacpi_kernel_alloc_zeroed(sizeof(*ctx)); + if (uacpi_unlikely(ctx == UACPI_NULL)) + return UACPI_STATUS_OUT_OF_MEMORY; + + if (out_obj != UACPI_NULL) { + ctx->ret = uacpi_create_object(UACPI_OBJECT_UNINITIALIZED); + if (uacpi_unlikely(ctx->ret == UACPI_NULL)) { + ret = UACPI_STATUS_OUT_OF_MEMORY; + goto out; + } + } + + ret = prepare_method_call(ctx, scope, method, METHOD_CALL_NATIVE, args); + if (uacpi_unlikely_error(ret)) + goto out; + + for (;;) { + if (!ctx_has_non_preempted_op(ctx)) { + if (ctx->cur_frame == UACPI_NULL) + break; + + if (maybe_end_block(ctx)) + continue; + + if (!call_frame_has_code(ctx->cur_frame)) { + ctx_reload_post_ret(ctx); + continue; + } + + ret = get_op(ctx); + if (uacpi_unlikely_error(ret)) + goto handle_method_abort; + + trace_op(ctx->cur_op, OP_TRACE_ACTION_BEGIN); + } + + ret = exec_op(ctx); + if (uacpi_unlikely_error(ret)) + goto handle_method_abort; + + continue; + + handle_method_abort: + uacpi_error("aborting %s due to previous error: %s\n", + ctx->cur_frame->method->named_objects_persist ? + "table load" : "method invocation", + uacpi_status_to_string(ret)); + stack_unwind(ctx); + + /* + * Having a frame here implies that we just aborted a dynamic table + * load. Signal to the caller that it failed by setting the return + * value to false. + */ + if (ctx->cur_frame) { + struct item *it; + + it = item_array_last(&ctx->cur_op_ctx->items); + if (it != UACPI_NULL && it->obj != UACPI_NULL) + it->obj->integer = 0; + } + } + +out: + if (ctx->ret != UACPI_NULL) { + uacpi_object *ret_obj = UACPI_NULL; + + if (ctx->ret->type != UACPI_OBJECT_UNINITIALIZED) { + ret_obj = ctx->ret; + uacpi_object_ref(ret_obj); + } + + *out_obj = ret_obj; + } + + execution_context_release(ctx); + return ret; +} + +uacpi_status uacpi_osi(uacpi_handle handle, uacpi_object *retval) +{ + struct execution_context *ctx = handle; + uacpi_bool is_supported; + uacpi_status ret; + uacpi_object *arg; + + arg = uacpi_unwrap_internal_reference(ctx->cur_frame->args[0]); + if (arg->type != UACPI_OBJECT_STRING) { + uacpi_error("_OSI: invalid argument type %s, expected a String\n", + uacpi_object_type_to_string(arg->type)); + return UACPI_STATUS_AML_INCOMPATIBLE_OBJECT_TYPE; + } + + if (retval == UACPI_NULL) + return UACPI_STATUS_OK; + + retval->type = UACPI_OBJECT_INTEGER; + + ret = uacpi_handle_osi(arg->buffer->text, &is_supported); + if (uacpi_unlikely_error(ret)) + return ret; + + retval->integer = is_supported ? ones() : 0; + + uacpi_trace("_OSI(%s) => reporting as %ssupported\n", + arg->buffer->text, is_supported ? "" : "un"); + return UACPI_STATUS_OK; +} + +#endif // !UACPI_BAREBONES_MODE |