diff options
Diffstat (limited to 'drivers/platform/x86/amd/hsmp.c')
| -rw-r--r-- | drivers/platform/x86/amd/hsmp.c | 241 | 
1 files changed, 213 insertions, 28 deletions
| diff --git a/drivers/platform/x86/amd/hsmp.c b/drivers/platform/x86/amd/hsmp.c index 31382ef52efb..b55d80e29139 100644 --- a/drivers/platform/x86/amd/hsmp.c +++ b/drivers/platform/x86/amd/hsmp.c @@ -20,7 +20,7 @@  #include <linux/semaphore.h>  #define DRIVER_NAME		"amd_hsmp" -#define DRIVER_VERSION		"1.0" +#define DRIVER_VERSION		"2.0"  /* HSMP Status / Error codes */  #define HSMP_STATUS_NOT_READY	0x00 @@ -47,9 +47,29 @@  #define HSMP_INDEX_REG		0xc4  #define HSMP_DATA_REG		0xc8 -static struct semaphore *hsmp_sem; +#define HSMP_CDEV_NAME		"hsmp_cdev" +#define HSMP_DEVNODE_NAME	"hsmp" +#define HSMP_METRICS_TABLE_NAME	"metrics_bin" -static struct miscdevice hsmp_device; +#define HSMP_ATTR_GRP_NAME_SIZE	10 + +struct hsmp_socket { +	struct bin_attribute hsmp_attr; +	void __iomem *metric_tbl_addr; +	struct semaphore hsmp_sem; +	char name[HSMP_ATTR_GRP_NAME_SIZE]; +	u16 sock_ind; +}; + +struct hsmp_plat_device { +	struct miscdevice hsmp_device; +	struct hsmp_socket *sock; +	struct device *dev; +	u32 proto_ver; +	u16 num_sockets; +}; + +static struct hsmp_plat_device plat_dev;  static int amd_hsmp_rdwr(struct pci_dev *root, u32 address,  			 u32 *value, bool write) @@ -188,6 +208,7 @@ static int validate_message(struct hsmp_message *msg)  int hsmp_send_message(struct hsmp_message *msg)  { +	struct hsmp_socket *sock = &plat_dev.sock[msg->sock_ind];  	struct amd_northbridge *nb;  	int ret; @@ -208,14 +229,13 @@ int hsmp_send_message(struct hsmp_message *msg)  	 * In SMP system timeout of 100 millisecs should  	 * be enough for the previous thread to finish the operation  	 */ -	ret = down_timeout(&hsmp_sem[msg->sock_ind], -			   msecs_to_jiffies(HSMP_MSG_TIMEOUT)); +	ret = down_timeout(&sock->hsmp_sem, msecs_to_jiffies(HSMP_MSG_TIMEOUT));  	if (ret < 0)  		return ret;  	ret = __hsmp_send_message(nb->root, msg); -	up(&hsmp_sem[msg->sock_ind]); +	up(&sock->hsmp_sem);  	return ret;  } @@ -317,32 +337,198 @@ static const struct file_operations hsmp_fops = {  	.compat_ioctl	= hsmp_ioctl,  }; +static ssize_t hsmp_metric_tbl_read(struct file *filp, struct kobject *kobj, +				    struct bin_attribute *bin_attr, char *buf, +				    loff_t off, size_t count) +{ +	struct hsmp_socket *sock = bin_attr->private; +	struct hsmp_message msg = { 0 }; +	int ret; + +	/* Do not support lseek(), reads entire metric table */ +	if (count < bin_attr->size) { +		dev_err(plat_dev.dev, "Wrong buffer size\n"); +		return -EINVAL; +	} + +	if (!sock) { +		dev_err(plat_dev.dev, "Failed to read attribute private data\n"); +		return -EINVAL; +	} + +	msg.msg_id	= HSMP_GET_METRIC_TABLE; +	msg.sock_ind	= sock->sock_ind; + +	ret = hsmp_send_message(&msg); +	if (ret) +		return ret; +	memcpy_fromio(buf, sock->metric_tbl_addr, bin_attr->size); + +	return bin_attr->size; +} + +static int hsmp_get_tbl_dram_base(u16 sock_ind) +{ +	struct hsmp_socket *sock = &plat_dev.sock[sock_ind]; +	struct hsmp_message msg = { 0 }; +	phys_addr_t dram_addr; +	int ret; + +	msg.sock_ind	= sock_ind; +	msg.response_sz	= hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz; +	msg.msg_id	= HSMP_GET_METRIC_TABLE_DRAM_ADDR; + +	ret = hsmp_send_message(&msg); +	if (ret) +		return ret; + +	/* +	 * calculate the metric table DRAM address from lower and upper 32 bits +	 * sent from SMU and ioremap it to virtual address. +	 */ +	dram_addr = msg.args[0] | ((u64)(msg.args[1]) << 32); +	if (!dram_addr) { +		dev_err(plat_dev.dev, "Invalid DRAM address for metric table\n"); +		return -ENOMEM; +	} +	sock->metric_tbl_addr = devm_ioremap(plat_dev.dev, dram_addr, +					     sizeof(struct hsmp_metric_table)); +	if (!sock->metric_tbl_addr) { +		dev_err(plat_dev.dev, "Failed to ioremap metric table addr\n"); +		return -ENOMEM; +	} +	return 0; +} + +static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj, +					 struct bin_attribute *battr, int id) +{ +	if (plat_dev.proto_ver == HSMP_PROTO_VER6) +		return battr->attr.mode; +	else +		return 0; +} + +static int hsmp_init_metric_tbl_bin_attr(struct bin_attribute **hattrs, u16 sock_ind) +{ +	struct bin_attribute *hattr = &plat_dev.sock[sock_ind].hsmp_attr; + +	sysfs_bin_attr_init(hattr); +	hattr->attr.name	= HSMP_METRICS_TABLE_NAME; +	hattr->attr.mode	= 0444; +	hattr->read		= hsmp_metric_tbl_read; +	hattr->size		= sizeof(struct hsmp_metric_table); +	hattr->private		= &plat_dev.sock[sock_ind]; +	hattrs[0]		= hattr; + +	if (plat_dev.proto_ver == HSMP_PROTO_VER6) +		return (hsmp_get_tbl_dram_base(sock_ind)); +	else +		return 0; +} + +/* One bin sysfs for metrics table*/ +#define NUM_HSMP_ATTRS		1 + +static int hsmp_create_sysfs_interface(void) +{ +	const struct attribute_group **hsmp_attr_grps; +	struct bin_attribute **hsmp_bin_attrs; +	struct attribute_group *attr_grp; +	int ret; +	u16 i; + +	/* String formatting is currently limited to u8 sockets */ +	if (WARN_ON(plat_dev.num_sockets > U8_MAX)) +		return -ERANGE; + +	hsmp_attr_grps = devm_kzalloc(plat_dev.dev, sizeof(struct attribute_group *) * +				      (plat_dev.num_sockets + 1), GFP_KERNEL); +	if (!hsmp_attr_grps) +		return -ENOMEM; + +	/* Create a sysfs directory for each socket */ +	for (i = 0; i < plat_dev.num_sockets; i++) { +		attr_grp = devm_kzalloc(plat_dev.dev, sizeof(struct attribute_group), GFP_KERNEL); +		if (!attr_grp) +			return -ENOMEM; + +		snprintf(plat_dev.sock[i].name, HSMP_ATTR_GRP_NAME_SIZE, "socket%u", (u8)i); +		attr_grp->name = plat_dev.sock[i].name; + +		/* Null terminated list of attributes */ +		hsmp_bin_attrs = devm_kzalloc(plat_dev.dev, sizeof(struct bin_attribute *) * +					      (NUM_HSMP_ATTRS + 1), GFP_KERNEL); +		if (!hsmp_bin_attrs) +			return -ENOMEM; + +		attr_grp->bin_attrs		= hsmp_bin_attrs; +		attr_grp->is_bin_visible	= hsmp_is_sock_attr_visible; +		hsmp_attr_grps[i]		= attr_grp; + +		/* Now create the leaf nodes */ +		ret = hsmp_init_metric_tbl_bin_attr(hsmp_bin_attrs, i); +		if (ret) +			return ret; +	} +	return devm_device_add_groups(plat_dev.dev, hsmp_attr_grps); +} + +static int hsmp_cache_proto_ver(void) +{ +	struct hsmp_message msg = { 0 }; +	int ret; + +	msg.msg_id	= HSMP_GET_PROTO_VER; +	msg.sock_ind	= 0; +	msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz; + +	ret = hsmp_send_message(&msg); +	if (!ret) +		plat_dev.proto_ver = msg.args[0]; + +	return ret; +} +  static int hsmp_pltdrv_probe(struct platform_device *pdev)  { -	int i; +	int ret, i; -	hsmp_sem = devm_kzalloc(&pdev->dev, -				(amd_nb_num() * sizeof(struct semaphore)), -				GFP_KERNEL); -	if (!hsmp_sem) +	plat_dev.sock = devm_kzalloc(&pdev->dev, +				     (plat_dev.num_sockets * sizeof(struct hsmp_socket)), +				     GFP_KERNEL); +	if (!plat_dev.sock)  		return -ENOMEM; +	plat_dev.dev = &pdev->dev; + +	for (i = 0; i < plat_dev.num_sockets; i++) { +		sema_init(&plat_dev.sock[i].hsmp_sem, 1); +		plat_dev.sock[i].sock_ind = i; +	} -	for (i = 0; i < amd_nb_num(); i++) -		sema_init(&hsmp_sem[i], 1); +	plat_dev.hsmp_device.name	= HSMP_CDEV_NAME; +	plat_dev.hsmp_device.minor	= MISC_DYNAMIC_MINOR; +	plat_dev.hsmp_device.fops	= &hsmp_fops; +	plat_dev.hsmp_device.parent	= &pdev->dev; +	plat_dev.hsmp_device.nodename	= HSMP_DEVNODE_NAME; +	plat_dev.hsmp_device.mode	= 0644; + +	ret = hsmp_cache_proto_ver(); +	if (ret) { +		dev_err(plat_dev.dev, "Failed to read HSMP protocol version\n"); +		return ret; +	} -	hsmp_device.name	= "hsmp_cdev"; -	hsmp_device.minor	= MISC_DYNAMIC_MINOR; -	hsmp_device.fops	= &hsmp_fops; -	hsmp_device.parent	= &pdev->dev; -	hsmp_device.nodename	= "hsmp"; -	hsmp_device.mode	= 0644; +	ret = hsmp_create_sysfs_interface(); +	if (ret) +		dev_err(plat_dev.dev, "Failed to create HSMP sysfs interface\n"); -	return misc_register(&hsmp_device); +	return misc_register(&plat_dev.hsmp_device);  }  static void hsmp_pltdrv_remove(struct platform_device *pdev)  { -	misc_deregister(&hsmp_device); +	misc_deregister(&plat_dev.hsmp_device);  }  static struct platform_driver amd_hsmp_driver = { @@ -358,7 +544,6 @@ static struct platform_device *amd_hsmp_platdev;  static int __init hsmp_plt_init(void)  {  	int ret = -ENODEV; -	u16 num_sockets;  	int i;  	if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD || boot_cpu_data.x86 < 0x19) { @@ -371,18 +556,18 @@ static int __init hsmp_plt_init(void)  	 * amd_nb_num() returns number of SMN/DF interfaces present in the system  	 * if we have N SMN/DF interfaces that ideally means N sockets  	 */ -	num_sockets = amd_nb_num(); -	if (num_sockets == 0) +	plat_dev.num_sockets = amd_nb_num(); +	if (plat_dev.num_sockets == 0)  		return ret;  	/* Test the hsmp interface on each socket */ -	for (i = 0; i < num_sockets; i++) { +	for (i = 0; i < plat_dev.num_sockets; i++) {  		ret = hsmp_test(i, 0xDEADBEEF);  		if (ret) { -			pr_err("HSMP is not supported on Fam:%x model:%x\n", +			pr_err("HSMP test message failed on Fam:%x model:%x\n",  			       boot_cpu_data.x86, boot_cpu_data.x86_model); -			pr_err("Or Is HSMP disabled in BIOS ?\n"); -			return -EOPNOTSUPP; +			pr_err("Is HSMP disabled in BIOS ?\n"); +			return ret;  		}  	} |