diff options
Diffstat (limited to 'drivers/platform/x86/intel/tpmi.c')
| -rw-r--r-- | drivers/platform/x86/intel/tpmi.c | 430 | 
1 files changed, 423 insertions, 7 deletions
| diff --git a/drivers/platform/x86/intel/tpmi.c b/drivers/platform/x86/intel/tpmi.c index d1fd6e69401c..311abcac894a 100644 --- a/drivers/platform/x86/intel/tpmi.c +++ b/drivers/platform/x86/intel/tpmi.c @@ -47,10 +47,17 @@   */  #include <linux/auxiliary_bus.h> +#include <linux/bitfield.h> +#include <linux/debugfs.h> +#include <linux/delay.h>  #include <linux/intel_tpmi.h>  #include <linux/io.h> +#include <linux/iopoll.h>  #include <linux/module.h>  #include <linux/pci.h> +#include <linux/security.h> +#include <linux/sizes.h> +#include <linux/string_helpers.h>  #include "vsec.h" @@ -83,12 +90,14 @@ struct intel_tpmi_pfs_entry {   * @vsec_offset: Starting MMIO address for this feature in bytes. Essentially   *		 this offset = "Address" from VSEC header + PFS Capability   *		 offset for this feature entry. + * @vsec_dev:	Pointer to intel_vsec_device structure for this TPMI device   *   * Represents TPMI instance information for one TPMI ID.   */  struct intel_tpmi_pm_feature {  	struct intel_tpmi_pfs_entry pfs_header;  	unsigned int vsec_offset; +	struct intel_vsec_device *vsec_dev;  };  /** @@ -98,6 +107,8 @@ struct intel_tpmi_pm_feature {   * @feature_count:	Number of TPMI of TPMI instances pointed by tpmi_features   * @pfs_start:		Start of PFS offset for the TPMI instances in this device   * @plat_info:		Stores platform info which can be used by the client drivers + * @tpmi_control_mem:	Memory mapped IO for getting control information + * @dbgfs_dir:		debugfs entry pointer   *   * Stores the information for all TPMI devices enumerated from a single PCI device.   */ @@ -107,6 +118,8 @@ struct intel_tpmi_info {  	int feature_count;  	u64 pfs_start;  	struct intel_tpmi_plat_info plat_info; +	void __iomem *tpmi_control_mem; +	struct dentry *dbgfs_dir;  };  /** @@ -130,6 +143,33 @@ struct tpmi_info_header {  	u64 lock:1;  } __packed; +/** + * struct tpmi_feature_state - Structure to read hardware state of a feature + * @enabled:	Enable state of a feature, 1: enabled, 0: disabled + * @reserved_1:	Reserved for future use + * @write_blocked: Writes are blocked means all write operations are ignored + * @read_blocked: Reads are blocked means will read 0xFFs + * @pcs_select:	Interface used by out of band software, not used in OS + * @reserved_2:	Reserved for future use + * @id:		TPMI ID of the feature + * @reserved_3:	Reserved for future use + * @locked:	When set to 1, OS can't change this register. + * + * The structure is used to read hardware state of a TPMI feature. This + * information is used for debug and restricting operations for this feature. + */ +struct tpmi_feature_state { +	u32 enabled:1; +	u32 reserved_1:3; +	u32 write_blocked:1; +	u32 read_blocked:1; +	u32 pcs_select:1; +	u32 reserved_2:1; +	u32 id:8; +	u32 reserved_3:15; +	u32 locked:1; +} __packed; +  /*   * List of supported TMPI IDs.   * Some TMPI IDs are not used by Linux, so the numbers are not consecutive. @@ -139,9 +179,19 @@ enum intel_tpmi_id {  	TPMI_ID_PEM = 1, /* Power and Perf excursion Monitor */  	TPMI_ID_UNCORE = 2, /* Uncore Frequency Scaling */  	TPMI_ID_SST = 5, /* Speed Select Technology */ +	TPMI_CONTROL_ID = 0x80, /* Special ID for getting feature status */  	TPMI_INFO_ID = 0x81, /* Special ID for PCI BDF and Package ID information */  }; +/* + * The size from hardware is in u32 units. This size is from a trusted hardware, + * but better to verify for pre silicon platforms. Set size to 0, when invalid. + */ +#define TPMI_GET_SINGLE_ENTRY_SIZE(pfs)							\ +({											\ +	pfs->pfs_header.entry_size > SZ_1K ? 0 : pfs->pfs_header.entry_size << 2;	\ +}) +  /* Used during auxbus device creation */  static DEFINE_IDA(intel_vsec_tpmi_ida); @@ -175,6 +225,353 @@ struct resource *tpmi_get_resource_at_index(struct auxiliary_device *auxdev, int  }  EXPORT_SYMBOL_NS_GPL(tpmi_get_resource_at_index, INTEL_TPMI); +/* TPMI Control Interface */ + +#define TPMI_CONTROL_STATUS_OFFSET	0x00 +#define TPMI_COMMAND_OFFSET		0x08 +#define TMPI_CONTROL_DATA_VAL_OFFSET	0x0c + +/* + * Spec is calling for max 1 seconds to get ownership at the worst + * case. Read at 10 ms timeouts and repeat up to 1 second. + */ +#define TPMI_CONTROL_TIMEOUT_US		(10 * USEC_PER_MSEC) +#define TPMI_CONTROL_TIMEOUT_MAX_US	(1 * USEC_PER_SEC) + +#define TPMI_RB_TIMEOUT_US		(10 * USEC_PER_MSEC) +#define TPMI_RB_TIMEOUT_MAX_US		USEC_PER_SEC + +/* TPMI Control status register defines */ + +#define TPMI_CONTROL_STATUS_RB		BIT_ULL(0) + +#define TPMI_CONTROL_STATUS_OWNER	GENMASK_ULL(5, 4) +#define TPMI_OWNER_NONE			0 +#define TPMI_OWNER_IN_BAND		1 + +#define TPMI_CONTROL_STATUS_CPL		BIT_ULL(6) +#define TPMI_CONTROL_STATUS_RESULT	GENMASK_ULL(15, 8) +#define TPMI_CONTROL_STATUS_LEN		GENMASK_ULL(31, 16) + +#define TPMI_CMD_PKT_LEN		2 +#define TPMI_CMD_STATUS_SUCCESS		0x40 + +/* TPMI command data registers */ +#define TMPI_CONTROL_DATA_CMD		GENMASK_ULL(7, 0) +#define TPMI_CONTROL_DATA_VAL_FEATURE	GENMASK_ULL(48, 40) + +/* Command to send via control interface */ +#define TPMI_CONTROL_GET_STATE_CMD	0x10 + +#define TPMI_CONTROL_CMD_MASK		GENMASK_ULL(48, 40) + +#define TPMI_CMD_LEN_MASK		GENMASK_ULL(18, 16) + +/* Mutex to complete get feature status without interruption */ +static DEFINE_MUTEX(tpmi_dev_lock); + +static int tpmi_wait_for_owner(struct intel_tpmi_info *tpmi_info, u8 owner) +{ +	u64 control; + +	return readq_poll_timeout(tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET, +				  control, owner == FIELD_GET(TPMI_CONTROL_STATUS_OWNER, control), +				  TPMI_CONTROL_TIMEOUT_US, TPMI_CONTROL_TIMEOUT_MAX_US); +} + +static int tpmi_read_feature_status(struct intel_tpmi_info *tpmi_info, int feature_id, +				    struct tpmi_feature_state *feature_state) +{ +	u64 control, data; +	int ret; + +	if (!tpmi_info->tpmi_control_mem) +		return -EFAULT; + +	mutex_lock(&tpmi_dev_lock); + +	/* Wait for owner bit set to 0 (none) */ +	ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_NONE); +	if (ret) +		goto err_unlock; + +	/* set command id to 0x10 for TPMI_GET_STATE */ +	data = FIELD_PREP(TMPI_CONTROL_DATA_CMD, TPMI_CONTROL_GET_STATE_CMD); + +	/* 32 bits for DATA offset and +8 for feature_id field */ +	data |= FIELD_PREP(TPMI_CONTROL_DATA_VAL_FEATURE, feature_id); + +	/* Write at command offset for qword access */ +	writeq(data, tpmi_info->tpmi_control_mem + TPMI_COMMAND_OFFSET); + +	/* Wait for owner bit set to in-band */ +	ret = tpmi_wait_for_owner(tpmi_info, TPMI_OWNER_IN_BAND); +	if (ret) +		goto err_unlock; + +	/* Set Run Busy and packet length of 2 dwords */ +	control = TPMI_CONTROL_STATUS_RB; +	control |= FIELD_PREP(TPMI_CONTROL_STATUS_LEN, TPMI_CMD_PKT_LEN); + +	/* Write at status offset for qword access */ +	writeq(control, tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET); + +	/* Wait for Run Busy clear */ +	ret = readq_poll_timeout(tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET, +				 control, !(control & TPMI_CONTROL_STATUS_RB), +				 TPMI_RB_TIMEOUT_US, TPMI_RB_TIMEOUT_MAX_US); +	if (ret) +		goto done_proc; + +	control = FIELD_GET(TPMI_CONTROL_STATUS_RESULT, control); +	if (control != TPMI_CMD_STATUS_SUCCESS) { +		ret = -EBUSY; +		goto done_proc; +	} + +	/* Response is ready */ +	memcpy_fromio(feature_state, tpmi_info->tpmi_control_mem + TMPI_CONTROL_DATA_VAL_OFFSET, +		      sizeof(*feature_state)); + +	ret = 0; + +done_proc: +	/* Set CPL "completion" bit */ +	writeq(TPMI_CONTROL_STATUS_CPL, tpmi_info->tpmi_control_mem + TPMI_CONTROL_STATUS_OFFSET); + +err_unlock: +	mutex_unlock(&tpmi_dev_lock); + +	return ret; +} + +int tpmi_get_feature_status(struct auxiliary_device *auxdev, int feature_id, +			    int *locked, int *disabled) +{ +	struct intel_vsec_device *intel_vsec_dev = dev_to_ivdev(auxdev->dev.parent); +	struct intel_tpmi_info *tpmi_info = auxiliary_get_drvdata(&intel_vsec_dev->auxdev); +	struct tpmi_feature_state feature_state; +	int ret; + +	ret = tpmi_read_feature_status(tpmi_info, feature_id, &feature_state); +	if (ret) +		return ret; + +	*locked = feature_state.locked; +	*disabled = !feature_state.enabled; + +	return 0; +} +EXPORT_SYMBOL_NS_GPL(tpmi_get_feature_status, INTEL_TPMI); + +static int tpmi_pfs_dbg_show(struct seq_file *s, void *unused) +{ +	struct intel_tpmi_info *tpmi_info = s->private; +	int locked, disabled, read_blocked, write_blocked; +	struct tpmi_feature_state feature_state; +	struct intel_tpmi_pm_feature *pfs; +	int ret, i; + + +	seq_printf(s, "tpmi PFS start offset 0x:%llx\n", tpmi_info->pfs_start); +	seq_puts(s, "tpmi_id\t\tentries\t\tsize\t\tcap_offset\tattribute\tvsec_offset\tlocked\tdisabled\tread_blocked\twrite_blocked\n"); +	for (i = 0; i < tpmi_info->feature_count; ++i) { +		pfs = &tpmi_info->tpmi_features[i]; +		ret = tpmi_read_feature_status(tpmi_info, pfs->pfs_header.tpmi_id, &feature_state); +		if (ret) { +			locked = 'U'; +			disabled = 'U'; +			read_blocked = 'U'; +			write_blocked = 'U'; +		} else { +			disabled = feature_state.enabled ? 'N' : 'Y'; +			locked = feature_state.locked ? 'Y' : 'N'; +			read_blocked = feature_state.read_blocked ? 'Y' : 'N'; +			write_blocked = feature_state.write_blocked ? 'Y' : 'N'; +		} +		seq_printf(s, "0x%02x\t\t0x%02x\t\t0x%04x\t\t0x%04x\t\t0x%02x\t\t0x%08x\t%c\t%c\t\t%c\t\t%c\n", +			   pfs->pfs_header.tpmi_id, pfs->pfs_header.num_entries, +			   pfs->pfs_header.entry_size, pfs->pfs_header.cap_offset, +			   pfs->pfs_header.attribute, pfs->vsec_offset, locked, disabled, +			   read_blocked, write_blocked); +	} + +	return 0; +} +DEFINE_SHOW_ATTRIBUTE(tpmi_pfs_dbg); + +#define MEM_DUMP_COLUMN_COUNT	8 + +static int tpmi_mem_dump_show(struct seq_file *s, void *unused) +{ +	size_t row_size = MEM_DUMP_COLUMN_COUNT * sizeof(u32); +	struct intel_tpmi_pm_feature *pfs = s->private; +	int count, ret = 0; +	void __iomem *mem; +	u32 off, size; +	u8 *buffer; + +	size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs); +	if (!size) +		return -EIO; + +	buffer = kmalloc(size, GFP_KERNEL); +	if (!buffer) +		return -ENOMEM; + +	off = pfs->vsec_offset; + +	mutex_lock(&tpmi_dev_lock); + +	for (count = 0; count < pfs->pfs_header.num_entries; ++count) { +		seq_printf(s, "TPMI Instance:%d offset:0x%x\n", count, off); + +		mem = ioremap(off, size); +		if (!mem) { +			ret = -ENOMEM; +			break; +		} + +		memcpy_fromio(buffer, mem, size); + +		seq_hex_dump(s, " ", DUMP_PREFIX_OFFSET, row_size, sizeof(u32), buffer, size, +			     false); + +		iounmap(mem); + +		off += size; +	} + +	mutex_unlock(&tpmi_dev_lock); + +	kfree(buffer); + +	return ret; +} +DEFINE_SHOW_ATTRIBUTE(tpmi_mem_dump); + +static ssize_t mem_write(struct file *file, const char __user *userbuf, size_t len, loff_t *ppos) +{ +	struct seq_file *m = file->private_data; +	struct intel_tpmi_pm_feature *pfs = m->private; +	u32 addr, value, punit, size; +	u32 num_elems, *array; +	void __iomem *mem; +	int ret; + +	size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs); +	if (!size) +		return -EIO; + +	ret = parse_int_array_user(userbuf, len, (int **)&array); +	if (ret < 0) +		return ret; + +	num_elems = *array; +	if (num_elems != 3) { +		ret = -EINVAL; +		goto exit_write; +	} + +	punit = array[1]; +	addr = array[2]; +	value = array[3]; + +	if (punit >= pfs->pfs_header.num_entries) { +		ret = -EINVAL; +		goto exit_write; +	} + +	if (addr >= size) { +		ret = -EINVAL; +		goto exit_write; +	} + +	mutex_lock(&tpmi_dev_lock); + +	mem = ioremap(pfs->vsec_offset + punit * size, size); +	if (!mem) { +		ret = -ENOMEM; +		goto unlock_mem_write; +	} + +	writel(value, mem + addr); + +	iounmap(mem); + +	ret = len; + +unlock_mem_write: +	mutex_unlock(&tpmi_dev_lock); + +exit_write: +	kfree(array); + +	return ret; +} + +static int mem_write_show(struct seq_file *s, void *unused) +{ +	return 0; +} + +static int mem_write_open(struct inode *inode, struct file *file) +{ +	return single_open(file, mem_write_show, inode->i_private); +} + +static const struct file_operations mem_write_ops = { +	.open           = mem_write_open, +	.read           = seq_read, +	.write          = mem_write, +	.llseek         = seq_lseek, +	.release        = single_release, +}; + +#define tpmi_to_dev(info)	(&info->vsec_dev->pcidev->dev) + +static void tpmi_dbgfs_register(struct intel_tpmi_info *tpmi_info) +{ +	char name[64]; +	int i; + +	snprintf(name, sizeof(name), "tpmi-%s", dev_name(tpmi_to_dev(tpmi_info))); +	tpmi_info->dbgfs_dir = debugfs_create_dir(name, NULL); + +	debugfs_create_file("pfs_dump", 0444, tpmi_info->dbgfs_dir, tpmi_info, &tpmi_pfs_dbg_fops); + +	for (i = 0; i < tpmi_info->feature_count; ++i) { +		struct intel_tpmi_pm_feature *pfs; +		struct dentry *dir; + +		pfs = &tpmi_info->tpmi_features[i]; +		snprintf(name, sizeof(name), "tpmi-id-%02x", pfs->pfs_header.tpmi_id); +		dir = debugfs_create_dir(name, tpmi_info->dbgfs_dir); + +		debugfs_create_file("mem_dump", 0444, dir, pfs, &tpmi_mem_dump_fops); +		debugfs_create_file("mem_write", 0644, dir, pfs, &mem_write_ops); +	} +} + +static void tpmi_set_control_base(struct auxiliary_device *auxdev, +				  struct intel_tpmi_info *tpmi_info, +				  struct intel_tpmi_pm_feature *pfs) +{ +	void __iomem *mem; +	u32 size; + +	size = TPMI_GET_SINGLE_ENTRY_SIZE(pfs); +	if (!size) +		return; + +	mem = devm_ioremap(&auxdev->dev, pfs->vsec_offset, size); +	if (!mem) +		return; + +	/* mem is pointing to TPMI CONTROL base */ +	tpmi_info->tpmi_control_mem = mem; +} +  static const char *intel_tpmi_name(enum intel_tpmi_id id)  {  	switch (id) { @@ -316,7 +713,7 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)  	struct pci_dev *pci_dev = vsec_dev->pcidev;  	struct intel_tpmi_info *tpmi_info;  	u64 pfs_start = 0; -	int i; +	int ret, i;  	tpmi_info = devm_kzalloc(&auxdev->dev, sizeof(*tpmi_info), GFP_KERNEL);  	if (!tpmi_info) @@ -339,6 +736,7 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)  		int size, ret;  		pfs = &tpmi_info->tpmi_features[i]; +		pfs->vsec_dev = vsec_dev;  		res = &vsec_dev->resource[i];  		if (!res) @@ -367,13 +765,29 @@ static int intel_vsec_tpmi_init(struct auxiliary_device *auxdev)  		 */  		if (pfs->pfs_header.tpmi_id == TPMI_INFO_ID)  			tpmi_process_info(tpmi_info, pfs); + +		if (pfs->pfs_header.tpmi_id == TPMI_CONTROL_ID) +			tpmi_set_control_base(auxdev, tpmi_info, pfs);  	}  	tpmi_info->pfs_start = pfs_start;  	auxiliary_set_drvdata(auxdev, tpmi_info); -	return tpmi_create_devices(tpmi_info); +	ret = tpmi_create_devices(tpmi_info); +	if (ret) +		return ret; + +	/* +	 * Allow debugfs when security policy allows. Everything this debugfs +	 * interface provides, can also be done via /dev/mem access. If +	 * /dev/mem interface is locked, don't allow debugfs to present any +	 * information. Also check for CAP_SYS_RAWIO as /dev/mem interface. +	 */ +	if (!security_locked_down(LOCKDOWN_DEV_MEM) && capable(CAP_SYS_RAWIO)) +		tpmi_dbgfs_register(tpmi_info); + +	return 0;  }  static int tpmi_probe(struct auxiliary_device *auxdev, @@ -382,11 +796,12 @@ static int tpmi_probe(struct auxiliary_device *auxdev,  	return intel_vsec_tpmi_init(auxdev);  } -/* - * Remove callback is not needed currently as there is no - * cleanup required. All memory allocs are device managed. All - * devices created by this modules are also device managed. - */ +static void tpmi_remove(struct auxiliary_device *auxdev) +{ +	struct intel_tpmi_info *tpmi_info = auxiliary_get_drvdata(auxdev); + +	debugfs_remove_recursive(tpmi_info->dbgfs_dir); +}  static const struct auxiliary_device_id tpmi_id_table[] = {  	{ .name = "intel_vsec.tpmi" }, @@ -397,6 +812,7 @@ MODULE_DEVICE_TABLE(auxiliary, tpmi_id_table);  static struct auxiliary_driver tpmi_aux_driver = {  	.id_table	= tpmi_id_table,  	.probe		= tpmi_probe, +	.remove         = tpmi_remove,  };  module_auxiliary_driver(tpmi_aux_driver); |