forked from mirrors/linux
		
	 ae21f41e1f
			
		
	
	
		ae21f41e1f
		
	
	
	
	
		
			
			memremap() doesn't return an iomem pointer, so we can just use memcpy() and drop the __iomem annotation here. This silences a sparse warning. Cc: Guenter Roeck <linux@roeck-us.net> Reviewed-by: Guenter Roeck <linux@roeck-us.net> Cc: Dmitry Torokhov <dmitry.torokhov@gmail.com> Cc: Anton Vasilyev <vasilyev@ispras.ru> Signed-off-by: Stephen Boyd <swboyd@chromium.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
		
			
				
	
	
		
			342 lines
		
	
	
	
		
			7.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			342 lines
		
	
	
	
		
			7.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * vpd.c
 | |
|  *
 | |
|  * Driver for exporting VPD content to sysfs.
 | |
|  *
 | |
|  * Copyright 2017 Google Inc.
 | |
|  *
 | |
|  * This program is free software; you can redistribute it and/or modify
 | |
|  * it under the terms of the GNU General Public License v2.0 as published by
 | |
|  * the Free Software Foundation.
 | |
|  *
 | |
|  * This program is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|  * GNU General Public License for more details.
 | |
|  */
 | |
| 
 | |
| #include <linux/ctype.h>
 | |
| #include <linux/init.h>
 | |
| #include <linux/io.h>
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/kobject.h>
 | |
| #include <linux/list.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/of_address.h>
 | |
| #include <linux/platform_device.h>
 | |
| #include <linux/slab.h>
 | |
| #include <linux/sysfs.h>
 | |
| 
 | |
| #include "coreboot_table.h"
 | |
| #include "vpd_decode.h"
 | |
| 
 | |
| #define CB_TAG_VPD      0x2c
 | |
| #define VPD_CBMEM_MAGIC 0x43524f53
 | |
| 
 | |
| static struct kobject *vpd_kobj;
 | |
| 
 | |
| struct vpd_cbmem {
 | |
| 	u32 magic;
 | |
| 	u32 version;
 | |
| 	u32 ro_size;
 | |
| 	u32 rw_size;
 | |
| 	u8  blob[0];
 | |
| };
 | |
| 
 | |
| struct vpd_section {
 | |
| 	bool enabled;
 | |
| 	const char *name;
 | |
| 	char *raw_name;                /* the string name_raw */
 | |
| 	struct kobject *kobj;          /* vpd/name directory */
 | |
| 	char *baseaddr;
 | |
| 	struct bin_attribute bin_attr; /* vpd/name_raw bin_attribute */
 | |
| 	struct list_head attribs;      /* key/value in vpd_attrib_info list */
 | |
| };
 | |
| 
 | |
| struct vpd_attrib_info {
 | |
| 	char *key;
 | |
| 	const char *value;
 | |
| 	struct bin_attribute bin_attr;
 | |
| 	struct list_head list;
 | |
| };
 | |
| 
 | |
| static struct vpd_section ro_vpd;
 | |
| static struct vpd_section rw_vpd;
 | |
| 
 | |
| static ssize_t vpd_attrib_read(struct file *filp, struct kobject *kobp,
 | |
| 			       struct bin_attribute *bin_attr, char *buf,
 | |
| 			       loff_t pos, size_t count)
 | |
| {
 | |
| 	struct vpd_attrib_info *info = bin_attr->private;
 | |
| 
 | |
| 	return memory_read_from_buffer(buf, count, &pos, info->value,
 | |
| 				       info->bin_attr.size);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * vpd_section_check_key_name()
 | |
|  *
 | |
|  * The VPD specification supports only [a-zA-Z0-9_]+ characters in key names but
 | |
|  * old firmware versions may have entries like "S/N" which are problematic when
 | |
|  * exporting them as sysfs attributes. These keys present in old firmwares are
 | |
|  * ignored.
 | |
|  *
 | |
|  * Returns VPD_OK for a valid key name, VPD_FAIL otherwise.
 | |
|  *
 | |
|  * @key: The key name to check
 | |
|  * @key_len: key name length
 | |
|  */
 | |
| static int vpd_section_check_key_name(const u8 *key, s32 key_len)
 | |
| {
 | |
| 	int c;
 | |
| 
 | |
| 	while (key_len-- > 0) {
 | |
| 		c = *key++;
 | |
| 
 | |
| 		if (!isalnum(c) && c != '_')
 | |
| 			return VPD_FAIL;
 | |
| 	}
 | |
| 
 | |
| 	return VPD_OK;
 | |
| }
 | |
| 
 | |
| static int vpd_section_attrib_add(const u8 *key, s32 key_len,
 | |
| 				  const u8 *value, s32 value_len,
 | |
| 				  void *arg)
 | |
| {
 | |
| 	int ret;
 | |
| 	struct vpd_section *sec = arg;
 | |
| 	struct vpd_attrib_info *info;
 | |
| 
 | |
| 	/*
 | |
| 	 * Return VPD_OK immediately to decode next entry if the current key
 | |
| 	 * name contains invalid characters.
 | |
| 	 */
 | |
| 	if (vpd_section_check_key_name(key, key_len) != VPD_OK)
 | |
| 		return VPD_OK;
 | |
| 
 | |
| 	info = kzalloc(sizeof(*info), GFP_KERNEL);
 | |
| 	if (!info)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	info->key = kstrndup(key, key_len, GFP_KERNEL);
 | |
| 	if (!info->key) {
 | |
| 		ret = -ENOMEM;
 | |
| 		goto free_info;
 | |
| 	}
 | |
| 
 | |
| 	sysfs_bin_attr_init(&info->bin_attr);
 | |
| 	info->bin_attr.attr.name = info->key;
 | |
| 	info->bin_attr.attr.mode = 0444;
 | |
| 	info->bin_attr.size = value_len;
 | |
| 	info->bin_attr.read = vpd_attrib_read;
 | |
| 	info->bin_attr.private = info;
 | |
| 
 | |
| 	info->value = value;
 | |
| 
 | |
| 	INIT_LIST_HEAD(&info->list);
 | |
| 
 | |
| 	ret = sysfs_create_bin_file(sec->kobj, &info->bin_attr);
 | |
| 	if (ret)
 | |
| 		goto free_info_key;
 | |
| 
 | |
| 	list_add_tail(&info->list, &sec->attribs);
 | |
| 	return 0;
 | |
| 
 | |
| free_info_key:
 | |
| 	kfree(info->key);
 | |
| free_info:
 | |
| 	kfree(info);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static void vpd_section_attrib_destroy(struct vpd_section *sec)
 | |
| {
 | |
| 	struct vpd_attrib_info *info;
 | |
| 	struct vpd_attrib_info *temp;
 | |
| 
 | |
| 	list_for_each_entry_safe(info, temp, &sec->attribs, list) {
 | |
| 		sysfs_remove_bin_file(sec->kobj, &info->bin_attr);
 | |
| 		kfree(info->key);
 | |
| 		kfree(info);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| static ssize_t vpd_section_read(struct file *filp, struct kobject *kobp,
 | |
| 				struct bin_attribute *bin_attr, char *buf,
 | |
| 				loff_t pos, size_t count)
 | |
| {
 | |
| 	struct vpd_section *sec = bin_attr->private;
 | |
| 
 | |
| 	return memory_read_from_buffer(buf, count, &pos, sec->baseaddr,
 | |
| 				       sec->bin_attr.size);
 | |
| }
 | |
| 
 | |
| static int vpd_section_create_attribs(struct vpd_section *sec)
 | |
| {
 | |
| 	s32 consumed;
 | |
| 	int ret;
 | |
| 
 | |
| 	consumed = 0;
 | |
| 	do {
 | |
| 		ret = vpd_decode_string(sec->bin_attr.size, sec->baseaddr,
 | |
| 					&consumed, vpd_section_attrib_add, sec);
 | |
| 	} while (ret == VPD_OK);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int vpd_section_init(const char *name, struct vpd_section *sec,
 | |
| 			    phys_addr_t physaddr, size_t size)
 | |
| {
 | |
| 	int err;
 | |
| 
 | |
| 	sec->baseaddr = memremap(physaddr, size, MEMREMAP_WB);
 | |
| 	if (!sec->baseaddr)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	sec->name = name;
 | |
| 
 | |
| 	/* We want to export the raw partition with name ${name}_raw */
 | |
| 	sec->raw_name = kasprintf(GFP_KERNEL, "%s_raw", name);
 | |
| 	if (!sec->raw_name) {
 | |
| 		err = -ENOMEM;
 | |
| 		goto err_memunmap;
 | |
| 	}
 | |
| 
 | |
| 	sysfs_bin_attr_init(&sec->bin_attr);
 | |
| 	sec->bin_attr.attr.name = sec->raw_name;
 | |
| 	sec->bin_attr.attr.mode = 0444;
 | |
| 	sec->bin_attr.size = size;
 | |
| 	sec->bin_attr.read = vpd_section_read;
 | |
| 	sec->bin_attr.private = sec;
 | |
| 
 | |
| 	err = sysfs_create_bin_file(vpd_kobj, &sec->bin_attr);
 | |
| 	if (err)
 | |
| 		goto err_free_raw_name;
 | |
| 
 | |
| 	sec->kobj = kobject_create_and_add(name, vpd_kobj);
 | |
| 	if (!sec->kobj) {
 | |
| 		err = -EINVAL;
 | |
| 		goto err_sysfs_remove;
 | |
| 	}
 | |
| 
 | |
| 	INIT_LIST_HEAD(&sec->attribs);
 | |
| 	vpd_section_create_attribs(sec);
 | |
| 
 | |
| 	sec->enabled = true;
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| err_sysfs_remove:
 | |
| 	sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
 | |
| err_free_raw_name:
 | |
| 	kfree(sec->raw_name);
 | |
| err_memunmap:
 | |
| 	memunmap(sec->baseaddr);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static int vpd_section_destroy(struct vpd_section *sec)
 | |
| {
 | |
| 	if (sec->enabled) {
 | |
| 		vpd_section_attrib_destroy(sec);
 | |
| 		kobject_put(sec->kobj);
 | |
| 		sysfs_remove_bin_file(vpd_kobj, &sec->bin_attr);
 | |
| 		kfree(sec->raw_name);
 | |
| 		memunmap(sec->baseaddr);
 | |
| 		sec->enabled = false;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int vpd_sections_init(phys_addr_t physaddr)
 | |
| {
 | |
| 	struct vpd_cbmem *temp;
 | |
| 	struct vpd_cbmem header;
 | |
| 	int ret = 0;
 | |
| 
 | |
| 	temp = memremap(physaddr, sizeof(struct vpd_cbmem), MEMREMAP_WB);
 | |
| 	if (!temp)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	memcpy(&header, temp, sizeof(struct vpd_cbmem));
 | |
| 	memunmap(temp);
 | |
| 
 | |
| 	if (header.magic != VPD_CBMEM_MAGIC)
 | |
| 		return -ENODEV;
 | |
| 
 | |
| 	if (header.ro_size) {
 | |
| 		ret = vpd_section_init("ro", &ro_vpd,
 | |
| 				       physaddr + sizeof(struct vpd_cbmem),
 | |
| 				       header.ro_size);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 	}
 | |
| 
 | |
| 	if (header.rw_size) {
 | |
| 		ret = vpd_section_init("rw", &rw_vpd,
 | |
| 				       physaddr + sizeof(struct vpd_cbmem) +
 | |
| 				       header.ro_size, header.rw_size);
 | |
| 		if (ret) {
 | |
| 			vpd_section_destroy(&ro_vpd);
 | |
| 			return ret;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int vpd_probe(struct coreboot_device *dev)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	vpd_kobj = kobject_create_and_add("vpd", firmware_kobj);
 | |
| 	if (!vpd_kobj)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	ret = vpd_sections_init(dev->cbmem_ref.cbmem_addr);
 | |
| 	if (ret) {
 | |
| 		kobject_put(vpd_kobj);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int vpd_remove(struct coreboot_device *dev)
 | |
| {
 | |
| 	vpd_section_destroy(&ro_vpd);
 | |
| 	vpd_section_destroy(&rw_vpd);
 | |
| 
 | |
| 	kobject_put(vpd_kobj);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static struct coreboot_driver vpd_driver = {
 | |
| 	.probe = vpd_probe,
 | |
| 	.remove = vpd_remove,
 | |
| 	.drv = {
 | |
| 		.name = "vpd",
 | |
| 	},
 | |
| 	.tag = CB_TAG_VPD,
 | |
| };
 | |
| 
 | |
| static int __init coreboot_vpd_init(void)
 | |
| {
 | |
| 	return coreboot_driver_register(&vpd_driver);
 | |
| }
 | |
| 
 | |
| static void __exit coreboot_vpd_exit(void)
 | |
| {
 | |
| 	coreboot_driver_unregister(&vpd_driver);
 | |
| }
 | |
| 
 | |
| module_init(coreboot_vpd_init);
 | |
| module_exit(coreboot_vpd_exit);
 | |
| 
 | |
| MODULE_AUTHOR("Google, Inc.");
 | |
| MODULE_LICENSE("GPL");
 |