diff --git a/Documentation/devicetree/bindings/chromeos-firmware.txt b/Documentation/devicetree/bindings/chromeos-firmware.txt index 7288f1d5ba9c..b0c51cb81d39 100644 --- a/Documentation/devicetree/bindings/chromeos-firmware.txt +++ b/Documentation/devicetree/bindings/chromeos-firmware.txt @@ -17,6 +17,8 @@ Optional properties: look for the block device used for storing vboot context in this property. It is a 4-tuple of block device phandle, LBA of the block storing vboot context, byte offset in the block, and size of vboot context in bytes. + - chromeos-vbc-ec : If nonvolatile-context-storage is "mkbp", kernel should + look for the phandle to the device tree node of EC in this property. Example: @@ -25,5 +27,6 @@ firmware { write-protect-gpio = <&gpd1 6 0 0x10000 0>; nonvolatile-context-storage = "disk"; chromeos-vbc-blk = <&mshc_0 0 0 16>; + chromeos-vbc-ec = <&ec>; }; }; diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig index dc1ff77d7441..5c03bab139ad 100644 --- a/drivers/platform/Kconfig +++ b/drivers/platform/Kconfig @@ -30,3 +30,12 @@ config CHROMEOS_VBC_BLK Provides access to vboot context stored on block device. Select this if you have a ChromeOS firmware that does not support storing vboot context in EC's nvram. + +config CHROMEOS_VBC_EC + bool + depends on CHROMEOS + + ---help--- + Provides access to vboot context stored on EC's nvram. Select this + if you have a ChromeOS firmware that supports storing vboot context + in EC's nvram. diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile index 720060b78a2f..d7d484e7c400 100644 --- a/drivers/platform/Makefile +++ b/drivers/platform/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_GOLDFISH) += goldfish/ obj-$(CONFIG_CHROME_PLATFORMS) += chrome/ obj-$(CONFIG_CHROMEOS) += chromeos.o obj-$(CONFIG_CHROMEOS_VBC_BLK) += chromeos_vbc_blk.o +obj-$(CONFIG_CHROMEOS_VBC_EC) += chromeos_vbc_ec.o diff --git a/drivers/platform/arm/Kconfig b/drivers/platform/arm/Kconfig index 4f7f2332a0a5..8e9b393ac975 100644 --- a/drivers/platform/arm/Kconfig +++ b/drivers/platform/arm/Kconfig @@ -17,6 +17,7 @@ config ARM_CHROMEOS_FIRMWARE bool "ChromeOS firmware interface driver" select CHROMEOS select CHROMEOS_VBC_BLK + select CHROMEOS_VBC_EC ---help--- This driver provides an interface to ChromeOS firmware. diff --git a/drivers/platform/chromeos_vbc_ec.c b/drivers/platform/chromeos_vbc_ec.c new file mode 100644 index 000000000000..116a18135123 --- /dev/null +++ b/drivers/platform/chromeos_vbc_ec.c @@ -0,0 +1,273 @@ +/* + * Copyright (C) 2012 The Chromium OS Authors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define pr_fmt(fmt) "chromeos_vbc_ec: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "chromeos.h" + +static phandle ec_phandle; + +static int match_of_node(struct device *dev, void *data) +{ + return dev->of_node == data; +} + +static struct device *get_ec_dev(phandle phandle) +{ + struct device_node *dn; + struct device *dev; + + if (!phandle) + return ERR_PTR(-ENODEV); + + dn = of_find_node_by_phandle(phandle); + if (!dn) + return ERR_PTR(-ENODEV); + + dev = bus_find_device(&platform_bus_type, NULL, dn, match_of_node); + of_node_put(dn); + if (!dev) + return ERR_PTR(-ENODEV); + + return dev; +} + +static int vbc_read(struct device *dev, void *buf, size_t count) +{ + struct cros_ec_device *ec; + struct ec_params_vbnvcontext param; + struct ec_response_vbnvcontext resp; + int err; + + ec = dev_get_drvdata(dev->parent); + + param.op = EC_VBNV_CONTEXT_OP_READ; + err = ec->command_sendrecv(ec, + EC_CMD_VBNV_CONTEXT | (EC_VER_VBNV_CONTEXT << 8), + ¶m, sizeof(param.op), + &resp, sizeof(resp)); + if (err < 0) + return err; + + count = min(count, sizeof(resp.block)); + memcpy(buf, resp.block, count); + + return count; +} + +static int vbc_write(struct device *dev, const void *buf, size_t count) +{ + struct cros_ec_device *ec; + struct ec_params_vbnvcontext param; + int err; + + ec = dev_get_drvdata(dev->parent); + + count = min(count, sizeof(param.block)); + + param.op = EC_VBNV_CONTEXT_OP_WRITE; + memcpy(param.block, buf, count); + err = ec->command_send(ec, + EC_CMD_VBNV_CONTEXT | (EC_VER_VBNV_CONTEXT << 8), + ¶m, sizeof(param)); + if (err < 0) + return err; + + return count; +} + +static ssize_t dev_attr_vbc_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ec_params_vbnvcontext param; + int i, j, count; + + count = vbc_read(dev, param.block, sizeof(param.block)); + if (count < 0) + return count; + + for (i = j = 0; i < count && j < PAGE_SIZE - 2; i++) { + buf[j++] = hex_asc_hi(param.block[i]); + buf[j++] = hex_asc_lo(param.block[i]); + } + buf[j++] = '\0'; + + return j; +} + +static ssize_t dev_attr_vbc_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct ec_params_vbnvcontext param; + int err; + + memset(param.block, 0x0, sizeof(param.block)); + err = hex2bin(param.block, buf, min(count / 2, sizeof(param.block))); + if (err < 0) + return err; + + err = vbc_write(dev, param.block, sizeof(param.block)); + if (err < 0) + return err; + + return count; +} + +static DEVICE_ATTR(vboot_context, S_IRUSR | S_IWUSR, + dev_attr_vbc_show, dev_attr_vbc_store); + +static struct attribute *vbc_attrs[] = { + &dev_attr_vboot_context.attr, + NULL +}; + +static const struct attribute_group vbc_attr_group = { + .attrs = vbc_attrs, +}; + +static int chromeos_vbc_ec_read(void *buf, size_t count) +{ + struct device *dev; + int err; + + dev = get_ec_dev(ec_phandle); + if (IS_ERR(dev)) + return PTR_ERR(dev); + + err = vbc_read(dev, buf, count); + put_device(dev); + + return err; +} + +static int chromeos_vbc_ec_write(const void *buf, size_t count) +{ + struct device *dev; + int err; + + dev = get_ec_dev(ec_phandle); + if (IS_ERR(dev)) + return PTR_ERR(dev); + + err = vbc_write(dev, buf, count); + put_device(dev); + + return err; +} + +static int chromeos_vbc_ec_probe(struct platform_device *pdev) +{ + pdev->dev.of_node = of_find_node_by_phandle(ec_phandle); + return 0; +} + +static struct chromeos_vbc chromeos_vbc_ec = { + .name = "cros-ec-vbc", + .read = chromeos_vbc_ec_read, + .write = chromeos_vbc_ec_write, +}; + +static struct platform_driver chromeos_vbc_ec_driver = { + .probe = chromeos_vbc_ec_probe, + .driver = { + .name = "cros-ec-vbc", + }, +}; + +static int __init chromeos_vbc_ec_init(void) +{ + struct device_node *of_node; + struct device *dev; + const char *vbc_type; + int err; + + of_node = of_find_compatible_node(NULL, NULL, "chromeos-firmware"); + if (!of_node) + return -ENODEV; + + err = of_property_read_string(of_node, "nonvolatile-context-storage", + &vbc_type); + if (err) + goto exit; + + if (strcmp(vbc_type, "mkbp")) { + err = 0; /* not configured to use vbc_ec, exit normally. */ + goto exit; + } + + err = of_property_read_u32(of_node, "chromeos-vbc-ec", &ec_phandle); + if (err) { + pr_err("Could not find chromeos-vbc-ec: err=%d\n", err); + goto exit; + } + + err = chromeos_vbc_register(&chromeos_vbc_ec); + if (err < 0) + goto exit; + + err = platform_driver_register(&chromeos_vbc_ec_driver); + if (err < 0) + goto exit; + + /* Create a sysfs entry for manual debugging/testing. */ + dev = get_ec_dev(ec_phandle); + if (IS_ERR(dev)) { + err = PTR_ERR(dev); + pr_err("Could not find ec device: err=%d\n", err); + goto exit; + } + err = sysfs_create_group(&dev->kobj, &vbc_attr_group); + put_device(dev); + if (err) { + pr_err("Could not create sysfs entries: err=%d\n", err); + goto exit; + } + + err = 0; +exit: + of_node_put(of_node); + return err; +} +module_init(chromeos_vbc_ec_init); + +static void __exit chromeos_vbc_ec_exit(void) +{ + struct device *dev = get_ec_dev(ec_phandle); + + if (IS_ERR(dev)) { + pr_warn("Could not find ec device: err=%ld\n", PTR_ERR(dev)); + } else { + sysfs_remove_group(&dev->kobj, &vbc_attr_group); + put_device(dev); + } + + platform_driver_unregister(&chromeos_vbc_ec_driver); +} +module_exit(chromeos_vbc_ec_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("ChromeOS vboot context on EC accessor");