chrome platform changes for v5.3

* CrOS EC:
 
 - Add new CrOS ISHTP transport protocol
 - Add proper documentation for debugfs entries and expose resume and uptime files
 - Select LPC transport protocol variant at runtime.
 - Add lid angle sensor driver
 - Fix oops on suspend/resume for lightbar driver
 - Set CrOS SPI transport protol in realtime
 
 * Wilco EC:
 
 - Add telemetry char device interface
 - Add support for event handling
 - Add new sysfs attributes
 
 * Misc:
 - Contains ib-mfd-cros-v5.3 immutable branch from mfd, with cros_ec_commands.h
   header freshly synced with Chrome OS's EC project.
 -----BEGIN PGP SIGNATURE-----
 
 iHUEABYKAB0WIQQCtZK6p/AktxXfkOlzbaomhzOwwgUCXSbP3AAKCRBzbaomhzOw
 wjoNAP4lrY3UboMaQklHLOCxPTFXwIHjImXxJUCrezJj4eBRcwEAz+adSNKieVEY
 xNf/yetCkjVnQNMVjGaBJRUp3F+2LwQ=
 =/Xj3
 -----END PGP SIGNATURE-----

Merge tag 'tag-chrome-platform-for-v5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux

Pull chrome platform updates from Benson Leung
 "CrOS EC:
   - Add new CrOS ISHTP transport protocol
   - Add proper documentation for debugfs entries and expose resume and
     uptime files
   - Select LPC transport protocol variant at runtime.
   - Add lid angle sensor driver
   - Fix oops on suspend/resume for lightbar driver
   - Set CrOS SPI transport protol in realtime

  Wilco EC:
   - Add telemetry char device interface
   - Add support for event handling
   - Add new sysfs attributes

  Misc:
   - Contains ib-mfd-cros-v5.3 immutable branch from mfd, with
     cros_ec_commands.h header freshly synced with Chrome OS's EC
     project"

* tag 'tag-chrome-platform-for-v5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux: (54 commits)
  mfd / platform: cros_ec_debugfs: Expose resume result via debugfs
  platform/chrome: lightbar: Get drvdata from parent in suspend/resume
  iio: cros_ec: Add lid angle driver
  platform/chrome: wilco_ec: Add circular buffer as event queue
  platform/chrome: cros_ec_lpc_mec: Fix kernel-doc comment first line
  platform/chrome: cros_ec_lpc: Choose Microchip EC at runtime
  platform/chrome: cros_ec_lpc: Merge cros_ec_lpc and cros_ec_lpc_reg
  Input: cros_ec_keyb: mask out extra flags in event_type
  platform/chrome: wilco_ec: Fix unreleased lock in event_read()
  platform/chrome: cros_ec_debugfs: cros_ec_uptime_fops can be static
  platform/chrome: cros_ec_debugfs: Add debugfs ABI documentation
  platform/chrome: cros_ec_debugfs: Fix kernel-doc comment first line
  platform/chrome: cros_ec_debugfs: Add debugfs entry to retrieve EC uptime
  mfd: cros_ec: Update I2S API
  mfd: cros_ec: Add Management API entry points
  mfd: cros_ec: Add SKU ID and Secure storage API
  mfd: cros_ec: Add API for rwsig
  mfd: cros_ec: Add API for Fingerprint support
  mfd: cros_ec: Add API for Touchpad support
  mfd: cros_ec: Add API for EC-EC communication
  ...
This commit is contained in:
Linus Torvalds 2019-07-11 18:45:29 -07:00
commit d7d170a8e3
33 changed files with 5643 additions and 995 deletions

View File

@ -0,0 +1,56 @@
What: /sys/kernel/debug/<cros-ec-device>/console_log
Date: September 2017
KernelVersion: 4.13
Description:
If the EC supports the CONSOLE_READ command type, this file
can be used to grab the EC logs. The kernel polls for the log
and keeps its own buffer but userspace should grab this and
write it out to some logs.
What: /sys/kernel/debug/<cros-ec-device>/panicinfo
Date: September 2017
KernelVersion: 4.13
Description:
This file dumps the EC panic information from the previous
reboot. This file will only exist if the PANIC_INFO command
type is supported by the EC.
What: /sys/kernel/debug/<cros-ec-device>/pdinfo
Date: June 2018
KernelVersion: 4.17
Description:
This file provides the port role, muxes and power debug
information for all the USB PD/type-C ports available. If
the are no ports available, this file will be just an empty
file.
What: /sys/kernel/debug/<cros-ec-device>/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.
What: /sys/kernel/debug/<cros-ec-device>/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".

View File

@ -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.

View File

@ -0,0 +1,40 @@
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.
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

View File

@ -21,3 +21,12 @@ config IIO_CROS_EC_SENSORS
Accelerometers, Gyroscope and Magnetometer that are
presented by the ChromeOS EC Sensor hub.
Creates an IIO device for each functions.
config IIO_CROS_EC_SENSORS_LID_ANGLE
tristate "ChromeOS EC Sensor for lid angle"
depends on IIO_CROS_EC_SENSORS_CORE
help
Module to report the angle between lid and base for some
convertible devices.
This module is loaded when the EC can calculate the angle between the base
and the lid.

View File

@ -5,3 +5,4 @@
obj-$(CONFIG_IIO_CROS_EC_SENSORS_CORE) += cros_ec_sensors_core.o
obj-$(CONFIG_IIO_CROS_EC_SENSORS) += cros_ec_sensors.o
obj-$(CONFIG_IIO_CROS_EC_SENSORS_LID_ANGLE) += cros_ec_lid_angle.o

View File

@ -0,0 +1,139 @@
// SPDX-License-Identifier: GPL-2.0
/*
* cros_ec_lid_angle - Driver for CrOS EC lid angle sensor.
*
* Copyright 2018 Google, Inc
*
* This driver uses the cros-ec interface to communicate with the Chrome OS
* EC about counter sensors. Counters are presented through
* iio sysfs.
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/iio/buffer.h>
#include <linux/iio/common/cros_ec_sensors_core.h>
#include <linux/iio/iio.h>
#include <linux/iio/kfifo_buf.h>
#include <linux/iio/trigger.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/iio/trigger_consumer.h>
#include <linux/kernel.h>
#include <linux/mfd/cros_ec.h>
#include <linux/mfd/cros_ec_commands.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#define DRV_NAME "cros-ec-lid-angle"
/*
* One channel for the lid angle, the other for timestamp.
*/
static const struct iio_chan_spec cros_ec_lid_angle_channels[] = {
{
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
.scan_type.realbits = CROS_EC_SENSOR_BITS,
.scan_type.storagebits = CROS_EC_SENSOR_BITS,
.scan_type.sign = 'u',
.type = IIO_ANGL
},
IIO_CHAN_SOFT_TIMESTAMP(1)
};
/* State data for ec_sensors iio driver. */
struct cros_ec_lid_angle_state {
/* Shared by all sensors */
struct cros_ec_sensors_core_state core;
};
static int cros_ec_sensors_read_lid_angle(struct iio_dev *indio_dev,
unsigned long scan_mask, s16 *data)
{
struct cros_ec_sensors_core_state *st = iio_priv(indio_dev);
int ret;
st->param.cmd = MOTIONSENSE_CMD_LID_ANGLE;
ret = cros_ec_motion_send_host_cmd(st, sizeof(st->resp->lid_angle));
if (ret) {
dev_warn(&indio_dev->dev, "Unable to read lid angle\n");
return ret;
}
*data = st->resp->lid_angle.value;
return 0;
}
static int cros_ec_lid_angle_read(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct cros_ec_lid_angle_state *st = iio_priv(indio_dev);
s16 data;
int ret;
mutex_lock(&st->core.cmd_lock);
ret = cros_ec_sensors_read_lid_angle(indio_dev, 1, &data);
if (ret == 0) {
*val = data;
ret = IIO_VAL_INT;
}
mutex_unlock(&st->core.cmd_lock);
return ret;
}
static const struct iio_info cros_ec_lid_angle_info = {
.read_raw = &cros_ec_lid_angle_read,
};
static int cros_ec_lid_angle_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct iio_dev *indio_dev;
struct cros_ec_lid_angle_state *state;
int ret;
indio_dev = devm_iio_device_alloc(dev, sizeof(*state));
if (!indio_dev)
return -ENOMEM;
ret = cros_ec_sensors_core_init(pdev, indio_dev, false);
if (ret)
return ret;
indio_dev->info = &cros_ec_lid_angle_info;
state = iio_priv(indio_dev);
indio_dev->channels = cros_ec_lid_angle_channels;
indio_dev->num_channels = ARRAY_SIZE(cros_ec_lid_angle_channels);
state->core.read_ec_sensors_data = cros_ec_sensors_read_lid_angle;
ret = devm_iio_triggered_buffer_setup(dev, indio_dev, NULL,
cros_ec_sensors_capture, NULL);
if (ret)
return ret;
return devm_iio_device_register(dev, indio_dev);
}
static const struct platform_device_id cros_ec_lid_angle_ids[] = {
{
.name = DRV_NAME,
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(platform, cros_ec_lid_angle_ids);
static struct platform_driver cros_ec_lid_angle_platform_driver = {
.driver = {
.name = DRV_NAME,
.pm = &cros_ec_sensors_pm_ops,
},
.probe = cros_ec_lid_angle_probe,
.id_table = cros_ec_lid_angle_ids,
};
module_platform_driver(cros_ec_lid_angle_platform_driver);
MODULE_DESCRIPTION("ChromeOS EC driver for reporting convertible lid angle.");
MODULE_LICENSE("GPL v2");

View File

@ -237,7 +237,7 @@ static int cros_ec_keyb_work(struct notifier_block *nb,
if (queued_during_suspend && !device_may_wakeup(ckdev->dev))
return NOTIFY_OK;
switch (ckdev->ec->event_data.event_type) {
switch (ckdev->ec->event_data.event_type & EC_MKBP_EVENT_TYPE_MASK) {
case EC_MKBP_EVENT_KEY_MATRIX:
pm_wakeup_event(ckdev->dev, 0);

View File

@ -102,12 +102,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;
}

View File

@ -72,6 +72,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
@ -83,28 +96,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

View File

@ -7,10 +7,10 @@ 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
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

View File

@ -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
@ -241,7 +242,35 @@ 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 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,
.read = cros_ec_console_log_read,
@ -250,13 +279,20 @@ 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,
.llseek = default_llseek,
};
static 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 +444,12 @@ 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);
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);

View File

@ -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 <linux/delay.h>
#include <linux/mfd/core.h>
#include <linux/mfd/cros_ec.h>
#include <linux/mfd/cros_ec_commands.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/intel-ish-client-if.h>
/*
* 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 <rushikesh.s.kadam@intel.com>");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("ishtp:*");

View File

@ -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,
};
@ -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;

View File

@ -23,7 +23,7 @@
#include <linux/printk.h>
#include <linux/suspend.h>
#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,96 @@
/* True if ACPI device is present */
static bool cros_ec_lpc_acpi_device_found;
/**
* 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;
for (i = 0; i < length; ++i) {
dest[i] = inb(offset + i);
sum += dest[i];
}
/* Return checksum of all bytes read */
return sum;
}
/*
* 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;
for (i = 0; i < length; ++i) {
outb(msg[i], offset + i);
sum += msg[i];
}
/* Return checksum of all bytes written */
return sum;
}
/*
* 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);
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) :
cros_ec_lpc_read_bytes(offset, length, dest);
}
/*
* 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);
if (in_range < 0)
return 0;
return in_range ?
cros_ec_lpc_io_bytes_mec(MEC_IO_WRITE,
offset - EC_HOST_CMD_REGION0,
length, (u8 *)msg) :
cros_ec_lpc_write_bytes(offset, length, msg);
}
static int ec_response_timed_out(void)
{
unsigned long one_second = jiffies + HZ;
@ -38,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);
@ -58,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");
@ -71,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;
@ -92,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,
@ -134,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");
@ -153,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,
@ -174,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) {
@ -205,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;
@ -248,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,
@ -405,7 +509,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)
};
@ -446,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;
}
@ -462,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();
}
}
@ -474,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);

View File

@ -17,12 +17,10 @@
static struct mutex io_mutex;
static u16 mec_emi_base, mec_emi_end;
/*
* cros_ec_lpc_mec_emi_write_address
/**
* cros_ec_lpc_mec_emi_write_address() - Initialize EMI at a given address.
*
* Initialize EMI read / write 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,

View File

@ -1,101 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
// LPC interface for ChromeOS Embedded Controller
//
// Copyright (C) 2016 Google, Inc
#include <linux/io.h>
#include <linux/mfd/cros_ec.h>
#include <linux/mfd/cros_ec_commands.h>
#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 */

View File

@ -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 */

View File

@ -12,7 +12,7 @@
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
#include <uapi/linux/sched/types.h>
/* 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 @@ static int do_cros_ec_cmd_xfer_spi(struct cros_ec_device *ec_dev,
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(&params.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, &params.work);
flush_work(&params.work);
destroy_work_on_stack(&params.work);
kthread_queue_work(ec_spi->high_pri_worker, &params.work);
kthread_flush_work(&params.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;
@ -703,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;
@ -732,6 +770,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");
@ -777,7 +819,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,

View File

@ -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,
};

View File

@ -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,
};

View File

@ -1,7 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
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
@ -19,3 +19,19 @@ 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().
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().

View File

@ -1,6 +1,10 @@
# SPDX-License-Identifier: GPL-2.0
wilco_ec-objs := core.o mailbox.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
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

View File

@ -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;
@ -89,8 +87,28 @@ 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;
}
/* 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:
if (ec->debugfs_pdev)
platform_device_unregister(ec->debugfs_pdev);
@ -102,6 +120,8 @@ 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);

View File

@ -16,14 +16,14 @@
#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)
/* 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 {
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;

View File

@ -0,0 +1,581 @@
// 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 <linux/acpi.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/idr.h>
#include <linux/io.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/spinlock.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
/* 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 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.
* @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.
* @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, 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 ec_event_queue *events;
spinlock_t queue_lock;
wait_queue_head_t wq;
struct device dev;
struct cdev cdev;
bool exist;
atomic_t available;
};
/**
* 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 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.
*/
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, *queue_event, *old_event;
size_t num_words, event_size;
u32 offset = 0;
while (offset < length) {
event = (struct ec_event *)(buf + offset);
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) {
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;
/* Copy event into the queue */
queue_event = kmemdup(event, event_size, GFP_KERNEL);
if (!queue_event)
return -ENOMEM;
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;
}
/**
* 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 };
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);
}
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);
stream_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 (!event_queue_empty(dev_data->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.
*
* 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
* 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 *event;
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;
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;
err = wait_event_interruptible(dev_data->wq,
!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;
spin_lock(&dev_data->queue_lock);
}
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;
kfree(event);
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);
event_queue_free(dev_data->events);
kfree(dev_data);
}
static void hangup_device(struct event_device_data *dev_data)
{
dev_data->exist = false;
/* 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\n", 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;
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);
/* 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\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\n", 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 <ncrews@chromium.org>");
MODULE_DESCRIPTION("Wilco EC ACPI event driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);

View File

@ -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;
}

View File

@ -0,0 +1,132 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2019 Google LLC
*/
#include <linux/platform_data/wilco-ec.h>
#include <linux/string.h>
#include <linux/unaligned/le_memmove.h>
/* 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);

View File

@ -0,0 +1,156 @@
// 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 <linux/platform_data/wilco-ec.h>
#include <linux/sysfs.h>
#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;
#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)
{
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 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,
};
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);
}

View File

@ -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 <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/platform_data/wilco-ec.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#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 <ncrews@chromium.org>");
MODULE_DESCRIPTION("Wilco EC telemetry driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DRV_NAME);

View File

@ -155,6 +155,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;
};
/**

File diff suppressed because it is too large Load Diff

View File

@ -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.
@ -32,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;
@ -43,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;
};
/**
@ -85,14 +84,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,
};
/**
@ -123,4 +120,87 @@ struct wilco_ec_message {
*/
int wilco_ec_mailbox(struct wilco_ec_device *ec, struct wilco_ec_message *msg);
/*
* 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.
*/
#define WILCO_EC_PROPERTY_MAX_SIZE 4
/**
* struct ec_property_set_msg - Message to get or set a property.
* @property_id: Which property to get or set.
* @length: Number of bytes of |data| that are used.
* @data: Actual property data.
*/
struct wilco_ec_property_msg {
u32 property_id;
int length;
u8 data[WILCO_EC_PROPERTY_MAX_SIZE];
};
/**
* wilco_ec_get_property() - Retrieve a property from the EC.
* @ec: Embedded Controller device.
* @prop_msg: Message for request and response.
*
* The property_id field of |prop_msg| should be filled before calling this
* function. The result will be stored in the data and length fields.
*
* Return: 0 on success, negative error code on failure.
*/
int wilco_ec_get_property(struct wilco_ec_device *ec,
struct wilco_ec_property_msg *prop_msg);
/**
* wilco_ec_set_property() - Store a property on the EC.
* @ec: Embedded Controller device.
* @prop_msg: Message for request and response.
*
* The property_id, length, and data fields of |prop_msg| should be
* filled before calling this function.
*
* Return: 0 on success, negative error code on failure.
*/
int wilco_ec_set_property(struct wilco_ec_device *ec,
struct wilco_ec_property_msg *prop_msg);
/**
* wilco_ec_get_byte_property() - Retrieve a byte-size property from the EC.
* @ec: Embedded Controller device.
* @property_id: Which property to retrieve.
* @val: The result value, will be filled by this function.
*
* Return: 0 on success, negative error code on failure.
*/
int wilco_ec_get_byte_property(struct wilco_ec_device *ec, u32 property_id,
u8 *val);
/**
* wilco_ec_get_byte_property() - Store a byte-size property on the EC.
* @ec: Embedded Controller device.
* @property_id: Which property to store.
* @val: Value to store.
*
* Return: 0 on success, negative error code on failure.
*/
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 */

View File

@ -38,21 +38,21 @@ static const DECLARE_TLV_DB_SCALE(ec_mic_gain_tlv, 0, 100, 0);
static int ec_command_get_gain(struct snd_soc_component *component,
struct ec_param_codec_i2s *param,
struct ec_response_codec_gain *resp)
struct ec_codec_i2s_gain *resp)
{
struct cros_ec_codec_data *codec_data =
snd_soc_component_get_drvdata(component);
struct cros_ec_device *ec_device = codec_data->ec_device;
u8 buffer[sizeof(struct cros_ec_command) +
max(sizeof(struct ec_param_codec_i2s),
sizeof(struct ec_response_codec_gain))];
sizeof(struct ec_codec_i2s_gain))];
struct cros_ec_command *msg = (struct cros_ec_command *)&buffer;
int ret;
msg->version = 0;
msg->command = EC_CMD_CODEC_I2S;
msg->outsize = sizeof(struct ec_param_codec_i2s);
msg->insize = sizeof(struct ec_response_codec_gain);
msg->insize = sizeof(struct ec_codec_i2s_gain);
memcpy(msg->data, param, msg->outsize);
@ -226,7 +226,7 @@ static int get_ec_mic_gain(struct snd_soc_component *component,
u8 *left, u8 *right)
{
struct ec_param_codec_i2s param;
struct ec_response_codec_gain resp;
struct ec_codec_i2s_gain resp;
int ret;
param.cmd = EC_CODEC_GET_GAIN;