#include #include #include #include #include #include #include #include #ifndef UACPI_BAREBONES_MODE struct uacpi_recursive_lock g_opregion_lock; uacpi_status uacpi_initialize_opregion(void) { return uacpi_recursive_lock_init(&g_opregion_lock); } void uacpi_deinitialize_opregion(void) { uacpi_recursive_lock_deinit(&g_opregion_lock); } void uacpi_trace_region_error( uacpi_namespace_node *node, uacpi_char *message, uacpi_status ret ) { const uacpi_char *path, *space_string = ""; uacpi_object *obj; path = uacpi_namespace_node_generate_absolute_path(node); obj = uacpi_namespace_node_get_object_typed( node, UACPI_OBJECT_OPERATION_REGION_BIT ); if (uacpi_likely(obj != UACPI_NULL)) space_string = uacpi_address_space_to_string(obj->op_region->space); uacpi_error( "%s (%s) operation region %s: %s\n", message, space_string, path, uacpi_status_to_string(ret) ); uacpi_free_dynamic_string(path); } static void trace_region_io( uacpi_field_unit *field, uacpi_address_space space, uacpi_u64 offset, uacpi_region_op op, union uacpi_opregion_io_data data ) { const uacpi_char *path; const uacpi_char *type_str; if (!uacpi_should_log(UACPI_LOG_TRACE)) return; switch (op) { case UACPI_REGION_OP_READ: type_str = "read from"; break; case UACPI_REGION_OP_WRITE: type_str = "write to"; break; default: type_str = ""; } path = uacpi_namespace_node_generate_absolute_path(field->region); switch (space) { case UACPI_ADDRESS_SPACE_IPMI: case UACPI_ADDRESS_SPACE_PRM: case UACPI_ADDRESS_SPACE_FFIXEDHW: uacpi_trace( "write-then-read from [%s] %s[0x%016"UACPI_PRIX64"] = " "\n", path, uacpi_address_space_to_string(space), UACPI_FMT64(offset), data.buffer.length ); break; case UACPI_ADDRESS_SPACE_SMBUS: case UACPI_ADDRESS_SPACE_GENERIC_SERIAL_BUS: uacpi_trace( "%s [%s] %s[0x%016"UACPI_PRIX64"] = " "\n", type_str, path, uacpi_address_space_to_string(space), UACPI_FMT64(offset), data.buffer.length ); break; case UACPI_ADDRESS_SPACE_GENERAL_PURPOSE_IO: uacpi_trace( "%s [%s] %s pins[%u..%u] = 0x%"UACPI_PRIX64"\n", type_str, path, uacpi_address_space_to_string(space), field->pin_offset, (field->pin_offset + field->bit_length) - 1, UACPI_FMT64(*data.integer) ); break; default: uacpi_trace( "%s [%s] (%d bytes) %s[0x%016"UACPI_PRIX64"] = 0x%"UACPI_PRIX64"\n", type_str, path, field->access_width_bytes, uacpi_address_space_to_string(space), UACPI_FMT64(offset), UACPI_FMT64(*data.integer) ); break; } uacpi_free_dynamic_string(path); } static uacpi_bool space_needs_reg(enum uacpi_address_space space) { if (space == UACPI_ADDRESS_SPACE_SYSTEM_MEMORY || space == UACPI_ADDRESS_SPACE_SYSTEM_IO || space == UACPI_ADDRESS_SPACE_TABLE_DATA) return UACPI_FALSE; return UACPI_TRUE; } static uacpi_status region_run_reg( uacpi_namespace_node *node, uacpi_u8 connection_code ) { uacpi_status ret; uacpi_namespace_node *reg_node; uacpi_object_array method_args; uacpi_object *reg_obj, *args[2]; ret = uacpi_namespace_node_resolve( node->parent, "_REG", UACPI_SHOULD_LOCK_NO, UACPI_MAY_SEARCH_ABOVE_PARENT_NO, UACPI_PERMANENT_ONLY_NO, ®_node ); if (uacpi_unlikely_error(ret)) return ret; reg_obj = uacpi_namespace_node_get_object_typed( reg_node, UACPI_OBJECT_METHOD_BIT ); if (uacpi_unlikely(reg_obj == UACPI_NULL)) return UACPI_STATUS_OK; args[0] = uacpi_create_object(UACPI_OBJECT_INTEGER); if (uacpi_unlikely(args[0] == UACPI_NULL)) return UACPI_STATUS_OUT_OF_MEMORY; args[1] = uacpi_create_object(UACPI_OBJECT_INTEGER); if (uacpi_unlikely(args[1] == UACPI_NULL)) { uacpi_object_unref(args[0]); return UACPI_STATUS_OUT_OF_MEMORY; } args[0]->integer = uacpi_namespace_node_get_object(node)->op_region->space; args[1]->integer = connection_code; method_args.objects = args; method_args.count = 2; ret = uacpi_execute_control_method( reg_node, reg_obj->method, &method_args, UACPI_NULL ); if (uacpi_unlikely_error(ret)) uacpi_trace_region_error(node, "error during _REG execution for", ret); uacpi_object_unref(args[0]); uacpi_object_unref(args[1]); return ret; } uacpi_address_space_handlers *uacpi_node_get_address_space_handlers( uacpi_namespace_node *node ) { uacpi_object *object; if (node == uacpi_namespace_root()) return g_uacpi_rt_ctx.root_object->address_space_handlers; object = uacpi_namespace_node_get_object(node); if (uacpi_unlikely(object == UACPI_NULL)) return UACPI_NULL; switch (object->type) { case UACPI_OBJECT_DEVICE: case UACPI_OBJECT_PROCESSOR: case UACPI_OBJECT_THERMAL_ZONE: return object->address_space_handlers; default: return UACPI_NULL; } } static uacpi_address_space_handler *find_handler( uacpi_address_space_handlers *handlers, enum uacpi_address_space space ) { uacpi_address_space_handler *handler = handlers->head; while (handler) { if (handler->space == space) return handler; handler = handler->next; } return UACPI_NULL; } static uacpi_operation_region *find_previous_region_link( uacpi_operation_region *region ) { uacpi_address_space_handler *handler = region->handler; uacpi_operation_region *parent = handler->regions; if (parent == region) // This is the last attached region, it has no previous link return region; while (parent->next != region) { parent = parent->next; if (uacpi_unlikely(parent == UACPI_NULL)) return UACPI_NULL; } return parent; } uacpi_status uacpi_opregion_attach(uacpi_namespace_node *node) { uacpi_object *obj; uacpi_operation_region *region; uacpi_address_space_handler *handler; uacpi_status ret; uacpi_region_attach_data attach_data = { 0 }; if (uacpi_namespace_node_is_dangling(node)) return UACPI_STATUS_NAMESPACE_NODE_DANGLING; obj = uacpi_namespace_node_get_object_typed( node, UACPI_OBJECT_OPERATION_REGION_BIT ); if (uacpi_unlikely(obj == UACPI_NULL)) return UACPI_STATUS_INVALID_ARGUMENT; region = obj->op_region; if (region->handler == UACPI_NULL) return UACPI_STATUS_NO_HANDLER; if (region->state_flags & UACPI_OP_REGION_STATE_ATTACHED) return UACPI_STATUS_OK; handler = region->handler; attach_data.region_node = node; switch (region->space) { case UACPI_ADDRESS_SPACE_PCC: if (region->length) { region->internal_buffer = uacpi_kernel_alloc_zeroed(region->length); if (uacpi_unlikely(region->internal_buffer == UACPI_NULL)) return UACPI_STATUS_OUT_OF_MEMORY; } attach_data.pcc_info.buffer.bytes = region->internal_buffer; attach_data.pcc_info.buffer.length = region->length; attach_data.pcc_info.subspace_id = region->offset; break; case UACPI_ADDRESS_SPACE_GENERAL_PURPOSE_IO: attach_data.gpio_info.num_pins = region->length; break; default: attach_data.generic_info.base = region->offset; attach_data.generic_info.length = region->length; break; } attach_data.handler_context = handler->user_context; uacpi_object_ref(obj); uacpi_namespace_write_unlock(); ret = handler->callback(UACPI_REGION_OP_ATTACH, &attach_data); uacpi_namespace_write_lock(); if (uacpi_unlikely_error(ret)) { uacpi_object_unref(obj); return ret; } region->state_flags |= UACPI_OP_REGION_STATE_ATTACHED; region->user_context = attach_data.out_region_context; uacpi_object_unref(obj); return ret; } static void region_install_handler( uacpi_namespace_node *node, uacpi_address_space_handler *handler ) { uacpi_operation_region *region; region = uacpi_namespace_node_get_object(node)->op_region; region->handler = handler; uacpi_shareable_ref(handler); region->next = handler->regions; handler->regions = region; } enum unreg { UNREG_NO = 0, UNREG_YES, }; static void region_uninstall_handler( uacpi_namespace_node *node, enum unreg unreg ) { uacpi_status ret; uacpi_object *obj; uacpi_address_space_handler *handler; uacpi_operation_region *region, *link; obj = uacpi_namespace_node_get_object_typed( node, UACPI_OBJECT_OPERATION_REGION_BIT ); if (uacpi_unlikely(obj == UACPI_NULL)) return; region = obj->op_region; handler = region->handler; if (handler == UACPI_NULL) return; link = find_previous_region_link(region); if (uacpi_unlikely(link == UACPI_NULL)) { uacpi_error("operation region @%p not in the handler@%p list(?)\n", region, handler); goto out; } else if (link == region) { link = link->next; handler->regions = link; } else { link->next = region->next; } out: if (region->state_flags & UACPI_OP_REGION_STATE_ATTACHED) { uacpi_region_detach_data detach_data = { 0 }; detach_data.region_node = node; detach_data.region_context = region->user_context; detach_data.handler_context = handler->user_context; uacpi_shareable_ref(node); uacpi_namespace_write_unlock(); ret = handler->callback(UACPI_REGION_OP_DETACH, &detach_data); uacpi_namespace_write_lock(); uacpi_namespace_node_unref(node); if (uacpi_unlikely_error(ret)) { uacpi_trace_region_error( node, "error during handler detach for", ret ); } } if ((region->state_flags & UACPI_OP_REGION_STATE_REG_EXECUTED) && unreg == UNREG_YES) { region_run_reg(node, ACPI_REG_DISCONNECT); region->state_flags &= ~UACPI_OP_REGION_STATE_REG_EXECUTED; } uacpi_address_space_handler_unref(region->handler); region->handler = UACPI_NULL; region->state_flags &= ~UACPI_OP_REGION_STATE_ATTACHED; } static uacpi_status upgrade_to_opregion_lock(void) { uacpi_status ret; /* * Drop the namespace lock, and reacquire it after the opregion lock * so we keep the ordering with user API. */ uacpi_namespace_write_unlock(); ret = uacpi_recursive_lock_acquire(&g_opregion_lock); uacpi_namespace_write_lock(); return ret; } void uacpi_opregion_uninstall_handler(uacpi_namespace_node *node) { if (uacpi_unlikely_error(upgrade_to_opregion_lock())) return; region_uninstall_handler(node, UNREG_YES); uacpi_recursive_lock_release(&g_opregion_lock); } uacpi_bool uacpi_address_space_handler_is_default( uacpi_address_space_handler *handler ) { return handler->flags & UACPI_ADDRESS_SPACE_HANDLER_DEFAULT; } enum opregion_iter_action { OPREGION_ITER_ACTION_UNINSTALL, OPREGION_ITER_ACTION_INSTALL, }; struct opregion_iter_ctx { enum opregion_iter_action action; uacpi_address_space_handler *handler; }; static uacpi_iteration_decision do_install_or_uninstall_handler( uacpi_handle opaque, uacpi_namespace_node *node, uacpi_u32 depth ) { struct opregion_iter_ctx *ctx = opaque; uacpi_address_space_handlers *handlers; uacpi_object *object; UACPI_UNUSED(depth); object = uacpi_namespace_node_get_object(node); if (object->type == UACPI_OBJECT_OPERATION_REGION) { uacpi_operation_region *region = object->op_region; if (region->space != ctx->handler->space) return UACPI_ITERATION_DECISION_CONTINUE; if (ctx->action == OPREGION_ITER_ACTION_INSTALL) { if (region->handler) region_uninstall_handler(node, UNREG_NO); region_install_handler(node, ctx->handler); } else { if (uacpi_unlikely(region->handler != ctx->handler)) { uacpi_trace_region_error( node, "handler mismatch for", UACPI_STATUS_INTERNAL_ERROR ); return UACPI_ITERATION_DECISION_CONTINUE; } region_uninstall_handler(node, UNREG_NO); } return UACPI_ITERATION_DECISION_CONTINUE; } handlers = uacpi_node_get_address_space_handlers(node); if (handlers == UACPI_NULL) return UACPI_ITERATION_DECISION_CONTINUE; // Device already has a handler for this space installed if (find_handler(handlers, ctx->handler->space) != UACPI_NULL) return UACPI_ITERATION_DECISION_NEXT_PEER; return UACPI_ITERATION_DECISION_CONTINUE; } struct reg_run_ctx { uacpi_u8 space; uacpi_u8 connection_code; uacpi_size reg_executed; uacpi_size reg_errors; }; static uacpi_iteration_decision do_run_reg( void *opaque, uacpi_namespace_node *node, uacpi_u32 depth ) { struct reg_run_ctx *ctx = opaque; uacpi_operation_region *region; uacpi_status ret; uacpi_bool was_regged; UACPI_UNUSED(depth); region = uacpi_namespace_node_get_object(node)->op_region; if (region->space != ctx->space) return UACPI_ITERATION_DECISION_CONTINUE; was_regged = region->state_flags & UACPI_OP_REGION_STATE_REG_EXECUTED; if (was_regged == (ctx->connection_code == ACPI_REG_CONNECT)) return UACPI_ITERATION_DECISION_CONTINUE; ret = region_run_reg(node, ctx->connection_code); if (ctx->connection_code == ACPI_REG_DISCONNECT) region->state_flags &= ~UACPI_OP_REGION_STATE_REG_EXECUTED; if (ret == UACPI_STATUS_NOT_FOUND) return UACPI_ITERATION_DECISION_CONTINUE; if (ctx->connection_code == ACPI_REG_CONNECT) region->state_flags |= UACPI_OP_REGION_STATE_REG_EXECUTED; ctx->reg_executed++; if (uacpi_unlikely_error(ret)) { ctx->reg_errors++; return UACPI_ITERATION_DECISION_CONTINUE; } return UACPI_ITERATION_DECISION_CONTINUE; } static uacpi_status reg_or_unreg_all_opregions( uacpi_namespace_node *device_node, enum uacpi_address_space space, uacpi_u8 connection_code ) { uacpi_address_space_handlers *handlers; uacpi_bool is_connect; enum uacpi_permanent_only perm_only; struct reg_run_ctx ctx = { 0 }; ctx.space = space; ctx.connection_code = connection_code; handlers = uacpi_node_get_address_space_handlers(device_node); if (uacpi_unlikely(handlers == UACPI_NULL)) return UACPI_STATUS_INVALID_ARGUMENT; is_connect = connection_code == ACPI_REG_CONNECT; if (uacpi_unlikely(is_connect && find_handler(handlers, space) == UACPI_NULL)) return UACPI_STATUS_NO_HANDLER; /* * We want to unreg non-permanent opregions as well, however, * registering them is handled separately and should not be * done by us. */ perm_only = is_connect ? UACPI_PERMANENT_ONLY_YES : UACPI_PERMANENT_ONLY_NO; uacpi_namespace_do_for_each_child( device_node, do_run_reg, UACPI_NULL, UACPI_OBJECT_OPERATION_REGION_BIT, UACPI_MAX_DEPTH_ANY, UACPI_SHOULD_LOCK_NO, perm_only, &ctx ); uacpi_trace( "%sactivated all '%s' opregions controlled by '%.4s', " "%zu _REG() calls (%zu errors)\n", connection_code == ACPI_REG_CONNECT ? "" : "de", uacpi_address_space_to_string(space), device_node->name.text, ctx.reg_executed, ctx.reg_errors ); return UACPI_STATUS_OK; } static uacpi_address_space_handlers *extract_handlers( uacpi_namespace_node *node ) { uacpi_object *handlers_obj; if (node == uacpi_namespace_root()) return g_uacpi_rt_ctx.root_object->address_space_handlers; handlers_obj = uacpi_namespace_node_get_object_typed( node, UACPI_OBJECT_DEVICE_BIT | UACPI_OBJECT_THERMAL_ZONE_BIT | UACPI_OBJECT_PROCESSOR_BIT ); if (uacpi_unlikely(handlers_obj == UACPI_NULL)) return UACPI_NULL; return handlers_obj->address_space_handlers; } uacpi_status uacpi_reg_all_opregions( uacpi_namespace_node *device_node, enum uacpi_address_space space ) { uacpi_status ret; UACPI_ENSURE_INIT_LEVEL_AT_LEAST(UACPI_INIT_LEVEL_NAMESPACE_LOADED); if (!space_needs_reg(space)) return UACPI_STATUS_OK; ret = uacpi_recursive_lock_acquire(&g_opregion_lock); if (uacpi_unlikely_error(ret)) return ret; ret = uacpi_namespace_write_lock(); if (uacpi_unlikely_error(ret)) { uacpi_recursive_lock_release(&g_opregion_lock); return ret; } if (uacpi_unlikely(extract_handlers(device_node) == UACPI_NULL)) { ret = UACPI_STATUS_INVALID_ARGUMENT; goto out; } ret = reg_or_unreg_all_opregions(device_node, space, ACPI_REG_CONNECT); out: uacpi_namespace_write_unlock(); uacpi_recursive_lock_release(&g_opregion_lock); return ret; } uacpi_status uacpi_install_address_space_handler_with_flags( uacpi_namespace_node *device_node, enum uacpi_address_space space, uacpi_region_handler handler, uacpi_handle handler_context, uacpi_u16 flags ) { uacpi_status ret; uacpi_address_space_handlers *handlers; uacpi_address_space_handler *this_handler, *new_handler; struct opregion_iter_ctx iter_ctx; ret = uacpi_recursive_lock_acquire(&g_opregion_lock); if (uacpi_unlikely_error(ret)) return ret; ret = uacpi_namespace_write_lock(); if (uacpi_unlikely_error(ret)) { uacpi_recursive_lock_release(&g_opregion_lock); return ret; } handlers = extract_handlers(device_node); if (uacpi_unlikely(handlers == UACPI_NULL)) { ret = UACPI_STATUS_INVALID_ARGUMENT; goto out; } this_handler = find_handler(handlers, space); if (this_handler != UACPI_NULL) { ret = UACPI_STATUS_ALREADY_EXISTS; goto out; } new_handler = uacpi_kernel_alloc(sizeof(*new_handler)); if (new_handler == UACPI_NULL) { ret = UACPI_STATUS_OUT_OF_MEMORY; goto out; } uacpi_shareable_init(new_handler); new_handler->next = handlers->head; new_handler->space = space; new_handler->user_context = handler_context; new_handler->callback = handler; new_handler->regions = UACPI_NULL; new_handler->flags = flags; handlers->head = new_handler; iter_ctx.handler = new_handler; iter_ctx.action = OPREGION_ITER_ACTION_INSTALL; uacpi_namespace_do_for_each_child( device_node, do_install_or_uninstall_handler, UACPI_NULL, UACPI_OBJECT_ANY_BIT, UACPI_MAX_DEPTH_ANY, UACPI_SHOULD_LOCK_NO, UACPI_PERMANENT_ONLY_YES, &iter_ctx ); if (!space_needs_reg(space)) goto out; /* * Installing an early address space handler, obviously not possible to * execute any _REG methods here. Just return and hope that it is either * a global address space handler, or a handler installed by a user who * will run uacpi_reg_all_opregions manually after loading/initializing * the namespace. */ if (g_uacpi_rt_ctx.init_level < UACPI_INIT_LEVEL_NAMESPACE_LOADED) goto out; // Init level is NAMESPACE_INITIALIZED, so we can safely run _REG now ret = reg_or_unreg_all_opregions( device_node, space, ACPI_REG_CONNECT ); out: uacpi_namespace_write_unlock(); uacpi_recursive_lock_release(&g_opregion_lock); return ret; } uacpi_status uacpi_install_address_space_handler( uacpi_namespace_node *device_node, enum uacpi_address_space space, uacpi_region_handler handler, uacpi_handle handler_context ) { return uacpi_install_address_space_handler_with_flags( device_node, space, handler, handler_context, 0 ); } uacpi_status uacpi_uninstall_address_space_handler( uacpi_namespace_node *device_node, enum uacpi_address_space space ) { uacpi_status ret; uacpi_address_space_handlers *handlers; uacpi_address_space_handler *handler = UACPI_NULL, *prev_handler; struct opregion_iter_ctx iter_ctx; ret = uacpi_recursive_lock_acquire(&g_opregion_lock); if (uacpi_unlikely_error(ret)) return ret; ret = uacpi_namespace_write_lock(); if (uacpi_unlikely_error(ret)) { uacpi_recursive_lock_release(&g_opregion_lock); return ret; } handlers = extract_handlers(device_node); if (uacpi_unlikely(handlers == UACPI_NULL)) { ret = UACPI_STATUS_INVALID_ARGUMENT; goto out; } handler = find_handler(handlers, space); if (uacpi_unlikely(handler == UACPI_NULL)) { ret = UACPI_STATUS_NO_HANDLER; goto out; } iter_ctx.handler = handler; iter_ctx.action = OPREGION_ITER_ACTION_UNINSTALL; uacpi_namespace_do_for_each_child( device_node, do_install_or_uninstall_handler, UACPI_NULL, UACPI_OBJECT_ANY_BIT, UACPI_MAX_DEPTH_ANY, UACPI_SHOULD_LOCK_NO, UACPI_PERMANENT_ONLY_NO, &iter_ctx ); prev_handler = handlers->head; // Are we the last linked handler? if (prev_handler == handler) { handlers->head = handler->next; goto out_unreg; } // Nope, we're somewhere in the middle. Do a search. while (prev_handler) { if (prev_handler->next == handler) { prev_handler->next = handler->next; goto out; } prev_handler = prev_handler->next; } out_unreg: if (space_needs_reg(space)) reg_or_unreg_all_opregions(device_node, space, ACPI_REG_DISCONNECT); out: if (handler != UACPI_NULL) uacpi_address_space_handler_unref(handler); uacpi_namespace_write_unlock(); uacpi_recursive_lock_release(&g_opregion_lock); return ret; } uacpi_status uacpi_initialize_opregion_node(uacpi_namespace_node *node) { uacpi_status ret; uacpi_namespace_node *parent = node->parent; uacpi_operation_region *region; uacpi_address_space_handlers *handlers; uacpi_address_space_handler *handler; ret = upgrade_to_opregion_lock(); if (uacpi_unlikely_error(ret)) return ret; region = uacpi_namespace_node_get_object(node)->op_region; ret = UACPI_STATUS_NOT_FOUND; while (parent) { handlers = uacpi_node_get_address_space_handlers(parent); if (handlers != UACPI_NULL) { handler = find_handler(handlers, region->space); if (handler != UACPI_NULL) { region_install_handler(node, handler); ret = UACPI_STATUS_OK; break; } } parent = parent->parent; } if (ret != UACPI_STATUS_OK) goto out; if (!space_needs_reg(region->space)) goto out; if (uacpi_get_current_init_level() < UACPI_INIT_LEVEL_NAMESPACE_LOADED) goto out; if (region_run_reg(node, ACPI_REG_CONNECT) != UACPI_STATUS_NOT_FOUND) region->state_flags |= UACPI_OP_REGION_STATE_REG_EXECUTED; out: uacpi_recursive_lock_release(&g_opregion_lock); return ret; } uacpi_bool uacpi_is_buffer_access_address_space(uacpi_address_space space) { switch (space) { case UACPI_ADDRESS_SPACE_SMBUS: case UACPI_ADDRESS_SPACE_IPMI: case UACPI_ADDRESS_SPACE_GENERIC_SERIAL_BUS: case UACPI_ADDRESS_SPACE_PRM: case UACPI_ADDRESS_SPACE_FFIXEDHW: return UACPI_TRUE; default: return UACPI_FALSE; } } static uacpi_bool space_needs_bounds_checking(uacpi_address_space space) { return !uacpi_is_buffer_access_address_space(space) && space != UACPI_ADDRESS_SPACE_GENERAL_PURPOSE_IO; } uacpi_status uacpi_dispatch_opregion_io( uacpi_field_unit *field, uacpi_u32 offset, uacpi_region_op op, union uacpi_opregion_io_data data ) { uacpi_status ret; uacpi_object *obj; uacpi_operation_region *region; uacpi_address_space_handler *handler; uacpi_address_space space; uacpi_u64 abs_offset, offset_end = offset; uacpi_bool is_oob = UACPI_FALSE; uacpi_region_op orig_op = op; union { uacpi_region_rw_data rw; uacpi_region_pcc_send_data pcc; uacpi_region_gpio_rw_data gpio; uacpi_region_ipmi_rw_data ipmi; uacpi_region_ffixedhw_rw_data ffixedhw; uacpi_region_prm_rw_data prm; uacpi_region_serial_rw_data serial; } handler_data; ret = upgrade_to_opregion_lock(); if (uacpi_unlikely_error(ret)) return ret; ret = uacpi_opregion_attach(field->region); if (uacpi_unlikely_error(ret)) { uacpi_trace_region_error( field->region, "unable to attach", ret ); goto out; } obj = uacpi_namespace_node_get_object_typed( field->region, UACPI_OBJECT_OPERATION_REGION_BIT ); if (uacpi_unlikely(obj == UACPI_NULL)) { ret = UACPI_STATUS_INVALID_ARGUMENT; goto out; } region = obj->op_region; space = region->space; handler = region->handler; abs_offset = region->offset + offset; offset_end += field->access_width_bytes; if (uacpi_likely(space_needs_bounds_checking(region->space))) is_oob = region->length < offset_end || abs_offset < offset; if (uacpi_unlikely(is_oob)) { const uacpi_char *path; path = uacpi_namespace_node_generate_absolute_path(field->region); uacpi_error( "out-of-bounds access to opregion %s[0x%"UACPI_PRIX64"->" "0x%"UACPI_PRIX64"] at 0x%"UACPI_PRIX64" (idx=%u, width=%d)\n", path, UACPI_FMT64(region->offset), UACPI_FMT64(region->offset + region->length), UACPI_FMT64(abs_offset), offset, field->access_width_bytes ); uacpi_free_dynamic_string(path); ret = UACPI_STATUS_AML_OUT_OF_BOUNDS_INDEX; goto out; } handler_data.rw.region_context = region->user_context; handler_data.rw.handler_context = handler->user_context; switch (region->space) { case UACPI_ADDRESS_SPACE_PCC: { uacpi_u8 *cursor; cursor = region->internal_buffer + offset; /* * Reads from PCC just return the current contents of the internal * buffer. */ if (op == UACPI_REGION_OP_READ) { uacpi_memcpy_zerout( data.integer, cursor, sizeof(*data.integer), field->access_width_bytes ); goto io_done; } uacpi_memcpy(cursor, data.integer, field->access_width_bytes); /* * Dispatch a PCC send command if this was a write to the command field * * ACPI 6.5: 14.3. Extended PCC Subspace Shared Memory Region */ if (offset >= 12 && offset < 16) { uacpi_memzero(&handler_data.pcc.buffer, sizeof(handler_data.pcc.buffer)); handler_data.pcc.buffer.bytes = region->internal_buffer; handler_data.pcc.buffer.length = region->length; op = UACPI_REGION_OP_PCC_SEND; break; } // No dispatch needed, IO is done goto io_done; } case UACPI_ADDRESS_SPACE_GENERAL_PURPOSE_IO: handler_data.gpio.pin_offset = field->pin_offset; handler_data.gpio.num_pins = field->bit_length; handler_data.gpio.value = *data.integer; ret = uacpi_object_get_string_or_buffer( field->connection, &handler_data.gpio.connection ); if (uacpi_unlikely_error(ret)) goto io_done; op = op == UACPI_REGION_OP_READ ? UACPI_REGION_OP_GPIO_READ : UACPI_REGION_OP_GPIO_WRITE; break; case UACPI_ADDRESS_SPACE_IPMI: handler_data.ipmi.in_out_message = data.buffer; handler_data.ipmi.command = abs_offset; op = UACPI_REGION_OP_IPMI_COMMAND; break; case UACPI_ADDRESS_SPACE_FFIXEDHW: handler_data.ffixedhw.in_out_message = data.buffer; handler_data.ffixedhw.command = abs_offset; op = UACPI_REGION_OP_FFIXEDHW_COMMAND; break; case UACPI_ADDRESS_SPACE_PRM: handler_data.prm.in_out_message = data.buffer; op = UACPI_REGION_OP_PRM_COMMAND; break; case UACPI_ADDRESS_SPACE_GENERIC_SERIAL_BUS: case UACPI_ADDRESS_SPACE_SMBUS: ret = uacpi_object_get_string_or_buffer( field->connection, &handler_data.serial.connection ); if (uacpi_unlikely_error(ret)) goto io_done; handler_data.serial.command = abs_offset; handler_data.serial.in_out_buffer = data.buffer; handler_data.serial.access_attribute = field->attributes; switch (field->attributes) { case UACPI_ACCESS_ATTRIBUTE_BYTES: case UACPI_ACCESS_ATTRIBUTE_RAW_BYTES: case UACPI_ACCESS_ATTRIBUTE_RAW_PROCESS_BYTES: handler_data.serial.access_length = field->access_length; break; default: handler_data.serial.access_length = 0; } op = op == UACPI_REGION_OP_READ ? UACPI_REGION_OP_SERIAL_READ : UACPI_REGION_OP_SERIAL_WRITE; break; default: handler_data.rw.byte_width = field->access_width_bytes; handler_data.rw.offset = abs_offset; handler_data.rw.value = *data.integer; break; } uacpi_object_ref(obj); uacpi_namespace_write_unlock(); ret = handler->callback(op, &handler_data); uacpi_namespace_write_lock(); uacpi_object_unref(obj); io_done: if (uacpi_unlikely_error(ret)) { uacpi_trace_region_error(field->region, "unable to perform IO", ret); goto out; } if (orig_op == UACPI_REGION_OP_READ) { switch (region->space) { case UACPI_ADDRESS_SPACE_PCC: case UACPI_ADDRESS_SPACE_IPMI: case UACPI_ADDRESS_SPACE_FFIXEDHW: case UACPI_ADDRESS_SPACE_PRM: case UACPI_ADDRESS_SPACE_GENERIC_SERIAL_BUS: case UACPI_ADDRESS_SPACE_SMBUS: break; case UACPI_ADDRESS_SPACE_GENERAL_PURPOSE_IO: *data.integer = handler_data.gpio.value; break; default: *data.integer = handler_data.rw.value; break; } } trace_region_io(field, space, abs_offset, orig_op, data); out: uacpi_recursive_lock_release(&g_opregion_lock); return ret; } #endif // !UACPI_BAREBONES_MODE