From 0c0b7ea23aed0b55ef2f9803f13ddaae1943713d Mon Sep 17 00:00:00 2001 From: Nick Crews Date: Wed, 24 Apr 2019 10:56:50 -0600 Subject: platform/chrome: wilco_ec: Add property helper library A Property is typically a data item that is stored to NVRAM by the EC. Each of these data items has an index associated with it, known as the Property ID (PID). Properties may have variable lengths, up to a max of WILCO_EC_PROPERTY_MAX_SIZE bytes. Properties can be simple integers, or they may be more complex binary data. This patch adds support for getting and setting properties. This will be useful for setting the charge algorithm and charge schedules, which all use properties. Signed-off-by: Nick Crews Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/wilco_ec/Makefile | 2 +- drivers/platform/chrome/wilco_ec/properties.c | 132 ++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 drivers/platform/chrome/wilco_ec/properties.c (limited to 'drivers/platform/chrome') diff --git a/drivers/platform/chrome/wilco_ec/Makefile b/drivers/platform/chrome/wilco_ec/Makefile index 063e7fb4ea17..29b734137786 100644 --- a/drivers/platform/chrome/wilco_ec/Makefile +++ b/drivers/platform/chrome/wilco_ec/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 -wilco_ec-objs := core.o mailbox.o +wilco_ec-objs := core.o mailbox.o properties.o obj-$(CONFIG_WILCO_EC) += wilco_ec.o wilco_ec_debugfs-objs := debugfs.o obj-$(CONFIG_WILCO_EC_DEBUGFS) += wilco_ec_debugfs.o diff --git a/drivers/platform/chrome/wilco_ec/properties.c b/drivers/platform/chrome/wilco_ec/properties.c new file mode 100644 index 000000000000..e69682c95ea2 --- /dev/null +++ b/drivers/platform/chrome/wilco_ec/properties.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 Google LLC + */ + +#include +#include +#include + +/* Operation code; what the EC should do with the property */ +enum ec_property_op { + EC_OP_GET = 0, + EC_OP_SET = 1, +}; + +struct ec_property_request { + u8 op; /* One of enum ec_property_op */ + u8 property_id[4]; /* The 32 bit PID is stored Little Endian */ + u8 length; + u8 data[WILCO_EC_PROPERTY_MAX_SIZE]; +} __packed; + +struct ec_property_response { + u8 reserved[2]; + u8 op; /* One of enum ec_property_op */ + u8 property_id[4]; /* The 32 bit PID is stored Little Endian */ + u8 length; + u8 data[WILCO_EC_PROPERTY_MAX_SIZE]; +} __packed; + +static int send_property_msg(struct wilco_ec_device *ec, + struct ec_property_request *rq, + struct ec_property_response *rs) +{ + struct wilco_ec_message ec_msg; + int ret; + + memset(&ec_msg, 0, sizeof(ec_msg)); + ec_msg.type = WILCO_EC_MSG_PROPERTY; + ec_msg.request_data = rq; + ec_msg.request_size = sizeof(*rq); + ec_msg.response_data = rs; + ec_msg.response_size = sizeof(*rs); + + ret = wilco_ec_mailbox(ec, &ec_msg); + if (ret < 0) + return ret; + if (rs->op != rq->op) + return -EBADMSG; + if (memcmp(rq->property_id, rs->property_id, sizeof(rs->property_id))) + return -EBADMSG; + + return 0; +} + +int wilco_ec_get_property(struct wilco_ec_device *ec, + struct wilco_ec_property_msg *prop_msg) +{ + struct ec_property_request rq; + struct ec_property_response rs; + int ret; + + memset(&rq, 0, sizeof(rq)); + rq.op = EC_OP_GET; + put_unaligned_le32(prop_msg->property_id, rq.property_id); + + ret = send_property_msg(ec, &rq, &rs); + if (ret < 0) + return ret; + + prop_msg->length = rs.length; + memcpy(prop_msg->data, rs.data, rs.length); + + return 0; +} +EXPORT_SYMBOL_GPL(wilco_ec_get_property); + +int wilco_ec_set_property(struct wilco_ec_device *ec, + struct wilco_ec_property_msg *prop_msg) +{ + struct ec_property_request rq; + struct ec_property_response rs; + int ret; + + memset(&rq, 0, sizeof(rq)); + rq.op = EC_OP_SET; + put_unaligned_le32(prop_msg->property_id, rq.property_id); + rq.length = prop_msg->length; + memcpy(rq.data, prop_msg->data, prop_msg->length); + + ret = send_property_msg(ec, &rq, &rs); + if (ret < 0) + return ret; + if (rs.length != prop_msg->length) + return -EBADMSG; + + return 0; +} +EXPORT_SYMBOL_GPL(wilco_ec_set_property); + +int wilco_ec_get_byte_property(struct wilco_ec_device *ec, u32 property_id, + u8 *val) +{ + struct wilco_ec_property_msg msg; + int ret; + + msg.property_id = property_id; + + ret = wilco_ec_get_property(ec, &msg); + if (ret < 0) + return ret; + if (msg.length != 1) + return -EBADMSG; + + *val = msg.data[0]; + + return 0; +} +EXPORT_SYMBOL_GPL(wilco_ec_get_byte_property); + +int wilco_ec_set_byte_property(struct wilco_ec_device *ec, u32 property_id, + u8 val) +{ + struct wilco_ec_property_msg msg; + + msg.property_id = property_id; + msg.data[0] = val; + msg.length = 1; + + return wilco_ec_set_property(ec, &msg); +} +EXPORT_SYMBOL_GPL(wilco_ec_set_byte_property); -- cgit v1.2.3-59-g8ed1b From 4c1ca625c622b7a9f04c2949fd1ffdc6effa86de Mon Sep 17 00:00:00 2001 From: Nick Crews Date: Tue, 16 Apr 2019 19:20:47 -0600 Subject: platform/chrome: wilco_ec: Add Boot on AC support Boot on AC is a policy which makes the device boot from S5 when AC power is connected. This is useful for users who want to run their device headless or with a dock. Signed-off-by: Nick Crews Signed-off-by: Enric Balletbo i Serra --- Documentation/ABI/testing/sysfs-platform-wilco-ec | 9 +++ drivers/platform/chrome/wilco_ec/Makefile | 2 +- drivers/platform/chrome/wilco_ec/core.c | 9 +++ drivers/platform/chrome/wilco_ec/sysfs.c | 77 +++++++++++++++++++++++ include/linux/platform_data/wilco-ec.h | 12 ++++ 5 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/sysfs-platform-wilco-ec create mode 100644 drivers/platform/chrome/wilco_ec/sysfs.c (limited to 'drivers/platform/chrome') diff --git a/Documentation/ABI/testing/sysfs-platform-wilco-ec b/Documentation/ABI/testing/sysfs-platform-wilco-ec new file mode 100644 index 000000000000..8e5d6eee44db --- /dev/null +++ b/Documentation/ABI/testing/sysfs-platform-wilco-ec @@ -0,0 +1,9 @@ +What: /sys/bus/platform/devices/GOOG000C\:00/boot_on_ac +Date: April 2019 +KernelVersion: 5.3 +Description: + Boot on AC is a policy which makes the device boot from S5 + when AC power is connected. This is useful for users who + want to run their device headless or with a dock. + + Input should be parseable by kstrtou8() to 0 or 1. diff --git a/drivers/platform/chrome/wilco_ec/Makefile b/drivers/platform/chrome/wilco_ec/Makefile index 29b734137786..72df9b5e1983 100644 --- a/drivers/platform/chrome/wilco_ec/Makefile +++ b/drivers/platform/chrome/wilco_ec/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 -wilco_ec-objs := core.o mailbox.o properties.o +wilco_ec-objs := core.o mailbox.o properties.o sysfs.o obj-$(CONFIG_WILCO_EC) += wilco_ec.o wilco_ec_debugfs-objs := debugfs.o obj-$(CONFIG_WILCO_EC_DEBUGFS) += wilco_ec_debugfs.o diff --git a/drivers/platform/chrome/wilco_ec/core.c b/drivers/platform/chrome/wilco_ec/core.c index 05e1e2be1c91..abd15d04e57b 100644 --- a/drivers/platform/chrome/wilco_ec/core.c +++ b/drivers/platform/chrome/wilco_ec/core.c @@ -89,8 +89,16 @@ static int wilco_ec_probe(struct platform_device *pdev) goto unregister_debugfs; } + ret = wilco_ec_add_sysfs(ec); + if (ret < 0) { + dev_err(dev, "Failed to create sysfs entries: %d", ret); + goto unregister_rtc; + } + return 0; +unregister_rtc: + platform_device_unregister(ec->rtc_pdev); unregister_debugfs: if (ec->debugfs_pdev) platform_device_unregister(ec->debugfs_pdev); @@ -102,6 +110,7 @@ static int wilco_ec_remove(struct platform_device *pdev) { struct wilco_ec_device *ec = platform_get_drvdata(pdev); + wilco_ec_remove_sysfs(ec); platform_device_unregister(ec->rtc_pdev); if (ec->debugfs_pdev) platform_device_unregister(ec->debugfs_pdev); diff --git a/drivers/platform/chrome/wilco_ec/sysfs.c b/drivers/platform/chrome/wilco_ec/sysfs.c new file mode 100644 index 000000000000..f84f0480460a --- /dev/null +++ b/drivers/platform/chrome/wilco_ec/sysfs.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2019 Google LLC + * + * Sysfs properties to view and modify EC-controlled features on Wilco devices. + * The entries will appear under /sys/bus/platform/devices/GOOG000C:00/ + * + * See Documentation/ABI/testing/sysfs-platform-wilco-ec for more information. + */ + +#include +#include + +#define CMD_KB_CMOS 0x7C +#define SUB_CMD_KB_CMOS_AUTO_ON 0x03 + +struct boot_on_ac_request { + u8 cmd; /* Always CMD_KB_CMOS */ + u8 reserved1; + u8 sub_cmd; /* Always SUB_CMD_KB_CMOS_AUTO_ON */ + u8 reserved3to5[3]; + u8 val; /* Either 0 or 1 */ + u8 reserved7; +} __packed; + +static ssize_t boot_on_ac_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct wilco_ec_device *ec = dev_get_drvdata(dev); + struct boot_on_ac_request rq; + struct wilco_ec_message msg; + int ret; + u8 val; + + ret = kstrtou8(buf, 10, &val); + if (ret < 0) + return ret; + if (val > 1) + return -EINVAL; + + memset(&rq, 0, sizeof(rq)); + rq.cmd = CMD_KB_CMOS; + rq.sub_cmd = SUB_CMD_KB_CMOS_AUTO_ON; + rq.val = val; + + memset(&msg, 0, sizeof(msg)); + msg.type = WILCO_EC_MSG_LEGACY; + msg.request_data = &rq; + msg.request_size = sizeof(rq); + ret = wilco_ec_mailbox(ec, &msg); + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR_WO(boot_on_ac); + +static struct attribute *wilco_dev_attrs[] = { + &dev_attr_boot_on_ac.attr, + NULL, +}; + +static struct attribute_group wilco_dev_attr_group = { + .attrs = wilco_dev_attrs, +}; + +int wilco_ec_add_sysfs(struct wilco_ec_device *ec) +{ + return sysfs_create_group(&ec->dev->kobj, &wilco_dev_attr_group); +} + +void wilco_ec_remove_sysfs(struct wilco_ec_device *ec) +{ + sysfs_remove_group(&ec->dev->kobj, &wilco_dev_attr_group); +} diff --git a/include/linux/platform_data/wilco-ec.h b/include/linux/platform_data/wilco-ec.h index 50a21bd5fd44..af68fc0563cc 100644 --- a/include/linux/platform_data/wilco-ec.h +++ b/include/linux/platform_data/wilco-ec.h @@ -194,4 +194,16 @@ int wilco_ec_get_byte_property(struct wilco_ec_device *ec, u32 property_id, int wilco_ec_set_byte_property(struct wilco_ec_device *ec, u32 property_id, u8 val); +/** + * wilco_ec_add_sysfs() - Create sysfs entries + * @ec: Wilco EC device + * + * wilco_ec_remove_sysfs() needs to be called afterwards + * to perform the necessary cleanup. + * + * Return: 0 on success or negative error code on failure. + */ +int wilco_ec_add_sysfs(struct wilco_ec_device *ec); +void wilco_ec_remove_sysfs(struct wilco_ec_device *ec); + #endif /* WILCO_EC_H */ -- cgit v1.2.3-59-g8ed1b From 2ad1f7a91449de48d4bd5d1ec361ba7bb9026505 Mon Sep 17 00:00:00 2001 From: Nick Crews Date: Wed, 8 May 2019 15:38:09 -0600 Subject: platform/chrome: wilco_ec: Remove 256 byte transfers The 0xF6 command, intended to send and receive 256 byte payloads to and from the EC, is not needed. The 0xF5 command for 32 byte payloads is sufficient. This patch removes support for the 0xF6 command and 256 byte payloads. Signed-off-by: Nick Crews Signed-off-by: Enric Balletbo i Serra --- Documentation/ABI/testing/debugfs-wilco-ec | 16 +++++++--------- drivers/platform/chrome/wilco_ec/core.c | 4 +--- drivers/platform/chrome/wilco_ec/debugfs.c | 10 ++-------- drivers/platform/chrome/wilco_ec/mailbox.c | 21 +++++---------------- include/linux/platform_data/wilco-ec.h | 9 ++------- 5 files changed, 17 insertions(+), 43 deletions(-) (limited to 'drivers/platform/chrome') diff --git a/Documentation/ABI/testing/debugfs-wilco-ec b/Documentation/ABI/testing/debugfs-wilco-ec index 73a5a66ddca6..9d8d9d2def5b 100644 --- a/Documentation/ABI/testing/debugfs-wilco-ec +++ b/Documentation/ABI/testing/debugfs-wilco-ec @@ -23,11 +23,9 @@ Description: For writing, bytes 0-1 indicate the message type, one of enum wilco_ec_msg_type. Byte 2+ consist of the data passed in the - request, starting at MBOX[0] - - At least three bytes are required for writing, two for the type - and at least a single byte of data. Only the first - EC_MAILBOX_DATA_SIZE bytes of MBOX will be used. + request, starting at MBOX[0]. At least three bytes are required + for writing, two for the type and at least a single byte of + data. Example: // Request EC info type 3 (EC firmware build date) @@ -40,7 +38,7 @@ Description: $ cat /sys/kernel/debug/wilco_ec/raw 00 00 31 32 2f 32 31 2f 31 38 00 38 00 01 00 2f 00 ..12/21/18.8... - Note that the first 32 bytes of the received MBOX[] will be - printed, even if some of the data is junk. It is up to you to - know how many of the first bytes of data are the actual - response. + Note that the first 16 bytes of the received MBOX[] will be + printed, even if some of the data is junk, and skipping bytes + 17 to 32. It is up to you to know how many of the first bytes of + data are the actual response. diff --git a/drivers/platform/chrome/wilco_ec/core.c b/drivers/platform/chrome/wilco_ec/core.c index abd15d04e57b..45cf3a5ed062 100644 --- a/drivers/platform/chrome/wilco_ec/core.c +++ b/drivers/platform/chrome/wilco_ec/core.c @@ -52,9 +52,7 @@ static int wilco_ec_probe(struct platform_device *pdev) ec->dev = dev; mutex_init(&ec->mailbox_lock); - /* Largest data buffer size requirement is extended data response */ - ec->data_size = sizeof(struct wilco_ec_response) + - EC_MAILBOX_DATA_SIZE_EXTENDED; + ec->data_size = sizeof(struct wilco_ec_response) + EC_MAILBOX_DATA_SIZE; ec->data_buffer = devm_kzalloc(dev, ec->data_size, GFP_KERNEL); if (!ec->data_buffer) return -ENOMEM; diff --git a/drivers/platform/chrome/wilco_ec/debugfs.c b/drivers/platform/chrome/wilco_ec/debugfs.c index f163476d080d..281ec595e8e0 100644 --- a/drivers/platform/chrome/wilco_ec/debugfs.c +++ b/drivers/platform/chrome/wilco_ec/debugfs.c @@ -17,13 +17,13 @@ #define DRV_NAME "wilco-ec-debugfs" /* The 256 raw bytes will take up more space when represented as a hex string */ -#define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE_EXTENDED * 4) +#define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE * 4) struct wilco_ec_debugfs { struct wilco_ec_device *ec; struct dentry *dir; size_t response_size; - u8 raw_data[EC_MAILBOX_DATA_SIZE_EXTENDED]; + u8 raw_data[EC_MAILBOX_DATA_SIZE]; u8 formatted_data[FORMATTED_BUFFER_SIZE]; }; static struct wilco_ec_debugfs *debug_info; @@ -124,12 +124,6 @@ static ssize_t raw_write(struct file *file, const char __user *user_buf, msg.response_data = debug_info->raw_data; msg.response_size = EC_MAILBOX_DATA_SIZE; - /* Telemetry commands use extended response data */ - if (msg.type == WILCO_EC_MSG_TELEMETRY_LONG) { - msg.flags |= WILCO_EC_FLAG_EXTENDED_DATA; - msg.response_size = EC_MAILBOX_DATA_SIZE_EXTENDED; - } - ret = wilco_ec_mailbox(debug_info->ec, &msg); if (ret < 0) return ret; diff --git a/drivers/platform/chrome/wilco_ec/mailbox.c b/drivers/platform/chrome/wilco_ec/mailbox.c index 7fb58b487963..ced1f9f3dcee 100644 --- a/drivers/platform/chrome/wilco_ec/mailbox.c +++ b/drivers/platform/chrome/wilco_ec/mailbox.c @@ -119,7 +119,6 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec, struct wilco_ec_response *rs; u8 checksum; u8 flag; - size_t size; /* Write request header, then data */ cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, 0, sizeof(*rq), (u8 *)rq); @@ -148,21 +147,11 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec, return -EIO; } - /* - * The EC always returns either EC_MAILBOX_DATA_SIZE or - * EC_MAILBOX_DATA_SIZE_EXTENDED bytes of data, so we need to - * calculate the checksum on **all** of this data, even if we - * won't use all of it. - */ - if (msg->flags & WILCO_EC_FLAG_EXTENDED_DATA) - size = EC_MAILBOX_DATA_SIZE_EXTENDED; - else - size = EC_MAILBOX_DATA_SIZE; - /* Read back response */ rs = ec->data_buffer; checksum = cros_ec_lpc_io_bytes_mec(MEC_IO_READ, 0, - sizeof(*rs) + size, (u8 *)rs); + sizeof(*rs) + EC_MAILBOX_DATA_SIZE, + (u8 *)rs); if (checksum) { dev_dbg(ec->dev, "bad packet checksum 0x%02x\n", rs->checksum); return -EBADMSG; @@ -173,9 +162,9 @@ static int wilco_ec_transfer(struct wilco_ec_device *ec, return -EBADMSG; } - if (rs->data_size != size) { - dev_dbg(ec->dev, "unexpected packet size (%u != %zu)", - rs->data_size, size); + if (rs->data_size != EC_MAILBOX_DATA_SIZE) { + dev_dbg(ec->dev, "unexpected packet size (%u != %u)", + rs->data_size, EC_MAILBOX_DATA_SIZE); return -EMSGSIZE; } diff --git a/include/linux/platform_data/wilco-ec.h b/include/linux/platform_data/wilco-ec.h index af68fc0563cc..e3ce9ce49b11 100644 --- a/include/linux/platform_data/wilco-ec.h +++ b/include/linux/platform_data/wilco-ec.h @@ -13,12 +13,9 @@ /* Message flags for using the mailbox() interface */ #define WILCO_EC_FLAG_NO_RESPONSE BIT(0) /* EC does not respond */ -#define WILCO_EC_FLAG_EXTENDED_DATA BIT(1) /* EC returns 256 data bytes */ /* Normal commands have a maximum 32 bytes of data */ #define EC_MAILBOX_DATA_SIZE 32 -/* Extended commands have 256 bytes of response data */ -#define EC_MAILBOX_DATA_SIZE_EXTENDED 256 /** * struct wilco_ec_device - Wilco Embedded Controller handle. @@ -85,14 +82,12 @@ struct wilco_ec_response { * enum wilco_ec_msg_type - Message type to select a set of command codes. * @WILCO_EC_MSG_LEGACY: Legacy EC messages for standard EC behavior. * @WILCO_EC_MSG_PROPERTY: Get/Set/Sync EC controlled NVRAM property. - * @WILCO_EC_MSG_TELEMETRY_SHORT: 32 bytes of telemetry data provided by the EC. - * @WILCO_EC_MSG_TELEMETRY_LONG: 256 bytes of telemetry data provided by the EC. + * @WILCO_EC_MSG_TELEMETRY: Request telemetry data from the EC. */ enum wilco_ec_msg_type { WILCO_EC_MSG_LEGACY = 0x00f0, WILCO_EC_MSG_PROPERTY = 0x00f2, - WILCO_EC_MSG_TELEMETRY_SHORT = 0x00f5, - WILCO_EC_MSG_TELEMETRY_LONG = 0x00f6, + WILCO_EC_MSG_TELEMETRY = 0x00f5, }; /** -- cgit v1.2.3-59-g8ed1b From 75501d2edc8044af145a2c237b0ccc2c03deb4ab Mon Sep 17 00:00:00 2001 From: Evan Green Date: Thu, 9 May 2019 11:17:50 -0700 Subject: platform/chrome: cros_ec_spi: Always add of_match_table The Chrome OS EC driver attaches to devices using the of_match_table even when ACPI is the underlying firmware. It does this using the magic PRP0001 ACPI HID, which tells ACPI to go find an OF compatible string under the hood and match on that. The cros_ec_spi driver needs to provide the of_match_table regardless of whether CONFIG_OF is enabled or not, since the table is used by ACPI for PRP0001 devices. Signed-off-by: Evan Green Reviewed-by: Benson Leung Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/cros_ec_spi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/platform/chrome') diff --git a/drivers/platform/chrome/cros_ec_spi.c b/drivers/platform/chrome/cros_ec_spi.c index 8e9451720e73..e24bed29d366 100644 --- a/drivers/platform/chrome/cros_ec_spi.c +++ b/drivers/platform/chrome/cros_ec_spi.c @@ -777,7 +777,7 @@ MODULE_DEVICE_TABLE(spi, cros_ec_spi_id); static struct spi_driver cros_ec_driver_spi = { .driver = { .name = "cros-ec-spi", - .of_match_table = of_match_ptr(cros_ec_spi_of_match), + .of_match_table = cros_ec_spi_of_match, .pm = &cros_ec_spi_pm_ops, }, .probe = cros_ec_spi_probe, -- cgit v1.2.3-59-g8ed1b From 26a14267aff218c60b89007fdb44ca392ba6122c Mon Sep 17 00:00:00 2001 From: Rushikesh S Kadam Date: Wed, 15 May 2019 16:08:41 +0530 Subject: platform/chrome: Add ChromeOS EC ISHTP driver This driver implements a slim layer to enable the ChromeOS EC kernel stack (cros_ec) to communicate with ChromeOS EC firmware running on the Intel Integrated Sensor Hub (ISH). The driver registers a ChromeOS EC MFD device to connect with cros_ec kernel stack (upper layer), and it registers a client with the ISH Transport Protocol bus (lower layer) to talk with the ISH firwmare. See description of the ISHTP protocol at Documentation/hid/intel-ish-hid.txt Signed-off-by: Rushikesh S Kadam Acked-by: Srinivas Pandruvada Reviewed-by: Jett Rink Tested-by: Jett Rink Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/Kconfig | 13 + drivers/platform/chrome/Makefile | 1 + drivers/platform/chrome/cros_ec_ishtp.c | 763 ++++++++++++++++++++++++++++++++ 3 files changed, 777 insertions(+) create mode 100644 drivers/platform/chrome/cros_ec_ishtp.c (limited to 'drivers/platform/chrome') diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index 997317d2f2b9..0b3c2b9ffd2f 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -71,6 +71,19 @@ config CROS_EC_RPMSG To compile this driver as a module, choose M here: the module will be called cros_ec_rpmsg. +config CROS_EC_ISHTP + tristate "ChromeOS Embedded Controller (ISHTP)" + depends on MFD_CROS_EC + depends on INTEL_ISH_HID + help + If you say Y here, you get support for talking to the ChromeOS EC + firmware running on Intel Integrated Sensor Hub (ISH), using the + ISH Transport protocol (ISH-TP). This uses a simple byte-level + protocol with a checksum. + + To compile this driver as a module, choose M here: the + module will be called cros_ec_ishtp. + config CROS_EC_SPI tristate "ChromeOS Embedded Controller (SPI)" depends on MFD_CROS_EC && SPI diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index 1b2f1dcfcd5c..c5583c48d1e5 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o obj-$(CONFIG_CHROMEOS_TBMC) += chromeos_tbmc.o obj-$(CONFIG_CROS_EC_I2C) += cros_ec_i2c.o +obj-$(CONFIG_CROS_EC_ISHTP) += cros_ec_ishtp.o obj-$(CONFIG_CROS_EC_RPMSG) += cros_ec_rpmsg.o obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.o cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_reg.o diff --git a/drivers/platform/chrome/cros_ec_ishtp.c b/drivers/platform/chrome/cros_ec_ishtp.c new file mode 100644 index 000000000000..e504d255d5ce --- /dev/null +++ b/drivers/platform/chrome/cros_ec_ishtp.c @@ -0,0 +1,763 @@ +// SPDX-License-Identifier: GPL-2.0 +// ISHTP interface for ChromeOS Embedded Controller +// +// Copyright (c) 2019, Intel Corporation. +// +// ISHTP client driver for talking to the Chrome OS EC firmware running +// on Intel Integrated Sensor Hub (ISH) using the ISH Transport protocol +// (ISH-TP). + +#include +#include +#include +#include +#include +#include +#include + +/* + * ISH TX/RX ring buffer pool size + * + * The AP->ISH messages and corresponding ISH->AP responses are + * serialized. We need 1 TX and 1 RX buffer for these. + * + * The MKBP ISH->AP events are serialized. We need one additional RX + * buffer for them. + */ +#define CROS_ISH_CL_TX_RING_SIZE 8 +#define CROS_ISH_CL_RX_RING_SIZE 8 + +/* ISH CrOS EC Host Commands */ +enum cros_ec_ish_channel { + CROS_EC_COMMAND = 1, /* AP->ISH message */ + CROS_MKBP_EVENT = 2, /* ISH->AP events */ +}; + +/* + * ISH firmware timeout for 1 message send failure is 1Hz, and the + * firmware will retry 2 times, so 3Hz is used for timeout. + */ +#define ISHTP_SEND_TIMEOUT (3 * HZ) + +/* ISH Transport CrOS EC ISH client unique GUID */ +static const guid_t cros_ish_guid = + GUID_INIT(0x7b7154d0, 0x56f4, 0x4bdc, + 0xb0, 0xd8, 0x9e, 0x7c, 0xda, 0xe0, 0xd6, 0xa0); + +struct header { + u8 channel; + u8 status; + u8 reserved[2]; +} __packed; + +struct cros_ish_out_msg { + struct header hdr; + struct ec_host_request ec_request; +} __packed; + +struct cros_ish_in_msg { + struct header hdr; + struct ec_host_response ec_response; +} __packed; + +#define IN_MSG_EC_RESPONSE_PREAMBLE \ + offsetof(struct cros_ish_in_msg, ec_response) + +#define OUT_MSG_EC_REQUEST_PREAMBLE \ + offsetof(struct cros_ish_out_msg, ec_request) + +#define cl_data_to_dev(client_data) ishtp_device((client_data)->cl_device) + +/* + * The Read-Write Semaphore is used to prevent message TX or RX while + * the ishtp client is being initialized or undergoing reset. + * + * The readers are the kernel function calls responsible for IA->ISH + * and ISH->AP messaging. + * + * The writers are .reset() and .probe() function. + */ +DECLARE_RWSEM(init_lock); + +/** + * struct response_info - Encapsulate firmware response related + * information for passing between function ish_send() and + * process_recv() callback. + * + * @data: Copy the data received from firmware here. + * @max_size: Max size allocated for the @data buffer. If the received + * data exceeds this value, we log an error. + * @size: Actual size of data received from firmware. + * @error: 0 for success, negative error code for a failure in process_recv(). + * @received: Set to true on receiving a valid firmware response to host command + * @wait_queue: Wait queue for host to wait for firmware response. + */ +struct response_info { + void *data; + size_t max_size; + size_t size; + int error; + bool received; + wait_queue_head_t wait_queue; +}; + +/** + * struct ishtp_cl_data - Encapsulate per ISH TP Client. + * + * @cros_ish_cl: ISHTP firmware client instance. + * @cl_device: ISHTP client device instance. + * @response: Response info passing between ish_send() and process_recv(). + * @work_ishtp_reset: Work queue reset handling. + * @work_ec_evt: Work queue for EC events. + * @ec_dev: CrOS EC MFD device. + * + * This structure is used to store per client data. + */ +struct ishtp_cl_data { + struct ishtp_cl *cros_ish_cl; + struct ishtp_cl_device *cl_device; + + /* + * Used for passing firmware response information between + * ish_send() and process_recv() callback. + */ + struct response_info response; + + struct work_struct work_ishtp_reset; + struct work_struct work_ec_evt; + struct cros_ec_device *ec_dev; +}; + +/** + * ish_evt_handler - ISH to AP event handler + * @work: Work struct + */ +static void ish_evt_handler(struct work_struct *work) +{ + struct ishtp_cl_data *client_data = + container_of(work, struct ishtp_cl_data, work_ec_evt); + struct cros_ec_device *ec_dev = client_data->ec_dev; + + if (cros_ec_get_next_event(ec_dev, NULL) > 0) { + blocking_notifier_call_chain(&ec_dev->event_notifier, + 0, ec_dev); + } +} + +/** + * ish_send() - Send message from host to firmware + * + * @client_data: Client data instance + * @out_msg: Message buffer to be sent to firmware + * @out_size: Size of out going message + * @in_msg: Message buffer where the incoming data is copied. This buffer + * is allocated by calling + * @in_size: Max size of incoming message + * + * Return: Number of bytes copied in the in_msg on success, negative + * error code on failure. + */ +static int ish_send(struct ishtp_cl_data *client_data, + u8 *out_msg, size_t out_size, + u8 *in_msg, size_t in_size) +{ + int rv; + struct header *out_hdr = (struct header *)out_msg; + struct ishtp_cl *cros_ish_cl = client_data->cros_ish_cl; + + dev_dbg(cl_data_to_dev(client_data), + "%s: channel=%02u status=%02u\n", + __func__, out_hdr->channel, out_hdr->status); + + /* Setup for incoming response */ + client_data->response.data = in_msg; + client_data->response.max_size = in_size; + client_data->response.error = 0; + client_data->response.received = false; + + rv = ishtp_cl_send(cros_ish_cl, out_msg, out_size); + if (rv) { + dev_err(cl_data_to_dev(client_data), + "ishtp_cl_send error %d\n", rv); + return rv; + } + + wait_event_interruptible_timeout(client_data->response.wait_queue, + client_data->response.received, + ISHTP_SEND_TIMEOUT); + if (!client_data->response.received) { + dev_err(cl_data_to_dev(client_data), + "Timed out for response to host message\n"); + return -ETIMEDOUT; + } + + if (client_data->response.error < 0) + return client_data->response.error; + + return client_data->response.size; +} + +/** + * process_recv() - Received and parse incoming packet + * @cros_ish_cl: Client instance to get stats + * @rb_in_proc: Host interface message buffer + * + * Parse the incoming packet. If it is a response packet then it will + * update per instance flags and wake up the caller waiting to for the + * response. If it is an event packet then it will schedule event work. + */ +static void process_recv(struct ishtp_cl *cros_ish_cl, + struct ishtp_cl_rb *rb_in_proc) +{ + size_t data_len = rb_in_proc->buf_idx; + struct ishtp_cl_data *client_data = + ishtp_get_client_data(cros_ish_cl); + struct device *dev = cl_data_to_dev(client_data); + struct cros_ish_in_msg *in_msg = + (struct cros_ish_in_msg *)rb_in_proc->buffer.data; + + /* Proceed only if reset or init is not in progress */ + if (!down_read_trylock(&init_lock)) { + /* Free the buffer */ + ishtp_cl_io_rb_recycle(rb_in_proc); + dev_warn(dev, + "Host is not ready to receive incoming messages\n"); + return; + } + + /* + * All firmware messages contain a header. Check the buffer size + * before accessing elements inside. + */ + if (!rb_in_proc->buffer.data) { + dev_warn(dev, "rb_in_proc->buffer.data returned null"); + client_data->response.error = -EBADMSG; + goto end_error; + } + + if (data_len < sizeof(struct header)) { + dev_err(dev, "data size %zu is less than header %zu\n", + data_len, sizeof(struct header)); + client_data->response.error = -EMSGSIZE; + goto end_error; + } + + dev_dbg(dev, "channel=%02u status=%02u\n", + in_msg->hdr.channel, in_msg->hdr.status); + + switch (in_msg->hdr.channel) { + case CROS_EC_COMMAND: + /* Sanity check */ + if (!client_data->response.data) { + dev_err(dev, + "Receiving buffer is null. Should be allocated by calling function\n"); + client_data->response.error = -EINVAL; + goto error_wake_up; + } + + if (client_data->response.received) { + dev_err(dev, + "Previous firmware message not yet processed\n"); + client_data->response.error = -EINVAL; + goto error_wake_up; + } + + if (data_len > client_data->response.max_size) { + dev_err(dev, + "Received buffer size %zu is larger than allocated buffer %zu\n", + data_len, client_data->response.max_size); + client_data->response.error = -EMSGSIZE; + goto error_wake_up; + } + + if (in_msg->hdr.status) { + dev_err(dev, "firmware returned status %d\n", + in_msg->hdr.status); + client_data->response.error = -EIO; + goto error_wake_up; + } + + /* Update the actual received buffer size */ + client_data->response.size = data_len; + + /* + * Copy the buffer received in firmware response for the + * calling thread. + */ + memcpy(client_data->response.data, + rb_in_proc->buffer.data, data_len); + + /* Set flag before waking up the caller */ + client_data->response.received = true; +error_wake_up: + /* Wake the calling thread */ + wake_up_interruptible(&client_data->response.wait_queue); + + break; + + case CROS_MKBP_EVENT: + /* The event system doesn't send any data in buffer */ + schedule_work(&client_data->work_ec_evt); + + break; + + default: + dev_err(dev, "Invalid channel=%02d\n", in_msg->hdr.channel); + } + +end_error: + /* Free the buffer */ + ishtp_cl_io_rb_recycle(rb_in_proc); + + up_read(&init_lock); +} + +/** + * ish_event_cb() - bus driver callback for incoming message + * @cl_device: ISHTP client device for which this message is targeted. + * + * Remove the packet from the list and process the message by calling + * process_recv. + */ +static void ish_event_cb(struct ishtp_cl_device *cl_device) +{ + struct ishtp_cl_rb *rb_in_proc; + struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device); + + while ((rb_in_proc = ishtp_cl_rx_get_rb(cros_ish_cl)) != NULL) { + /* Decide what to do with received data */ + process_recv(cros_ish_cl, rb_in_proc); + } +} + +/** + * cros_ish_init() - Init function for ISHTP client + * @cros_ish_cl: ISHTP client instance + * + * This function complete the initializtion of the client. + * + * Return: 0 for success, negative error code for failure. + */ +static int cros_ish_init(struct ishtp_cl *cros_ish_cl) +{ + int rv; + struct ishtp_device *dev; + struct ishtp_fw_client *fw_client; + struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl); + + rv = ishtp_cl_link(cros_ish_cl); + if (rv) { + dev_err(cl_data_to_dev(client_data), + "ishtp_cl_link failed\n"); + return rv; + } + + dev = ishtp_get_ishtp_device(cros_ish_cl); + + /* Connect to firmware client */ + ishtp_set_tx_ring_size(cros_ish_cl, CROS_ISH_CL_TX_RING_SIZE); + ishtp_set_rx_ring_size(cros_ish_cl, CROS_ISH_CL_RX_RING_SIZE); + + fw_client = ishtp_fw_cl_get_client(dev, &cros_ish_guid); + if (!fw_client) { + dev_err(cl_data_to_dev(client_data), + "ish client uuid not found\n"); + rv = -ENOENT; + goto err_cl_unlink; + } + + ishtp_cl_set_fw_client_id(cros_ish_cl, + ishtp_get_fw_client_id(fw_client)); + ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_CONNECTING); + + rv = ishtp_cl_connect(cros_ish_cl); + if (rv) { + dev_err(cl_data_to_dev(client_data), + "client connect fail\n"); + goto err_cl_unlink; + } + + ishtp_register_event_cb(client_data->cl_device, ish_event_cb); + return 0; + +err_cl_unlink: + ishtp_cl_unlink(cros_ish_cl); + return rv; +} + +/** + * cros_ish_deinit() - Deinit function for ISHTP client + * @cros_ish_cl: ISHTP client instance + * + * Unlink and free cros_ec client + */ +static void cros_ish_deinit(struct ishtp_cl *cros_ish_cl) +{ + ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING); + ishtp_cl_disconnect(cros_ish_cl); + ishtp_cl_unlink(cros_ish_cl); + ishtp_cl_flush_queues(cros_ish_cl); + + /* Disband and free all Tx and Rx client-level rings */ + ishtp_cl_free(cros_ish_cl); +} + +/** + * prepare_cros_ec_rx() - Check & prepare receive buffer + * @ec_dev: CrOS EC MFD device. + * @in_msg: Incoming message buffer + * @msg: cros_ec command used to send & receive data + * + * Return: 0 for success, negative error code for failure. + * + * Check the received buffer. Convert to cros_ec_command format. + */ +static int prepare_cros_ec_rx(struct cros_ec_device *ec_dev, + const struct cros_ish_in_msg *in_msg, + struct cros_ec_command *msg) +{ + u8 sum = 0; + int i, rv, offset; + + /* Check response error code */ + msg->result = in_msg->ec_response.result; + rv = cros_ec_check_result(ec_dev, msg); + if (rv < 0) + return rv; + + if (in_msg->ec_response.data_len > msg->insize) { + dev_err(ec_dev->dev, "Packet too long (%d bytes, expected %d)", + in_msg->ec_response.data_len, msg->insize); + return -ENOSPC; + } + + /* Copy response packet payload and compute checksum */ + for (i = 0; i < sizeof(struct ec_host_response); i++) + sum += ((u8 *)in_msg)[IN_MSG_EC_RESPONSE_PREAMBLE + i]; + + offset = sizeof(struct cros_ish_in_msg); + for (i = 0; i < in_msg->ec_response.data_len; i++) + sum += msg->data[i] = ((u8 *)in_msg)[offset + i]; + + if (sum) { + dev_dbg(ec_dev->dev, "Bad received packet checksum %d\n", sum); + return -EBADMSG; + } + + return 0; +} + +static int cros_ec_pkt_xfer_ish(struct cros_ec_device *ec_dev, + struct cros_ec_command *msg) +{ + int rv; + struct ishtp_cl *cros_ish_cl = ec_dev->priv; + struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl); + struct device *dev = cl_data_to_dev(client_data); + struct cros_ish_in_msg *in_msg = (struct cros_ish_in_msg *)ec_dev->din; + struct cros_ish_out_msg *out_msg = + (struct cros_ish_out_msg *)ec_dev->dout; + size_t in_size = sizeof(struct cros_ish_in_msg) + msg->insize; + size_t out_size = sizeof(struct cros_ish_out_msg) + msg->outsize; + + /* Sanity checks */ + if (in_size > ec_dev->din_size) { + dev_err(dev, + "Incoming payload size %zu is too large for ec_dev->din_size %d\n", + in_size, ec_dev->din_size); + return -EMSGSIZE; + } + + if (out_size > ec_dev->dout_size) { + dev_err(dev, + "Outgoing payload size %zu is too large for ec_dev->dout_size %d\n", + out_size, ec_dev->dout_size); + return -EMSGSIZE; + } + + /* Proceed only if reset-init is not in progress */ + if (!down_read_trylock(&init_lock)) { + dev_warn(dev, + "Host is not ready to send messages to ISH. Try again\n"); + return -EAGAIN; + } + + /* Prepare the package to be sent over ISH TP */ + out_msg->hdr.channel = CROS_EC_COMMAND; + out_msg->hdr.status = 0; + + ec_dev->dout += OUT_MSG_EC_REQUEST_PREAMBLE; + cros_ec_prepare_tx(ec_dev, msg); + ec_dev->dout -= OUT_MSG_EC_REQUEST_PREAMBLE; + + dev_dbg(dev, + "out_msg: struct_ver=0x%x checksum=0x%x command=0x%x command_ver=0x%x data_len=0x%x\n", + out_msg->ec_request.struct_version, + out_msg->ec_request.checksum, + out_msg->ec_request.command, + out_msg->ec_request.command_version, + out_msg->ec_request.data_len); + + /* Send command to ISH EC firmware and read response */ + rv = ish_send(client_data, + (u8 *)out_msg, out_size, + (u8 *)in_msg, in_size); + if (rv < 0) + goto end_error; + + rv = prepare_cros_ec_rx(ec_dev, in_msg, msg); + if (rv) + goto end_error; + + rv = in_msg->ec_response.data_len; + + dev_dbg(dev, + "in_msg: struct_ver=0x%x checksum=0x%x result=0x%x data_len=0x%x\n", + in_msg->ec_response.struct_version, + in_msg->ec_response.checksum, + in_msg->ec_response.result, + in_msg->ec_response.data_len); + +end_error: + if (msg->command == EC_CMD_REBOOT_EC) + msleep(EC_REBOOT_DELAY_MS); + + up_read(&init_lock); + + return rv; +} + +static int cros_ec_dev_init(struct ishtp_cl_data *client_data) +{ + struct cros_ec_device *ec_dev; + struct device *dev = cl_data_to_dev(client_data); + + ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL); + if (!ec_dev) + return -ENOMEM; + + client_data->ec_dev = ec_dev; + dev->driver_data = ec_dev; + + ec_dev->dev = dev; + ec_dev->priv = client_data->cros_ish_cl; + ec_dev->cmd_xfer = NULL; + ec_dev->pkt_xfer = cros_ec_pkt_xfer_ish; + ec_dev->phys_name = dev_name(dev); + ec_dev->din_size = sizeof(struct cros_ish_in_msg) + + sizeof(struct ec_response_get_protocol_info); + ec_dev->dout_size = sizeof(struct cros_ish_out_msg); + + return cros_ec_register(ec_dev); +} + +static void reset_handler(struct work_struct *work) +{ + int rv; + struct device *dev; + struct ishtp_cl *cros_ish_cl; + struct ishtp_cl_device *cl_device; + struct ishtp_cl_data *client_data = + container_of(work, struct ishtp_cl_data, work_ishtp_reset); + + /* Lock for reset to complete */ + down_write(&init_lock); + + cros_ish_cl = client_data->cros_ish_cl; + cl_device = client_data->cl_device; + + /* Unlink, flush queues & start again */ + ishtp_cl_unlink(cros_ish_cl); + ishtp_cl_flush_queues(cros_ish_cl); + ishtp_cl_free(cros_ish_cl); + + cros_ish_cl = ishtp_cl_allocate(cl_device); + if (!cros_ish_cl) { + up_write(&init_lock); + return; + } + + ishtp_set_drvdata(cl_device, cros_ish_cl); + ishtp_set_client_data(cros_ish_cl, client_data); + client_data->cros_ish_cl = cros_ish_cl; + + rv = cros_ish_init(cros_ish_cl); + if (rv) { + ishtp_cl_free(cros_ish_cl); + dev_err(cl_data_to_dev(client_data), "Reset Failed\n"); + up_write(&init_lock); + return; + } + + /* Refresh ec_dev device pointers */ + client_data->ec_dev->priv = client_data->cros_ish_cl; + dev = cl_data_to_dev(client_data); + dev->driver_data = client_data->ec_dev; + + dev_info(cl_data_to_dev(client_data), "Chrome EC ISH reset done\n"); + + up_write(&init_lock); +} + +/** + * cros_ec_ishtp_probe() - ISHTP client driver probe callback + * @cl_device: ISHTP client device instance + * + * Return: 0 for success, negative error code for failure. + */ +static int cros_ec_ishtp_probe(struct ishtp_cl_device *cl_device) +{ + int rv; + struct ishtp_cl *cros_ish_cl; + struct ishtp_cl_data *client_data = + devm_kzalloc(ishtp_device(cl_device), + sizeof(*client_data), GFP_KERNEL); + if (!client_data) + return -ENOMEM; + + /* Lock for initialization to complete */ + down_write(&init_lock); + + cros_ish_cl = ishtp_cl_allocate(cl_device); + if (!cros_ish_cl) { + rv = -ENOMEM; + goto end_ishtp_cl_alloc_error; + } + + ishtp_set_drvdata(cl_device, cros_ish_cl); + ishtp_set_client_data(cros_ish_cl, client_data); + client_data->cros_ish_cl = cros_ish_cl; + client_data->cl_device = cl_device; + + init_waitqueue_head(&client_data->response.wait_queue); + + INIT_WORK(&client_data->work_ishtp_reset, + reset_handler); + INIT_WORK(&client_data->work_ec_evt, + ish_evt_handler); + + rv = cros_ish_init(cros_ish_cl); + if (rv) + goto end_ishtp_cl_init_error; + + ishtp_get_device(cl_device); + + up_write(&init_lock); + + /* Register croc_ec_dev mfd */ + rv = cros_ec_dev_init(client_data); + if (rv) + goto end_cros_ec_dev_init_error; + + return 0; + +end_cros_ec_dev_init_error: + ishtp_set_connection_state(cros_ish_cl, ISHTP_CL_DISCONNECTING); + ishtp_cl_disconnect(cros_ish_cl); + ishtp_cl_unlink(cros_ish_cl); + ishtp_cl_flush_queues(cros_ish_cl); + ishtp_put_device(cl_device); +end_ishtp_cl_init_error: + ishtp_cl_free(cros_ish_cl); +end_ishtp_cl_alloc_error: + up_write(&init_lock); + return rv; +} + +/** + * cros_ec_ishtp_remove() - ISHTP client driver remove callback + * @cl_device: ISHTP client device instance + * + * Return: 0 + */ +static int cros_ec_ishtp_remove(struct ishtp_cl_device *cl_device) +{ + struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device); + struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl); + + cancel_work_sync(&client_data->work_ishtp_reset); + cancel_work_sync(&client_data->work_ec_evt); + cros_ish_deinit(cros_ish_cl); + ishtp_put_device(cl_device); + + return 0; +} + +/** + * cros_ec_ishtp_reset() - ISHTP client driver reset callback + * @cl_device: ISHTP client device instance + * + * Return: 0 + */ +static int cros_ec_ishtp_reset(struct ishtp_cl_device *cl_device) +{ + struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device); + struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl); + + schedule_work(&client_data->work_ishtp_reset); + + return 0; +} + +/** + * cros_ec_ishtp_suspend() - ISHTP client driver suspend callback + * @device: device instance + * + * Return: 0 for success, negative error code for failure. + */ +static int __maybe_unused cros_ec_ishtp_suspend(struct device *device) +{ + struct ishtp_cl_device *cl_device = dev_get_drvdata(device); + struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device); + struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl); + + return cros_ec_suspend(client_data->ec_dev); +} + +/** + * cros_ec_ishtp_resume() - ISHTP client driver resume callback + * @device: device instance + * + * Return: 0 for success, negative error code for failure. + */ +static int __maybe_unused cros_ec_ishtp_resume(struct device *device) +{ + struct ishtp_cl_device *cl_device = dev_get_drvdata(device); + struct ishtp_cl *cros_ish_cl = ishtp_get_drvdata(cl_device); + struct ishtp_cl_data *client_data = ishtp_get_client_data(cros_ish_cl); + + return cros_ec_resume(client_data->ec_dev); +} + +static SIMPLE_DEV_PM_OPS(cros_ec_ishtp_pm_ops, cros_ec_ishtp_suspend, + cros_ec_ishtp_resume); + +static struct ishtp_cl_driver cros_ec_ishtp_driver = { + .name = "cros_ec_ishtp", + .guid = &cros_ish_guid, + .probe = cros_ec_ishtp_probe, + .remove = cros_ec_ishtp_remove, + .reset = cros_ec_ishtp_reset, + .driver = { + .pm = &cros_ec_ishtp_pm_ops, + }, +}; + +static int __init cros_ec_ishtp_mod_init(void) +{ + return ishtp_cl_driver_register(&cros_ec_ishtp_driver, THIS_MODULE); +} + +static void __exit cros_ec_ishtp_mod_exit(void) +{ + ishtp_cl_driver_unregister(&cros_ec_ishtp_driver); +} + +module_init(cros_ec_ishtp_mod_init); +module_exit(cros_ec_ishtp_mod_exit); + +MODULE_DESCRIPTION("ChromeOS EC ISHTP Client Driver"); +MODULE_AUTHOR("Rushikesh S Kadam "); + +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("ishtp:*"); -- cgit v1.2.3-59-g8ed1b From 7dadf88f8d4ea2a582348dc00860979059f7bcb0 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Wed, 15 May 2019 09:48:11 -0700 Subject: platform/chrome: cros_ec_spi: Move to real time priority for transfers In commit 37a186225a0c ("platform/chrome: cros_ec_spi: Transfer messages at high priority") we moved transfers to a high priority workqueue. This helped make them much more reliable. ...but, we still saw failures. We were actually finding ourselves competing for time with dm-crypt which also scheduled work on HIGHPRI workqueues. While we can consider reverting the change that made dm-crypt run its work at HIGHPRI, the argument in commit a1b89132dc4f ("dm crypt: use WQ_HIGHPRI for the IO and crypt workqueues") is somewhat compelling. It does make sense for IO to be scheduled at a priority that's higher than the default user priority. It also turns out that dm-crypt isn't alone in using high priority like this. loop_prepare_queue() does something similar for loopback devices. Looking in more detail, it can be seen that the high priority workqueue isn't actually that high of a priority. It runs at MIN_NICE which is _fairly_ high priority but still below all real time priority. Should we move cros_ec_spi to real time priority to fix our problems, or is this just escalating a priority war? I'll argue here that cros_ec_spi _does_ belong at real time priority. Specifically cros_ec_spi actually needs to run quickly for correctness. As I understand this is exactly what real time priority is for. There currently doesn't appear to be any way to use the standard workqueue APIs with a real time priority, so we'll switch over to using using a kthread worker. We'll match the priority that the SPI core uses when it wants to do things on a realtime thread and just use "MAX_RT_PRIO - 1". This commit plus the patch ("platform/chrome: cros_ec_spi: Request the SPI thread be realtime") are enough to get communications very close to 100% reliable (the only known problem left is when serial console is turned on, which isn't something that happens in shipping devices). Specifically this test case now passes (tested on rk3288-veyron-jerry): dd if=/dev/zero of=/var/log/foo.txt bs=4M count=512& while true; do ectool version > /dev/null; done It should be noted that "/var/log" is encrypted (and goes through dm-crypt) and also passes through a loopback device. Signed-off-by: Douglas Anderson Reviewed-by: Guenter Roeck Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/cros_ec_spi.c | 65 ++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 12 deletions(-) (limited to 'drivers/platform/chrome') diff --git a/drivers/platform/chrome/cros_ec_spi.c b/drivers/platform/chrome/cros_ec_spi.c index e24bed29d366..5877a572e0bd 100644 --- a/drivers/platform/chrome/cros_ec_spi.c +++ b/drivers/platform/chrome/cros_ec_spi.c @@ -12,7 +12,7 @@ #include #include #include - +#include /* The header byte, which follows the preamble */ #define EC_MSG_HEADER 0xec @@ -67,12 +67,14 @@ * is sent when we want to turn on CS at the start of a transaction. * @end_of_msg_delay: used to set the delay_usecs on the spi_transfer that * is sent when we want to turn off CS at the end of a transaction. + * @high_pri_worker: Used to schedule high priority work. */ struct cros_ec_spi { struct spi_device *spi; s64 last_transfer_ns; unsigned int start_of_msg_delay; unsigned int end_of_msg_delay; + struct kthread_worker *high_pri_worker; }; typedef int (*cros_ec_xfer_fn_t) (struct cros_ec_device *ec_dev, @@ -89,7 +91,7 @@ typedef int (*cros_ec_xfer_fn_t) (struct cros_ec_device *ec_dev, */ struct cros_ec_xfer_work_params { - struct work_struct work; + struct kthread_work work; cros_ec_xfer_fn_t fn; struct cros_ec_device *ec_dev; struct cros_ec_command *ec_msg; @@ -632,7 +634,7 @@ exit: return ret; } -static void cros_ec_xfer_high_pri_work(struct work_struct *work) +static void cros_ec_xfer_high_pri_work(struct kthread_work *work) { struct cros_ec_xfer_work_params *params; @@ -644,12 +646,14 @@ static int cros_ec_xfer_high_pri(struct cros_ec_device *ec_dev, struct cros_ec_command *ec_msg, cros_ec_xfer_fn_t fn) { - struct cros_ec_xfer_work_params params; - - INIT_WORK_ONSTACK(¶ms.work, cros_ec_xfer_high_pri_work); - params.ec_dev = ec_dev; - params.ec_msg = ec_msg; - params.fn = fn; + struct cros_ec_spi *ec_spi = ec_dev->priv; + struct cros_ec_xfer_work_params params = { + .work = KTHREAD_WORK_INIT(params.work, + cros_ec_xfer_high_pri_work), + .ec_dev = ec_dev, + .ec_msg = ec_msg, + .fn = fn, + }; /* * This looks a bit ridiculous. Why do the work on a @@ -660,9 +664,8 @@ static int cros_ec_xfer_high_pri(struct cros_ec_device *ec_dev, * context switched out for too long and the EC giving up on * the transfer. */ - queue_work(system_highpri_wq, ¶ms.work); - flush_work(¶ms.work); - destroy_work_on_stack(¶ms.work); + kthread_queue_work(ec_spi->high_pri_worker, ¶ms.work); + kthread_flush_work(¶ms.work); return params.ret; } @@ -694,6 +697,40 @@ static void cros_ec_spi_dt_probe(struct cros_ec_spi *ec_spi, struct device *dev) ec_spi->end_of_msg_delay = val; } +static void cros_ec_spi_high_pri_release(void *worker) +{ + kthread_destroy_worker(worker); +} + +static int cros_ec_spi_devm_high_pri_alloc(struct device *dev, + struct cros_ec_spi *ec_spi) +{ + struct sched_param sched_priority = { + .sched_priority = MAX_RT_PRIO - 1, + }; + int err; + + ec_spi->high_pri_worker = + kthread_create_worker(0, "cros_ec_spi_high_pri"); + + if (IS_ERR(ec_spi->high_pri_worker)) { + err = PTR_ERR(ec_spi->high_pri_worker); + dev_err(dev, "Can't create cros_ec high pri worker: %d\n", err); + return err; + } + + err = devm_add_action_or_reset(dev, cros_ec_spi_high_pri_release, + ec_spi->high_pri_worker); + if (err) + return err; + + err = sched_setscheduler_nocheck(ec_spi->high_pri_worker->task, + SCHED_FIFO, &sched_priority); + if (err) + dev_err(dev, "Can't set cros_ec high pri priority: %d\n", err); + return err; +} + static int cros_ec_spi_probe(struct spi_device *spi) { struct device *dev = &spi->dev; @@ -732,6 +769,10 @@ static int cros_ec_spi_probe(struct spi_device *spi) ec_spi->last_transfer_ns = ktime_get_ns(); + err = cros_ec_spi_devm_high_pri_alloc(dev, ec_spi); + if (err) + return err; + err = cros_ec_register(ec_dev); if (err) { dev_err(dev, "cannot register EC\n"); -- cgit v1.2.3-59-g8ed1b From ac5bdfdc0e1ccce0f23898f5500874e31198f5c2 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Wed, 15 May 2019 09:48:13 -0700 Subject: platform/chrome: cros_ec_spi: Request the SPI thread be realtime All currently known ECs in the wild are very sensitive to timing. Specifically the ECs are known to drop a transfer if more than 8 ms passes from the assertion of the chip select until the transfer finishes. Let's use the new feature introduced in the patch (spi: Allow SPI devices to request the pumping thread be realtime") to request the SPI pumping thread be realtime. This means that if we get shunted off to the SPI thread for whatever reason we won't get downgraded to low priority. NOTES: - We still need to keep ourselves as high priority since the SPI core doesn't guarantee that all transfers end up on the pumping thread (in fact, it tries pretty hard to do them in the calling context). - If future Chrome OS ECs ever fix themselves to be less sensitive then we could consider adding a property (or compatible string) to not set this property. For now we need it across the board. Signed-off-by: Douglas Anderson Reviewed-by: Guenter Roeck Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/cros_ec_spi.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/platform/chrome') diff --git a/drivers/platform/chrome/cros_ec_spi.c b/drivers/platform/chrome/cros_ec_spi.c index 5877a572e0bd..006a8ff64057 100644 --- a/drivers/platform/chrome/cros_ec_spi.c +++ b/drivers/platform/chrome/cros_ec_spi.c @@ -740,6 +740,7 @@ static int cros_ec_spi_probe(struct spi_device *spi) spi->bits_per_word = 8; spi->mode = SPI_MODE_0; + spi->rt = true; err = spi_setup(spi); if (err < 0) return err; -- cgit v1.2.3-59-g8ed1b From f7b0bc5eafa44941ddf92df6f46dd82cbfacec3e Mon Sep 17 00:00:00 2001 From: Nick Crews Date: Thu, 23 May 2019 17:06:24 -0600 Subject: platform/chrome: wilco_ec: Add event handling The Wilco Embedded Controller can create custom events that are not handled as standard ACPI objects. These events can contain information about changes in EC controlled features, such as errors and events in the dock or display. For example, an event is triggered if the dock is plugged into a display incorrectly. These events are needed for telemetry and diagnostics reasons, and for possibly alerting the user. These events are triggered by the EC with an ACPI Notify(0x90), and then the BIOS reads the event buffer from EC RAM via an ACPI method. When the OS receives these events via ACPI, it passes them along to this driver. The events are put into a queue which can be read by a userspace daemon via a char device that implements read() and poll(). The event queue acts as a circular buffer of size 64, so if there are no userspace consumers the kernel will not run out of memory. The char device will appear at /dev/wilco_event{n}, where n is some small non-negative integer, starting from 0. Standard ACPI events such as the battery getting plugged/unplugged can also come through this path, but they are dealt with via other paths, and are ignored here. To test, you can tail the binary data with $ cat /dev/wilco_event0 | hexdump -ve '1/1 "%x\n"' and then create an event by plugging/unplugging the battery. Signed-off-by: Nick Crews Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/wilco_ec/Kconfig | 9 + drivers/platform/chrome/wilco_ec/Makefile | 2 + drivers/platform/chrome/wilco_ec/event.c | 541 ++++++++++++++++++++++++++++++ 3 files changed, 552 insertions(+) create mode 100644 drivers/platform/chrome/wilco_ec/event.c (limited to 'drivers/platform/chrome') diff --git a/drivers/platform/chrome/wilco_ec/Kconfig b/drivers/platform/chrome/wilco_ec/Kconfig index e09e4cebe9b4..2c8f6f15f28f 100644 --- a/drivers/platform/chrome/wilco_ec/Kconfig +++ b/drivers/platform/chrome/wilco_ec/Kconfig @@ -18,3 +18,12 @@ config WILCO_EC_DEBUGFS manipulation and allow for testing arbitrary commands. This interface is intended for debug only and will not be present on production devices. + +config WILCO_EC_EVENTS + tristate "Enable event forwarding from EC to userspace" + depends on WILCO_EC + help + If you say Y here, you get support for the EC to send events + (such as power state changes) to userspace. The EC sends the events + over ACPI, and a driver queues up the events to be read by a + userspace daemon from /dev/wilco_event using read() and poll(). diff --git a/drivers/platform/chrome/wilco_ec/Makefile b/drivers/platform/chrome/wilco_ec/Makefile index 72df9b5e1983..4d8a5f068f8b 100644 --- a/drivers/platform/chrome/wilco_ec/Makefile +++ b/drivers/platform/chrome/wilco_ec/Makefile @@ -4,3 +4,5 @@ wilco_ec-objs := core.o mailbox.o properties.o sysfs.o obj-$(CONFIG_WILCO_EC) += wilco_ec.o wilco_ec_debugfs-objs := debugfs.o obj-$(CONFIG_WILCO_EC_DEBUGFS) += wilco_ec_debugfs.o +wilco_ec_events-objs := event.o +obj-$(CONFIG_WILCO_EC_EVENTS) += wilco_ec_events.o diff --git a/drivers/platform/chrome/wilco_ec/event.c b/drivers/platform/chrome/wilco_ec/event.c new file mode 100644 index 000000000000..4d2776f77dbd --- /dev/null +++ b/drivers/platform/chrome/wilco_ec/event.c @@ -0,0 +1,541 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ACPI event handling for Wilco Embedded Controller + * + * Copyright 2019 Google LLC + * + * The Wilco Embedded Controller can create custom events that + * are not handled as standard ACPI objects. These events can + * contain information about changes in EC controlled features, + * such as errors and events in the dock or display. For example, + * an event is triggered if the dock is plugged into a display + * incorrectly. These events are needed for telemetry and + * diagnostics reasons, and for possibly alerting the user. + + * These events are triggered by the EC with an ACPI Notify(0x90), + * and then the BIOS reads the event buffer from EC RAM via an + * ACPI method. When the OS receives these events via ACPI, + * it passes them along to this driver. The events are put into + * a queue which can be read by a userspace daemon via a char device + * that implements read() and poll(). The event queue acts as a + * circular buffer of size 64, so if there are no userspace consumers + * the kernel will not run out of memory. The char device will appear at + * /dev/wilco_event{n}, where n is some small non-negative integer, + * starting from 0. Standard ACPI events such as the battery getting + * plugged/unplugged can also come through this path, but they are + * dealt with via other paths, and are ignored here. + + * To test, you can tail the binary data with + * $ cat /dev/wilco_event0 | hexdump -ve '1/1 "%x\n"' + * and then create an event by plugging/unplugging the battery. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* ACPI Notify event code indicating event data is available. */ +#define EC_ACPI_NOTIFY_EVENT 0x90 +/* ACPI Method to execute to retrieve event data buffer from the EC. */ +#define EC_ACPI_GET_EVENT "QSET" +/* Maximum number of words in event data returned by the EC. */ +#define EC_ACPI_MAX_EVENT_WORDS 6 +#define EC_ACPI_MAX_EVENT_SIZE \ + (sizeof(struct ec_event) + (EC_ACPI_MAX_EVENT_WORDS) * sizeof(u16)) + +/* Node will appear in /dev/EVENT_DEV_NAME */ +#define EVENT_DEV_NAME "wilco_event" +#define EVENT_CLASS_NAME EVENT_DEV_NAME +#define DRV_NAME EVENT_DEV_NAME +#define EVENT_DEV_NAME_FMT (EVENT_DEV_NAME "%d") +static struct class event_class = { + .owner = THIS_MODULE, + .name = EVENT_CLASS_NAME, +}; + +/* Keep track of all the device numbers used. */ +#define EVENT_MAX_DEV 128 +static int event_major; +static DEFINE_IDA(event_ida); + +/* Size of circular queue of events. */ +#define MAX_NUM_EVENTS 64 + +/** + * struct event_device_data - Data for a Wilco EC device that responds to ACPI. + * @events: Circular queue of EC events to be provided to userspace. + * @num_events: Number of events in the queue. + * @lock: Mutex to guard the queue. + * @wq: Wait queue to notify processes when events or available or the + * device has been removed. + * @cdev: Char dev that userspace reads() and polls() from. + * @dev: Device associated with the %cdev. + * @exist: Has the device been not been removed? Once a device has been removed, + * writes, reads, and new opens will fail. + * @available: Guarantee only one client can open() file and read from queue. + * + * There will be one of these structs for each ACPI device registered. This data + * is the queue of events received from ACPI that still need to be read from + * userspace (plus a supporting lock and wait queue), as well as the device and + * char device that userspace is using, plus a flag on whether the ACPI device + * has been removed. + */ +struct event_device_data { + struct list_head events; + size_t num_events; + struct mutex lock; + wait_queue_head_t wq; + struct device dev; + struct cdev cdev; + bool exist; + atomic_t available; +}; + +/** + * struct ec_event - Extended event returned by the EC. + * @size: Number of words in structure after the size word. + * @type: Extended event type from &enum ec_event_type. + * @event: Event data words. Max count is %EC_ACPI_MAX_EVENT_WORDS. + */ +struct ec_event { + u16 size; + u16 type; + u16 event[0]; +} __packed; + +/** + * struct ec_event_entry - Event queue entry. + * @list: List node. + * @size: Number of bytes in event structure. + * @event: Extended event returned by the EC. This should be the last + * element because &struct ec_event includes a zero length array. + */ +struct ec_event_entry { + struct list_head list; + size_t size; + struct ec_event event; +}; + +/** + * enqueue_events() - Place EC events in queue to be read by userspace. + * @adev: Device the events came from. + * @buf: Buffer of event data. + * @length: Length of event data buffer. + * + * %buf contains a number of ec_event's, packed one after the other. + * Each ec_event is of variable length. Start with the first event, copy it + * into a containing ev_event_entry, store that entry in a list, move on + * to the next ec_event in buf, and repeat. + * + * Return: 0 on success or negative error code on failure. + */ +static int enqueue_events(struct acpi_device *adev, const u8 *buf, u32 length) +{ + struct event_device_data *dev_data = adev->driver_data; + struct ec_event *event; + struct ec_event_entry *entry, *oldest_entry; + size_t event_size, num_words, word_size; + u32 offset = 0; + + while (offset < length) { + event = (struct ec_event *)(buf + offset); + if (!event) + return -EINVAL; + + /* Number of 16bit event data words is size - 1 */ + num_words = event->size - 1; + word_size = num_words * sizeof(u16); + event_size = sizeof(*event) + word_size; + if (num_words > EC_ACPI_MAX_EVENT_WORDS) { + dev_err(&adev->dev, "Too many event words: %zu > %d\n", + num_words, EC_ACPI_MAX_EVENT_WORDS); + return -EOVERFLOW; + }; + + /* Ensure event does not overflow the available buffer */ + if ((offset + event_size) > length) { + dev_err(&adev->dev, "Event exceeds buffer: %zu > %d\n", + offset + event_size, length); + return -EOVERFLOW; + } + + /* Point to the next event in the buffer */ + offset += event_size; + + /* Create event entry for the queue */ + entry = kzalloc(sizeof(struct ec_event_entry) + word_size, + GFP_KERNEL); + if (!entry) + return -ENOMEM; + entry->size = event_size; + memcpy(&entry->event, event, entry->size); + + mutex_lock(&dev_data->lock); + + /* If the queue is full, delete the oldest event */ + if (dev_data->num_events >= MAX_NUM_EVENTS) { + oldest_entry = list_first_entry(&dev_data->events, + struct ec_event_entry, + list); + list_del(&oldest_entry->list); + kfree(oldest_entry); + dev_data->num_events--; + } + + /* Add this event to the queue */ + list_add_tail(&entry->list, &dev_data->events); + dev_data->num_events++; + + mutex_unlock(&dev_data->lock); + } + + return 0; +} + +/** + * event_device_notify() - Callback when EC generates an event over ACPI. + * @adev: The device that the event is coming from. + * @value: Value passed to Notify() in ACPI. + * + * This function will read the events from the device and enqueue them. + */ +static void event_device_notify(struct acpi_device *adev, u32 value) +{ + struct acpi_buffer event_buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct event_device_data *dev_data = adev->driver_data; + union acpi_object *obj; + acpi_status status; + + if (value != EC_ACPI_NOTIFY_EVENT) { + dev_err(&adev->dev, "Invalid event: 0x%08x\n", value); + return; + } + + /* Execute ACPI method to get event data buffer. */ + status = acpi_evaluate_object(adev->handle, EC_ACPI_GET_EVENT, + NULL, &event_buffer); + if (ACPI_FAILURE(status)) { + dev_err(&adev->dev, "Error executing ACPI method %s()\n", + EC_ACPI_GET_EVENT); + return; + } + + obj = (union acpi_object *)event_buffer.pointer; + if (!obj) { + dev_err(&adev->dev, "Nothing returned from %s()\n", + EC_ACPI_GET_EVENT); + return; + } + if (obj->type != ACPI_TYPE_BUFFER) { + dev_err(&adev->dev, "Invalid object returned from %s()\n", + EC_ACPI_GET_EVENT); + kfree(obj); + return; + } + if (obj->buffer.length < sizeof(struct ec_event)) { + dev_err(&adev->dev, "Invalid buffer length %d from %s()\n", + obj->buffer.length, EC_ACPI_GET_EVENT); + kfree(obj); + return; + } + + enqueue_events(adev, obj->buffer.pointer, obj->buffer.length); + kfree(obj); + + if (dev_data->num_events) + wake_up_interruptible(&dev_data->wq); +} + +static int event_open(struct inode *inode, struct file *filp) +{ + struct event_device_data *dev_data; + + dev_data = container_of(inode->i_cdev, struct event_device_data, cdev); + if (!dev_data->exist) + return -ENODEV; + + if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0) + return -EBUSY; + + /* Increase refcount on device so dev_data is not freed */ + get_device(&dev_data->dev); + nonseekable_open(inode, filp); + filp->private_data = dev_data; + + return 0; +} + +static __poll_t event_poll(struct file *filp, poll_table *wait) +{ + struct event_device_data *dev_data = filp->private_data; + __poll_t mask = 0; + + poll_wait(filp, &dev_data->wq, wait); + if (!dev_data->exist) + return EPOLLHUP; + if (dev_data->num_events) + mask |= EPOLLIN | EPOLLRDNORM | EPOLLPRI; + return mask; +} + +/** + * event_read() - Callback for passing event data to userspace via read(). + * @filp: The file we are reading from. + * @buf: Pointer to userspace buffer to fill with one event. + * @count: Number of bytes requested. Must be at least EC_ACPI_MAX_EVENT_SIZE. + * @pos: File position pointer, irrelevant since we don't support seeking. + * + * Fills the passed buffer with the data from the first event in the queue, + * removes that event from the queue. On error, the event remains in the queue. + * + * If there are no events in the the queue, then one of two things happens, + * depending on if the file was opened in nonblocking mode: If in nonblocking + * mode, then return -EAGAIN to say there's no data. If in blocking mode, then + * block until an event is available. + * + * Return: Number of bytes placed in buffer, negative error code on failure. + */ +static ssize_t event_read(struct file *filp, char __user *buf, size_t count, + loff_t *pos) +{ + struct event_device_data *dev_data = filp->private_data; + struct ec_event_entry *entry; + ssize_t n_bytes_written = 0; + int err; + + /* We only will give them the entire event at once */ + if (count != 0 && count < EC_ACPI_MAX_EVENT_SIZE) + return -EINVAL; + + mutex_lock(&dev_data->lock); + + while (dev_data->num_events == 0) { + if (filp->f_flags & O_NONBLOCK) { + mutex_unlock(&dev_data->lock); + return -EAGAIN; + } + /* Need to unlock so that data can actually get added to the + * queue, and since we recheck before use and it's just + * comparing pointers, this is safe unlocked. + */ + mutex_unlock(&dev_data->lock); + err = wait_event_interruptible(dev_data->wq, + dev_data->num_events); + if (err) + return err; + + /* Device was removed as we waited? */ + if (!dev_data->exist) + return -ENODEV; + mutex_lock(&dev_data->lock); + } + + entry = list_first_entry(&dev_data->events, + struct ec_event_entry, list); + n_bytes_written = entry->size; + if (copy_to_user(buf, &entry->event, n_bytes_written)) + return -EFAULT; + list_del(&entry->list); + kfree(entry); + dev_data->num_events--; + + mutex_unlock(&dev_data->lock); + + return n_bytes_written; +} + +static int event_release(struct inode *inode, struct file *filp) +{ + struct event_device_data *dev_data = filp->private_data; + + atomic_set(&dev_data->available, 1); + put_device(&dev_data->dev); + + return 0; +} + +static const struct file_operations event_fops = { + .open = event_open, + .poll = event_poll, + .read = event_read, + .release = event_release, + .llseek = no_llseek, + .owner = THIS_MODULE, +}; + +/** + * free_device_data() - Callback to free the event_device_data structure. + * @d: The device embedded in our device data, which we have been ref counting. + * + * This is called only after event_device_remove() has been called and all + * userspace programs have called event_release() on all the open file + * descriptors. + */ +static void free_device_data(struct device *d) +{ + struct event_device_data *dev_data; + + dev_data = container_of(d, struct event_device_data, dev); + kfree(dev_data); +} + +static void hangup_device(struct event_device_data *dev_data) +{ + mutex_lock(&dev_data->lock); + dev_data->exist = false; + mutex_unlock(&dev_data->lock); + + /* Wake up the waiting processes so they can close. */ + wake_up_interruptible(&dev_data->wq); + put_device(&dev_data->dev); +} + +/** + * event_device_add() - Callback when creating a new device. + * @adev: ACPI device that we will be receiving events from. + * + * This finds a free minor number for the device, allocates and initializes + * some device data, and creates a new device and char dev node. + * + * The device data is freed in free_device_data(), which is called when + * %dev_data->dev is release()ed. This happens after all references to + * %dev_data->dev are dropped, which happens once both event_device_remove() + * has been called and every open()ed file descriptor has been release()ed. + * + * Return: 0 on success, negative error code on failure. + */ +static int event_device_add(struct acpi_device *adev) +{ + struct event_device_data *dev_data; + int error, minor; + + minor = ida_alloc_max(&event_ida, EVENT_MAX_DEV-1, GFP_KERNEL); + if (minor < 0) { + error = minor; + dev_err(&adev->dev, "Failed to find minor number: %d", error); + return error; + } + + dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL); + if (!dev_data) { + error = -ENOMEM; + goto free_minor; + } + + /* Initialize the device data. */ + adev->driver_data = dev_data; + INIT_LIST_HEAD(&dev_data->events); + mutex_init(&dev_data->lock); + init_waitqueue_head(&dev_data->wq); + dev_data->exist = true; + atomic_set(&dev_data->available, 1); + + /* Initialize the device. */ + dev_data->dev.devt = MKDEV(event_major, minor); + dev_data->dev.class = &event_class; + dev_data->dev.release = free_device_data; + dev_set_name(&dev_data->dev, EVENT_DEV_NAME_FMT, minor); + device_initialize(&dev_data->dev); + + /* Initialize the character device, and add it to userspace. */ + cdev_init(&dev_data->cdev, &event_fops); + error = cdev_device_add(&dev_data->cdev, &dev_data->dev); + if (error) + goto free_dev_data; + + return 0; + +free_dev_data: + hangup_device(dev_data); +free_minor: + ida_simple_remove(&event_ida, minor); + return error; +} + +static int event_device_remove(struct acpi_device *adev) +{ + struct event_device_data *dev_data = adev->driver_data; + + cdev_device_del(&dev_data->cdev, &dev_data->dev); + ida_simple_remove(&event_ida, MINOR(dev_data->dev.devt)); + hangup_device(dev_data); + + return 0; +} + +static const struct acpi_device_id event_acpi_ids[] = { + { "GOOG000D", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, event_acpi_ids); + +static struct acpi_driver event_driver = { + .name = DRV_NAME, + .class = DRV_NAME, + .ids = event_acpi_ids, + .ops = { + .add = event_device_add, + .notify = event_device_notify, + .remove = event_device_remove, + }, + .owner = THIS_MODULE, +}; + +static int __init event_module_init(void) +{ + dev_t dev_num = 0; + int ret; + + ret = class_register(&event_class); + if (ret) { + pr_err(DRV_NAME ": Failed registering class: %d", ret); + return ret; + } + + /* Request device numbers, starting with minor=0. Save the major num. */ + ret = alloc_chrdev_region(&dev_num, 0, EVENT_MAX_DEV, EVENT_DEV_NAME); + if (ret) { + pr_err(DRV_NAME ": Failed allocating dev numbers: %d", ret); + goto destroy_class; + } + event_major = MAJOR(dev_num); + + ret = acpi_bus_register_driver(&event_driver); + if (ret < 0) { + pr_err(DRV_NAME ": Failed registering driver: %d\n", ret); + goto unregister_region; + } + + return 0; + +unregister_region: + unregister_chrdev_region(MKDEV(event_major, 0), EVENT_MAX_DEV); +destroy_class: + class_unregister(&event_class); + ida_destroy(&event_ida); + return ret; +} + +static void __exit event_module_exit(void) +{ + acpi_bus_unregister_driver(&event_driver); + unregister_chrdev_region(MKDEV(event_major, 0), EVENT_MAX_DEV); + class_unregister(&event_class); + ida_destroy(&event_ida); +} + +module_init(event_module_init); +module_exit(event_module_exit); + +MODULE_AUTHOR("Nick Crews "); +MODULE_DESCRIPTION("Wilco EC ACPI event driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); -- cgit v1.2.3-59-g8ed1b From 1210d1e6bad1e7ccccb19627b880a50d7c15dd51 Mon Sep 17 00:00:00 2001 From: Nick Crews Date: Tue, 21 May 2019 13:20:45 -0600 Subject: platform/chrome: wilco_ec: Add telemetry char device interface The Wilco Embedded Controller is able to send telemetry data which is useful for enterprise applications. A daemon running on the OS sends a command to the EC via a write() to a char device, and can read the response with a read(). The write() request is verified by the driver to ensure that it is performing only one of the whitelisted commands, and that no extraneous data is being transmitted to the EC. The response is passed directly back to the reader with no modification. The character device will appear as /dev/wilco_telemN, where N is some small non-negative integer, starting with 0. Only one process may have the file descriptor open at a time. The calling userspace program needs to keep the device file descriptor open between the calls to write() and read() in order to preserve the response. Up to 32 bytes will be available for reading. For testing purposes, try requesting the EC's firmware build date, by sending the WILCO_EC_TELEM_GET_VERSION command with argument index=3. i.e. write [0x38, 0x00, 0x03] to the device node. An ASCII string of the build date is returned. Signed-off-by: Nick Crews Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/wilco_ec/Kconfig | 7 + drivers/platform/chrome/wilco_ec/Makefile | 2 + drivers/platform/chrome/wilco_ec/core.c | 13 + drivers/platform/chrome/wilco_ec/debugfs.c | 2 +- drivers/platform/chrome/wilco_ec/telemetry.c | 450 +++++++++++++++++++++++++++ include/linux/platform_data/wilco-ec.h | 2 + 6 files changed, 475 insertions(+), 1 deletion(-) create mode 100644 drivers/platform/chrome/wilco_ec/telemetry.c (limited to 'drivers/platform/chrome') diff --git a/drivers/platform/chrome/wilco_ec/Kconfig b/drivers/platform/chrome/wilco_ec/Kconfig index 2c8f6f15f28f..90336874af59 100644 --- a/drivers/platform/chrome/wilco_ec/Kconfig +++ b/drivers/platform/chrome/wilco_ec/Kconfig @@ -27,3 +27,10 @@ config WILCO_EC_EVENTS (such as power state changes) to userspace. The EC sends the events over ACPI, and a driver queues up the events to be read by a userspace daemon from /dev/wilco_event using read() and poll(). + +config WILCO_EC_TELEMETRY + tristate "Enable querying telemetry data from EC" + depends on WILCO_EC + help + If you say Y here, you get support to query EC telemetry data from + /dev/wilco_telem0 using write() and then read(). diff --git a/drivers/platform/chrome/wilco_ec/Makefile b/drivers/platform/chrome/wilco_ec/Makefile index 4d8a5f068f8b..bc817164596e 100644 --- a/drivers/platform/chrome/wilco_ec/Makefile +++ b/drivers/platform/chrome/wilco_ec/Makefile @@ -6,3 +6,5 @@ wilco_ec_debugfs-objs := debugfs.o obj-$(CONFIG_WILCO_EC_DEBUGFS) += wilco_ec_debugfs.o wilco_ec_events-objs := event.o obj-$(CONFIG_WILCO_EC_EVENTS) += wilco_ec_events.o +wilco_ec_telem-objs := telemetry.o +obj-$(CONFIG_WILCO_EC_TELEMETRY) += wilco_ec_telem.o diff --git a/drivers/platform/chrome/wilco_ec/core.c b/drivers/platform/chrome/wilco_ec/core.c index 45cf3a5ed062..3724bf4b77c6 100644 --- a/drivers/platform/chrome/wilco_ec/core.c +++ b/drivers/platform/chrome/wilco_ec/core.c @@ -93,8 +93,20 @@ static int wilco_ec_probe(struct platform_device *pdev) goto unregister_rtc; } + /* Register child device that will be found by the telemetry driver. */ + ec->telem_pdev = platform_device_register_data(dev, "wilco_telem", + PLATFORM_DEVID_AUTO, + ec, sizeof(*ec)); + if (IS_ERR(ec->telem_pdev)) { + dev_err(dev, "Failed to create telemetry platform device\n"); + ret = PTR_ERR(ec->telem_pdev); + goto remove_sysfs; + } + return 0; +remove_sysfs: + wilco_ec_remove_sysfs(ec); unregister_rtc: platform_device_unregister(ec->rtc_pdev); unregister_debugfs: @@ -109,6 +121,7 @@ static int wilco_ec_remove(struct platform_device *pdev) struct wilco_ec_device *ec = platform_get_drvdata(pdev); wilco_ec_remove_sysfs(ec); + platform_device_unregister(ec->telem_pdev); platform_device_unregister(ec->rtc_pdev); if (ec->debugfs_pdev) platform_device_unregister(ec->debugfs_pdev); diff --git a/drivers/platform/chrome/wilco_ec/debugfs.c b/drivers/platform/chrome/wilco_ec/debugfs.c index 281ec595e8e0..8d65a1e2f1a3 100644 --- a/drivers/platform/chrome/wilco_ec/debugfs.c +++ b/drivers/platform/chrome/wilco_ec/debugfs.c @@ -16,7 +16,7 @@ #define DRV_NAME "wilco-ec-debugfs" -/* The 256 raw bytes will take up more space when represented as a hex string */ +/* The raw bytes will take up more space when represented as a hex string */ #define FORMATTED_BUFFER_SIZE (EC_MAILBOX_DATA_SIZE * 4) struct wilco_ec_debugfs { diff --git a/drivers/platform/chrome/wilco_ec/telemetry.c b/drivers/platform/chrome/wilco_ec/telemetry.c new file mode 100644 index 000000000000..94cdc166c840 --- /dev/null +++ b/drivers/platform/chrome/wilco_ec/telemetry.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Telemetry communication for Wilco EC + * + * Copyright 2019 Google LLC + * + * The Wilco Embedded Controller is able to send telemetry data + * which is useful for enterprise applications. A daemon running on + * the OS sends a command to the EC via a write() to a char device, + * and can read the response with a read(). The write() request is + * verified by the driver to ensure that it is performing only one + * of the whitelisted commands, and that no extraneous data is + * being transmitted to the EC. The response is passed directly + * back to the reader with no modification. + * + * The character device will appear as /dev/wilco_telemN, where N + * is some small non-negative integer, starting with 0. Only one + * process may have the file descriptor open at a time. The calling + * userspace program needs to keep the device file descriptor open + * between the calls to write() and read() in order to preserve the + * response. Up to 32 bytes will be available for reading. + * + * For testing purposes, try requesting the EC's firmware build + * date, by sending the WILCO_EC_TELEM_GET_VERSION command with + * argument index=3. i.e. write [0x38, 0x00, 0x03] + * to the device node. An ASCII string of the build date is + * returned. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TELEM_DEV_NAME "wilco_telem" +#define TELEM_CLASS_NAME TELEM_DEV_NAME +#define DRV_NAME TELEM_DEV_NAME +#define TELEM_DEV_NAME_FMT (TELEM_DEV_NAME "%d") +static struct class telem_class = { + .owner = THIS_MODULE, + .name = TELEM_CLASS_NAME, +}; + +/* Keep track of all the device numbers used. */ +#define TELEM_MAX_DEV 128 +static int telem_major; +static DEFINE_IDA(telem_ida); + +/* EC telemetry command codes */ +#define WILCO_EC_TELEM_GET_LOG 0x99 +#define WILCO_EC_TELEM_GET_VERSION 0x38 +#define WILCO_EC_TELEM_GET_FAN_INFO 0x2E +#define WILCO_EC_TELEM_GET_DIAG_INFO 0xFA +#define WILCO_EC_TELEM_GET_TEMP_INFO 0x95 +#define WILCO_EC_TELEM_GET_TEMP_READ 0x2C +#define WILCO_EC_TELEM_GET_BATT_EXT_INFO 0x07 + +#define TELEM_ARGS_SIZE_MAX 30 + +/** + * struct wilco_ec_telem_request - Telemetry command and arguments sent to EC. + * @command: One of WILCO_EC_TELEM_GET_* command codes. + * @reserved: Must be 0. + * @args: The first N bytes are one of telem_args_get_* structs, the rest is 0. + */ +struct wilco_ec_telem_request { + u8 command; + u8 reserved; + u8 args[TELEM_ARGS_SIZE_MAX]; +} __packed; + +/* + * The following telem_args_get_* structs are embedded within the |args| field + * of wilco_ec_telem_request. + */ + +struct telem_args_get_log { + u8 log_type; + u8 log_index; +} __packed; + +/* + * Get a piece of info about the EC firmware version: + * 0 = label + * 1 = svn_rev + * 2 = model_no + * 3 = build_date + * 4 = frio_version + */ +struct telem_args_get_version { + u8 index; +} __packed; + +struct telem_args_get_fan_info { + u8 command; + u8 fan_number; + u8 arg; +} __packed; + +struct telem_args_get_diag_info { + u8 type; + u8 sub_type; +} __packed; + +struct telem_args_get_temp_info { + u8 command; + u8 index; + u8 field; + u8 zone; +} __packed; + +struct telem_args_get_temp_read { + u8 sensor_index; +} __packed; + +struct telem_args_get_batt_ext_info { + u8 var_args[5]; +} __packed; + +/** + * check_telem_request() - Ensure that a request from userspace is valid. + * @rq: Request buffer copied from userspace. + * @size: Number of bytes copied from userspace. + * + * Return: 0 if valid, -EINVAL if bad command or reserved byte is non-zero, + * -EMSGSIZE if the request is too long. + * + * We do not want to allow userspace to send arbitrary telemetry commands to + * the EC. Therefore we check to ensure that + * 1. The request follows the format of struct wilco_ec_telem_request. + * 2. The supplied command code is one of the whitelisted commands. + * 3. The request only contains the necessary data for the header and arguments. + */ +static int check_telem_request(struct wilco_ec_telem_request *rq, + size_t size) +{ + size_t max_size = offsetof(struct wilco_ec_telem_request, args); + + if (rq->reserved) + return -EINVAL; + + switch (rq->command) { + case WILCO_EC_TELEM_GET_LOG: + max_size += sizeof(struct telem_args_get_log); + break; + case WILCO_EC_TELEM_GET_VERSION: + max_size += sizeof(struct telem_args_get_version); + break; + case WILCO_EC_TELEM_GET_FAN_INFO: + max_size += sizeof(struct telem_args_get_fan_info); + break; + case WILCO_EC_TELEM_GET_DIAG_INFO: + max_size += sizeof(struct telem_args_get_diag_info); + break; + case WILCO_EC_TELEM_GET_TEMP_INFO: + max_size += sizeof(struct telem_args_get_temp_info); + break; + case WILCO_EC_TELEM_GET_TEMP_READ: + max_size += sizeof(struct telem_args_get_temp_read); + break; + case WILCO_EC_TELEM_GET_BATT_EXT_INFO: + max_size += sizeof(struct telem_args_get_batt_ext_info); + break; + default: + return -EINVAL; + } + + return (size <= max_size) ? 0 : -EMSGSIZE; +} + +/** + * struct telem_device_data - Data for a Wilco EC device that queries telemetry. + * @cdev: Char dev that userspace reads and polls from. + * @dev: Device associated with the %cdev. + * @ec: Wilco EC that we will be communicating with using the mailbox interface. + * @available: Boolean of if the device can be opened. + */ +struct telem_device_data { + struct device dev; + struct cdev cdev; + struct wilco_ec_device *ec; + atomic_t available; +}; + +#define TELEM_RESPONSE_SIZE EC_MAILBOX_DATA_SIZE + +/** + * struct telem_session_data - Data that exists between open() and release(). + * @dev_data: Pointer to get back to the device data and EC. + * @request: Command and arguments sent to EC. + * @response: Response buffer of data from EC. + * @has_msg: Is there data available to read from a previous write? + */ +struct telem_session_data { + struct telem_device_data *dev_data; + struct wilco_ec_telem_request request; + u8 response[TELEM_RESPONSE_SIZE]; + bool has_msg; +}; + +/** + * telem_open() - Callback for when the device node is opened. + * @inode: inode for this char device node. + * @filp: file for this char device node. + * + * We need to ensure that after writing a command to the device, + * the same userspace process reads the corresponding result. + * Therefore, we increment a refcount on opening the device, so that + * only one process can communicate with the EC at a time. + * + * Return: 0 on success, or negative error code on failure. + */ +static int telem_open(struct inode *inode, struct file *filp) +{ + struct telem_device_data *dev_data; + struct telem_session_data *sess_data; + + /* Ensure device isn't already open */ + dev_data = container_of(inode->i_cdev, struct telem_device_data, cdev); + if (atomic_cmpxchg(&dev_data->available, 1, 0) == 0) + return -EBUSY; + + get_device(&dev_data->dev); + + sess_data = kzalloc(sizeof(*sess_data), GFP_KERNEL); + if (!sess_data) { + atomic_set(&dev_data->available, 1); + return -ENOMEM; + } + sess_data->dev_data = dev_data; + sess_data->has_msg = false; + + nonseekable_open(inode, filp); + filp->private_data = sess_data; + + return 0; +} + +static ssize_t telem_write(struct file *filp, const char __user *buf, + size_t count, loff_t *pos) +{ + struct telem_session_data *sess_data = filp->private_data; + struct wilco_ec_message msg = {}; + int ret; + + if (count > sizeof(sess_data->request)) + return -EMSGSIZE; + if (copy_from_user(&sess_data->request, buf, count)) + return -EFAULT; + ret = check_telem_request(&sess_data->request, count); + if (ret < 0) + return ret; + + memset(sess_data->response, 0, sizeof(sess_data->response)); + msg.type = WILCO_EC_MSG_TELEMETRY; + msg.request_data = &sess_data->request; + msg.request_size = sizeof(sess_data->request); + msg.response_data = sess_data->response; + msg.response_size = sizeof(sess_data->response); + + ret = wilco_ec_mailbox(sess_data->dev_data->ec, &msg); + if (ret < 0) + return ret; + if (ret != sizeof(sess_data->response)) + return -EMSGSIZE; + + sess_data->has_msg = true; + + return count; +} + +static ssize_t telem_read(struct file *filp, char __user *buf, size_t count, + loff_t *pos) +{ + struct telem_session_data *sess_data = filp->private_data; + + if (!sess_data->has_msg) + return -ENODATA; + if (count > sizeof(sess_data->response)) + return -EINVAL; + + if (copy_to_user(buf, sess_data->response, count)) + return -EFAULT; + + sess_data->has_msg = false; + + return count; +} + +static int telem_release(struct inode *inode, struct file *filp) +{ + struct telem_session_data *sess_data = filp->private_data; + + atomic_set(&sess_data->dev_data->available, 1); + put_device(&sess_data->dev_data->dev); + kfree(sess_data); + + return 0; +} + +static const struct file_operations telem_fops = { + .open = telem_open, + .write = telem_write, + .read = telem_read, + .release = telem_release, + .llseek = no_llseek, + .owner = THIS_MODULE, +}; + +/** + * telem_device_free() - Callback to free the telem_device_data structure. + * @d: The device embedded in our device data, which we have been ref counting. + * + * Once all open file descriptors are closed and the device has been removed, + * the refcount of the device will fall to 0 and this will be called. + */ +static void telem_device_free(struct device *d) +{ + struct telem_device_data *dev_data; + + dev_data = container_of(d, struct telem_device_data, dev); + kfree(dev_data); +} + +/** + * telem_device_probe() - Callback when creating a new device. + * @pdev: platform device that we will be receiving telems from. + * + * This finds a free minor number for the device, allocates and initializes + * some device data, and creates a new device and char dev node. + * + * Return: 0 on success, negative error code on failure. + */ +static int telem_device_probe(struct platform_device *pdev) +{ + struct telem_device_data *dev_data; + int error, minor; + + /* Get the next available device number */ + minor = ida_alloc_max(&telem_ida, TELEM_MAX_DEV-1, GFP_KERNEL); + if (minor < 0) { + error = minor; + dev_err(&pdev->dev, "Failed to find minor number: %d", error); + return error; + } + + dev_data = kzalloc(sizeof(*dev_data), GFP_KERNEL); + if (!dev_data) { + ida_simple_remove(&telem_ida, minor); + return -ENOMEM; + } + + /* Initialize the device data */ + dev_data->ec = dev_get_platdata(&pdev->dev); + atomic_set(&dev_data->available, 1); + platform_set_drvdata(pdev, dev_data); + + /* Initialize the device */ + dev_data->dev.devt = MKDEV(telem_major, minor); + dev_data->dev.class = &telem_class; + dev_data->dev.release = telem_device_free; + dev_set_name(&dev_data->dev, TELEM_DEV_NAME_FMT, minor); + device_initialize(&dev_data->dev); + + /* Initialize the character device and add it to userspace */; + cdev_init(&dev_data->cdev, &telem_fops); + error = cdev_device_add(&dev_data->cdev, &dev_data->dev); + if (error) { + put_device(&dev_data->dev); + ida_simple_remove(&telem_ida, minor); + return error; + } + + return 0; +} + +static int telem_device_remove(struct platform_device *pdev) +{ + struct telem_device_data *dev_data = platform_get_drvdata(pdev); + + cdev_device_del(&dev_data->cdev, &dev_data->dev); + put_device(&dev_data->dev); + ida_simple_remove(&telem_ida, MINOR(dev_data->dev.devt)); + + return 0; +} + +static struct platform_driver telem_driver = { + .probe = telem_device_probe, + .remove = telem_device_remove, + .driver = { + .name = DRV_NAME, + }, +}; + +static int __init telem_module_init(void) +{ + dev_t dev_num = 0; + int ret; + + ret = class_register(&telem_class); + if (ret) { + pr_err(DRV_NAME ": Failed registering class: %d", ret); + return ret; + } + + /* Request the kernel for device numbers, starting with minor=0 */ + ret = alloc_chrdev_region(&dev_num, 0, TELEM_MAX_DEV, TELEM_DEV_NAME); + if (ret) { + pr_err(DRV_NAME ": Failed allocating dev numbers: %d", ret); + goto destroy_class; + } + telem_major = MAJOR(dev_num); + + ret = platform_driver_register(&telem_driver); + if (ret < 0) { + pr_err(DRV_NAME ": Failed registering driver: %d\n", ret); + goto unregister_region; + } + + return 0; + +unregister_region: + unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV); +destroy_class: + class_unregister(&telem_class); + ida_destroy(&telem_ida); + return ret; +} + +static void __exit telem_module_exit(void) +{ + platform_driver_unregister(&telem_driver); + unregister_chrdev_region(MKDEV(telem_major, 0), TELEM_MAX_DEV); + class_unregister(&telem_class); + ida_destroy(&telem_ida); +} + +module_init(telem_module_init); +module_exit(telem_module_exit); + +MODULE_AUTHOR("Nick Crews "); +MODULE_DESCRIPTION("Wilco EC telemetry driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/include/linux/platform_data/wilco-ec.h b/include/linux/platform_data/wilco-ec.h index e3ce9ce49b11..ad03b586a095 100644 --- a/include/linux/platform_data/wilco-ec.h +++ b/include/linux/platform_data/wilco-ec.h @@ -29,6 +29,7 @@ * @data_size: Size of the data buffer used for EC communication. * @debugfs_pdev: The child platform_device used by the debugfs sub-driver. * @rtc_pdev: The child platform_device used by the RTC sub-driver. + * @telem_pdev: The child platform_device used by the telemetry sub-driver. */ struct wilco_ec_device { struct device *dev; @@ -40,6 +41,7 @@ struct wilco_ec_device { size_t data_size; struct platform_device *debugfs_pdev; struct platform_device *rtc_pdev; + struct platform_device *telem_pdev; }; /** -- cgit v1.2.3-59-g8ed1b From 79e3f1d3db3d99ac7ae4f29b00da545df231a5a7 Mon Sep 17 00:00:00 2001 From: Raul E Rangel Date: Mon, 3 Jun 2019 12:16:49 -0600 Subject: platform/chrome: wilco_ec: Add version sysfs entries Add the ability to extract version information from the EC. Example Output: $ cd /sys/bus/platform/devices/GOOG000C:00 $ tail build_date build_revision version model_number ==> build_date <== 04/25/19 ==> build_revision <== d2592cae0 ==> version <== 00.00.14 ==> model_number <== 08B6 Signed-off-by: Raul E Rangel Reviewed-by: Nick Crews Signed-off-by: Enric Balletbo i Serra --- Documentation/ABI/testing/sysfs-platform-wilco-ec | 31 +++++++++ drivers/platform/chrome/wilco_ec/sysfs.c | 79 +++++++++++++++++++++++ 2 files changed, 110 insertions(+) (limited to 'drivers/platform/chrome') diff --git a/Documentation/ABI/testing/sysfs-platform-wilco-ec b/Documentation/ABI/testing/sysfs-platform-wilco-ec index 8e5d6eee44db..8827a734f933 100644 --- a/Documentation/ABI/testing/sysfs-platform-wilco-ec +++ b/Documentation/ABI/testing/sysfs-platform-wilco-ec @@ -7,3 +7,34 @@ Description: want to run their device headless or with a dock. Input should be parseable by kstrtou8() to 0 or 1. + +What: /sys/bus/platform/devices/GOOG000C\:00/build_date +Date: May 2019 +KernelVersion: 5.3 +Description: + Display Wilco Embedded Controller firmware build date. + Output will a MM/DD/YY string. + +What: /sys/bus/platform/devices/GOOG000C\:00/build_revision +Date: May 2019 +KernelVersion: 5.3 +Description: + Display Wilco Embedded Controller build revision. + Output will a version string be similar to the example below: + d2592cae0 + +What: /sys/bus/platform/devices/GOOG000C\:00/model_number +Date: May 2019 +KernelVersion: 5.3 +Description: + Display Wilco Embedded Controller model number. + Output will a version string be similar to the example below: + 08B6 + +What: /sys/bus/platform/devices/GOOG000C\:00/version +Date: May 2019 +KernelVersion: 5.3 +Description: + Display Wilco Embedded Controller firmware version. + The format of the string is x.y.z. Where x is major, y is minor + and z is the build number. For example: 95.00.06 diff --git a/drivers/platform/chrome/wilco_ec/sysfs.c b/drivers/platform/chrome/wilco_ec/sysfs.c index f84f0480460a..3b86a21005d3 100644 --- a/drivers/platform/chrome/wilco_ec/sysfs.c +++ b/drivers/platform/chrome/wilco_ec/sysfs.c @@ -23,6 +23,25 @@ struct boot_on_ac_request { u8 reserved7; } __packed; +#define CMD_EC_INFO 0x38 +enum get_ec_info_op { + CMD_GET_EC_LABEL = 0, + CMD_GET_EC_REV = 1, + CMD_GET_EC_MODEL = 2, + CMD_GET_EC_BUILD_DATE = 3, +}; + +struct get_ec_info_req { + u8 cmd; /* Always CMD_EC_INFO */ + u8 reserved; + u8 op; /* One of enum get_ec_info_op */ +} __packed; + +struct get_ec_info_resp { + u8 reserved[2]; + char value[9]; /* __nonstring: might not be null terminated */ +} __packed; + static ssize_t boot_on_ac_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -57,8 +76,68 @@ static ssize_t boot_on_ac_store(struct device *dev, static DEVICE_ATTR_WO(boot_on_ac); +static ssize_t get_info(struct device *dev, char *buf, enum get_ec_info_op op) +{ + struct wilco_ec_device *ec = dev_get_drvdata(dev); + struct get_ec_info_req req = { .cmd = CMD_EC_INFO, .op = op }; + struct get_ec_info_resp resp; + int ret; + + struct wilco_ec_message msg = { + .type = WILCO_EC_MSG_LEGACY, + .request_data = &req, + .request_size = sizeof(req), + .response_data = &resp, + .response_size = sizeof(resp), + }; + + ret = wilco_ec_mailbox(ec, &msg); + if (ret < 0) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%.*s\n", (int)sizeof(resp.value), + (char *)&resp.value); +} + +static ssize_t version_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return get_info(dev, buf, CMD_GET_EC_LABEL); +} + +static DEVICE_ATTR_RO(version); + +static ssize_t build_revision_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return get_info(dev, buf, CMD_GET_EC_REV); +} + +static DEVICE_ATTR_RO(build_revision); + +static ssize_t build_date_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return get_info(dev, buf, CMD_GET_EC_BUILD_DATE); +} + +static DEVICE_ATTR_RO(build_date); + +static ssize_t model_number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return get_info(dev, buf, CMD_GET_EC_MODEL); +} + +static DEVICE_ATTR_RO(model_number); + + static struct attribute *wilco_dev_attrs[] = { &dev_attr_boot_on_ac.attr, + &dev_attr_build_date.attr, + &dev_attr_build_revision.attr, + &dev_attr_model_number.attr, + &dev_attr_version.attr, NULL, }; -- cgit v1.2.3-59-g8ed1b From 81bc8c03e1b06a79df6a855ccf55af44965dbdba Mon Sep 17 00:00:00 2001 From: YueHaibing Date: Thu, 30 May 2019 16:49:32 +0800 Subject: platform/chrome: cros_ec: Make some symbols static Fix sparse warning: drivers/platform/chrome/cros_ec_debugfs.c:256:30: warning: symbol 'cros_ec_console_log_fops' was not declared. Should it be static? drivers/platform/chrome/cros_ec_debugfs.c:265:30: warning: symbol 'cros_ec_pdinfo_fops' was not declared. Should it be static? drivers/platform/chrome/cros_ec_lightbar.c:550:24: warning: symbol 'cros_ec_lightbar_attr_group' was not declared. Should it be static? drivers/platform/chrome/cros_ec_sysfs.c:338:24: warning: symbol 'cros_ec_attr_group' was not declared. Should it be static? drivers/platform/chrome/cros_ec_vbc.c:104:24: warning: symbol 'cros_ec_vbc_attr_group' was not declared. Should it be static? drivers/platform/chrome/cros_ec_lpc.c:408:25: warning: symbol 'cros_ec_lpc_pm_ops' was not declared. Should it be static? Reported-by: Hulk Robot Signed-off-by: YueHaibing Reviewed-by: Benson Leung Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/cros_ec_debugfs.c | 4 ++-- drivers/platform/chrome/cros_ec_lightbar.c | 2 +- drivers/platform/chrome/cros_ec_lpc.c | 2 +- drivers/platform/chrome/cros_ec_sysfs.c | 2 +- drivers/platform/chrome/cros_ec_vbc.c | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/platform/chrome') diff --git a/drivers/platform/chrome/cros_ec_debugfs.c b/drivers/platform/chrome/cros_ec_debugfs.c index 4c2a27f6a6d0..4578eb3e0731 100644 --- a/drivers/platform/chrome/cros_ec_debugfs.c +++ b/drivers/platform/chrome/cros_ec_debugfs.c @@ -241,7 +241,7 @@ static ssize_t cros_ec_pdinfo_read(struct file *file, read_buf, p - read_buf); } -const struct file_operations cros_ec_console_log_fops = { +static const struct file_operations cros_ec_console_log_fops = { .owner = THIS_MODULE, .open = cros_ec_console_log_open, .read = cros_ec_console_log_read, @@ -250,7 +250,7 @@ const struct file_operations cros_ec_console_log_fops = { .release = cros_ec_console_log_release, }; -const struct file_operations cros_ec_pdinfo_fops = { +static const struct file_operations cros_ec_pdinfo_fops = { .owner = THIS_MODULE, .open = simple_open, .read = cros_ec_pdinfo_read, diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c index d30a6650b0b5..23a82ee4c785 100644 --- a/drivers/platform/chrome/cros_ec_lightbar.c +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -547,7 +547,7 @@ static struct attribute *__lb_cmds_attrs[] = { NULL, }; -struct attribute_group cros_ec_lightbar_attr_group = { +static struct attribute_group cros_ec_lightbar_attr_group = { .name = "lightbar", .attrs = __lb_cmds_attrs, }; diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index c9c240fbe7c6..aaa21803633a 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -405,7 +405,7 @@ static int cros_ec_lpc_resume(struct device *dev) } #endif -const struct dev_pm_ops cros_ec_lpc_pm_ops = { +static const struct dev_pm_ops cros_ec_lpc_pm_ops = { SET_LATE_SYSTEM_SLEEP_PM_OPS(cros_ec_lpc_suspend, cros_ec_lpc_resume) }; diff --git a/drivers/platform/chrome/cros_ec_sysfs.c b/drivers/platform/chrome/cros_ec_sysfs.c index fe0b7614ae1b..3edb237bf8ed 100644 --- a/drivers/platform/chrome/cros_ec_sysfs.c +++ b/drivers/platform/chrome/cros_ec_sysfs.c @@ -335,7 +335,7 @@ static umode_t cros_ec_ctrl_visible(struct kobject *kobj, return a->mode; } -struct attribute_group cros_ec_attr_group = { +static struct attribute_group cros_ec_attr_group = { .attrs = __ec_attrs, .is_visible = cros_ec_ctrl_visible, }; diff --git a/drivers/platform/chrome/cros_ec_vbc.c b/drivers/platform/chrome/cros_ec_vbc.c index 8392a1ec33a7..2aaefed87eb4 100644 --- a/drivers/platform/chrome/cros_ec_vbc.c +++ b/drivers/platform/chrome/cros_ec_vbc.c @@ -101,7 +101,7 @@ static struct bin_attribute *cros_ec_vbc_bin_attrs[] = { NULL }; -struct attribute_group cros_ec_vbc_attr_group = { +static struct attribute_group cros_ec_vbc_attr_group = { .name = "vbc", .bin_attrs = cros_ec_vbc_bin_attrs, }; -- cgit v1.2.3-59-g8ed1b From e90716a6612150218aaff1fd47ca6de954100a06 Mon Sep 17 00:00:00 2001 From: Tim Wawrzynczak Date: Thu, 13 Jun 2019 11:57:36 -0600 Subject: platform/chrome: cros_ec_debugfs: Add debugfs entry to retrieve EC uptime The new debugfs entry 'uptime' is being made available to userspace so that a userspace daemon can synchronize EC logs with host time. Signed-off-by: Tim Wawrzynczak [rework based on Tim's first approach] Signed-off-by: Enric Balletbo i Serra Tested-by: Tim Wawrzynczak --- Documentation/ABI/testing/debugfs-cros-ec | 8 +++++++ drivers/platform/chrome/cros_ec_debugfs.c | 38 +++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 Documentation/ABI/testing/debugfs-cros-ec (limited to 'drivers/platform/chrome') diff --git a/Documentation/ABI/testing/debugfs-cros-ec b/Documentation/ABI/testing/debugfs-cros-ec new file mode 100644 index 000000000000..c91da2d374aa --- /dev/null +++ b/Documentation/ABI/testing/debugfs-cros-ec @@ -0,0 +1,8 @@ +What: /sys/kernel/debug//uptime +Date: June 2019 +KernelVersion: 5.3 +Description: + A u32 providing the time since EC booted in ms. This is + is used for synchronizing the AP host time with the EC + log. An error is returned if the command is not supported + by the EC or there is a communication problem. diff --git a/drivers/platform/chrome/cros_ec_debugfs.c b/drivers/platform/chrome/cros_ec_debugfs.c index 4578eb3e0731..970ba13df9a1 100644 --- a/drivers/platform/chrome/cros_ec_debugfs.c +++ b/drivers/platform/chrome/cros_ec_debugfs.c @@ -241,6 +241,34 @@ static ssize_t cros_ec_pdinfo_read(struct file *file, read_buf, p - read_buf); } +static ssize_t cros_ec_uptime_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct cros_ec_debugfs *debug_info = file->private_data; + struct cros_ec_device *ec_dev = debug_info->ec->ec_dev; + struct { + struct cros_ec_command cmd; + struct ec_response_uptime_info resp; + } __packed msg = {}; + struct ec_response_uptime_info *resp; + char read_buf[32]; + int ret; + + resp = (struct ec_response_uptime_info *)&msg.resp; + + msg.cmd.command = EC_CMD_GET_UPTIME_INFO; + msg.cmd.insize = sizeof(*resp); + + ret = cros_ec_cmd_xfer_status(ec_dev, &msg.cmd); + if (ret < 0) + return ret; + + ret = scnprintf(read_buf, sizeof(read_buf), "%u\n", + resp->time_since_ec_boot_ms); + + return simple_read_from_buffer(user_buf, count, ppos, read_buf, ret); +} + static const struct file_operations cros_ec_console_log_fops = { .owner = THIS_MODULE, .open = cros_ec_console_log_open, @@ -257,6 +285,13 @@ static const struct file_operations cros_ec_pdinfo_fops = { .llseek = default_llseek, }; +const struct file_operations cros_ec_uptime_fops = { + .owner = THIS_MODULE, + .open = simple_open, + .read = cros_ec_uptime_read, + .llseek = default_llseek, +}; + static int ec_read_version_supported(struct cros_ec_dev *ec) { struct ec_params_get_cmd_versions_v1 *params; @@ -408,6 +443,9 @@ static int cros_ec_debugfs_probe(struct platform_device *pd) debugfs_create_file("pdinfo", 0444, debug_info->dir, debug_info, &cros_ec_pdinfo_fops); + debugfs_create_file("uptime", 0444, debug_info->dir, debug_info, + &cros_ec_uptime_fops); + ec->debug_info = debug_info; dev_set_drvdata(&pd->dev, ec); -- cgit v1.2.3-59-g8ed1b From cb78a163c1510a35c4bee2576c38a7dc95cf1ba1 Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Fri, 14 Jun 2019 11:51:24 +0200 Subject: platform/chrome: cros_ec_debugfs: Fix kernel-doc comment first line kernel-doc comments have a prescribed format. To be _particularly_ correct we should also capitalise the brief description and terminate it with a period. Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/cros_ec_debugfs.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/platform/chrome') diff --git a/drivers/platform/chrome/cros_ec_debugfs.c b/drivers/platform/chrome/cros_ec_debugfs.c index 970ba13df9a1..cd3fb9c22a44 100644 --- a/drivers/platform/chrome/cros_ec_debugfs.c +++ b/drivers/platform/chrome/cros_ec_debugfs.c @@ -25,7 +25,8 @@ #define CIRC_ADD(idx, size, value) (((idx) + (value)) & ((size) - 1)) -/* struct cros_ec_debugfs - ChromeOS EC debugging information +/** + * struct cros_ec_debugfs - EC debugging information. * * @ec: EC device this debugfs information belongs to * @dir: dentry for debugfs files -- cgit v1.2.3-59-g8ed1b From 35b52b3315a21d3314f859814717ca3583660f73 Mon Sep 17 00:00:00 2001 From: kbuild test robot Date: Tue, 18 Jun 2019 10:32:31 +0200 Subject: platform/chrome: cros_ec_debugfs: cros_ec_uptime_fops can be static Fixes: 909447f683b3 ("platform/chrome: cros_ec_debugfs: Add debugfs entry to retrieve EC uptime") Signed-off-by: kbuild test robot Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/cros_ec_debugfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/platform/chrome') diff --git a/drivers/platform/chrome/cros_ec_debugfs.c b/drivers/platform/chrome/cros_ec_debugfs.c index cd3fb9c22a44..7ee060743844 100644 --- a/drivers/platform/chrome/cros_ec_debugfs.c +++ b/drivers/platform/chrome/cros_ec_debugfs.c @@ -286,7 +286,7 @@ static const struct file_operations cros_ec_pdinfo_fops = { .llseek = default_llseek, }; -const struct file_operations cros_ec_uptime_fops = { +static const struct file_operations cros_ec_uptime_fops = { .owner = THIS_MODULE, .open = simple_open, .read = cros_ec_uptime_read, -- cgit v1.2.3-59-g8ed1b From aa8b8f9f9bef4696b6d9f1ca4d0de2b403db65c5 Mon Sep 17 00:00:00 2001 From: Nick Crews Date: Fri, 14 Jun 2019 14:56:31 -0600 Subject: platform/chrome: wilco_ec: Fix unreleased lock in event_read() When copying an event to userspace failed, the event queue lock was never released. This fixes that. Reported-by: Dan Carpenter Signed-off-by: Nick Crews Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/wilco_ec/event.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/platform/chrome') diff --git a/drivers/platform/chrome/wilco_ec/event.c b/drivers/platform/chrome/wilco_ec/event.c index 4d2776f77dbd..1eed55681598 100644 --- a/drivers/platform/chrome/wilco_ec/event.c +++ b/drivers/platform/chrome/wilco_ec/event.c @@ -342,7 +342,7 @@ static ssize_t event_read(struct file *filp, char __user *buf, size_t count, struct ec_event_entry, list); n_bytes_written = entry->size; if (copy_to_user(buf, &entry->event, n_bytes_written)) - return -EFAULT; + n_bytes_written = -EFAULT; list_del(&entry->list); kfree(entry); dev_data->num_events--; -- cgit v1.2.3-59-g8ed1b From 4116fd25c5262e90427f9840e861a8553374a867 Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Fri, 14 Jun 2019 23:43:00 +0200 Subject: platform/chrome: cros_ec_lpc: Merge cros_ec_lpc and cros_ec_lpc_reg The cros_ec_lpc_reg files are only used by the cros_ec_lpc core and there isn't logical separation between them. So, merge those files into the cros_ec_lpc also allowing us to drop the header file used for the interface between the two. Signed-off-by: Enric Balletbo i Serra Reviewed-by: Nick Crews --- drivers/platform/chrome/Makefile | 2 +- drivers/platform/chrome/cros_ec_lpc.c | 97 +++++++++++++++++++++++++++- drivers/platform/chrome/cros_ec_lpc_reg.c | 101 ------------------------------ drivers/platform/chrome/cros_ec_lpc_reg.h | 45 ------------- 4 files changed, 97 insertions(+), 148 deletions(-) delete mode 100644 drivers/platform/chrome/cros_ec_lpc_reg.c delete mode 100644 drivers/platform/chrome/cros_ec_lpc_reg.h (limited to 'drivers/platform/chrome') diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index c5583c48d1e5..5a2bcef4b07c 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -10,7 +10,7 @@ obj-$(CONFIG_CROS_EC_I2C) += cros_ec_i2c.o obj-$(CONFIG_CROS_EC_ISHTP) += cros_ec_ishtp.o obj-$(CONFIG_CROS_EC_RPMSG) += cros_ec_rpmsg.o obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.o -cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_reg.o +cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpcs-$(CONFIG_CROS_EC_LPC_MEC) += cros_ec_lpc_mec.o obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpcs.o obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o cros_ec_trace.o diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index aaa21803633a..8d06addec5e2 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -23,7 +23,7 @@ #include #include -#include "cros_ec_lpc_reg.h" +#include "cros_ec_lpc_mec.h" #define DRV_NAME "cros_ec_lpcs" #define ACPI_DRV_NAME "GOOG0004" @@ -31,6 +31,101 @@ /* True if ACPI device is present */ static bool cros_ec_lpc_acpi_device_found; +static u8 lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest) +{ + int sum = 0; + int i; + + for (i = 0; i < length; ++i) { + dest[i] = inb(offset + i); + sum += dest[i]; + } + + /* Return checksum of all bytes read */ + return sum; +} + +static u8 lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg) +{ + int sum = 0; + int i; + + for (i = 0; i < length; ++i) { + outb(msg[i], offset + i); + sum += msg[i]; + } + + /* Return checksum of all bytes written */ + return sum; +} + +#ifdef CONFIG_CROS_EC_LPC_MEC + +static u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, + u8 *dest) +{ + int in_range = cros_ec_lpc_mec_in_range(offset, length); + + if (in_range < 0) + return 0; + + return in_range ? + cros_ec_lpc_io_bytes_mec(MEC_IO_READ, + offset - EC_HOST_CMD_REGION0, + length, dest) : + lpc_read_bytes(offset, length, dest); +} + +static u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, + u8 *msg) +{ + int in_range = cros_ec_lpc_mec_in_range(offset, length); + + if (in_range < 0) + return 0; + + return in_range ? + cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, + offset - EC_HOST_CMD_REGION0, + length, msg) : + lpc_write_bytes(offset, length, msg); +} + +static void cros_ec_lpc_reg_init(void) +{ + cros_ec_lpc_mec_init(EC_HOST_CMD_REGION0, + EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE); +} + +static void cros_ec_lpc_reg_destroy(void) +{ + cros_ec_lpc_mec_destroy(); +} + +#else /* CONFIG_CROS_EC_LPC_MEC */ + +static u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, + u8 *dest) +{ + return lpc_read_bytes(offset, length, dest); +} + +static u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, + u8 *msg) +{ + return lpc_write_bytes(offset, length, msg); +} + +static void cros_ec_lpc_reg_init(void) +{ +} + +static void cros_ec_lpc_reg_destroy(void) +{ +} + +#endif /* CONFIG_CROS_EC_LPC_MEC */ + static int ec_response_timed_out(void) { unsigned long one_second = jiffies + HZ; diff --git a/drivers/platform/chrome/cros_ec_lpc_reg.c b/drivers/platform/chrome/cros_ec_lpc_reg.c deleted file mode 100644 index 0f5cd0ac8b49..000000000000 --- a/drivers/platform/chrome/cros_ec_lpc_reg.c +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// LPC interface for ChromeOS Embedded Controller -// -// Copyright (C) 2016 Google, Inc - -#include -#include -#include - -#include "cros_ec_lpc_mec.h" - -static u8 lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest) -{ - int i; - int sum = 0; - - for (i = 0; i < length; ++i) { - dest[i] = inb(offset + i); - sum += dest[i]; - } - - /* Return checksum of all bytes read */ - return sum; -} - -static u8 lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg) -{ - int i; - int sum = 0; - - for (i = 0; i < length; ++i) { - outb(msg[i], offset + i); - sum += msg[i]; - } - - /* Return checksum of all bytes written */ - return sum; -} - -#ifdef CONFIG_CROS_EC_LPC_MEC - -u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest) -{ - int in_range = cros_ec_lpc_mec_in_range(offset, length); - - if (in_range < 0) - return 0; - - return in_range ? - cros_ec_lpc_io_bytes_mec(MEC_IO_READ, - offset - EC_HOST_CMD_REGION0, - length, dest) : - lpc_read_bytes(offset, length, dest); -} - -u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg) -{ - int in_range = cros_ec_lpc_mec_in_range(offset, length); - - if (in_range < 0) - return 0; - - return in_range ? - cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, - offset - EC_HOST_CMD_REGION0, - length, msg) : - lpc_write_bytes(offset, length, msg); -} - -void cros_ec_lpc_reg_init(void) -{ - cros_ec_lpc_mec_init(EC_HOST_CMD_REGION0, - EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE); -} - -void cros_ec_lpc_reg_destroy(void) -{ - cros_ec_lpc_mec_destroy(); -} - -#else /* CONFIG_CROS_EC_LPC_MEC */ - -u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest) -{ - return lpc_read_bytes(offset, length, dest); -} - -u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg) -{ - return lpc_write_bytes(offset, length, msg); -} - -void cros_ec_lpc_reg_init(void) -{ -} - -void cros_ec_lpc_reg_destroy(void) -{ -} - -#endif /* CONFIG_CROS_EC_LPC_MEC */ diff --git a/drivers/platform/chrome/cros_ec_lpc_reg.h b/drivers/platform/chrome/cros_ec_lpc_reg.h deleted file mode 100644 index 416fd2572182..000000000000 --- a/drivers/platform/chrome/cros_ec_lpc_reg.h +++ /dev/null @@ -1,45 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * LPC interface for ChromeOS Embedded Controller - * - * Copyright (C) 2016 Google, Inc - */ - -#ifndef __CROS_EC_LPC_REG_H -#define __CROS_EC_LPC_REG_H - -/** - * cros_ec_lpc_read_bytes - Read bytes from a given LPC-mapped address. - * Returns 8-bit checksum of all bytes read. - * - * @offset: Base read address - * @length: Number of bytes to read - * @dest: Destination buffer - */ -u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest); - -/** - * cros_ec_lpc_write_bytes - Write bytes to a given LPC-mapped address. - * Returns 8-bit checksum of all bytes written. - * - * @offset: Base write address - * @length: Number of bytes to write - * @msg: Write data buffer - */ -u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg); - -/** - * cros_ec_lpc_reg_init - * - * Initialize register I/O. - */ -void cros_ec_lpc_reg_init(void); - -/** - * cros_ec_lpc_reg_destroy - * - * Cleanup reg I/O. - */ -void cros_ec_lpc_reg_destroy(void); - -#endif /* __CROS_EC_LPC_REG_H */ -- cgit v1.2.3-59-g8ed1b From 22c040fa21b604b9b3d88645e108fb2f0a74474b Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Fri, 14 Jun 2019 23:43:01 +0200 Subject: platform/chrome: cros_ec_lpc: Choose Microchip EC at runtime On many boards, communication between the kernel and the Embedded Controller happens over an LPC bus. In these cases, the kernel config CONFIG_CROS_EC_LPC is enabled. Some of these LPC boards contain a Microchip Embedded Controller (MEC) that is different from the regular EC. On these devices, the same LPC bus is used, but the protocol is a little different. In these cases, the CONFIG_CROS_EC_LPC_MEC kernel config is enabled. Currently, the kernel decides at compile-time whether or not to use the MEC variant, and, when that kernel option is selected it breaks the other boards. We would like a kind of runtime detection to avoid this. This patch adds that detection mechanism by probing the protocol at runtime, first we assume that a MEC variant is connected, and if the protocol fails it fallbacks to the regular EC. This adds a bit of overload because we try to read twice on those LPC boards that doesn't contain a MEC variant, but is a better solution than having to select the EC variant at compile-time. While here also fix the alignment in Kconfig file for this config option replacing the spaces by tabs. Signed-off-by: Enric Balletbo i Serra Reviewed-by: Ezequiel Garcia Tested-by: Nick Crews Reviewed-by: Nick Crews --- drivers/platform/chrome/Kconfig | 29 ++---- drivers/platform/chrome/Makefile | 3 +- drivers/platform/chrome/cros_ec_lpc.c | 158 ++++++++++++++++--------------- drivers/platform/chrome/wilco_ec/Kconfig | 2 +- 4 files changed, 95 insertions(+), 97 deletions(-) (limited to 'drivers/platform/chrome') diff --git a/drivers/platform/chrome/Kconfig b/drivers/platform/chrome/Kconfig index 0b3c2b9ffd2f..8010e7e395ce 100644 --- a/drivers/platform/chrome/Kconfig +++ b/drivers/platform/chrome/Kconfig @@ -95,28 +95,17 @@ config CROS_EC_SPI 'pre-amble' bytes before the response actually starts. config CROS_EC_LPC - tristate "ChromeOS Embedded Controller (LPC)" - depends on MFD_CROS_EC && ACPI && (X86 || COMPILE_TEST) - help - If you say Y here, you get support for talking to the ChromeOS EC - over an LPC bus. This uses a simple byte-level protocol with a - checksum. This is used for userspace access only. The kernel - typically has its own communication methods. - - To compile this driver as a module, choose M here: the - module will be called cros_ec_lpc. - -config CROS_EC_LPC_MEC - bool "ChromeOS Embedded Controller LPC Microchip EC (MEC) variant" - depends on CROS_EC_LPC - default n + tristate "ChromeOS Embedded Controller (LPC)" + depends on MFD_CROS_EC && ACPI && (X86 || COMPILE_TEST) help - If you say Y here, a variant LPC protocol for the Microchip EC - will be used. Note that this variant is not backward compatible - with non-Microchip ECs. + If you say Y here, you get support for talking to the ChromeOS EC + over an LPC bus, including the LPC Microchip EC (MEC) variant. + This uses a simple byte-level protocol with a checksum. This is + used for userspace access only. The kernel typically has its own + communication methods. - If you have a ChromeOS Embedded Controller Microchip EC variant - choose Y here. + To compile this driver as a module, choose M here: the + module will be called cros_ec_lpcs. config CROS_EC_PROTO bool diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile index 5a2bcef4b07c..fd0af05cc14c 100644 --- a/drivers/platform/chrome/Makefile +++ b/drivers/platform/chrome/Makefile @@ -10,8 +10,7 @@ obj-$(CONFIG_CROS_EC_I2C) += cros_ec_i2c.o obj-$(CONFIG_CROS_EC_ISHTP) += cros_ec_ishtp.o obj-$(CONFIG_CROS_EC_RPMSG) += cros_ec_rpmsg.o obj-$(CONFIG_CROS_EC_SPI) += cros_ec_spi.o -cros_ec_lpcs-objs := cros_ec_lpc.o -cros_ec_lpcs-$(CONFIG_CROS_EC_LPC_MEC) += cros_ec_lpc_mec.o +cros_ec_lpcs-objs := cros_ec_lpc.o cros_ec_lpc_mec.o obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpcs.o obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o cros_ec_trace.o obj-$(CONFIG_CROS_KBD_LED_BACKLIGHT) += cros_kbd_led_backlight.o diff --git a/drivers/platform/chrome/cros_ec_lpc.c b/drivers/platform/chrome/cros_ec_lpc.c index 8d06addec5e2..2c44c7f3322a 100644 --- a/drivers/platform/chrome/cros_ec_lpc.c +++ b/drivers/platform/chrome/cros_ec_lpc.c @@ -31,7 +31,26 @@ /* True if ACPI device is present */ static bool cros_ec_lpc_acpi_device_found; -static u8 lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest) +/** + * struct lpc_driver_ops - LPC driver operations + * @read: Copy length bytes from EC address offset into buffer dest. Returns + * the 8-bit checksum of all bytes read. + * @write: Copy length bytes from buffer msg into EC address offset. Returns + * the 8-bit checksum of all bytes written. + */ +struct lpc_driver_ops { + u8 (*read)(unsigned int offset, unsigned int length, u8 *dest); + u8 (*write)(unsigned int offset, unsigned int length, const u8 *msg); +}; + +static struct lpc_driver_ops cros_ec_lpc_ops = { }; + +/* + * A generic instance of the read function of struct lpc_driver_ops, used for + * the LPC EC. + */ +static u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, + u8 *dest) { int sum = 0; int i; @@ -45,7 +64,12 @@ static u8 lpc_read_bytes(unsigned int offset, unsigned int length, u8 *dest) return sum; } -static u8 lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg) +/* + * A generic instance of the write function of struct lpc_driver_ops, used for + * the LPC EC. + */ +static u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, + const u8 *msg) { int sum = 0; int i; @@ -59,10 +83,12 @@ static u8 lpc_write_bytes(unsigned int offset, unsigned int length, u8 *msg) return sum; } -#ifdef CONFIG_CROS_EC_LPC_MEC - -static u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, - u8 *dest) +/* + * An instance of the read function of struct lpc_driver_ops, used for the + * MEC variant of LPC EC. + */ +static u8 cros_ec_lpc_mec_read_bytes(unsigned int offset, unsigned int length, + u8 *dest) { int in_range = cros_ec_lpc_mec_in_range(offset, length); @@ -73,11 +99,15 @@ static u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, cros_ec_lpc_io_bytes_mec(MEC_IO_READ, offset - EC_HOST_CMD_REGION0, length, dest) : - lpc_read_bytes(offset, length, dest); + cros_ec_lpc_read_bytes(offset, length, dest); } -static u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, - u8 *msg) +/* + * An instance of the write function of struct lpc_driver_ops, used for the + * MEC variant of LPC EC. + */ +static u8 cros_ec_lpc_mec_write_bytes(unsigned int offset, unsigned int length, + const u8 *msg) { int in_range = cros_ec_lpc_mec_in_range(offset, length); @@ -87,45 +117,10 @@ static u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, return in_range ? cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE, offset - EC_HOST_CMD_REGION0, - length, msg) : - lpc_write_bytes(offset, length, msg); -} - -static void cros_ec_lpc_reg_init(void) -{ - cros_ec_lpc_mec_init(EC_HOST_CMD_REGION0, - EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE); + length, (u8 *)msg) : + cros_ec_lpc_write_bytes(offset, length, msg); } -static void cros_ec_lpc_reg_destroy(void) -{ - cros_ec_lpc_mec_destroy(); -} - -#else /* CONFIG_CROS_EC_LPC_MEC */ - -static u8 cros_ec_lpc_read_bytes(unsigned int offset, unsigned int length, - u8 *dest) -{ - return lpc_read_bytes(offset, length, dest); -} - -static u8 cros_ec_lpc_write_bytes(unsigned int offset, unsigned int length, - u8 *msg) -{ - return lpc_write_bytes(offset, length, msg); -} - -static void cros_ec_lpc_reg_init(void) -{ -} - -static void cros_ec_lpc_reg_destroy(void) -{ -} - -#endif /* CONFIG_CROS_EC_LPC_MEC */ - static int ec_response_timed_out(void) { unsigned long one_second = jiffies + HZ; @@ -133,7 +128,7 @@ static int ec_response_timed_out(void) usleep_range(200, 300); do { - if (!(cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_CMD, 1, &data) & + if (!(cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_CMD, 1, &data) & EC_LPC_STATUS_BUSY_MASK)) return 0; usleep_range(100, 200); @@ -153,11 +148,11 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec, ret = cros_ec_prepare_tx(ec, msg); /* Write buffer */ - cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout); + cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PACKET, ret, ec->dout); /* Here we go */ sum = EC_COMMAND_PROTOCOL_3; - cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_CMD, 1, &sum); + cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum); if (ec_response_timed_out()) { dev_warn(ec->dev, "EC responsed timed out\n"); @@ -166,15 +161,15 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec, } /* Check result */ - msg->result = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_DATA, 1, &sum); + msg->result = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum); ret = cros_ec_check_result(ec, msg); if (ret) goto done; /* Read back response */ dout = (u8 *)&response; - sum = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET, sizeof(response), - dout); + sum = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET, sizeof(response), + dout); msg->result = response.result; @@ -187,9 +182,9 @@ static int cros_ec_pkt_xfer_lpc(struct cros_ec_device *ec, } /* Read response and process checksum */ - sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PACKET + - sizeof(response), response.data_len, - msg->data); + sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PACKET + + sizeof(response), response.data_len, + msg->data); if (sum) { dev_err(ec->dev, @@ -229,17 +224,17 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, sum = msg->command + args.flags + args.command_version + args.data_size; /* Copy data and update checksum */ - sum += cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_PARAM, msg->outsize, - msg->data); + sum += cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_PARAM, msg->outsize, + msg->data); /* Finalize checksum and write args */ args.checksum = sum; - cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args), - (u8 *)&args); + cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_ARGS, sizeof(args), + (u8 *)&args); /* Here we go */ sum = msg->command; - cros_ec_lpc_write_bytes(EC_LPC_ADDR_HOST_CMD, 1, &sum); + cros_ec_lpc_ops.write(EC_LPC_ADDR_HOST_CMD, 1, &sum); if (ec_response_timed_out()) { dev_warn(ec->dev, "EC responsed timed out\n"); @@ -248,14 +243,13 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, } /* Check result */ - msg->result = cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_DATA, 1, &sum); + msg->result = cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_DATA, 1, &sum); ret = cros_ec_check_result(ec, msg); if (ret) goto done; /* Read back args */ - cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_ARGS, sizeof(args), - (u8 *)&args); + cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_ARGS, sizeof(args), (u8 *)&args); if (args.data_size > msg->insize) { dev_err(ec->dev, @@ -269,8 +263,8 @@ static int cros_ec_cmd_xfer_lpc(struct cros_ec_device *ec, sum = msg->command + args.flags + args.command_version + args.data_size; /* Read response and update checksum */ - sum += cros_ec_lpc_read_bytes(EC_LPC_ADDR_HOST_PARAM, args.data_size, - msg->data); + sum += cros_ec_lpc_ops.read(EC_LPC_ADDR_HOST_PARAM, args.data_size, + msg->data); /* Verify checksum */ if (args.checksum != sum) { @@ -300,13 +294,13 @@ static int cros_ec_lpc_readmem(struct cros_ec_device *ec, unsigned int offset, /* fixed length */ if (bytes) { - cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + offset, bytes, s); + cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + offset, bytes, s); return bytes; } /* string */ for (; i < EC_MEMMAP_SIZE; i++, s++) { - cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + i, 1, s); + cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + i, 1, s); cnt++; if (!*s) break; @@ -343,10 +337,25 @@ static int cros_ec_lpc_probe(struct platform_device *pdev) return -EBUSY; } - cros_ec_lpc_read_bytes(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf); + /* + * Read the mapped ID twice, the first one is assuming the + * EC is a Microchip Embedded Controller (MEC) variant, if the + * protocol fails, fallback to the non MEC variant and try to + * read again the ID. + */ + cros_ec_lpc_ops.read = cros_ec_lpc_mec_read_bytes; + cros_ec_lpc_ops.write = cros_ec_lpc_mec_write_bytes; + cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, buf); if (buf[0] != 'E' || buf[1] != 'C') { - dev_err(dev, "EC ID not detected\n"); - return -ENODEV; + /* Re-assign read/write operations for the non MEC variant */ + cros_ec_lpc_ops.read = cros_ec_lpc_read_bytes; + cros_ec_lpc_ops.write = cros_ec_lpc_write_bytes; + cros_ec_lpc_ops.read(EC_LPC_ADDR_MEMMAP + EC_MEMMAP_ID, 2, + buf); + if (buf[0] != 'E' || buf[1] != 'C') { + dev_err(dev, "EC ID not detected\n"); + return -ENODEV; + } } if (!devm_request_region(dev, EC_HOST_CMD_REGION0, @@ -541,13 +550,14 @@ static int __init cros_ec_lpc_init(void) return -ENODEV; } - cros_ec_lpc_reg_init(); + cros_ec_lpc_mec_init(EC_HOST_CMD_REGION0, + EC_LPC_ADDR_MEMMAP + EC_MEMMAP_SIZE); /* Register the driver */ ret = platform_driver_register(&cros_ec_lpc_driver); if (ret) { pr_err(DRV_NAME ": can't register driver: %d\n", ret); - cros_ec_lpc_reg_destroy(); + cros_ec_lpc_mec_destroy(); return ret; } @@ -557,7 +567,7 @@ static int __init cros_ec_lpc_init(void) if (ret) { pr_err(DRV_NAME ": can't register device: %d\n", ret); platform_driver_unregister(&cros_ec_lpc_driver); - cros_ec_lpc_reg_destroy(); + cros_ec_lpc_mec_destroy(); } } @@ -569,7 +579,7 @@ static void __exit cros_ec_lpc_exit(void) if (!cros_ec_lpc_acpi_device_found) platform_device_unregister(&cros_ec_lpc_device); platform_driver_unregister(&cros_ec_lpc_driver); - cros_ec_lpc_reg_destroy(); + cros_ec_lpc_mec_destroy(); } module_init(cros_ec_lpc_init); diff --git a/drivers/platform/chrome/wilco_ec/Kconfig b/drivers/platform/chrome/wilco_ec/Kconfig index 90336874af59..5b7e1801980c 100644 --- a/drivers/platform/chrome/wilco_ec/Kconfig +++ b/drivers/platform/chrome/wilco_ec/Kconfig @@ -1,6 +1,6 @@ config WILCO_EC tristate "ChromeOS Wilco Embedded Controller" - depends on ACPI && X86 && CROS_EC_LPC && CROS_EC_LPC_MEC + depends on ACPI && X86 && CROS_EC_LPC help If you say Y here, you get support for talking to the ChromeOS Wilco EC over an eSPI bus. This uses a simple byte-level protocol -- cgit v1.2.3-59-g8ed1b From 9eecd07b34507de9d6a9c264d13d30e1ee5fabe8 Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Fri, 14 Jun 2019 23:43:02 +0200 Subject: platform/chrome: cros_ec_lpc_mec: Fix kernel-doc comment first line kernel-doc comments have a prescribed format. To be _particularly_ correct we should also capitalise the brief description and terminate it with a period. Signed-off-by: Enric Balletbo i Serra Reviewed-by: Nick Crews --- drivers/platform/chrome/cros_ec_lpc_mec.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'drivers/platform/chrome') diff --git a/drivers/platform/chrome/cros_ec_lpc_mec.c b/drivers/platform/chrome/cros_ec_lpc_mec.c index d8890bafb55d..9035b17e8c86 100644 --- a/drivers/platform/chrome/cros_ec_lpc_mec.c +++ b/drivers/platform/chrome/cros_ec_lpc_mec.c @@ -17,12 +17,10 @@ static struct mutex io_mutex; static u16 mec_emi_base, mec_emi_end; -/* - * cros_ec_lpc_mec_emi_write_address - * - * Initialize EMI read / write at a given address. +/** + * cros_ec_lpc_mec_emi_write_address() - Initialize EMI at a given address. * - * @addr: Starting read / write address + * @addr: Starting read / write address * @access_type: Type of access, typically 32-bit auto-increment */ static void cros_ec_lpc_mec_emi_write_address(u16 addr, @@ -61,15 +59,15 @@ int cros_ec_lpc_mec_in_range(unsigned int offset, unsigned int length) return 0; } -/* - * cros_ec_lpc_io_bytes_mec - Read / write bytes to MEC EMI port +/** + * cros_ec_lpc_io_bytes_mec() - Read / write bytes to MEC EMI port. * * @io_type: MEC_IO_READ or MEC_IO_WRITE, depending on request * @offset: Base read / write address * @length: Number of bytes to read / write * @buf: Destination / source buffer * - * @return 8-bit checksum of all bytes read / written + * Return: 8-bit checksum of all bytes read / written */ u8 cros_ec_lpc_io_bytes_mec(enum cros_ec_lpc_mec_io_type io_type, unsigned int offset, unsigned int length, -- cgit v1.2.3-59-g8ed1b From 1d333ef3d599026897fb11b4f89f9f8f30838139 Mon Sep 17 00:00:00 2001 From: Nick Crews Date: Mon, 24 Jun 2019 13:00:42 -0600 Subject: platform/chrome: wilco_ec: Add circular buffer as event queue The current implementation of the event queue both wastes space using a doubly linked list and isn't super obvious in how it behaves. This converts the queue to an actual circular buffer. The size of the queue is a tunable module parameter. This also fixes a few other things: - A memory leak that occurred when the ACPI device was removed, but the events were not freed from the queue. - Now kfree() the oldest event from outside all locks. - Add newline to logging messages. - Add helper macros to calculate size of events. - Remove unneeded lock around a check for dev_data->exist in hangup_device(). - Remove an unneeded null event pointer check in enqueue_events(). - Correct some comments. Signed-off-by: Nick Crews Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/wilco_ec/event.c | 258 ++++++++++++++++++------------- 1 file changed, 149 insertions(+), 109 deletions(-) (limited to 'drivers/platform/chrome') diff --git a/drivers/platform/chrome/wilco_ec/event.c b/drivers/platform/chrome/wilco_ec/event.c index 1eed55681598..dba3d445623f 100644 --- a/drivers/platform/chrome/wilco_ec/event.c +++ b/drivers/platform/chrome/wilco_ec/event.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -69,12 +70,110 @@ static DEFINE_IDA(event_ida); /* Size of circular queue of events. */ #define MAX_NUM_EVENTS 64 +/** + * struct ec_event - Extended event returned by the EC. + * @size: Number of 16bit words in structure after the size word. + * @type: Extended event type, meaningless for us. + * @event: Event data words. Max count is %EC_ACPI_MAX_EVENT_WORDS. + */ +struct ec_event { + u16 size; + u16 type; + u16 event[0]; +} __packed; + +#define ec_event_num_words(ev) (ev->size - 1) +#define ec_event_size(ev) (sizeof(*ev) + (ec_event_num_words(ev) * sizeof(u16))) + +/** + * struct ec_event_queue - Circular queue for events. + * @capacity: Number of elements the queue can hold. + * @head: Next index to write to. + * @tail: Next index to read from. + * @entries: Array of events. + */ +struct ec_event_queue { + int capacity; + int head; + int tail; + struct ec_event *entries[0]; +}; + +/* Maximum number of events to store in ec_event_queue */ +static int queue_size = 64; +module_param(queue_size, int, 0644); + +static struct ec_event_queue *event_queue_new(int capacity) +{ + struct ec_event_queue *q; + + q = kzalloc(struct_size(q, entries, capacity), GFP_KERNEL); + if (!q) + return NULL; + + q->capacity = capacity; + + return q; +} + +static inline bool event_queue_empty(struct ec_event_queue *q) +{ + /* head==tail when both full and empty, but head==NULL when empty */ + return q->head == q->tail && !q->entries[q->head]; +} + +static inline bool event_queue_full(struct ec_event_queue *q) +{ + /* head==tail when both full and empty, but head!=NULL when full */ + return q->head == q->tail && q->entries[q->head]; +} + +static struct ec_event *event_queue_pop(struct ec_event_queue *q) +{ + struct ec_event *ev; + + if (event_queue_empty(q)) + return NULL; + + ev = q->entries[q->tail]; + q->entries[q->tail] = NULL; + q->tail = (q->tail + 1) % q->capacity; + + return ev; +} + +/* + * If full, overwrite the oldest event and return it so the caller + * can kfree it. If not full, return NULL. + */ +static struct ec_event *event_queue_push(struct ec_event_queue *q, + struct ec_event *ev) +{ + struct ec_event *popped = NULL; + + if (event_queue_full(q)) + popped = event_queue_pop(q); + q->entries[q->head] = ev; + q->head = (q->head + 1) % q->capacity; + + return popped; +} + +static void event_queue_free(struct ec_event_queue *q) +{ + struct ec_event *event; + + while ((event = event_queue_pop(q)) != NULL) + kfree(event); + + kfree(q); +} + /** * struct event_device_data - Data for a Wilco EC device that responds to ACPI. * @events: Circular queue of EC events to be provided to userspace. - * @num_events: Number of events in the queue. - * @lock: Mutex to guard the queue. - * @wq: Wait queue to notify processes when events or available or the + * @queue_lock: Protect the queue from simultaneous read/writes. + * @wq: Wait queue to notify processes when events are available or the * device has been removed. * @cdev: Char dev that userspace reads() and polls() from. * @dev: Device associated with the %cdev. @@ -84,14 +183,13 @@ static DEFINE_IDA(event_ida); * * There will be one of these structs for each ACPI device registered. This data * is the queue of events received from ACPI that still need to be read from - * userspace (plus a supporting lock and wait queue), as well as the device and - * char device that userspace is using, plus a flag on whether the ACPI device - * has been removed. + * userspace, the device and char device that userspace is using, a wait queue + * used to notify different threads when something has changed, plus a flag + * on whether the ACPI device has been removed. */ struct event_device_data { - struct list_head events; - size_t num_events; - struct mutex lock; + struct ec_event_queue *events; + spinlock_t queue_lock; wait_queue_head_t wq; struct device dev; struct cdev cdev; @@ -99,31 +197,6 @@ struct event_device_data { atomic_t available; }; -/** - * struct ec_event - Extended event returned by the EC. - * @size: Number of words in structure after the size word. - * @type: Extended event type from &enum ec_event_type. - * @event: Event data words. Max count is %EC_ACPI_MAX_EVENT_WORDS. - */ -struct ec_event { - u16 size; - u16 type; - u16 event[0]; -} __packed; - -/** - * struct ec_event_entry - Event queue entry. - * @list: List node. - * @size: Number of bytes in event structure. - * @event: Extended event returned by the EC. This should be the last - * element because &struct ec_event includes a zero length array. - */ -struct ec_event_entry { - struct list_head list; - size_t size; - struct ec_event event; -}; - /** * enqueue_events() - Place EC events in queue to be read by userspace. * @adev: Device the events came from. @@ -132,7 +205,7 @@ struct ec_event_entry { * * %buf contains a number of ec_event's, packed one after the other. * Each ec_event is of variable length. Start with the first event, copy it - * into a containing ev_event_entry, store that entry in a list, move on + * into a persistent ec_event, store that entry in the queue, move on * to the next ec_event in buf, and repeat. * * Return: 0 on success or negative error code on failure. @@ -140,25 +213,20 @@ struct ec_event_entry { static int enqueue_events(struct acpi_device *adev, const u8 *buf, u32 length) { struct event_device_data *dev_data = adev->driver_data; - struct ec_event *event; - struct ec_event_entry *entry, *oldest_entry; - size_t event_size, num_words, word_size; + struct ec_event *event, *queue_event, *old_event; + size_t num_words, event_size; u32 offset = 0; while (offset < length) { event = (struct ec_event *)(buf + offset); - if (!event) - return -EINVAL; - /* Number of 16bit event data words is size - 1 */ - num_words = event->size - 1; - word_size = num_words * sizeof(u16); - event_size = sizeof(*event) + word_size; + num_words = ec_event_num_words(event); + event_size = ec_event_size(event); if (num_words > EC_ACPI_MAX_EVENT_WORDS) { dev_err(&adev->dev, "Too many event words: %zu > %d\n", num_words, EC_ACPI_MAX_EVENT_WORDS); return -EOVERFLOW; - }; + } /* Ensure event does not overflow the available buffer */ if ((offset + event_size) > length) { @@ -170,31 +238,15 @@ static int enqueue_events(struct acpi_device *adev, const u8 *buf, u32 length) /* Point to the next event in the buffer */ offset += event_size; - /* Create event entry for the queue */ - entry = kzalloc(sizeof(struct ec_event_entry) + word_size, - GFP_KERNEL); - if (!entry) + /* Copy event into the queue */ + queue_event = kmemdup(event, event_size, GFP_KERNEL); + if (!queue_event) return -ENOMEM; - entry->size = event_size; - memcpy(&entry->event, event, entry->size); - - mutex_lock(&dev_data->lock); - - /* If the queue is full, delete the oldest event */ - if (dev_data->num_events >= MAX_NUM_EVENTS) { - oldest_entry = list_first_entry(&dev_data->events, - struct ec_event_entry, - list); - list_del(&oldest_entry->list); - kfree(oldest_entry); - dev_data->num_events--; - } - - /* Add this event to the queue */ - list_add_tail(&entry->list, &dev_data->events); - dev_data->num_events++; - - mutex_unlock(&dev_data->lock); + spin_lock(&dev_data->queue_lock); + old_event = event_queue_push(dev_data->events, queue_event); + spin_unlock(&dev_data->queue_lock); + kfree(old_event); + wake_up_interruptible(&dev_data->wq); } return 0; @@ -210,7 +262,6 @@ static int enqueue_events(struct acpi_device *adev, const u8 *buf, u32 length) static void event_device_notify(struct acpi_device *adev, u32 value) { struct acpi_buffer event_buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - struct event_device_data *dev_data = adev->driver_data; union acpi_object *obj; acpi_status status; @@ -249,9 +300,6 @@ static void event_device_notify(struct acpi_device *adev, u32 value) enqueue_events(adev, obj->buffer.pointer, obj->buffer.length); kfree(obj); - - if (dev_data->num_events) - wake_up_interruptible(&dev_data->wq); } static int event_open(struct inode *inode, struct file *filp) @@ -267,7 +315,7 @@ static int event_open(struct inode *inode, struct file *filp) /* Increase refcount on device so dev_data is not freed */ get_device(&dev_data->dev); - nonseekable_open(inode, filp); + stream_open(inode, filp); filp->private_data = dev_data; return 0; @@ -281,7 +329,7 @@ static __poll_t event_poll(struct file *filp, poll_table *wait) poll_wait(filp, &dev_data->wq, wait); if (!dev_data->exist) return EPOLLHUP; - if (dev_data->num_events) + if (!event_queue_empty(dev_data->events)) mask |= EPOLLIN | EPOLLRDNORM | EPOLLPRI; return mask; } @@ -293,8 +341,7 @@ static __poll_t event_poll(struct file *filp, poll_table *wait) * @count: Number of bytes requested. Must be at least EC_ACPI_MAX_EVENT_SIZE. * @pos: File position pointer, irrelevant since we don't support seeking. * - * Fills the passed buffer with the data from the first event in the queue, - * removes that event from the queue. On error, the event remains in the queue. + * Removes the first event from the queue, places it in the passed buffer. * * If there are no events in the the queue, then one of two things happens, * depending on if the file was opened in nonblocking mode: If in nonblocking @@ -307,7 +354,7 @@ static ssize_t event_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) { struct event_device_data *dev_data = filp->private_data; - struct ec_event_entry *entry; + struct ec_event *event; ssize_t n_bytes_written = 0; int err; @@ -315,39 +362,29 @@ static ssize_t event_read(struct file *filp, char __user *buf, size_t count, if (count != 0 && count < EC_ACPI_MAX_EVENT_SIZE) return -EINVAL; - mutex_lock(&dev_data->lock); - - while (dev_data->num_events == 0) { - if (filp->f_flags & O_NONBLOCK) { - mutex_unlock(&dev_data->lock); + spin_lock(&dev_data->queue_lock); + while (event_queue_empty(dev_data->events)) { + spin_unlock(&dev_data->queue_lock); + if (filp->f_flags & O_NONBLOCK) return -EAGAIN; - } - /* Need to unlock so that data can actually get added to the - * queue, and since we recheck before use and it's just - * comparing pointers, this is safe unlocked. - */ - mutex_unlock(&dev_data->lock); + err = wait_event_interruptible(dev_data->wq, - dev_data->num_events); + !event_queue_empty(dev_data->events) || + !dev_data->exist); if (err) return err; /* Device was removed as we waited? */ if (!dev_data->exist) return -ENODEV; - mutex_lock(&dev_data->lock); + spin_lock(&dev_data->queue_lock); } - - entry = list_first_entry(&dev_data->events, - struct ec_event_entry, list); - n_bytes_written = entry->size; - if (copy_to_user(buf, &entry->event, n_bytes_written)) + event = event_queue_pop(dev_data->events); + spin_unlock(&dev_data->queue_lock); + n_bytes_written = ec_event_size(event); + if (copy_to_user(buf, event, n_bytes_written)) n_bytes_written = -EFAULT; - list_del(&entry->list); - kfree(entry); - dev_data->num_events--; - - mutex_unlock(&dev_data->lock); + kfree(event); return n_bytes_written; } @@ -384,15 +421,13 @@ static void free_device_data(struct device *d) struct event_device_data *dev_data; dev_data = container_of(d, struct event_device_data, dev); + event_queue_free(dev_data->events); kfree(dev_data); } static void hangup_device(struct event_device_data *dev_data) { - mutex_lock(&dev_data->lock); dev_data->exist = false; - mutex_unlock(&dev_data->lock); - /* Wake up the waiting processes so they can close. */ wake_up_interruptible(&dev_data->wq); put_device(&dev_data->dev); @@ -420,7 +455,7 @@ static int event_device_add(struct acpi_device *adev) minor = ida_alloc_max(&event_ida, EVENT_MAX_DEV-1, GFP_KERNEL); if (minor < 0) { error = minor; - dev_err(&adev->dev, "Failed to find minor number: %d", error); + dev_err(&adev->dev, "Failed to find minor number: %d\n", error); return error; } @@ -432,8 +467,13 @@ static int event_device_add(struct acpi_device *adev) /* Initialize the device data. */ adev->driver_data = dev_data; - INIT_LIST_HEAD(&dev_data->events); - mutex_init(&dev_data->lock); + dev_data->events = event_queue_new(queue_size); + if (!dev_data->events) { + kfree(dev_data); + error = -ENOMEM; + goto free_minor; + } + spin_lock_init(&dev_data->queue_lock); init_waitqueue_head(&dev_data->wq); dev_data->exist = true; atomic_set(&dev_data->available, 1); @@ -496,14 +536,14 @@ static int __init event_module_init(void) ret = class_register(&event_class); if (ret) { - pr_err(DRV_NAME ": Failed registering class: %d", ret); + pr_err(DRV_NAME ": Failed registering class: %d\n", ret); return ret; } /* Request device numbers, starting with minor=0. Save the major num. */ ret = alloc_chrdev_region(&dev_num, 0, EVENT_MAX_DEV, EVENT_DEV_NAME); if (ret) { - pr_err(DRV_NAME ": Failed allocating dev numbers: %d", ret); + pr_err(DRV_NAME ": Failed allocating dev numbers: %d\n", ret); goto destroy_class; } event_major = MAJOR(dev_num); -- cgit v1.2.3-59-g8ed1b From c18e6ea1014ca1ade0030aca5e80af33c8873e1e Mon Sep 17 00:00:00 2001 From: Rajat Jain Date: Thu, 27 Jun 2019 14:47:38 -0700 Subject: platform/chrome: lightbar: Get drvdata from parent in suspend/resume The lightbar driver never assigned the drvdata in probe method, and thus there is nothing there. Need to get the ec_dev from the parent's drvdata. Signed-off-by: Rajat Jain Reviewed-by: Gwendal Grignou Signed-off-by: Enric Balletbo i Serra --- drivers/platform/chrome/cros_ec_lightbar.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers/platform/chrome') diff --git a/drivers/platform/chrome/cros_ec_lightbar.c b/drivers/platform/chrome/cros_ec_lightbar.c index 23a82ee4c785..609598bbb6c3 100644 --- a/drivers/platform/chrome/cros_ec_lightbar.c +++ b/drivers/platform/chrome/cros_ec_lightbar.c @@ -600,7 +600,7 @@ static int cros_ec_lightbar_remove(struct platform_device *pd) static int __maybe_unused cros_ec_lightbar_resume(struct device *dev) { - struct cros_ec_dev *ec_dev = dev_get_drvdata(dev); + struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); if (userspace_control) return 0; @@ -610,7 +610,7 @@ static int __maybe_unused cros_ec_lightbar_resume(struct device *dev) static int __maybe_unused cros_ec_lightbar_suspend(struct device *dev) { - struct cros_ec_dev *ec_dev = dev_get_drvdata(dev); + struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); if (userspace_control) return 0; -- cgit v1.2.3-59-g8ed1b From 8c3166e17cf10161d2871dfb1d017287c7b79ff1 Mon Sep 17 00:00:00 2001 From: Evan Green Date: Thu, 27 Jun 2019 13:44:45 -0700 Subject: mfd / platform: cros_ec_debugfs: Expose resume result via debugfs For ECs that support it, the EC returns the number of slp_s0 transitions and whether or not there was a timeout in the resume response. Expose the last resume result to usermode via debugfs so that usermode can detect and report S0ix timeouts. Signed-off-by: Evan Green Acked-by: Lee Jones Signed-off-by: Enric Balletbo i Serra --- Documentation/ABI/testing/debugfs-cros-ec | 22 ++++++++++++++++++++++ drivers/mfd/cros_ec.c | 6 +++++- drivers/platform/chrome/cros_ec_debugfs.c | 3 +++ include/linux/mfd/cros_ec.h | 1 + 4 files changed, 31 insertions(+), 1 deletion(-) (limited to 'drivers/platform/chrome') diff --git a/Documentation/ABI/testing/debugfs-cros-ec b/Documentation/ABI/testing/debugfs-cros-ec index 573a82d23c89..1fe0add99a2a 100644 --- a/Documentation/ABI/testing/debugfs-cros-ec +++ b/Documentation/ABI/testing/debugfs-cros-ec @@ -32,3 +32,25 @@ Description: is used for synchronizing the AP host time with the EC log. An error is returned if the command is not supported by the EC or there is a communication problem. + +What: /sys/kernel/debug//last_resume_result +Date: June 2019 +KernelVersion: 5.3 +Description: + Some ECs have a feature where they will track transitions to + the (Intel) processor's SLP_S0 line, in order to detect cases + where a system failed to go into S0ix. When the system resumes, + an EC with this feature will return a summary of SLP_S0 + transitions that occurred. The last_resume_result file returns + the most recent response from the AP's resume message to the EC. + + The bottom 31 bits contain a count of the number of SLP_S0 + transitions that occurred since the suspend message was + received. Bit 31 is set if the EC attempted to wake the + system due to a timeout when watching for SLP_S0 transitions. + Callers can use this to detect a wake from the EC due to + S0ix timeouts. The result will be zero if no suspend + transitions have been attempted, or the EC does not support + this feature. + + Output will be in the format: "0x%08x\n". diff --git a/drivers/mfd/cros_ec.c b/drivers/mfd/cros_ec.c index bd2bcdd4718b..64a2d3adc729 100644 --- a/drivers/mfd/cros_ec.c +++ b/drivers/mfd/cros_ec.c @@ -110,12 +110,16 @@ static int cros_ec_sleep_event(struct cros_ec_device *ec_dev, u8 sleep_event) /* For now, report failure to transition to S0ix with a warning. */ if (ret >= 0 && ec_dev->host_sleep_v1 && - (sleep_event == HOST_SLEEP_EVENT_S0IX_RESUME)) + (sleep_event == HOST_SLEEP_EVENT_S0IX_RESUME)) { + ec_dev->last_resume_result = + buf.u.resp1.resume_response.sleep_transitions; + WARN_ONCE(buf.u.resp1.resume_response.sleep_transitions & EC_HOST_RESUME_SLEEP_TIMEOUT, "EC detected sleep transition timeout. Total slp_s0 transitions: %d", buf.u.resp1.resume_response.sleep_transitions & EC_HOST_RESUME_SLEEP_TRANSITIONS_MASK); + } return ret; } diff --git a/drivers/platform/chrome/cros_ec_debugfs.c b/drivers/platform/chrome/cros_ec_debugfs.c index 7ee060743844..8ec1cc2889f2 100644 --- a/drivers/platform/chrome/cros_ec_debugfs.c +++ b/drivers/platform/chrome/cros_ec_debugfs.c @@ -447,6 +447,9 @@ static int cros_ec_debugfs_probe(struct platform_device *pd) debugfs_create_file("uptime", 0444, debug_info->dir, debug_info, &cros_ec_uptime_fops); + debugfs_create_x32("last_resume_result", 0444, debug_info->dir, + &ec->ec_dev->last_resume_result); + ec->debug_info = debug_info; dev_set_drvdata(&pd->dev, ec); diff --git a/include/linux/mfd/cros_ec.h b/include/linux/mfd/cros_ec.h index cfa78bb4990f..d50ade418a83 100644 --- a/include/linux/mfd/cros_ec.h +++ b/include/linux/mfd/cros_ec.h @@ -163,6 +163,7 @@ struct cros_ec_device { struct ec_response_get_next_event_v1 event_data; int event_size; u32 host_event_wake_mask; + u32 last_resume_result; }; /** -- cgit v1.2.3-59-g8ed1b