linux/drivers/char/tpm/tpm_ppi.c
Gang Wei 1631cfb7ce driver/char/tpm: fix regression causesd by ppi
This patch try to fix the S3 regression https://lkml.org/lkml/2012/10/5/433,
which includes below line:
[ 1554.684638] sysfs: cannot create duplicate filename '/devices/pnp0/00:0c/ppi'

The root cause is that ppi sysfs teardown code is MIA, so while S3 resume,
the ppi kobject will be created again upon existing one.

To make the tear down code simple, change the ppi subfolder creation from
using kobject_create_and_add to just using a named ppi attribute_group. Then
ppi sysfs teardown could be done with a simple sysfs_remove_group call.

Adjusted the name & return type for ppi sysfs init function.

Reported-by: Ben Guthro <ben@guthro.net>
Signed-off-by: Gang Wei <gang.wei@intel.com>
Signed-off-by: Kent Yoder <key@linux.vnet.ibm.com>
2012-10-10 09:50:56 -05:00

463 lines
13 KiB
C

#include <linux/acpi.h>
#include <acpi/acpi_drivers.h>
#include "tpm.h"
static const u8 tpm_ppi_uuid[] = {
0xA6, 0xFA, 0xDD, 0x3D,
0x1B, 0x36,
0xB4, 0x4E,
0xA4, 0x24,
0x8D, 0x10, 0x08, 0x9D, 0x16, 0x53
};
static char *tpm_device_name = "TPM";
#define TPM_PPI_REVISION_ID 1
#define TPM_PPI_FN_VERSION 1
#define TPM_PPI_FN_SUBREQ 2
#define TPM_PPI_FN_GETREQ 3
#define TPM_PPI_FN_GETACT 4
#define TPM_PPI_FN_GETRSP 5
#define TPM_PPI_FN_SUBREQ2 7
#define TPM_PPI_FN_GETOPR 8
#define PPI_TPM_REQ_MAX 22
#define PPI_VS_REQ_START 128
#define PPI_VS_REQ_END 255
#define PPI_VERSION_LEN 3
static acpi_status ppi_callback(acpi_handle handle, u32 level, void *context,
void **return_value)
{
acpi_status status;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
status = acpi_get_name(handle, ACPI_FULL_PATHNAME, &buffer);
if (strstr(buffer.pointer, context) != NULL) {
*return_value = handle;
kfree(buffer.pointer);
return AE_CTRL_TERMINATE;
}
return AE_OK;
}
static inline void ppi_assign_params(union acpi_object params[4],
u64 function_num)
{
params[0].type = ACPI_TYPE_BUFFER;
params[0].buffer.length = sizeof(tpm_ppi_uuid);
params[0].buffer.pointer = (char *)tpm_ppi_uuid;
params[1].type = ACPI_TYPE_INTEGER;
params[1].integer.value = TPM_PPI_REVISION_ID;
params[2].type = ACPI_TYPE_INTEGER;
params[2].integer.value = function_num;
params[3].type = ACPI_TYPE_PACKAGE;
params[3].package.count = 0;
params[3].package.elements = NULL;
}
static ssize_t tpm_show_ppi_version(struct device *dev,
struct device_attribute *attr, char *buf)
{
acpi_handle handle;
acpi_status status;
struct acpi_object_list input;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object params[4];
union acpi_object *obj;
input.count = 4;
ppi_assign_params(params, TPM_PPI_FN_VERSION);
input.pointer = params;
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
ACPI_UINT32_MAX, ppi_callback, NULL,
tpm_device_name, &handle);
if (ACPI_FAILURE(status))
return -ENXIO;
status = acpi_evaluate_object_typed(handle, "_DSM", &input, &output,
ACPI_TYPE_STRING);
if (ACPI_FAILURE(status))
return -ENOMEM;
obj = (union acpi_object *)output.pointer;
status = scnprintf(buf, PAGE_SIZE, "%s\n", obj->string.pointer);
kfree(output.pointer);
return status;
}
static ssize_t tpm_show_ppi_request(struct device *dev,
struct device_attribute *attr, char *buf)
{
acpi_handle handle;
acpi_status status;
struct acpi_object_list input;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object params[4];
union acpi_object *ret_obj;
input.count = 4;
ppi_assign_params(params, TPM_PPI_FN_GETREQ);
input.pointer = params;
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
ACPI_UINT32_MAX, ppi_callback, NULL,
tpm_device_name, &handle);
if (ACPI_FAILURE(status))
return -ENXIO;
status = acpi_evaluate_object_typed(handle, "_DSM", &input, &output,
ACPI_TYPE_PACKAGE);
if (ACPI_FAILURE(status))
return -ENOMEM;
/*
* output.pointer should be of package type, including two integers.
* The first is function return code, 0 means success and 1 means
* error. The second is pending TPM operation requested by the OS, 0
* means none and >0 means operation value.
*/
ret_obj = ((union acpi_object *)output.pointer)->package.elements;
if (ret_obj->type == ACPI_TYPE_INTEGER) {
if (ret_obj->integer.value) {
status = -EFAULT;
goto cleanup;
}
ret_obj++;
if (ret_obj->type == ACPI_TYPE_INTEGER)
status = scnprintf(buf, PAGE_SIZE, "%llu\n",
ret_obj->integer.value);
else
status = -EINVAL;
} else {
status = -EINVAL;
}
cleanup:
kfree(output.pointer);
return status;
}
static ssize_t tpm_store_ppi_request(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
char version[PPI_VERSION_LEN + 1];
acpi_handle handle;
acpi_status status;
struct acpi_object_list input;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object params[4];
union acpi_object obj;
u32 req;
u64 ret;
input.count = 4;
ppi_assign_params(params, TPM_PPI_FN_VERSION);
input.pointer = params;
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
ACPI_UINT32_MAX, ppi_callback, NULL,
tpm_device_name, &handle);
if (ACPI_FAILURE(status))
return -ENXIO;
status = acpi_evaluate_object_typed(handle, "_DSM", &input, &output,
ACPI_TYPE_STRING);
if (ACPI_FAILURE(status))
return -ENOMEM;
strncpy(version,
((union acpi_object *)output.pointer)->string.pointer,
PPI_VERSION_LEN);
kfree(output.pointer);
output.length = ACPI_ALLOCATE_BUFFER;
output.pointer = NULL;
/*
* the function to submit TPM operation request to pre-os environment
* is updated with function index from SUBREQ to SUBREQ2 since PPI
* version 1.1
*/
if (strcmp(version, "1.1") == -1)
params[2].integer.value = TPM_PPI_FN_SUBREQ;
else
params[2].integer.value = TPM_PPI_FN_SUBREQ2;
/*
* PPI spec defines params[3].type as ACPI_TYPE_PACKAGE. Some BIOS
* accept buffer/string/integer type, but some BIOS accept buffer/
* string/package type. For PPI version 1.0 and 1.1, use buffer type
* for compatibility, and use package type since 1.2 according to spec.
*/
if (strcmp(version, "1.2") == -1) {
params[3].type = ACPI_TYPE_BUFFER;
params[3].buffer.length = sizeof(req);
sscanf(buf, "%d", &req);
params[3].buffer.pointer = (char *)&req;
} else {
params[3].package.count = 1;
obj.type = ACPI_TYPE_INTEGER;
sscanf(buf, "%llu", &obj.integer.value);
params[3].package.elements = &obj;
}
status = acpi_evaluate_object_typed(handle, "_DSM", &input, &output,
ACPI_TYPE_INTEGER);
if (ACPI_FAILURE(status))
return -ENOMEM;
ret = ((union acpi_object *)output.pointer)->integer.value;
if (ret == 0)
status = (acpi_status)count;
else if (ret == 1)
status = -EPERM;
else
status = -EFAULT;
kfree(output.pointer);
return status;
}
static ssize_t tpm_show_ppi_transition_action(struct device *dev,
struct device_attribute *attr,
char *buf)
{
char version[PPI_VERSION_LEN + 1];
acpi_handle handle;
acpi_status status;
struct acpi_object_list input;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object params[4];
u32 ret;
char *info[] = {
"None",
"Shutdown",
"Reboot",
"OS Vendor-specific",
"Error",
};
input.count = 4;
ppi_assign_params(params, TPM_PPI_FN_VERSION);
input.pointer = params;
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
ACPI_UINT32_MAX, ppi_callback, NULL,
tpm_device_name, &handle);
if (ACPI_FAILURE(status))
return -ENXIO;
status = acpi_evaluate_object_typed(handle, "_DSM", &input, &output,
ACPI_TYPE_STRING);
if (ACPI_FAILURE(status))
return -ENOMEM;
strncpy(version,
((union acpi_object *)output.pointer)->string.pointer,
PPI_VERSION_LEN);
/*
* PPI spec defines params[3].type as empty package, but some platforms
* (e.g. Capella with PPI 1.0) need integer/string/buffer type, so for
* compatibility, define params[3].type as buffer, if PPI version < 1.2
*/
if (strcmp(version, "1.2") == -1) {
params[3].type = ACPI_TYPE_BUFFER;
params[3].buffer.length = 0;
params[3].buffer.pointer = NULL;
}
params[2].integer.value = TPM_PPI_FN_GETACT;
kfree(output.pointer);
output.length = ACPI_ALLOCATE_BUFFER;
output.pointer = NULL;
status = acpi_evaluate_object_typed(handle, "_DSM", &input, &output,
ACPI_TYPE_INTEGER);
if (ACPI_FAILURE(status))
return -ENOMEM;
ret = ((union acpi_object *)output.pointer)->integer.value;
if (ret < ARRAY_SIZE(info) - 1)
status = scnprintf(buf, PAGE_SIZE, "%d: %s\n", ret, info[ret]);
else
status = scnprintf(buf, PAGE_SIZE, "%d: %s\n", ret,
info[ARRAY_SIZE(info)-1]);
kfree(output.pointer);
return status;
}
static ssize_t tpm_show_ppi_response(struct device *dev,
struct device_attribute *attr,
char *buf)
{
acpi_handle handle;
acpi_status status;
struct acpi_object_list input;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object params[4];
union acpi_object *ret_obj;
u64 req;
input.count = 4;
ppi_assign_params(params, TPM_PPI_FN_GETRSP);
input.pointer = params;
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
ACPI_UINT32_MAX, ppi_callback, NULL,
tpm_device_name, &handle);
if (ACPI_FAILURE(status))
return -ENXIO;
status = acpi_evaluate_object_typed(handle, "_DSM", &input, &output,
ACPI_TYPE_PACKAGE);
if (ACPI_FAILURE(status))
return -ENOMEM;
/*
* parameter output.pointer should be of package type, including
* 3 integers. The first means function return code, the second means
* most recent TPM operation request, and the last means response to
* the most recent TPM operation request. Only if the first is 0, and
* the second integer is not 0, the response makes sense.
*/
ret_obj = ((union acpi_object *)output.pointer)->package.elements;
if (ret_obj->type != ACPI_TYPE_INTEGER) {
status = -EINVAL;
goto cleanup;
}
if (ret_obj->integer.value) {
status = -EFAULT;
goto cleanup;
}
ret_obj++;
if (ret_obj->type != ACPI_TYPE_INTEGER) {
status = -EINVAL;
goto cleanup;
}
if (ret_obj->integer.value) {
req = ret_obj->integer.value;
ret_obj++;
if (ret_obj->type != ACPI_TYPE_INTEGER) {
status = -EINVAL;
goto cleanup;
}
if (ret_obj->integer.value == 0)
status = scnprintf(buf, PAGE_SIZE, "%llu %s\n", req,
"0: Success");
else if (ret_obj->integer.value == 0xFFFFFFF0)
status = scnprintf(buf, PAGE_SIZE, "%llu %s\n", req,
"0xFFFFFFF0: User Abort");
else if (ret_obj->integer.value == 0xFFFFFFF1)
status = scnprintf(buf, PAGE_SIZE, "%llu %s\n", req,
"0xFFFFFFF1: BIOS Failure");
else if (ret_obj->integer.value >= 1 &&
ret_obj->integer.value <= 0x00000FFF)
status = scnprintf(buf, PAGE_SIZE, "%llu %llu: %s\n",
req, ret_obj->integer.value,
"Corresponding TPM error");
else
status = scnprintf(buf, PAGE_SIZE, "%llu %llu: %s\n",
req, ret_obj->integer.value,
"Error");
} else {
status = scnprintf(buf, PAGE_SIZE, "%llu: %s\n",
ret_obj->integer.value, "No Recent Request");
}
cleanup:
kfree(output.pointer);
return status;
}
static ssize_t show_ppi_operations(char *buf, u32 start, u32 end)
{
char *str = buf;
char version[PPI_VERSION_LEN];
acpi_handle handle;
acpi_status status;
struct acpi_object_list input;
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object params[4];
union acpi_object obj;
int i;
u32 ret;
char *info[] = {
"Not implemented",
"BIOS only",
"Blocked for OS by BIOS",
"User required",
"User not required",
};
input.count = 4;
ppi_assign_params(params, TPM_PPI_FN_VERSION);
input.pointer = params;
status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT,
ACPI_UINT32_MAX, ppi_callback, NULL,
tpm_device_name, &handle);
if (ACPI_FAILURE(status))
return -ENXIO;
status = acpi_evaluate_object_typed(handle, "_DSM", &input, &output,
ACPI_TYPE_STRING);
if (ACPI_FAILURE(status))
return -ENOMEM;
strncpy(version,
((union acpi_object *)output.pointer)->string.pointer,
PPI_VERSION_LEN);
kfree(output.pointer);
output.length = ACPI_ALLOCATE_BUFFER;
output.pointer = NULL;
if (strcmp(version, "1.2") == -1)
return -EPERM;
params[2].integer.value = TPM_PPI_FN_GETOPR;
params[3].package.count = 1;
obj.type = ACPI_TYPE_INTEGER;
params[3].package.elements = &obj;
for (i = start; i <= end; i++) {
obj.integer.value = i;
status = acpi_evaluate_object_typed(handle, "_DSM",
&input, &output, ACPI_TYPE_INTEGER);
if (ACPI_FAILURE(status))
return -ENOMEM;
ret = ((union acpi_object *)output.pointer)->integer.value;
if (ret > 0 && ret < ARRAY_SIZE(info))
str += scnprintf(str, PAGE_SIZE, "%d %d: %s\n",
i, ret, info[ret]);
kfree(output.pointer);
output.length = ACPI_ALLOCATE_BUFFER;
output.pointer = NULL;
}
return str - buf;
}
static ssize_t tpm_show_ppi_tcg_operations(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return show_ppi_operations(buf, 0, PPI_TPM_REQ_MAX);
}
static ssize_t tpm_show_ppi_vs_operations(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return show_ppi_operations(buf, PPI_VS_REQ_START, PPI_VS_REQ_END);
}
static DEVICE_ATTR(version, S_IRUGO, tpm_show_ppi_version, NULL);
static DEVICE_ATTR(request, S_IRUGO | S_IWUSR | S_IWGRP,
tpm_show_ppi_request, tpm_store_ppi_request);
static DEVICE_ATTR(transition_action, S_IRUGO,
tpm_show_ppi_transition_action, NULL);
static DEVICE_ATTR(response, S_IRUGO, tpm_show_ppi_response, NULL);
static DEVICE_ATTR(tcg_operations, S_IRUGO, tpm_show_ppi_tcg_operations, NULL);
static DEVICE_ATTR(vs_operations, S_IRUGO, tpm_show_ppi_vs_operations, NULL);
static struct attribute *ppi_attrs[] = {
&dev_attr_version.attr,
&dev_attr_request.attr,
&dev_attr_transition_action.attr,
&dev_attr_response.attr,
&dev_attr_tcg_operations.attr,
&dev_attr_vs_operations.attr, NULL,
};
static struct attribute_group ppi_attr_grp = {
.name = "ppi",
.attrs = ppi_attrs
};
int tpm_add_ppi(struct kobject *parent)
{
return sysfs_create_group(parent, &ppi_attr_grp);
}
EXPORT_SYMBOL_GPL(tpm_add_ppi);
void tpm_remove_ppi(struct kobject *parent)
{
sysfs_remove_group(parent, &ppi_attr_grp);
}
EXPORT_SYMBOL_GPL(tpm_remove_ppi);
MODULE_LICENSE("GPL");