285 lines
6.4 KiB
C
285 lines
6.4 KiB
C
/*
|
|
* 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 <linux/err.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/stat.h>
|
|
#include <linux/string.h>
|
|
#include <linux/mfd/cros_ec.h>
|
|
|
|
#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;
|
|
struct cros_ec_command msg = {
|
|
.version = EC_VER_VBNV_CONTEXT,
|
|
.command = EC_CMD_VBNV_CONTEXT,
|
|
.outdata = (uint8_t *)¶m,
|
|
.outsize = sizeof(param.op),
|
|
.indata = (uint8_t *)&resp,
|
|
.insize = sizeof(resp),
|
|
};
|
|
int err;
|
|
|
|
ec = dev_get_drvdata(dev->parent);
|
|
|
|
param.op = EC_VBNV_CONTEXT_OP_READ;
|
|
err = cros_ec_cmd_xfer(ec, &msg);
|
|
if (err < 0)
|
|
return err;
|
|
/* FIXME: This assumes msg.result == EC_RES_SUCCESS */
|
|
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;
|
|
struct cros_ec_command msg = {
|
|
.version = EC_VER_VBNV_CONTEXT,
|
|
.command = EC_CMD_VBNV_CONTEXT,
|
|
.outdata = (uint8_t *)¶m,
|
|
.outsize = sizeof(param),
|
|
.indata = NULL,
|
|
.insize = 0,
|
|
};
|
|
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 = cros_ec_cmd_xfer(ec, &msg);
|
|
if (err < 0)
|
|
return err;
|
|
/* FIXME: This assumes msg.result == EC_RES_SUCCESS */
|
|
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");
|