u-boot-brain/drivers/core/acpi.c
Simon Glass 18434aec1b acpi: Don't reset the tables with every new generation
At present if SSDT and DSDT code is created, only the latter is retained
for examination by the 'acpi items' command. Fix this by only resetting
the list when explicitly requested.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Bin Meng <bmeng.cn@gmail.com>
2020-11-06 09:51:31 +08:00

354 lines
7.7 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Core driver model support for ACPI table generation
*
* Copyright 2019 Google LLC
* Written by Simon Glass <sjg@chromium.org>
*/
#define LOG_CATEOGRY LOGC_ACPI
#include <common.h>
#include <dm.h>
#include <log.h>
#include <malloc.h>
#include <acpi/acpi_device.h>
#include <dm/acpi.h>
#include <dm/device-internal.h>
#include <dm/root.h>
#define MAX_ACPI_ITEMS 100
/* Type of table that we collected */
enum gen_type_t {
TYPE_NONE,
TYPE_SSDT,
TYPE_DSDT,
};
/* Type of method to call */
enum method_t {
METHOD_WRITE_TABLES,
METHOD_FILL_SSDT,
METHOD_INJECT_DSDT,
METHOD_SETUP_NHLT,
};
/* Prototype for all methods */
typedef int (*acpi_method)(const struct udevice *dev, struct acpi_ctx *ctx);
/**
* struct acpi_item - Holds info about ACPI data generated by a driver method
*
* @dev: Device that generated this data
* @type: Table type it refers to
* @buf: Buffer containing the data
* @size: Size of the data in bytes
*/
struct acpi_item {
struct udevice *dev;
enum gen_type_t type;
char *buf;
int size;
};
/* List of ACPI items collected */
static struct acpi_item acpi_item[MAX_ACPI_ITEMS];
static int item_count;
int acpi_copy_name(char *out_name, const char *name)
{
strncpy(out_name, name, ACPI_NAME_LEN);
out_name[ACPI_NAME_LEN] = '\0';
return 0;
}
int acpi_get_name(const struct udevice *dev, char *out_name)
{
struct acpi_ops *aops;
const char *name;
int ret;
aops = device_get_acpi_ops(dev);
if (aops && aops->get_name)
return aops->get_name(dev, out_name);
name = dev_read_string(dev, "acpi,name");
if (name)
return acpi_copy_name(out_name, name);
ret = acpi_device_infer_name(dev, out_name);
if (ret)
return log_msg_ret("dev", ret);
return 0;
}
int acpi_get_path(const struct udevice *dev, char *out_path, int maxlen)
{
const char *path;
int ret;
path = dev_read_string(dev, "acpi,path");
if (path) {
if (strlen(path) >= maxlen)
return -E2BIG;
strcpy(out_path, path);
return 0;
}
ret = acpi_device_path(dev, out_path, maxlen);
if (ret)
return log_msg_ret("dev", ret);
return 0;
}
/**
* acpi_add_item() - Add a new item to the list of data collected
*
* @ctx: ACPI context
* @dev: Device that generated the data
* @type: Table type it refers to
* @start: The start of the data (the end is obtained from ctx->current)
* @return 0 if OK, -ENOSPC if too many items, -ENOMEM if out of memory
*/
static int acpi_add_item(struct acpi_ctx *ctx, struct udevice *dev,
enum gen_type_t type, void *start)
{
struct acpi_item *item;
void *end = ctx->current;
if (item_count == MAX_ACPI_ITEMS) {
log_err("Too many items\n");
return log_msg_ret("mem", -ENOSPC);
}
item = &acpi_item[item_count];
item->dev = dev;
item->type = type;
item->size = end - start;
if (!item->size)
return 0;
item->buf = malloc(item->size);
if (!item->buf)
return log_msg_ret("mem", -ENOMEM);
memcpy(item->buf, start, item->size);
item_count++;
log_debug("* %s: Added type %d, %p, size %x\n", dev->name, type, start,
item->size);
return 0;
}
void acpi_dump_items(enum acpi_dump_option option)
{
int i;
for (i = 0; i < item_count; i++) {
struct acpi_item *item = &acpi_item[i];
printf("dev '%s', type %d, size %x\n", item->dev->name,
item->type, item->size);
if (option == ACPI_DUMP_CONTENTS) {
print_buffer(0, item->buf, 1, item->size, 0);
printf("\n");
}
}
}
static struct acpi_item *find_acpi_item(const char *devname)
{
int i;
for (i = 0; i < item_count; i++) {
struct acpi_item *item = &acpi_item[i];
if (!strcmp(devname, item->dev->name))
return item;
}
return NULL;
}
/**
* sort_acpi_item_type - Sort the ACPI items into the desired order
*
* This looks up the ordering in the device tree and then adds each item one by
* one into the supplied buffer
*
* @ctx: ACPI context
* @start: Start position to put the sorted items. The items will follow each
* other in sorted order
* @type: Type of items to sort
* @return 0 if OK, -ve on error
*/
static int sort_acpi_item_type(struct acpi_ctx *ctx, void *start,
enum gen_type_t type)
{
const u32 *order;
int size;
int count;
void *ptr;
void *end = ctx->current;
ptr = start;
order = ofnode_read_chosen_prop(type == TYPE_DSDT ?
"u-boot,acpi-dsdt-order" :
"u-boot,acpi-ssdt-order", &size);
if (!order) {
log_debug("Failed to find ordering, leaving as is\n");
return 0;
}
/*
* This algorithm rewrites the context buffer without changing its
* length. So there is no need to update ctx-current
*/
count = size / sizeof(u32);
while (count--) {
struct acpi_item *item;
const char *name;
ofnode node;
node = ofnode_get_by_phandle(fdt32_to_cpu(*order++));
name = ofnode_get_name(node);
item = find_acpi_item(name);
if (!item) {
log_err("Failed to find item '%s'\n", name);
return log_msg_ret("find", -ENOENT);
}
if (item->type == type) {
log_debug(" - add %s\n", item->dev->name);
memcpy(ptr, item->buf, item->size);
ptr += item->size;
}
}
/*
* If the sort order is missing an item then the output will be too
* small. Report this error since the item needs to be added to the
* ordering for the ACPI tables to be complete.
*/
if (ptr != end) {
log_warning("*** Missing bytes: ptr=%p, end=%p\n", ptr, end);
return -ENXIO;
}
return 0;
}
acpi_method acpi_get_method(struct udevice *dev, enum method_t method)
{
struct acpi_ops *aops;
aops = device_get_acpi_ops(dev);
if (aops) {
switch (method) {
case METHOD_WRITE_TABLES:
return aops->write_tables;
case METHOD_FILL_SSDT:
return aops->fill_ssdt;
case METHOD_INJECT_DSDT:
return aops->inject_dsdt;
case METHOD_SETUP_NHLT:
return aops->setup_nhlt;
}
}
return NULL;
}
int acpi_recurse_method(struct acpi_ctx *ctx, struct udevice *parent,
enum method_t method, enum gen_type_t type)
{
struct udevice *dev;
acpi_method func;
int ret;
func = acpi_get_method(parent, method);
if (func) {
void *start = ctx->current;
log_debug("- method %d, %s %p\n", method, parent->name, func);
ret = device_ofdata_to_platdata(parent);
if (ret)
return log_msg_ret("ofdata", ret);
ret = func(parent, ctx);
if (ret)
return log_msg_ret("func", ret);
/* Add the item to the internal list */
if (type != TYPE_NONE) {
ret = acpi_add_item(ctx, parent, type, start);
if (ret)
return log_msg_ret("add", ret);
}
}
device_foreach_child(dev, parent) {
ret = acpi_recurse_method(ctx, dev, method, type);
if (ret)
return log_msg_ret("recurse", ret);
}
return 0;
}
int acpi_fill_ssdt(struct acpi_ctx *ctx)
{
void *start = ctx->current;
int ret;
log_debug("Writing SSDT tables\n");
ret = acpi_recurse_method(ctx, dm_root(), METHOD_FILL_SSDT, TYPE_SSDT);
log_debug("Writing SSDT finished, err=%d\n", ret);
ret = sort_acpi_item_type(ctx, start, TYPE_SSDT);
if (ret)
return log_msg_ret("build", ret);
return ret;
}
int acpi_inject_dsdt(struct acpi_ctx *ctx)
{
void *start = ctx->current;
int ret;
log_debug("Writing DSDT tables\n");
ret = acpi_recurse_method(ctx, dm_root(), METHOD_INJECT_DSDT,
TYPE_DSDT);
log_debug("Writing DSDT finished, err=%d\n", ret);
ret = sort_acpi_item_type(ctx, start, TYPE_DSDT);
if (ret)
return log_msg_ret("build", ret);
return ret;
}
void acpi_reset_items(void)
{
item_count = 0;
}
int acpi_write_dev_tables(struct acpi_ctx *ctx)
{
int ret;
log_debug("Writing device tables\n");
ret = acpi_recurse_method(ctx, dm_root(), METHOD_WRITE_TABLES,
TYPE_NONE);
log_debug("Writing finished, err=%d\n", ret);
return ret;
}
int acpi_setup_nhlt(struct acpi_ctx *ctx, struct nhlt *nhlt)
{
int ret;
log_debug("Setup NHLT\n");
ctx->nhlt = nhlt;
ret = acpi_recurse_method(ctx, dm_root(), METHOD_SETUP_NHLT, TYPE_NONE);
log_debug("Setup finished, err=%d\n", ret);
return ret;
}