| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <acpisvc/simple.h> |
| |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <zircon/syscalls.h> |
| |
| #define MAX_RETURNED_HANDLES 1 |
| |
| // Wait for the message with the given request id. |
| // |
| // *response* is a pointer to where to store the response. |
| // *len* is a pointer to where to store the length of the response. |
| // *handles* is a pointer to an array populated by wait_for_message. |
| // *num_handles* is an in/out parameter of the number of elements in |
| // the *handles* array, and is populated with the number of elements |
| // written. |
| static zx_status_t wait_for_message( |
| zx_handle_t h, uint32_t req_id, |
| void** response, size_t* len, |
| zx_handle_t* handles, size_t* num_handles) { |
| |
| zx_signals_t pending; |
| zx_status_t status = zx_object_wait_one(h, |
| ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, |
| ZX_TIME_INFINITE, |
| &pending); |
| if (status != ZX_OK) { |
| return status; |
| } |
| if (pending & ZX_CHANNEL_READABLE) { |
| uint32_t rsp_len = 0; |
| uint32_t num_handles_returned = 0; |
| status = zx_channel_read(h, 0, NULL, NULL, rsp_len, |
| num_handles_returned, &rsp_len, &num_handles_returned); |
| if (status != ZX_ERR_BUFFER_TOO_SMALL) { |
| return status; |
| } |
| if (rsp_len < sizeof(acpi_rsp_hdr_t)) { |
| return ZX_ERR_BAD_STATE; |
| } |
| if (num_handles_returned > MAX_RETURNED_HANDLES) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| acpi_rsp_hdr_t* rsp = malloc(rsp_len); |
| if (!rsp) { |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| zx_handle_t handles_returned[MAX_RETURNED_HANDLES]; |
| status = zx_channel_read(h, 0, rsp, handles_returned, rsp_len, |
| num_handles_returned, &rsp_len, &num_handles_returned); |
| if (status != ZX_OK) { |
| free(rsp); |
| return status; |
| } |
| |
| if (rsp_len < sizeof(*rsp) || |
| rsp->request_id != req_id || |
| *num_handles < num_handles_returned) { |
| |
| free(rsp); |
| for (uint32_t i = 0; i < num_handles_returned; ++i) { |
| zx_handle_close(handles_returned[i]); |
| } |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| *response = rsp; |
| *len = rsp_len; |
| *num_handles = num_handles_returned; |
| memcpy(handles, handles_returned, |
| sizeof(zx_handle_t) * num_handles_returned); |
| |
| return ZX_OK; |
| } else if (pending & ZX_CHANNEL_PEER_CLOSED) { |
| return ZX_ERR_PEER_CLOSED; |
| } else { |
| // Shouldn't happen; if status == ZX_OK, then one of the signals |
| // should be pending. |
| return ZX_ERR_BAD_STATE; |
| } |
| } |
| |
| // Execute one round of the command-response protocol |
| // |
| // *cmd* is pointer to the cmd buffer. The request_id will be populated in this |
| // function. |
| // *rsp* is a pointer to where to store the rsp buffer. |
| // *rsp_handles* is an array of handles to be populated |
| // *num_rsp_handles* is the size of that array |
| // |
| // This function will return an error if: |
| // - There was a problem sending the command or receiving response. |
| // - The response was an error response. |
| // - The response was malformed. |
| // - The response had an unexpected number of handles |
| static zx_status_t run_txn( |
| acpi_handle_t* h, |
| void* cmd, size_t cmd_len, |
| void** rsp, size_t* rsp_len, |
| zx_handle_t cmd_handle, |
| zx_handle_t* rsp_handles, size_t num_rsp_handles) { |
| |
| if (cmd_len < sizeof(acpi_cmd_hdr_t)) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| *rsp = NULL; |
| |
| mtx_lock(&h->lock); |
| |
| uint32_t req_id = h->next_req_id++; |
| |
| acpi_cmd_hdr_t* cmd_hdr = cmd; |
| cmd_hdr->request_id = req_id; |
| |
| zx_status_t status = zx_channel_write(h->pipe, 0, cmd, cmd_len, &cmd_handle, (cmd_handle > 0) ? 1 : 0); |
| if (status != ZX_OK) { |
| if (cmd_handle) { |
| zx_handle_close(cmd_handle); |
| } |
| goto exit; |
| } |
| |
| size_t handle_count = num_rsp_handles; |
| status = wait_for_message(h->pipe, req_id, rsp, rsp_len, rsp_handles, &handle_count); |
| if (status != ZX_OK) { |
| goto exit; |
| } |
| |
| acpi_rsp_hdr_t* rsp_hdr = *(acpi_rsp_hdr_t**)rsp; |
| |
| // Validate the response |
| if (rsp_hdr->status != ZX_OK) { |
| status = rsp_hdr->status; |
| goto cleanup; |
| } |
| if (rsp_hdr->len != *rsp_len) { |
| status = ZX_ERR_BAD_STATE; |
| goto cleanup; |
| } |
| if (handle_count != num_rsp_handles) { |
| status = ZX_ERR_BAD_STATE; |
| goto cleanup; |
| } |
| |
| status = ZX_OK; |
| goto exit; |
| |
| cleanup: |
| for (uint32_t i = 0; i < handle_count; ++i) { |
| zx_handle_close(rsp_handles[i]); |
| rsp_handles[i] = 0; |
| } |
| |
| free(*rsp); |
| *rsp = NULL; |
| exit: |
| mtx_unlock(&h->lock); |
| return status; |
| } |
| |
| zx_status_t acpi_list_children(acpi_handle_t* h, |
| acpi_rsp_list_children_t** response, size_t* len) { |
| |
| acpi_cmd_list_children_t cmd = { |
| .hdr = { |
| .version = 0, |
| .cmd = ACPI_CMD_LIST_CHILDREN, |
| .len = sizeof(cmd), |
| }, |
| }; |
| |
| acpi_rsp_list_children_t* rsp; |
| size_t rsp_len; |
| zx_status_t status = |
| run_txn(h, &cmd, sizeof(cmd), (void**)&rsp, &rsp_len, 0, NULL, 0); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Validate the response |
| if (rsp_len != sizeof(*rsp) + sizeof(rsp->children[0]) * rsp->num_children) { |
| free(rsp); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| *response = rsp; |
| *len = rsp_len; |
| return ZX_OK; |
| } |
| |
| zx_status_t acpi_get_child_handle(acpi_handle_t* h, const char name[4], |
| acpi_handle_t* child) { |
| acpi_cmd_get_child_handle_t cmd = { |
| .hdr = { |
| .version = 0, |
| .cmd = ACPI_CMD_GET_CHILD_HANDLE, |
| .len = sizeof(cmd), |
| }, |
| }; |
| memcpy(cmd.name, name, sizeof(cmd.name)); |
| |
| acpi_rsp_get_child_handle_t* rsp = NULL; |
| size_t rsp_len; |
| |
| zx_handle_t handles[1] = {0}; |
| zx_status_t status = |
| run_txn(h, &cmd, sizeof(cmd), (void**)&rsp, &rsp_len, 0, handles, countof(handles)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| acpi_handle_init(child, handles[0]); |
| free(rsp); |
| return ZX_OK; |
| } |
| |
| zx_status_t acpi_get_pci_init_arg(acpi_handle_t* h, |
| acpi_rsp_get_pci_init_arg_t** response, |
| size_t* len) { |
| acpi_cmd_get_pci_init_arg_t cmd = { |
| .hdr = { |
| .version = 0, |
| .cmd = ACPI_CMD_GET_PCI_INIT_ARG, |
| .len = sizeof(cmd), |
| }, |
| }; |
| |
| acpi_rsp_get_pci_init_arg_t* rsp; |
| size_t rsp_len; |
| |
| zx_status_t status = |
| run_txn(h, &cmd, sizeof(cmd), (void**)&rsp, &rsp_len, 0, NULL, 0); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| *response = rsp; |
| *len = rsp_len; |
| return ZX_OK; |
| } |
| |
| zx_status_t acpi_s_state_transition(acpi_handle_t* h, uint8_t target_state) { |
| acpi_cmd_s_state_transition_t cmd = { |
| .hdr = { |
| .version = 0, |
| .cmd = ACPI_CMD_S_STATE_TRANSITION, |
| .len = sizeof(cmd), |
| }, |
| .target_state = target_state, |
| }; |
| |
| acpi_rsp_s_state_transition_t* rsp; |
| size_t rsp_len; |
| zx_status_t status = |
| run_txn(h, &cmd, sizeof(cmd), (void**)&rsp, &rsp_len, 0, NULL, 0); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // This state should be unreachable. |
| abort(); |
| } |
| |
| zx_status_t acpi_ps0(acpi_handle_t* h, char* path, size_t len) { |
| acpi_cmd_ps0_t cmd = { |
| .hdr = { |
| .version = 0, |
| .cmd = ACPI_CMD_PS0, |
| .len = sizeof(cmd), |
| }, |
| }; |
| memcpy(cmd.name, path, len); |
| |
| acpi_rsp_ps0_t* rsp; |
| size_t rsp_len; |
| zx_status_t status = |
| run_txn(h, &cmd, sizeof(cmd), (void**)&rsp, &rsp_len, 0, NULL, 0); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| free(rsp); |
| return ZX_OK; |
| } |
| |
| zx_status_t acpi_bst(acpi_handle_t* h, acpi_rsp_bst_t** response) { |
| acpi_cmd_bst_t cmd = { |
| .hdr = { |
| .version = 0, |
| .cmd = ACPI_CMD_BST, |
| .len = sizeof(cmd), |
| }, |
| }; |
| |
| acpi_rsp_bst_t* rsp; |
| size_t rsp_len; |
| |
| zx_status_t status = |
| run_txn(h, &cmd, sizeof(cmd), (void**)&rsp, &rsp_len, 0, NULL, 0); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| *response = rsp; |
| return ZX_OK; |
| } |
| |
| zx_status_t acpi_bif(acpi_handle_t* h, acpi_rsp_bif_t** response) { |
| acpi_cmd_bst_t cmd = { |
| .hdr = { |
| .version = 0, |
| .cmd = ACPI_CMD_BIF, |
| .len = sizeof(cmd), |
| }, |
| }; |
| |
| acpi_rsp_bif_t* rsp; |
| size_t rsp_len; |
| |
| zx_status_t status = |
| run_txn(h, &cmd, sizeof(cmd), (void**)&rsp, &rsp_len, 0, NULL, 0); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| *response = rsp; |
| return ZX_OK; |
| } |
| |
| zx_status_t acpi_enable_event(acpi_handle_t* _h, zx_handle_t port, uint64_t key, uint16_t events) { |
| if (_h == NULL) { |
| zx_handle_close(port); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (port == ZX_HANDLE_INVALID) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| zx_status_t status; |
| acpi_cmd_enable_event_t cmd = { |
| .hdr = { |
| .version = 0, |
| .cmd = ACPI_CMD_ENABLE_EVENT, |
| .len = sizeof(cmd), |
| }, |
| .key = key, |
| .type = events, |
| }; |
| |
| acpi_cmd_enable_event_t* rsp; |
| size_t rsp_len; |
| if ((status = run_txn(_h, &cmd, sizeof(cmd), (void**)&rsp, &rsp_len, port, NULL, 0)) < 0) { |
| zx_handle_close(port); |
| return status; |
| } |
| free(rsp); |
| return ZX_OK; |
| } |
| |
| zx_handle_t acpi_clone_handle(acpi_handle_t* _h) { |
| acpi_cmd_hdr_t cmd = { |
| .version = 0, |
| .cmd = ACPI_CMD_NEW_CONNECTION, |
| .len = sizeof(cmd), |
| }; |
| |
| zx_handle_t h[2]; |
| zx_status_t status; |
| if ((status = zx_channel_create(0, &h[0], &h[1])) < 0) { |
| return status; |
| } |
| |
| acpi_rsp_hdr_t* rsp; |
| size_t rsp_len; |
| if ((status = run_txn(_h, &cmd, sizeof(cmd), (void**)&rsp, &rsp_len, h[1], NULL, 0)) < 0) { |
| zx_handle_close(h[0]); |
| return status; |
| } |
| free(rsp); |
| return h[0]; |
| } |