From patchwork Mon Mar 9 03:44:54 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 243408 List-Id: U-Boot discussion From: sjg at chromium.org (Simon Glass) Date: Sun, 8 Mar 2020 21:44:54 -0600 Subject: [PATCH v2 30/39] acpi: Add functions to generate ACPI code In-Reply-To: <20200309034504.149659-1-sjg@chromium.org> References: <20200309034504.149659-1-sjg@chromium.org> Message-ID: <20200308214442.v2.30.Ie25c3492416531983761521cbc51e188052e18b4@changeid> Sometimes we need to generate ACPI code on the fly based on things only known at run time. Add a new 'acpigen' library to handle this. This code comes from coreboot and has been modified to support the acpi_ctx struct. Also add acpi_device.c to the build, since these files are co-dependent. Signed-off-by: Simon Glass --- Changes in v2: None include/acpigen.h | 482 +++++++++++++ include/dm/acpi.h | 7 + include/irq.h | 2 + lib/acpi/Makefile | 2 + lib/acpi/acpigen.c | 1683 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 2176 insertions(+) create mode 100644 include/acpigen.h create mode 100644 lib/acpi/acpigen.c diff --git a/include/acpigen.h b/include/acpigen.h new file mode 100644 index 0000000000..08000831b9 --- /dev/null +++ b/include/acpigen.h @@ -0,0 +1,482 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Core ACPI (Advanced Configuration and Power Interface) support + * + * Copyright 2019 Google LLC + * + * Modified from coreboot file acpigen.h + */ + +#ifndef _ACPIGEN_H +#define _ACPIGEN_H + +#include + +struct acpi_cstate; +struct acpi_pld; +struct acpi_gpio; +struct acpi_tstate; + +/* Values that can be returned for ACPI Device _STA method */ +#define ACPI_STATUS_DEVICE_PRESENT BIT(0) +#define ACPI_STATUS_DEVICE_ENABLED BIT(1) +#define ACPI_STATUS_DEVICE_SHOW_IN_UI BIT(2) +#define ACPI_STATUS_DEVICE_STATE_OK BIT(3) + +#define ACPI_STATUS_DEVICE_ALL_OFF 0 +#define ACPI_STATUS_DEVICE_ALL_ON (ACPI_STATUS_DEVICE_PRESENT |\ + ACPI_STATUS_DEVICE_ENABLED |\ + ACPI_STATUS_DEVICE_SHOW_IN_UI |\ + ACPI_STATUS_DEVICE_STATE_OK) +#define ACPI_STATUS_DEVICE_HIDDEN_ON (ACPI_STATUS_DEVICE_PRESENT |\ + ACPI_STATUS_DEVICE_ENABLED |\ + ACPI_STATUS_DEVICE_STATE_OK) + +/* ACPI Op/Prefix Codes */ +enum { + ZERO_OP = 0x00, + ONE_OP = 0x01, + ALIAS_OP = 0x06, + NAME_OP = 0x08, + BYTE_PREFIX = 0x0A, + WORD_PREFIX = 0x0B, + DWORD_PREFIX = 0x0C, + STRING_PREFIX = 0x0D, + QWORD_PREFIX = 0x0E, + SCOPE_OP = 0x10, + BUFFER_OP = 0x11, + PACKAGE_OP = 0x12, + VARIABLE_PACKAGE_OP = 0x13, + METHOD_OP = 0x14, + EXTERNAL_OP = 0x15, + DUAL_NAME_PREFIX = 0x2E, + MULTI_NAME_PREFIX = 0x2F, + EXT_OP_PREFIX = 0x5B, + + MUTEX_OP = 0x01, + EVENT_OP = 0x01, + SF_RIGHT_OP = 0x10, + SF_LEFT_OP = 0x11, + COND_REFOF_OP = 0x12, + CREATEFIELD_OP = 0x13, + LOAD_TABLE_OP = 0x1f, + LOAD_OP = 0x20, + STALL_OP = 0x21, + SLEEP_OP = 0x22, + ACQUIRE_OP = 0x23, + SIGNAL_OP = 0x24, + WAIT_OP = 0x25, + RST_OP = 0x26, + RELEASE_OP = 0x27, + FROM_BCD_OP = 0x28, + TO_BCD_OP = 0x29, + UNLOAD_OP = 0x2A, + REVISON_OP = 0x30, + DEBUG_OP = 0x31, + FATAL_OP = 0x32, + TIMER_OP = 0x33, + OPREGION_OP = 0x80, + FIELD_OP = 0x81, + DEVICE_OP = 0x82, + PROCESSOR_OP = 0x83, + POWER_RES_OP = 0x84, + THERMAL_ZONE_OP = 0x85, + INDEX_FIELD_OP = 0x86, + BANK_FIELD_OP = 0x87, + DATA_REGION_OP = 0x88, + + ROOT_PREFIX = 0x5C, + PARENT_PREFIX = 0x5D, + LOCAL0_OP = 0x60, + LOCAL1_OP = 0x61, + LOCAL2_OP = 0x62, + LOCAL3_OP = 0x63, + LOCAL4_OP = 0x64, + LOCAL5_OP = 0x65, + LOCAL6_OP = 0x66, + LOCAL7_OP = 0x67, + ARG0_OP = 0x68, + ARG1_OP = 0x69, + ARG2_OP = 0x6A, + ARG3_OP = 0x6B, + ARG4_OP = 0x6C, + ARG5_OP = 0x6D, + ARG6_OP = 0x6E, + STORE_OP = 0x70, + REF_OF_OP = 0x71, + ADD_OP = 0x72, + CONCATENATE_OP = 0x73, + SUBTRACT_OP = 0x74, + INCREMENT_OP = 0x75, + DECREMENT_OP = 0x76, + MULTIPLY_OP = 0x77, + DIVIDE_OP = 0x78, + SHIFT_LEFT_OP = 0x79, + SHIFT_RIGHT_OP = 0x7A, + AND_OP = 0x7B, + NAND_OP = 0x7C, + OR_OP = 0x7D, + NOR_OP = 0x7E, + XOR_OP = 0x7F, + NOT_OP = 0x80, + FD_SHIFT_LEFT_BIT_OR = 0x81, + FD_SHIFT_RIGHT_BIT_OR = 0x82, + DEREF_OP = 0x83, + CONCATENATE_TEMP_OP = 0x84, + MOD_OP = 0x85, + NOTIFY_OP = 0x86, + SIZEOF_OP = 0x87, + INDEX_OP = 0x88, + MATCH_OP = 0x89, + CREATE_DWORD_OP = 0x8A, + CREATE_WORD_OP = 0x8B, + CREATE_BYTE_OP = 0x8C, + CREATE_BIT_OP = 0x8D, + OBJ_TYPE_OP = 0x8E, + CREATE_QWORD_OP = 0x8F, + LAND_OP = 0x90, + LOR_OP = 0x91, + LNOT_OP = 0x92, + LEQUAL_OP = 0x93, + LGREATER_OP = 0x94, + LLESS_OP = 0x95, + TO_BUFFER_OP = 0x96, + TO_DEC_STRING_OP = 0x97, + TO_HEX_STRING_OP = 0x98, + TO_INTEGER_OP = 0x99, + TO_STRING_OP = 0x9C, + CP_OBJ_OP = 0x9D, + MID_OP = 0x9E, + CONTINUE_OP = 0x9F, + IF_OP = 0xA0, + ELSE_OP = 0xA1, + WHILE_OP = 0xA2, + NOOP_OP = 0xA3, + RETURN_OP = 0xA4, + BREAK_OP = 0xA5, + COMMENT_OP = 0xA9, + BREAKPIONT_OP = 0xCC, + ONES_OP = 0xFF, +}; + +#define FIELDLIST_OFFSET(_bits) { .type = OFFSET, \ + .name = "", \ + .bits = _bits * 8, \ + } +#define FIELDLIST_NAMESTR(_name, _bits) { .type = NAME_STRING, \ + .name = _name, \ + .bits = _bits, \ + } + +#define FIELD_ANYACC 0 +#define FIELD_BYTEACC 1 +#define FIELD_WORDACC 2 +#define FIELD_DWORDACC 3 +#define FIELD_QWORDACC 4 +#define FIELD_BUFFERACC 5 +#define FIELD_NOLOCK (0 << 4) +#define FIELD_LOCK (1 << 4) +#define FIELD_PRESERVE (0 << 5) +#define FIELD_WRITEASONES (1 << 5) +#define FIELD_WRITEASZEROS (2 << 5) + +enum field_type { + OFFSET, + NAME_STRING, + FIELD_TYPE_MAX, +}; + +struct fieldlist { + enum field_type type; + const char *name; + u32 bits; +}; + +#define OPREGION(rname, space, offset, len) {.name = rname, \ + .regionspace = space, \ + .regionoffset = offset, \ + .regionlen = len, \ + } + +enum region_space { + SYSTEMMEMORY, + SYSTEMIO, + PCI_CONFIG, + EMBEDDEDCONTROL, + SMBUS, + CMOS, + PCIBARTARGET, + IPMI, + GPIO_REGION, + GPSERIALBUS, + PCC, + FIXED_HARDWARE = 0x7F, + REGION_SPACE_MAX, +}; + +struct opregion { + const char *name; + enum region_space regionspace; + unsigned long regionoffset; + unsigned long regionlen; +}; + +#define DSM_UUID(DSM_UUID, DSM_CALLBACKS, DSM_COUNT, DSM_ARG) \ + { .uuid = DSM_UUID, \ + .callbacks = DSM_CALLBACKS, \ + .count = DSM_COUNT, \ + .arg = DSM_ARG, \ + } + +typedef void (*hid_callback_func)(struct acpi_ctx *ctx, void *arg); + +struct dsm_uuid { + const char *uuid; + hid_callback_func *callbacks; + size_t count; + void *arg; +}; + +/* version 1 has 15 fields, version 2 has 19, and version 3 has 21 */ +enum cppc_fields { + CPPC_HIGHEST_PERF, /* can be DWORD */ + CPPC_NOMINAL_PERF, /* can be DWORD */ + CPPC_LOWEST_NONL_PERF, /* can be DWORD */ + CPPC_LOWEST_PERF, /* can be DWORD */ + CPPC_GUARANTEED_PERF, + CPPC_DESIRED_PERF, + CPPC_MIN_PERF, + CPPC_MAX_PERF, + CPPC_PERF_REDUCE_TOLERANCE, + CPPC_TIME_WINDOW, + CPPC_COUNTER_WRAP, /* can be DWORD */ + CPPC_REF_PERF_COUNTER, + CPPC_DELIVERED_PERF_COUNTER, + CPPC_PERF_LIMITED, + CPPC_ENABLE, /* can be System I/O */ + CPPC_MAX_FIELDS_VER_1, + CPPC_AUTO_SELECT = /* can be DWORD */ + CPPC_MAX_FIELDS_VER_1, + CPPC_AUTO_ACTIVITY_WINDOW, + CPPC_PERF_PREF, + CPPC_REF_PERF, /* can be DWORD */ + CPPC_MAX_FIELDS_VER_2, + CPPC_LOWEST_FREQ = /* can be DWORD */ + CPPC_MAX_FIELDS_VER_2, + CPPC_NOMINAL_FREQ, /* can be DWORD */ + CPPC_MAX_FIELDS_VER_3, +}; + +struct cppc_config { + u32 version; /* must be 1, 2, or 3 */ + /* + * The generic struct acpi_gen_regaddr structure is being used, though + * anything besides PPC or FFIXED generally requires checking + * if the OS has advertised support for it (via _OSC). + * + * NOTE: some fields permit DWORDs to be used. If you + * provide a System Memory register with all zeros (which + * represents unsupported) then this will be used as-is. + * Otherwise, a System Memory register with a 32-bit + * width will be converted into a DWORD field (the value + * of which will be the value of 'addrl'. Any other use + * of System Memory register is currently undefined. + * (i.e., if you have an actual need for System Memory + * then you'll need to adjust this kludge). + */ + struct acpi_gen_regaddr regs[CPPC_MAX_FIELDS_VER_3]; +}; + +void acpigen_write_return_integer(struct acpi_ctx *ctx, u64 arg); +void acpigen_write_return_string(struct acpi_ctx *ctx, const char *arg); +void acpigen_write_len_f(struct acpi_ctx *ctx); +void acpigen_pop_len(struct acpi_ctx *ctx); +void acpigen_set_current(struct acpi_ctx *ctx, char *curr); +char *acpigen_get_current(struct acpi_ctx *ctx); +char *acpigen_write_package(struct acpi_ctx *ctx, int nr_el); +void acpigen_write_zero(struct acpi_ctx *ctx); +void acpigen_write_one(struct acpi_ctx *ctx); +void acpigen_write_ones(struct acpi_ctx *ctx); +void acpigen_write_byte(struct acpi_ctx *ctx, unsigned int data); +void acpigen_emit_byte(struct acpi_ctx *ctx, unsigned char data); +void acpigen_emit_ext_op(struct acpi_ctx *ctx, u8 op); +void acpigen_emit_word(struct acpi_ctx *ctx, unsigned int data); +void acpigen_emit_dword(struct acpi_ctx *ctx, unsigned int data); +void acpigen_emit_stream(struct acpi_ctx *ctx, const char *data, int size); +void acpigen_emit_string(struct acpi_ctx *ctx, const char *string); +void acpigen_emit_namestring(struct acpi_ctx *ctx, const char *namepath); +void acpigen_emit_eisaid(struct acpi_ctx *ctx, const char *eisaid); +void acpigen_write_word(struct acpi_ctx *ctx, unsigned int data); +void acpigen_write_dword(struct acpi_ctx *ctx, unsigned int data); +void acpigen_write_qword(struct acpi_ctx *ctx, u64 data); +void acpigen_write_integer(struct acpi_ctx *ctx, u64 data); +void acpigen_write_string(struct acpi_ctx *ctx, const char *string); +void acpigen_write_coreboot_hid(struct acpi_ctx *ctx, + enum coreboot_acpi_ids id); +void acpigen_write_name(struct acpi_ctx *ctx, const char *name); +void acpigen_write_name_zero(struct acpi_ctx *ctx, const char *name); +void acpigen_write_name_one(struct acpi_ctx *ctx, const char *name); +void acpigen_write_name_string(struct acpi_ctx *ctx, const char *name, + const char *string); +void acpigen_write_name_dword(struct acpi_ctx *ctx, const char *name, u32 val); +void acpigen_write_name_qword(struct acpi_ctx *ctx, const char *name, u64 val); +void acpigen_write_name_byte(struct acpi_ctx *ctx, const char *name, u8 val); +void acpigen_write_name_integer(struct acpi_ctx *ctx, const char *name, + u64 val); +void acpigen_write_scope(struct acpi_ctx *ctx, const char *name); +void acpigen_write_method(struct acpi_ctx *ctx, const char *name, int nargs); +void acpigen_write_method_serialized(struct acpi_ctx *ctx, const char *name, + int nargs); +void acpigen_write_device(struct acpi_ctx *ctx, const char *name); +void acpigen_write_ppc(struct acpi_ctx *ctx, u8 nr); +void acpigen_write_ppc_nvs(struct acpi_ctx *ctx); +void acpigen_write_empty_pct(struct acpi_ctx *ctx); +void acpigen_write_empty_ptc(struct acpi_ctx *ctx); +void acpigen_write_prw(struct acpi_ctx *ctx, u32 wake, u32 level); +void acpigen_write_sta(struct acpi_ctx *ctx, u8 status); +void acpigen_write_tpc(struct acpi_ctx *ctx, const char *gnvs_tpc_limit); +void acpigen_write_pss_package(struct acpi_ctx *ctx, u32 corefreq, u32 power, + u32 translat, + u32 busmlat, u32 control, u32 status); +enum psd_coord { + SW_ALL = 0xfc, + SW_ANY = 0xfd, + HW_ALL = 0xfe +}; + +void acpigen_write_psd_package(struct acpi_ctx *ctx, u32 domain, u32 numprocs, + enum psd_coord coordtype); +void acpigen_write_cst_package_entry(struct acpi_ctx *ctx, + struct acpi_cstate *cstate); +void acpigen_write_cst_package(struct acpi_ctx *ctx, struct acpi_cstate *entry, + int nentries); + +enum csd_coord { + CSD_HW_ALL = 0xfe, +}; + +void acpigen_write_CSD_package(struct acpi_ctx *ctx, u32 domain, u32 numprocs, + enum csd_coord coordtype, u32 index); +void acpigen_write_processor(struct acpi_ctx *ctx, u8 cpuindex, u32 pblock_addr, + u8 pblock_len); +void acpigen_write_processor_package(struct acpi_ctx *ctx, const char *name, + uint first_core, uint core_count); +void acpigen_write_processor_cnot(struct acpi_ctx *ctx, const uint num_cores); +void acpigen_write_tss_package(struct acpi_ctx *ctx, int entries, + struct acpi_tstate *tstate_list); +void acpigen_write_tsd_package(struct acpi_ctx *ctx, u32 domain, u32 numprocs, + enum psd_coord coordtype); +void acpigen_write_mem32fixed(struct acpi_ctx *ctx, int readwrite, u32 base, + u32 size); +void acpigen_write_register_resource(struct acpi_ctx *ctx, + const struct acpi_gen_regaddr *addr); +void acpigen_write_irq(struct acpi_ctx *ctx, u16 mask); +void acpigen_write_resourcetemplate_header(struct acpi_ctx *ctx); +void acpigen_write_resourcetemplate_footer(struct acpi_ctx *ctx); +int acpigen_write_uuid(struct acpi_ctx *ctx, const char *uuid); +void acpigen_write_power_res(struct acpi_ctx *ctx, const char *name, u8 level, + u16 order, const char *const dev_states[], + size_t dev_states_count); +void acpigen_write_sleep(struct acpi_ctx *ctx, u64 sleep_ms); +void acpigen_write_store(struct acpi_ctx *ctx); +void acpigen_write_store_ops(struct acpi_ctx *ctx, u8 src, u8 dst); +void acpigen_write_or(struct acpi_ctx *ctx, u8 arg1, u8 arg2, u8 res); +void acpigen_write_and(struct acpi_ctx *ctx, u8 arg1, u8 arg2, u8 res); +void acpigen_write_not(struct acpi_ctx *ctx, u8 arg, u8 res); +void acpigen_write_debug_string(struct acpi_ctx *ctx, const char *str); +void acpigen_write_debug_integer(struct acpi_ctx *ctx, u64 val); +void acpigen_write_debug_op(struct acpi_ctx *ctx, u8 op); +void acpigen_write_if(struct acpi_ctx *ctx); +void acpigen_write_if_and(struct acpi_ctx *ctx, u8 arg1, u8 arg2); +void acpigen_write_if_lequal_op_int(struct acpi_ctx *ctx, u8 op, u64 val); +void acpigen_write_else(struct acpi_ctx *ctx); +void acpigen_write_to_buffer(struct acpi_ctx *ctx, u8 src, u8 dst); +void acpigen_write_to_integer(struct acpi_ctx *ctx, u8 src, u8 dst); +void acpigen_write_byte_buffer(struct acpi_ctx *ctx, u8 *arr, size_t size); +void acpigen_write_return_byte_buffer(struct acpi_ctx *ctx, u8 *arr, + size_t size); +void acpigen_write_return_singleton_buffer(struct acpi_ctx *ctx, u8 arg); +void acpigen_write_return_byte(struct acpi_ctx *ctx, u8 arg); +void acpigen_write_upc(struct acpi_ctx *ctx, enum acpi_upc_type type); + +/* + * Generate ACPI AML code for _DSM method. + * This function takes as input uuid for the device, set of callbacks and + * argument to pass into the callbacks. Callbacks should ensure that Local0 and + * Local1 are left untouched. Use of Local2-Local7 is permitted in callbacks. + */ +int acpigen_write_dsm(struct acpi_ctx *ctx, const char *uuid, + hid_callback_func callbacks[], size_t count, void *arg); +int acpigen_write_dsm_uuid_arr(struct acpi_ctx *ctx, struct dsm_uuid *ids, + size_t count); + +/* + * Generate ACPI AML code for _CPC (struct acpi_ctx *ctx, Continuous Perfmance + * Control). Execute the package function once to create a global table, then + * execute the method function within each processor object to + * create a method that points to the global table. + */ +int acpigen_write_cppc_package(struct acpi_ctx *ctx, + const struct cppc_config *config); +void acpigen_write_cppc_method(struct acpi_ctx *ctx); + +/* + * Generate ACPI AML code for _ROM method. + * This function takes as input ROM data and ROM length. + * The ROM length has to be multiple of 4096 and has to be less + * than the current implementation limit of 0x40000. + */ +int acpigen_write_rom(struct acpi_ctx *ctx, void *bios, const size_t length); +/* + * Generate ACPI AML code for OperationRegion + * This function takes input region name, region space, region offset & region + * length. + */ +void acpigen_write_opregion(struct acpi_ctx *ctx, struct opregion *opreg); +/* + * Generate ACPI AML code for Field + * This function takes input region name, fieldlist, count & flags. + */ +int acpigen_write_field(struct acpi_ctx *ctx, const char *name, + struct fieldlist *l, size_t count, uint flags); +/* + * Generate ACPI AML code for IndexField + * This function takes input index name, data name, fieldlist, count & flags. + */ +int acpigen_write_indexfield(struct acpi_ctx *ctx, const char *idx, + const char *data, struct fieldlist *l, + size_t count, uint flags); + +/* + * Soc-implemented functions for generating ACPI AML code for GPIO handling. All + * these functions are expected to use only Local5, Local6 and Local7 + * variables. If the functions call into another ACPI method, then there is no + * restriction on the use of Local variables. In case of get/read functions, + * return value is expected to be stored in Local0 variable. + * + * All functions return 0 on success and -1 on error. + */ + +/* Generate ACPI AML code to return Rx value of GPIO in Local0. */ +int acpigen_soc_read_rx_gpio(struct acpi_ctx *ctx, uint gpio_num); + +/* Generate ACPI AML code to return Tx value of GPIO in Local0. */ +int acpigen_soc_get_tx_gpio(struct acpi_ctx *ctx, uint gpio_num); + +/* Generate ACPI AML code to set Tx value of GPIO to 1. */ +int acpigen_soc_set_tx_gpio(struct acpi_ctx *ctx, uint gpio_num); + +/* Generate ACPI AML code to set Tx value of GPIO to 0. */ +int acpigen_soc_clear_tx_gpio(struct acpi_ctx *ctx, uint gpio_num); + +/* + * Helper functions for enabling/disabling Tx GPIOs based on the GPIO + * polarity. These functions end up calling acpigen_soc_{set,clear}_tx_gpio to + * make callbacks into SoC acpigen code. + * + * Returns 0 on success and -1 on error. + */ +int acpigen_enable_tx_gpio(struct acpi_ctx *ctx, struct acpi_gpio *gpio); +int acpigen_disable_tx_gpio(struct acpi_ctx *ctx, struct acpi_gpio *gpio); + +#endif diff --git a/include/dm/acpi.h b/include/dm/acpi.h index f3e9d73b78..0f78a506da 100644 --- a/include/dm/acpi.h +++ b/include/dm/acpi.h @@ -22,6 +22,9 @@ /* Length of an ACPI name string including nul terminator */ #define ACPI_NAME_MAX 5 +/* Number of nested objects supported */ +#define ACPIGEN_LENSTACK_SIZE 10 + #if !defined(__ACPI__) /** @@ -34,12 +37,16 @@ * adding a new table. The RSDP holds pointers to the RSDP and XSDT. * @rsdt: Pointer to the Root System Description Table * @xsdt: Pointer to the Extended System Description Table + * @len_stack: Stack of 'length' words to fix up later + * @ltop: Points to current top of stack (0 = empty) */ struct acpi_ctx { void *current; struct acpi_rsdp *rsdp; struct acpi_rsdt *rsdt; struct acpi_xsdt *xsdt; + char *len_stack[ACPIGEN_LENSTACK_SIZE]; + int ltop; }; /** diff --git a/include/irq.h b/include/irq.h index b71afe9bee..d4948e6dc4 100644 --- a/include/irq.h +++ b/include/irq.h @@ -8,6 +8,8 @@ #ifndef __irq_H #define __irq_H +struct ofnode_phandle_args; + /* * Interrupt controller types available. You can find a particular one with * irq_first_device_type() diff --git a/lib/acpi/Makefile b/lib/acpi/Makefile index 660491ef71..85a1f774ad 100644 --- a/lib/acpi/Makefile +++ b/lib/acpi/Makefile @@ -1,4 +1,6 @@ # SPDX-License-Identifier: GPL-2.0+ # +obj-y += acpigen.o +obj-y += acpi_device.o obj-y += acpi_table.o diff --git a/lib/acpi/acpigen.c b/lib/acpi/acpigen.c new file mode 100644 index 0000000000..4e7d1ce51e --- /dev/null +++ b/lib/acpi/acpigen.c @@ -0,0 +1,1683 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generation of ACPI (Advanced Configuration and Power Interface) tables + * + * Copyright 2019 Google LLC + * Mostly taken from coreboot + */ + +#define LOG_CATEGORY LOGC_ACPI + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Maximum length for an ACPI object generated by this code, + * + * If you need to change this, change acpigen_write_len_f(ctx) and + * acpigen_pop_len(ctx) + */ +#define ACPIGEN_MAXLEN 0xfffff + +/* CPU path format */ +#define ACPI_CPU_STRING "\\_PR.CP%02d" + +void acpigen_write_len_f(struct acpi_ctx *ctx) +{ + assert(ctx->ltop < (ACPIGEN_LENSTACK_SIZE - 1)); + ctx->len_stack[ctx->ltop++] = ctx->current; + acpigen_emit_byte(ctx, 0); + acpigen_emit_byte(ctx, 0); + acpigen_emit_byte(ctx, 0); +} + +void acpigen_pop_len(struct acpi_ctx *ctx) +{ + int len; + char *p; + + assert(ctx->ltop > 0); + p = ctx->len_stack[--ctx->ltop]; + len = ctx->current - (void *)p; + assert(len <= ACPIGEN_MAXLEN); + /* generate store length for 0xfffff max */ + p[0] = (0x80 | (len & 0xf)); + p[1] = (len >> 4 & 0xff); + p[2] = (len >> 12 & 0xff); +} + +void acpigen_set_current(struct acpi_ctx *ctx, char *curr) +{ + ctx->current = curr; +} + +char *acpigen_get_current(struct acpi_ctx *ctx) +{ + return ctx->current; +} + +void acpigen_emit_byte(struct acpi_ctx *ctx, unsigned char b) +{ + *(u8 *)ctx->current++ = b; +} + +void acpigen_emit_ext_op(struct acpi_ctx *ctx, u8 op) +{ + acpigen_emit_byte(ctx, EXT_OP_PREFIX); + acpigen_emit_byte(ctx, op); +} + +void acpigen_emit_word(struct acpi_ctx *ctx, unsigned int data) +{ + acpigen_emit_byte(ctx, data & 0xff); + acpigen_emit_byte(ctx, (data >> 8) & 0xff); +} + +void acpigen_emit_dword(struct acpi_ctx *ctx, unsigned int data) +{ + acpigen_emit_byte(ctx, data & 0xff); + acpigen_emit_byte(ctx, (data >> 8) & 0xff); + acpigen_emit_byte(ctx, (data >> 16) & 0xff); + acpigen_emit_byte(ctx, (data >> 24) & 0xff); +} + +char *acpigen_write_package(struct acpi_ctx *ctx, int nr_el) +{ + char *p; + + acpigen_emit_byte(ctx, PACKAGE_OP); + acpigen_write_len_f(ctx); + p = ctx->current; + acpigen_emit_byte(ctx, nr_el); + + return p; +} + +void acpigen_write_byte(struct acpi_ctx *ctx, unsigned int data) +{ + acpigen_emit_byte(ctx, BYTE_PREFIX); + acpigen_emit_byte(ctx, data & 0xff); +} + +void acpigen_write_word(struct acpi_ctx *ctx, unsigned int data) +{ + acpigen_emit_byte(ctx, WORD_PREFIX); + acpigen_emit_word(ctx, data); +} + +void acpigen_write_dword(struct acpi_ctx *ctx, unsigned int data) +{ + acpigen_emit_byte(ctx, DWORD_PREFIX); + acpigen_emit_dword(ctx, data); +} + +void acpigen_write_qword(struct acpi_ctx *ctx, u64 data) +{ + acpigen_emit_byte(ctx, QWORD_PREFIX); + acpigen_emit_dword(ctx, data & 0xffffffff); + acpigen_emit_dword(ctx, (data >> 32) & 0xffffffff); +} + +void acpigen_write_zero(struct acpi_ctx *ctx) +{ + acpigen_emit_byte(ctx, ZERO_OP); +} + +void acpigen_write_one(struct acpi_ctx *ctx) +{ + acpigen_emit_byte(ctx, ONE_OP); +} + +void acpigen_write_ones(struct acpi_ctx *ctx) +{ + acpigen_emit_byte(ctx, ONES_OP); +} + +void acpigen_write_integer(struct acpi_ctx *ctx, u64 data) +{ + if (data == 0) + acpigen_write_zero(ctx); + else if (data == 1) + acpigen_write_one(ctx); + else if (data <= 0xff) + acpigen_write_byte(ctx, (unsigned char)data); + else if (data <= 0xffff) + acpigen_write_word(ctx, (unsigned int)data); + else if (data <= 0xffffffff) + acpigen_write_dword(ctx, (unsigned int)data); + else + acpigen_write_qword(ctx, data); +} + +void acpigen_write_name_zero(struct acpi_ctx *ctx, const char *name) +{ + acpigen_write_name(ctx, name); + acpigen_write_one(ctx); +} + +void acpigen_write_name_one(struct acpi_ctx *ctx, const char *name) +{ + acpigen_write_name(ctx, name); + acpigen_write_zero(ctx); +} + +void acpigen_write_name_byte(struct acpi_ctx *ctx, const char *name, u8 val) +{ + acpigen_write_name(ctx, name); + acpigen_write_byte(ctx, val); +} + +void acpigen_write_name_dword(struct acpi_ctx *ctx, const char *name, u32 val) +{ + acpigen_write_name(ctx, name); + acpigen_write_dword(ctx, val); +} + +void acpigen_write_name_qword(struct acpi_ctx *ctx, const char *name, u64 val) +{ + acpigen_write_name(ctx, name); + acpigen_write_qword(ctx, val); +} + +void acpigen_write_name_integer(struct acpi_ctx *ctx, const char *name, u64 val) +{ + acpigen_write_name(ctx, name); + acpigen_write_integer(ctx, val); +} + +void acpigen_write_name_string(struct acpi_ctx *ctx, const char *name, + const char *string) +{ + acpigen_write_name(ctx, name); + acpigen_write_string(ctx, string); +} + +void acpigen_emit_stream(struct acpi_ctx *ctx, const char *data, int size) +{ + int i; + + for (i = 0; i < size; i++) + acpigen_emit_byte(ctx, data[i]); +} + +void acpigen_emit_string(struct acpi_ctx *ctx, const char *string) +{ + acpigen_emit_stream(ctx, string, string ? strlen(string) : 0); + acpigen_emit_byte(ctx, '\0'); /* NUL */ +} + +void acpigen_write_string(struct acpi_ctx *ctx, const char *string) +{ + acpigen_emit_byte(ctx, STRING_PREFIX); + acpigen_emit_string(ctx, string); +} + +void acpigen_write_coreboot_hid(struct acpi_ctx *ctx, enum coreboot_acpi_ids id) +{ + char hid[9]; /* BOOTxxxx */ + + snprintf(hid, sizeof(hid), "%.4s%04X", COREBOOT_ACPI_ID, id); + acpigen_write_name_string(ctx, "_HID", hid); +} + +/* + * The naming conventions for ACPI namespace names are a bit tricky as + * each element has to be 4 chars wide ("All names are a fixed 32 bits.") + * and "By convention, when an ASL compiler pads a name shorter than 4 + * characters, it is done so with trailing underscores ('_')". + * + * Check sections 5.3, 18.2.2 and 18.4 of ACPI spec 3.0 for details. + */ + +static void acpigen_emit_simple_namestring(struct acpi_ctx *ctx, + const char *name) +{ + char ud[] = "____"; + int i; + + for (i = 0; i < 4; i++) { + if ((name[i] == '\0') || (name[i] == '.')) { + acpigen_emit_stream(ctx, ud, 4 - i); + break; + } + acpigen_emit_byte(ctx, name[i]); + } +} + +static void acpigen_emit_double_namestring(struct acpi_ctx *ctx, + const char *name, int dotpos) +{ + acpigen_emit_byte(ctx, DUAL_NAME_PREFIX); + acpigen_emit_simple_namestring(ctx, name); + acpigen_emit_simple_namestring(ctx, &name[dotpos + 1]); +} + +static void acpigen_emit_multi_namestring(struct acpi_ctx *ctx, + const char *name) +{ + int count = 0; + unsigned char *pathlen; + + acpigen_emit_byte(ctx, MULTI_NAME_PREFIX); + acpigen_emit_byte(ctx, ZERO_OP); + pathlen = ((unsigned char *)ctx->current) - 1; + + while (name[0] != '\0') { + acpigen_emit_simple_namestring(ctx, name); + /* find end or next entity */ + while ((name[0] != '.') && (name[0] != '\0')) + name++; + /* forward to next */ + if (name[0] == '.') + name++; + count++; + } + + pathlen[0] = count; +} + +void acpigen_emit_namestring(struct acpi_ctx *ctx, const char *namepath) +{ + int dotcount = 0, i; + int dotpos = 0; + + /* We can start with a '\'. */ + if (namepath[0] == '\\') { + acpigen_emit_byte(ctx, '\\'); + namepath++; + } + + /* And there can be any number of '^' */ + while (namepath[0] == '^') { + acpigen_emit_byte(ctx, '^'); + namepath++; + } + + /* + * If we have only \\ or only ^...^ then we need to add a null name + * (0x00) + */ + if (namepath[0] == '\0') { + acpigen_emit_byte(ctx, ZERO_OP); + return; + } + + i = 0; + while (namepath[i] != '\0') { + if (namepath[i] == '.') { + dotcount++; + dotpos = i; + } + i++; + } + + if (dotcount == 0) + acpigen_emit_simple_namestring(ctx, namepath); + else if (dotcount == 1) + acpigen_emit_double_namestring(ctx, namepath, dotpos); + else + acpigen_emit_multi_namestring(ctx, namepath); +} + +void acpigen_write_name(struct acpi_ctx *ctx, const char *name) +{ + acpigen_emit_byte(ctx, NAME_OP); + acpigen_emit_namestring(ctx, name); +} + +void acpigen_write_scope(struct acpi_ctx *ctx, const char *name) +{ + acpigen_emit_byte(ctx, SCOPE_OP); + acpigen_write_len_f(ctx); + acpigen_emit_namestring(ctx, name); +} + +void acpigen_write_processor(struct acpi_ctx *ctx, u8 cpuindex, u32 pblock_addr, + u8 pblock_len) +{ + /* + * Processor (\_PR.CPcpuindex, cpuindex, pblock_addr, pblock_len) + * { + */ + char pscope[16]; + + acpigen_emit_ext_op(ctx, PROCESSOR_OP); + acpigen_write_len_f(ctx); + + snprintf(pscope, sizeof(pscope), ACPI_CPU_STRING, (uint)cpuindex); + acpigen_emit_namestring(ctx, pscope); + acpigen_emit_byte(ctx, cpuindex); + acpigen_emit_dword(ctx, pblock_addr); + acpigen_emit_byte(ctx, pblock_len); +} + +void acpigen_write_processor_package(struct acpi_ctx *ctx, + const char *const name, + const uint first_core, + const uint core_count) +{ + uint i; + char pscope[16]; + + acpigen_write_name(ctx, name); + acpigen_write_package(ctx, core_count); + for (i = first_core; i < first_core + core_count; ++i) { + snprintf(pscope, sizeof(pscope), ACPI_CPU_STRING, i); + acpigen_emit_namestring(ctx, pscope); + } + acpigen_pop_len(ctx); +} + +/* Method to notify all CPU cores */ +void acpigen_write_processor_cnot(struct acpi_ctx *ctx, const uint num_cores) +{ + int core_id; + + acpigen_write_method(ctx, "\\_PR.CNOT", 1); + for (core_id = 0; core_id < num_cores; core_id++) { + char buffer[30]; + + snprintf(buffer, sizeof(buffer), ACPI_CPU_STRING, core_id); + acpigen_emit_byte(ctx, NOTIFY_OP); + acpigen_emit_namestring(ctx, buffer); + acpigen_emit_byte(ctx, ARG0_OP); + } + acpigen_pop_len(ctx); +} + +/* + * Generate ACPI AML code for OperationRegion + * Arg0: Pointer to struct opregion opreg = OPREGION(rname, space, offset, len) + * where rname is region name, space is region space, offset is region offset & + * len is region length. + * OperationRegion(regionname, regionspace, regionoffset, regionlength) + */ +void acpigen_write_opregion(struct acpi_ctx *ctx, struct opregion *opreg) +{ + /* OpregionOp */ + acpigen_emit_ext_op(ctx, OPREGION_OP); + /* NameString 4 chars only */ + acpigen_emit_simple_namestring(ctx, opreg->name); + /* RegionSpace */ + acpigen_emit_byte(ctx, opreg->regionspace); + /* RegionOffset & RegionLen, it can be byte word or double word */ + acpigen_write_integer(ctx, opreg->regionoffset); + acpigen_write_integer(ctx, opreg->regionlen); +} + +static void acpigen_write_field_length(struct acpi_ctx *ctx, u32 len) +{ + u8 i, j; + u8 emit[4]; + + i = 1; + if (len < 0x40) { + emit[0] = len & 0x3F; + } else { + emit[0] = len & 0xF; + len >>= 4; + while (len) { + emit[i] = len & 0xFF; + i++; + len >>= 8; + } + } + /* Update bit 7:6 : Number of bytes followed by emit[0] */ + emit[0] |= (i - 1) << 6; + + for (j = 0; j < i; j++) + acpigen_emit_byte(ctx, emit[j]); +} + +static int acpigen_write_field_offset(struct acpi_ctx *ctx, u32 offset, + u32 current_bit_pos) +{ + u32 diff_bits; + + if (offset < current_bit_pos) { + log_warning("Cannot move offset backward"); + return -ESPIPE; + } + + diff_bits = offset - current_bit_pos; + /* Upper limit */ + if (diff_bits > 0xFFFFFFF) { + log_warning("Offset very large to encode"); + return E2BIG; + } + + acpigen_emit_byte(ctx, 0); + acpigen_write_field_length(ctx, diff_bits); + + return 0; +} + +static void acpigen_write_field_name(struct acpi_ctx *ctx, const char *name, + u32 size) +{ + acpigen_emit_simple_namestring(ctx, name); + acpigen_write_field_length(ctx, size); +} + +/* + * Generate ACPI AML code for Field + * Arg0: region name + * Arg1: Pointer to struct fieldlist. + * Arg2: no. of entries in Arg1 + * Arg3: flags which indicate filed access type, lock rule & update rule. + * Example with fieldlist + * struct fieldlist l[] = { + * FIELDLIST_OFFSET(0x84), + * FIELDLIST_NAMESTR("PMCS", 2), + * }; + * acpigen_write_field("UART", l, ARRAY_SIZE(l), FIELD_ANYACC | FIELD_NOLOCK | + * FIELD_PRESERVE); + * Output: + * Field (UART, AnyAcc, NoLock, Preserve) + * { + * Offset (0x84), + * PMCS, 2 + * } + */ +int acpigen_write_field(struct acpi_ctx *ctx, const char *name, + struct fieldlist *l, size_t count, uint flags) +{ + int i; + u32 current_bit_pos = 0; + int ret; + + /* FieldOp */ + acpigen_emit_ext_op(ctx, FIELD_OP); + /* Package Length */ + acpigen_write_len_f(ctx); + /* NameString 4 chars only */ + acpigen_emit_simple_namestring(ctx, name); + /* Field Flag */ + acpigen_emit_byte(ctx, flags); + + for (i = 0; i < count; i++) { + switch (l[i].type) { + case NAME_STRING: + acpigen_write_field_name(ctx, l[i].name, l[i].bits); + current_bit_pos += l[i].bits; + break; + case OFFSET: + ret = acpigen_write_field_offset(ctx, l[i].bits, + current_bit_pos); + if (ret) + return log_msg_ret("field", ret); + current_bit_pos = l[i].bits; + break; + default: + log_err("Invalid field type %#x\n", l[i].type); + return -EDOM; + } + } + acpigen_pop_len(ctx); + + return 0; +} + +/* + * Generate ACPI AML code for IndexField + * Arg0: region name + * Arg1: Pointer to struct fieldlist. + * Arg2: no. of entries in Arg1 + * Arg3: flags which indicate filed access type, lock rule & update rule. + * Example with fieldlist + * struct fieldlist l[] = { + * FIELDLIST_OFFSET(0x84), + * FIELDLIST_NAMESTR("PMCS", 2), + * }; + * acpigen_write_field("IDX", "DATA" l, ARRAY_SIZE(l), FIELD_ANYACC | + * FIELD_NOLOCK | + * FIELD_PRESERVE); + * Output: + * IndexField (IDX, DATA, AnyAcc, NoLock, Preserve) + * { + * Offset (0x84), + * PMCS, 2 + * } + */ +int acpigen_write_indexfield(struct acpi_ctx *ctx, const char *idx, + const char *data, struct fieldlist *l, + size_t count, uint flags) +{ + u16 i; + u32 current_bit_pos = 0; + int ret; + + /* FieldOp */ + acpigen_emit_ext_op(ctx, INDEX_FIELD_OP); + /* Package Length */ + acpigen_write_len_f(ctx); + /* NameString 4 chars only */ + acpigen_emit_simple_namestring(ctx, idx); + /* NameString 4 chars only */ + acpigen_emit_simple_namestring(ctx, data); + /* Field Flag */ + acpigen_emit_byte(ctx, flags); + + for (i = 0; i < count; i++) { + switch (l[i].type) { + case NAME_STRING: + acpigen_write_field_name(ctx, l[i].name, l[i].bits); + current_bit_pos += l[i].bits; + break; + case OFFSET: + ret = acpigen_write_field_offset(ctx, l[i].bits, + current_bit_pos); + if (ret) + return log_msg_ret("field", ret); + current_bit_pos = l[i].bits; + break; + default: + log_err("Invalid field type 0x%X\n", l[i].type); + return -EDOM; + } + } + acpigen_pop_len(ctx); + + return 0; +} + +void acpigen_write_empty_pct(struct acpi_ctx *ctx) +{ + /* + * Name (_PCT, Package (0x02) + * { + * ResourceTemplate () + * { + * Register (FFixedHW, + * 0x00, // Bit Width + * 0x00, // Bit Offset + * 0x0000000000000000, // Address + * ,) + * }, + * + * ResourceTemplate () + * { + * Register (FFixedHW, + * 0x00, // Bit Width + * 0x00, // Bit Offset + * 0x0000000000000000, // Address + * ,) + * } + * }) + */ + static char stream[] = { + /* 00000030 "0._PCT.," */ + 0x08, 0x5F, 0x50, 0x43, 0x54, 0x12, 0x2C, + /* 00000038 "........" */ + 0x02, 0x11, 0x14, 0x0A, 0x11, 0x82, 0x0C, 0x00, + /* 00000040 "........" */ + 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* 00000048 "....y..." */ + 0x00, 0x00, 0x00, 0x00, 0x79, 0x00, 0x11, 0x14, + /* 00000050 "........" */ + 0x0A, 0x11, 0x82, 0x0C, 0x00, 0x7F, 0x00, 0x00, + /* 00000058 "........" */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x79, 0x00 + }; + acpigen_emit_stream(ctx, stream, ARRAY_SIZE(stream)); +} + +void acpigen_write_empty_ptc(struct acpi_ctx *ctx) +{ + /* + * Name (_PTC, Package (0x02) + * { + * ResourceTemplate () + * { + * Register (FFixedHW, + * 0x00, // Bit Width + * 0x00, // Bit Offset + * 0x0000000000000000, // Address + * ,) + * }, + * + * ResourceTemplate () + * { + * Register (FFixedHW, + * 0x00, // Bit Width + * 0x00, // Bit Offset + * 0x0000000000000000, // Address + * ,) + * } + * }) + */ + struct acpi_gen_regaddr addr = { + .space_id = ACPI_ADDRESS_SPACE_FIXED, + .bit_width = 0, + .bit_offset = 0, + .access_size = 0, + .addrl = 0, + .addrh = 0, + }; + + acpigen_write_name(ctx, "_PTC"); + acpigen_write_package(ctx, 2); + + /* ControlRegister */ + acpigen_write_register_resource(ctx, &addr); + + /* StatusRegister */ + acpigen_write_register_resource(ctx, &addr); + + acpigen_pop_len(ctx); +} + +static void __acpigen_write_method(struct acpi_ctx *ctx, const char *name, + u8 flags) +{ + acpigen_emit_byte(ctx, METHOD_OP); + acpigen_write_len_f(ctx); + acpigen_emit_namestring(ctx, name); + acpigen_emit_byte(ctx, flags); +} + +/* Method (name, nargs, NotSerialized) */ +void acpigen_write_method(struct acpi_ctx *ctx, const char *name, int nargs) +{ + __acpigen_write_method(ctx, name, (nargs & 7)); +} + +/* Method (name, nargs, Serialized) */ +void acpigen_write_method_serialized(struct acpi_ctx *ctx, const char *name, + int nargs) +{ + __acpigen_write_method(ctx, name, (nargs & 7) | (1 << 3)); +} + +void acpigen_write_device(struct acpi_ctx *ctx, const char *name) +{ + acpigen_emit_ext_op(ctx, DEVICE_OP); + acpigen_write_len_f(ctx); + acpigen_emit_namestring(ctx, name); +} + +void acpigen_write_sta(struct acpi_ctx *ctx, u8 status) +{ + /* Method (_STA, 0, NotSerialized) { Return (status) } */ + acpigen_write_method(ctx, "_STA", 0); + acpigen_emit_byte(ctx, RETURN_OP); + acpigen_write_byte(ctx, status); + acpigen_pop_len(ctx); +} + +/* + * Generates a func with max supported P-states. + */ +void acpigen_write_ppc(struct acpi_ctx *ctx, u8 nr) +{ + /* + * Method (_PPC, 0, NotSerialized) + * { + * Return (nr) + * } + */ + acpigen_write_method(ctx, "_PPC", 0); + acpigen_emit_byte(ctx, RETURN_OP); + /* arg */ + acpigen_write_byte(ctx, nr); + acpigen_pop_len(ctx); +} + +/* + * Generates a func with max supported P-states saved + * in the variable PPCM. + */ +void acpigen_write_ppc_nvs(struct acpi_ctx *ctx) +{ + /* + * Method (_PPC, 0, NotSerialized) + * { + * Return (PPCM) + * } + */ + acpigen_write_method(ctx, "_PPC", 0); + acpigen_emit_byte(ctx, RETURN_OP); + /* arg */ + acpigen_emit_namestring(ctx, "PPCM"); + acpigen_pop_len(ctx); +} + +void acpigen_write_tpc(struct acpi_ctx *ctx, const char *gnvs_tpc_limit) +{ + /* + * // Sample _TPC method + * Method (_TPC, 0, NotSerialized) + * { + * Return (\TLVL) + * } + */ + acpigen_write_method(ctx, "_TPC", 0); + acpigen_emit_byte(ctx, RETURN_OP); + acpigen_emit_namestring(ctx, gnvs_tpc_limit); + acpigen_pop_len(ctx); +} + +void acpigen_write_prw(struct acpi_ctx *ctx, u32 wake, u32 level) +{ + /* + * Name (_PRW, Package () { wake, level } + */ + acpigen_write_name(ctx, "_PRW"); + acpigen_write_package(ctx, 2); + acpigen_write_integer(ctx, wake); + acpigen_write_integer(ctx, level); + acpigen_pop_len(ctx); +} + +void acpigen_write_pss_package(struct acpi_ctx *ctx, u32 core_freq, u32 power, + u32 trans_lat, u32 busm_lat, u32 control, + u32 status) +{ + acpigen_write_package(ctx, 6); + acpigen_write_dword(ctx, core_freq); + acpigen_write_dword(ctx, power); + acpigen_write_dword(ctx, trans_lat); + acpigen_write_dword(ctx, busm_lat); + acpigen_write_dword(ctx, control); + acpigen_write_dword(ctx, status); + acpigen_pop_len(ctx); + + log_debug("PSS: %uMHz power %u control 0x%x status 0x%x\n", + core_freq, power, control, status); +} + +void acpigen_write_psd_package(struct acpi_ctx *ctx, u32 domain, u32 numprocs, + enum psd_coord coordtype) +{ + acpigen_write_name(ctx, "_PSD"); + acpigen_write_package(ctx, 1); + acpigen_write_package(ctx, 5); + acpigen_write_byte(ctx, 5); // 5 values + acpigen_write_byte(ctx, 0); // revision 0 + acpigen_write_dword(ctx, domain); + acpigen_write_dword(ctx, coordtype); + acpigen_write_dword(ctx, numprocs); + acpigen_pop_len(ctx); + acpigen_pop_len(ctx); +} + +void acpigen_write_cst_package_entry(struct acpi_ctx *ctx, + struct acpi_cstate *cstate) +{ + acpigen_write_package(ctx, 4); + acpigen_write_register_resource(ctx, &cstate->resource); + acpigen_write_dword(ctx, cstate->ctype); + acpigen_write_dword(ctx, cstate->latency); + acpigen_write_dword(ctx, cstate->power); + acpigen_pop_len(ctx); +} + +void acpigen_write_cst_package(struct acpi_ctx *ctx, struct acpi_cstate *cstate, + int nentries) +{ + int i; + + acpigen_write_name(ctx, "_CST"); + acpigen_write_package(ctx, nentries + 1); + acpigen_write_dword(ctx, nentries); + + for (i = 0; i < nentries; i++) + acpigen_write_cst_package_entry(ctx, cstate + i); + + acpigen_pop_len(ctx); +} + +void acpigen_write_csd_package(struct acpi_ctx *ctx, u32 domain, u32 numprocs, + enum csd_coord coordtype, u32 index) +{ + acpigen_write_name(ctx, "_CSD"); + acpigen_write_package(ctx, 1); + acpigen_write_package(ctx, 6); + acpigen_write_byte(ctx, 6); // 6 values + acpigen_write_byte(ctx, 0); // revision 0 + acpigen_write_dword(ctx, domain); + acpigen_write_dword(ctx, coordtype); + acpigen_write_dword(ctx, numprocs); + acpigen_write_dword(ctx, index); + acpigen_pop_len(ctx); + acpigen_pop_len(ctx); +} + +void acpigen_write_tss_package(struct acpi_ctx *ctx, int entries, + struct acpi_tstate *tstate_list) +{ + /* + * Sample _TSS package with 100% and 50% duty cycles + * Name (_TSS, Package (0x02) + * { + * Package(){100, 1000, 0, 0x00, 0) + * Package(){50, 520, 0, 0x18, 0) + * }) + */ + int i; + struct acpi_tstate *tstate = tstate_list; + + acpigen_write_name(ctx, "_TSS"); + acpigen_write_package(ctx, entries); + + for (i = 0; i < entries; i++) { + acpigen_write_package(ctx, 5); + acpigen_write_dword(ctx, tstate->percent); + acpigen_write_dword(ctx, tstate->power); + acpigen_write_dword(ctx, tstate->latency); + acpigen_write_dword(ctx, tstate->control); + acpigen_write_dword(ctx, tstate->status); + acpigen_pop_len(ctx); + tstate++; + } + + acpigen_pop_len(ctx); +} + +void acpigen_write_tsd_package(struct acpi_ctx *ctx, u32 domain, u32 numprocs, + enum psd_coord coordtype) +{ + acpigen_write_name(ctx, "_TSD"); + acpigen_write_package(ctx, 1); + acpigen_write_package(ctx, 5); + acpigen_write_byte(ctx, 5); // 5 values + acpigen_write_byte(ctx, 0); // revision 0 + acpigen_write_dword(ctx, domain); + acpigen_write_dword(ctx, coordtype); + acpigen_write_dword(ctx, numprocs); + acpigen_pop_len(ctx); + acpigen_pop_len(ctx); +} + +void acpigen_write_mem32fixed(struct acpi_ctx *ctx, int readwrite, u32 base, + u32 size) +{ + /* + * ACPI 4.0 section 6.4.3.4: 32-Bit Fixed Memory Range Descriptor + * Byte 0: + * Bit7 : 1 => big item + * Bit6-0: 0000110 (0x6) => 32-bit fixed memory + */ + acpigen_emit_byte(ctx, 0x86); + /* Byte 1+2: length (0x0009) */ + acpigen_emit_byte(ctx, 0x09); + acpigen_emit_byte(ctx, 0x00); + /* bit1-7 are ignored */ + acpigen_emit_byte(ctx, readwrite ? 0x01 : 0x00); + acpigen_emit_dword(ctx, base); + acpigen_emit_dword(ctx, size); +} + +static void acpigen_write_register(struct acpi_ctx *ctx, + const struct acpi_gen_regaddr *addr) +{ + acpigen_emit_byte(ctx, 0x82); /* Register Descriptor */ + acpigen_emit_byte(ctx, 0x0c); /* Register Length 7:0 */ + acpigen_emit_byte(ctx, 0x00); /* Register Length 15:8 */ + acpigen_emit_byte(ctx, addr->space_id); + acpigen_emit_byte(ctx, addr->bit_width); + acpigen_emit_byte(ctx, addr->bit_offset); + acpigen_emit_byte(ctx, addr->access_size); + acpigen_emit_dword(ctx, addr->addrl); + acpigen_emit_dword(ctx, addr->addrh); +} + +void acpigen_write_register_resource(struct acpi_ctx *ctx, + const struct acpi_gen_regaddr *addr) +{ + acpigen_write_resourcetemplate_header(ctx); + acpigen_write_register(ctx, addr); + acpigen_write_resourcetemplate_footer(ctx); +} + +void acpigen_write_irq(struct acpi_ctx *ctx, u16 mask) +{ + /* + * ACPI 3.0b section 6.4.2.1: IRQ Descriptor + * Byte 0: + * Bit7 : 0 => small item + * Bit6-3: 0100 (0x4) => IRQ port descriptor + * Bit2-0: 010 (0x2) => 2 Bytes long + */ + acpigen_emit_byte(ctx, 0x22); + acpigen_emit_byte(ctx, mask & 0xff); + acpigen_emit_byte(ctx, (mask >> 8) & 0xff); +} + +void acpigen_write_resourcetemplate_header(struct acpi_ctx *ctx) +{ + /* + * A ResourceTemplate() is a Buffer() with a + * (Byte|Word|DWord) containing the length, followed by one or more + * resource items, terminated by the end tag. + * (small item 0xf, len 1) + */ + acpigen_emit_byte(ctx, BUFFER_OP); + acpigen_write_len_f(ctx); + acpigen_emit_byte(ctx, WORD_PREFIX); + ctx->len_stack[ctx->ltop++] = ctx->current; + /* + * Add two dummy bytes for the ACPI word (keep aligned with the + * calclulation in acpigen_write_resourcetemplate() below) + */ + acpigen_emit_byte(ctx, 0x00); + acpigen_emit_byte(ctx, 0x00); +} + +void acpigen_write_resourcetemplate_footer(struct acpi_ctx *ctx) +{ + char *p = ctx->len_stack[--ctx->ltop]; + int len; + /* + * end tag (acpi 4.0 Section 6.4.2.8) + * 0x79 + * 0x00 is treated as a good checksum according to the spec + * and is what iasl generates. + */ + acpigen_emit_byte(ctx, 0x79); + acpigen_emit_byte(ctx, 0x00); + + /* + * Start counting past the 2-bytes length added in + * acpigen_write_resourcetemplate() above + */ + len = (char *)ctx->current - (p + 2); + + /* patch len word */ + p[0] = len & 0xff; + p[1] = (len >> 8) & 0xff; + /* patch len field */ + acpigen_pop_len(ctx); +} + +/* + * ToUUID(uuid) + * + * ACPI 6.1 Section 19.6.136 table 19-385 defines a special output + * order for the bytes that make up a UUID Buffer object. + * UUID byte order for input: + * aabbccdd-eeff-gghh-iijj-kkllmmnnoopp + * UUID byte order for output: + * ddccbbaa-ffee-hhgg-iijj-kkllmmnnoopp + */ +#define UUID_LEN 16 +int acpigen_write_uuid(struct acpi_ctx *ctx, const char *uuid) +{ + u8 buf[UUID_LEN]; + size_t i, order[UUID_LEN] = { 3, 2, 1, 0, 5, 4, 7, 6, + 8, 9, 10, 11, 12, 13, 14, 15 }; + int ret; + + /* Parse UUID string into bytes */ + ret = uuid_str_to_bin(uuid, buf, UUID_STR_FORMAT_STD); + if (ret) + return log_msg_ret("bad hex", -EINVAL); + + /* BufferOp */ + acpigen_emit_byte(ctx, BUFFER_OP); + acpigen_write_len_f(ctx); + + /* Buffer length in bytes */ + acpigen_write_word(ctx, UUID_LEN); + + /* Output UUID in expected order */ + for (i = 0; i < UUID_LEN; i++) + acpigen_emit_byte(ctx, buf[order[i]]); + + acpigen_pop_len(ctx); + + return 0; +} + +/* + * Name (_PRx, Package(One) { name }) + * ... + * PowerResource (name, level, order) + */ +void acpigen_write_power_res(struct acpi_ctx *ctx, const char *name, u8 level, + u16 order, const char *const dev_states[], + size_t dev_states_count) +{ + size_t i; + + for (i = 0; i < dev_states_count; i++) { + acpigen_write_name(ctx, dev_states[i]); + acpigen_write_package(ctx, 1); + acpigen_emit_simple_namestring(ctx, name); + acpigen_pop_len(ctx); /* Package */ + } + + acpigen_emit_ext_op(ctx, POWER_RES_OP); + + acpigen_write_len_f(ctx); + + acpigen_emit_simple_namestring(ctx, name); + acpigen_emit_byte(ctx, level); + acpigen_emit_word(ctx, order); +} + +/* Sleep (ms) */ +void acpigen_write_sleep(struct acpi_ctx *ctx, u64 sleep_ms) +{ + acpigen_emit_ext_op(ctx, SLEEP_OP); + acpigen_write_integer(ctx, sleep_ms); +} + +void acpigen_write_store(struct acpi_ctx *ctx) +{ + acpigen_emit_byte(ctx, STORE_OP); +} + +/* Store (src, dst) */ +void acpigen_write_store_ops(struct acpi_ctx *ctx, u8 src, u8 dst) +{ + acpigen_write_store(ctx); + acpigen_emit_byte(ctx, src); + acpigen_emit_byte(ctx, dst); +} + +/* Or (arg1, arg2, res) */ +void acpigen_write_or(struct acpi_ctx *ctx, u8 arg1, u8 arg2, u8 res) +{ + acpigen_emit_byte(ctx, OR_OP); + acpigen_emit_byte(ctx, arg1); + acpigen_emit_byte(ctx, arg2); + acpigen_emit_byte(ctx, res); +} + +/* And (arg1, arg2, res) */ +void acpigen_write_and(struct acpi_ctx *ctx, u8 arg1, u8 arg2, u8 res) +{ + acpigen_emit_byte(ctx, AND_OP); + acpigen_emit_byte(ctx, arg1); + acpigen_emit_byte(ctx, arg2); + acpigen_emit_byte(ctx, res); +} + +/* Not (arg, res) */ +void acpigen_write_not(struct acpi_ctx *ctx, u8 arg, u8 res) +{ + acpigen_emit_byte(ctx, NOT_OP); + acpigen_emit_byte(ctx, arg); + acpigen_emit_byte(ctx, res); +} + +/* Store (str, DEBUG) */ +void acpigen_write_debug_string(struct acpi_ctx *ctx, const char *str) +{ + acpigen_write_store(ctx); + acpigen_write_string(ctx, str); + acpigen_emit_ext_op(ctx, DEBUG_OP); +} + +/* Store (val, DEBUG) */ +void acpigen_write_debug_integer(struct acpi_ctx *ctx, u64 val) +{ + acpigen_write_store(ctx); + acpigen_write_integer(ctx, val); + acpigen_emit_ext_op(ctx, DEBUG_OP); +} + +/* Store (op, DEBUG) */ +void acpigen_write_debug_op(struct acpi_ctx *ctx, u8 op) +{ + acpigen_write_store(ctx); + acpigen_emit_byte(ctx, op); + acpigen_emit_ext_op(ctx, DEBUG_OP); +} + +void acpigen_write_if(struct acpi_ctx *ctx) +{ + acpigen_emit_byte(ctx, IF_OP); + acpigen_write_len_f(ctx); +} + +/* If (And (arg1, arg2)) */ +void acpigen_write_if_and(struct acpi_ctx *ctx, u8 arg1, u8 arg2) +{ + acpigen_write_if(ctx); + acpigen_emit_byte(ctx, AND_OP); + acpigen_emit_byte(ctx, arg1); + acpigen_emit_byte(ctx, arg2); +} + +/* + * Generates ACPI code for checking if operand1 and operand2 are equal, where, + * operand1 is ACPI op and operand2 is an integer. + * + * If (Lequal (op, val)) + */ +void acpigen_write_if_lequal_op_int(struct acpi_ctx *ctx, u8 op, u64 val) +{ + acpigen_write_if(ctx); + acpigen_emit_byte(ctx, LEQUAL_OP); + acpigen_emit_byte(ctx, op); + acpigen_write_integer(ctx, val); +} + +void acpigen_write_else(struct acpi_ctx *ctx) +{ + acpigen_emit_byte(ctx, ELSE_OP); + acpigen_write_len_f(ctx); +} + +void acpigen_write_to_buffer(struct acpi_ctx *ctx, u8 src, u8 dst) +{ + acpigen_emit_byte(ctx, TO_BUFFER_OP); + acpigen_emit_byte(ctx, src); + acpigen_emit_byte(ctx, dst); +} + +void acpigen_write_to_integer(struct acpi_ctx *ctx, u8 src, u8 dst) +{ + acpigen_emit_byte(ctx, TO_INTEGER_OP); + acpigen_emit_byte(ctx, src); + acpigen_emit_byte(ctx, dst); +} + +void acpigen_write_byte_buffer(struct acpi_ctx *ctx, u8 *arr, size_t size) +{ + size_t i; + + acpigen_emit_byte(ctx, BUFFER_OP); + acpigen_write_len_f(ctx); + acpigen_write_integer(ctx, size); + + for (i = 0; i < size; i++) + acpigen_emit_byte(ctx, arr[i]); + + acpigen_pop_len(ctx); +} + +void acpigen_write_return_byte_buffer(struct acpi_ctx *ctx, u8 *arr, + size_t size) +{ + acpigen_emit_byte(ctx, RETURN_OP); + acpigen_write_byte_buffer(ctx, arr, size); +} + +void acpigen_write_return_singleton_buffer(struct acpi_ctx *ctx, u8 arg) +{ + acpigen_write_return_byte_buffer(ctx, &arg, 1); +} + +void acpigen_write_return_byte(struct acpi_ctx *ctx, u8 arg) +{ + acpigen_emit_byte(ctx, RETURN_OP); + acpigen_write_byte(ctx, arg); +} + +void acpigen_write_return_integer(struct acpi_ctx *ctx, u64 arg) +{ + acpigen_emit_byte(ctx, RETURN_OP); + acpigen_write_integer(ctx, arg); +} + +void acpigen_write_return_string(struct acpi_ctx *ctx, const char *arg) +{ + acpigen_emit_byte(ctx, RETURN_OP); + acpigen_write_string(ctx, arg); +} + +void acpigen_write_upc(struct acpi_ctx *ctx, enum acpi_upc_type type) +{ + acpigen_write_name(ctx, "_UPC"); + acpigen_write_package(ctx, 4); + /* Connectable */ + acpigen_write_byte(ctx, type == UPC_TYPE_UNUSED ? 0 : 0xff); + /* Type */ + acpigen_write_byte(ctx, type); + /* Reserved0 */ + acpigen_write_zero(ctx); + /* Reserved1 */ + acpigen_write_zero(ctx); + acpigen_pop_len(ctx); +} + +int acpigen_write_dsm(struct acpi_ctx *ctx, const char *uuid, + hid_callback_func callbacks[], size_t count, void *arg) +{ + struct dsm_uuid id = DSM_UUID(uuid, callbacks, count, arg); + int ret; + + ret = acpigen_write_dsm_uuid_arr(ctx, &id, 1); + if (ret) + return log_msg_ret("uuid_arr", ret); + + return 0; +} + +static int acpigen_write_dsm_uuid(struct acpi_ctx *ctx, struct dsm_uuid *id) +{ + size_t i; + int ret; + + /* If (LEqual (Local0, ToUUID(uuid))) */ + acpigen_write_if(ctx); + acpigen_emit_byte(ctx, LEQUAL_OP); + acpigen_emit_byte(ctx, LOCAL0_OP); + ret = acpigen_write_uuid(ctx, id->uuid); + if (ret) + return log_msg_ret("uuid", ret); + + /* ToInteger (Arg2, Local1) */ + acpigen_write_to_integer(ctx, ARG2_OP, LOCAL1_OP); + + for (i = 0; i < id->count; i++) { + /* If (LEqual (Local1, i)) */ + acpigen_write_if_lequal_op_int(ctx, LOCAL1_OP, i); + + /* Callback to write if handler. */ + if (id->callbacks[i]) + id->callbacks[i](ctx, id->arg); + + acpigen_pop_len(ctx); /* If */ + } + + /* Default case: Return (Buffer (One) { 0x0 }) */ + acpigen_write_return_singleton_buffer(ctx, 0x0); + + acpigen_pop_len(ctx); /* If (LEqual (Local0, ToUUID(uuid))) */ + + return 0; +} + +/* + * Generate ACPI AML code for _DSM method. + * This function takes as input array of uuid for the device, set of callbacks + * and argument to pass into the callbacks. Callbacks should ensure that Local0 + * and Local1 are left untouched. Use of Local2-Local7 is permitted in + * callbacks. + * + * Arguments passed into _DSM method: + * Arg0 = UUID + * Arg1 = Revision + * Arg2 = Function index + * Arg3 = Function specific arguments + * + * AML code generated would look like: + * Method (_DSM, 4, Serialized) { + * ToBuffer (Arg0, Local0) + * If (LEqual (Local0, ToUUID(uuid))) { + * ToInteger (Arg2, Local1) + * If (LEqual (Local1, 0)) { + * + * } + * ... + * If (LEqual (Local1, n)) { + * + * } + * Return (Buffer (One) { 0x0 }) + * } + * ... + * If (LEqual (Local0, ToUUID(uuidn))) { + * ... + * } + * Return (Buffer (One) { 0x0 }) + * } + */ +int acpigen_write_dsm_uuid_arr(struct acpi_ctx *ctx, struct dsm_uuid *ids, + size_t count) +{ + size_t i; + int ret; + + /* Method (_DSM, 4, Serialized) */ + acpigen_write_method_serialized(ctx, "_DSM", 0x4); + + /* ToBuffer (Arg0, Local0) */ + acpigen_write_to_buffer(ctx, ARG0_OP, LOCAL0_OP); + + for (i = 0; i < count; i++) { + ret = acpigen_write_dsm_uuid(ctx, &ids[i]); + if (ret) + return ret; + } + + /* Return (Buffer (One) { 0x0 }) */ + acpigen_write_return_singleton_buffer(ctx, 0x0); + + acpigen_pop_len(ctx); /* Method _DSM */ + + return 0; +} + +#define CPPC_PACKAGE_NAME "\\GCPC" + +int acpigen_write_cppc_package(struct acpi_ctx *ctx, + const struct cppc_config *config) +{ + u32 i; + u32 max; + + switch (config->version) { + case 1: + max = CPPC_MAX_FIELDS_VER_1; + break; + case 2: + max = CPPC_MAX_FIELDS_VER_2; + break; + case 3: + max = CPPC_MAX_FIELDS_VER_3; + break; + default: + log_err("CPPC version %u is not implemented\n", + config->version); + return -EDOM; + } + acpigen_write_name(ctx, CPPC_PACKAGE_NAME); + + /* Adding 2 to account for length and version fields */ + acpigen_write_package(ctx, max + 2); + acpigen_write_dword(ctx, max + 2); + + acpigen_write_byte(ctx, config->version); + + for (i = 0; i < max; ++i) { + const struct acpi_gen_regaddr *reg = &config->regs[i]; + + if (reg->space_id == ACPI_ADDRESS_SPACE_MEMORY && + reg->bit_width == 32 && reg->access_size == 0) { + acpigen_write_dword(ctx, reg->addrl); + } else { + acpigen_write_register_resource(ctx, reg); + } + } + acpigen_pop_len(ctx); + + return 0; +} + +void acpigen_write_cppc_method(struct acpi_ctx *ctx) +{ + acpigen_write_method(ctx, "_CPC", 0); + acpigen_emit_byte(ctx, RETURN_OP); + acpigen_emit_namestring(ctx, CPPC_PACKAGE_NAME); + acpigen_pop_len(ctx); +} + +/* + * Generate ACPI AML code for _ROM method. + * This function takes as input ROM data and ROM length. + * + * The ACPI spec isn't clear about what should happen at the end of the + * ROM. Tests showed that it shouldn't truncate, but fill the remaining + * bytes in the returned buffer with zeros. + * + * Arguments passed into _DSM method: + * Arg0 = Offset in Bytes + * Arg1 = Bytes to return + * + * Example: + * acpigen_write_rom(0xdeadbeef, 0x10000) + * + * AML code generated would look like: + * Method (_ROM, 2, NotSerialized) { + * + * OperationRegion("ROMS", SYSTEMMEMORY, 0xdeadbeef, 0x10000) + * Field (ROMS, AnyAcc, NoLock, Preserve) + * { + * Offset (0), + * RBF0, 0x80000 + * } + * + * Store (Arg0, Local0) + * Store (Arg1, Local1) + * + * If (LGreater (Local1, 0x1000)) + * { + * Store (0x1000, Local1) + * } + * + * Store (Local1, Local3) + * + * If (LGreater (Local0, 0x10000)) + * { + * Return(Buffer(Local1){0}) + * } + * + * If (LGreater (Local0, 0x0f000)) + * { + * Subtract (0x10000, Local0, Local2) + * If (LGreater (Local1, Local2)) + * { + * Store (Local2, Local1) + * } + * } + * + * Name (ROM1, Buffer (Local3) {0}) + * + * Multiply (Local0, 0x08, Local0) + * Multiply (Local1, 0x08, Local1) + * + * CreateField (RBF0, Local0, Local1, TMPB) + * Store (TMPB, ROM1) + * Return (ROM1) + * } + */ +int acpigen_write_rom(struct acpi_ctx *ctx, void *bios, const size_t length) +{ + int ret; + + assert(bios); + assert(length); + + /* Method (_ROM, 2, Serialized) */ + acpigen_write_method_serialized(ctx, "_ROM", 2); + + /* OperationRegion("ROMS", SYSTEMMEMORY, current, length) */ + struct opregion opreg = OPREGION("ROMS", SYSTEMMEMORY, + (uintptr_t)bios, length); + acpigen_write_opregion(ctx, &opreg); + + struct fieldlist l[] = { + FIELDLIST_OFFSET(0), + FIELDLIST_NAMESTR("RBF0", 8 * length), + }; + + /* + * Field (ROMS, AnyAcc, NoLock, Preserve) + * { + * Offset (0), + * RBF0, 0x80000 + * } + */ + ret = acpigen_write_field(ctx, opreg.name, l, 2, FIELD_ANYACC | + FIELD_NOLOCK | FIELD_PRESERVE); + if (ret) + return log_msg_ret("field", ret); + + /* Store (Arg0, Local0) */ + acpigen_write_store(ctx); + acpigen_emit_byte(ctx, ARG0_OP); + acpigen_emit_byte(ctx, LOCAL0_OP); + + /* Store (Arg1, Local1) */ + acpigen_write_store(ctx); + acpigen_emit_byte(ctx, ARG1_OP); + acpigen_emit_byte(ctx, LOCAL1_OP); + + /* ACPI SPEC requires to return at maximum 4KiB */ + /* If (LGreater (Local1, 0x1000)) */ + acpigen_write_if(ctx); + acpigen_emit_byte(ctx, LGREATER_OP); + acpigen_emit_byte(ctx, LOCAL1_OP); + acpigen_write_integer(ctx, 0x1000); + + /* Store (0x1000, Local1) */ + acpigen_write_store(ctx); + acpigen_write_integer(ctx, 0x1000); + acpigen_emit_byte(ctx, LOCAL1_OP); + + /* Pop if */ + acpigen_pop_len(ctx); + + /* Store (Local1, Local3) */ + acpigen_write_store(ctx); + acpigen_emit_byte(ctx, LOCAL1_OP); + acpigen_emit_byte(ctx, LOCAL3_OP); + + /* If (LGreater (Local0, length)) */ + acpigen_write_if(ctx); + acpigen_emit_byte(ctx, LGREATER_OP); + acpigen_emit_byte(ctx, LOCAL0_OP); + acpigen_write_integer(ctx, length); + + /* Return(Buffer(Local1){0}) */ + acpigen_emit_byte(ctx, RETURN_OP); + acpigen_emit_byte(ctx, BUFFER_OP); + acpigen_write_len_f(ctx); + acpigen_emit_byte(ctx, LOCAL1_OP); + acpigen_emit_byte(ctx, 0); + acpigen_pop_len(ctx); + + /* Pop if */ + acpigen_pop_len(ctx); + + /* If (LGreater (Local0, length - 4096)) */ + acpigen_write_if(ctx); + acpigen_emit_byte(ctx, LGREATER_OP); + acpigen_emit_byte(ctx, LOCAL0_OP); + acpigen_write_integer(ctx, length - 4096); + + /* Subtract (length, Local0, Local2) */ + acpigen_emit_byte(ctx, SUBTRACT_OP); + acpigen_write_integer(ctx, length); + acpigen_emit_byte(ctx, LOCAL0_OP); + acpigen_emit_byte(ctx, LOCAL2_OP); + + /* If (LGreater (Local1, Local2)) */ + acpigen_write_if(ctx); + acpigen_emit_byte(ctx, LGREATER_OP); + acpigen_emit_byte(ctx, LOCAL1_OP); + acpigen_emit_byte(ctx, LOCAL2_OP); + + /* Store (Local2, Local1) */ + acpigen_write_store(ctx); + acpigen_emit_byte(ctx, LOCAL2_OP); + acpigen_emit_byte(ctx, LOCAL1_OP); + + /* Pop if */ + acpigen_pop_len(ctx); + + /* Pop if */ + acpigen_pop_len(ctx); + + /* Name (ROM1, Buffer (Local3) {0}) */ + acpigen_write_name(ctx, "ROM1"); + acpigen_emit_byte(ctx, BUFFER_OP); + acpigen_write_len_f(ctx); + acpigen_emit_byte(ctx, LOCAL3_OP); + acpigen_emit_byte(ctx, 0); + acpigen_pop_len(ctx); + + /* Multiply (Local1, 0x08, Local1) */ + acpigen_emit_byte(ctx, MULTIPLY_OP); + acpigen_emit_byte(ctx, LOCAL1_OP); + acpigen_write_integer(ctx, 0x08); + acpigen_emit_byte(ctx, LOCAL1_OP); + + /* Multiply (Local0, 0x08, Local0) */ + acpigen_emit_byte(ctx, MULTIPLY_OP); + acpigen_emit_byte(ctx, LOCAL0_OP); + acpigen_write_integer(ctx, 0x08); + acpigen_emit_byte(ctx, LOCAL0_OP); + + /* CreateField (RBF0, Local0, Local1, TMPB) */ + acpigen_emit_ext_op(ctx, CREATEFIELD_OP); + acpigen_emit_namestring(ctx, "RBF0"); + acpigen_emit_byte(ctx, LOCAL0_OP); + acpigen_emit_byte(ctx, LOCAL1_OP); + acpigen_emit_namestring(ctx, "TMPB"); + + /* Store (TMPB, ROM1) */ + acpigen_write_store(ctx); + acpigen_emit_namestring(ctx, "TMPB"); + acpigen_emit_namestring(ctx, "ROM1"); + + /* Return (ROM1) */ + acpigen_emit_byte(ctx, RETURN_OP); + acpigen_emit_namestring(ctx, "ROM1"); + + /* Pop method */ + acpigen_pop_len(ctx); + + return 0; +} + +/* Soc-implemented functions -- weak definitions. */ +int __weak acpigen_soc_read_rx_gpio(struct acpi_ctx *ctx, unsigned int gpio_num) +{ + log_err("not implemented\n"); + acpigen_write_debug_string(ctx, "read_rx_gpio not available"); + + return -ENOTSUPP; +} + +int __weak acpigen_soc_get_tx_gpio(struct acpi_ctx *ctx, unsigned int gpio_num) +{ + log_err("not implemented\n"); + acpigen_write_debug_string(ctx, "get_tx_gpio not available"); + + return -ENOTSUPP; +} + +int __weak acpigen_soc_set_tx_gpio(struct acpi_ctx *ctx, unsigned int gpio_num) +{ + log_err("not implemented\n"); + acpigen_write_debug_string(ctx, "set_tx_gpio not available"); + + return -ENOTSUPP; +} + +int __weak acpigen_soc_clear_tx_gpio(struct acpi_ctx *ctx, + unsigned int gpio_num) +{ + log_err("not implemented\n"); + acpigen_write_debug_string(ctx, "clear_tx_gpio not available"); + + return -ENOTSUPP; +} + +/* + * Helper functions for enabling/disabling Tx GPIOs based on the GPIO + * polarity. These functions end up calling acpigen_soc_{set,clear}_tx_gpio to + * make callbacks into SoC acpigen code. + * + * Returns 0 on success and -1 on error. + */ +int acpigen_enable_tx_gpio(struct acpi_ctx *ctx, struct acpi_gpio *gpio) +{ + int ret; + + if (gpio->polarity == ACPI_GPIO_ACTIVE_HIGH) + ret = acpigen_soc_set_tx_gpio(ctx, gpio->pins[0]); + else + ret = acpigen_soc_clear_tx_gpio(ctx, gpio->pins[0]); + if (ret) + return log_msg_ret("call", ret); + + return 0; +} + +int acpigen_disable_tx_gpio(struct acpi_ctx *ctx, struct acpi_gpio *gpio) +{ + int ret; + + if (gpio->polarity == ACPI_GPIO_ACTIVE_LOW) + return acpigen_soc_set_tx_gpio(ctx, gpio->pins[0]); + else + return acpigen_soc_clear_tx_gpio(ctx, gpio->pins[0]); + if (ret) + return log_msg_ret("call", ret); + + return 0; +}