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

Pull chrome platform updates from Benson Leung:
 "CrOS EC / MFD Migration:
    - Move cros_ec core driver from mfd into chrome platform.

  Wilco EC:
    - Add batt_ppid_info command to Wilco telemetry driver.

  CrOS EC:
    - cros_ec_rpmsg : Add support to inform EC of suspend/resume status
    - cros_ec_rpmsg : Fix race condition on probe failed
    - cros_ec_chardev : Add a poll handler to receive MKBP events

  Misc:
    - bugfixes in cros_usbpd_logger and cros_ec_ishtp"

* tag 'tag-chrome-platform-for-v5.4' of git://git.kernel.org/pub/scm/linux/kernel/git/chrome-platform/linux:
  platform/chrome: cros_usbpd_logger: null check create_singlethread_workqueue
  platform/chrome: cros_ec_chardev: Add a poll handler to receive MKBP events
  platform/chrome: cros_ec_rpmsg: Fix race with host command when probe failed
  platform/chrome: chromeos_tbmc: Report wake events
  mfd: cros_ec: Use mfd_add_hotplug_devices() helper
  mfd: cros_ec: Add convenience struct to define autodetectable CrOS EC subdevices
  mfd: cros_ec: Add convenience struct to define dedicated CrOS EC MCUs
  mfd: cros_ec: Use kzalloc and cros_ec_cmd_xfer_status helper
  mfd / platform: cros_ec: Reorganize platform and mfd includes
  mfd / platform: cros_ec: Rename config to a better name
  mfd: cros_ec: Switch to use the new cros-ec-chardev driver
  mfd / platform: cros_ec: Miscellaneous character device to talk with the EC
  mfd / platform: cros_ec: Move cros-ec core driver out from MFD
  mfd / platform: cros_ec: Handle chained ECs as platform devices
  platform/chrome: cros_ec_rpmsg: Add host command AP sleep state support
  platform/chrome: chromeos_laptop: drop checks of NULL-safe functions
  platform/chrome: wilco_ec: Add batt_ppid_info command to telemetry driver
This commit is contained in:
Linus Torvalds
2019-09-19 14:14:28 -07:00
55 changed files with 1164 additions and 776 deletions

View File

@@ -3,6 +3,16 @@
# Platform support for Chrome OS hardware (Chromebooks and Chromeboxes)
#
config MFD_CROS_EC
tristate "Platform support for Chrome hardware (transitional)"
select CHROME_PLATFORMS
select CROS_EC
select CONFIG_MFD_CROS_EC_DEV
depends on X86 || ARM || ARM64 || COMPILE_TEST
help
This is a transitional Kconfig option and will be removed after
everyone enables the parts individually.
menuconfig CHROME_PLATFORMS
bool "Platform support for Chrome hardware"
depends on X86 || ARM || ARM64 || COMPILE_TEST
@@ -50,9 +60,22 @@ config CHROMEOS_TBMC
To compile this driver as a module, choose M here: the
module will be called chromeos_tbmc.
config CROS_EC
tristate "ChromeOS Embedded Controller"
select CROS_EC_PROTO
depends on X86 || ARM || ARM64 || COMPILE_TEST
help
If you say Y here you get support for the ChromeOS Embedded
Controller (EC) providing keyboard, battery and power services.
You also need to enable the driver for the bus you are using. The
protocol for talking to the EC is defined by the bus driver.
To compile this driver as a module, choose M here: the
module will be called cros_ec.
config CROS_EC_I2C
tristate "ChromeOS Embedded Controller (I2C)"
depends on MFD_CROS_EC && I2C
depends on CROS_EC && I2C
help
If you say Y here, you get support for talking to the ChromeOS
@@ -62,7 +85,7 @@ config CROS_EC_I2C
config CROS_EC_RPMSG
tristate "ChromeOS Embedded Controller (rpmsg)"
depends on MFD_CROS_EC && RPMSG && OF
depends on CROS_EC && RPMSG && OF
help
If you say Y here, you get support for talking to the ChromeOS EC
through rpmsg. This uses a simple byte-level protocol with a
@@ -74,7 +97,7 @@ config CROS_EC_RPMSG
config CROS_EC_ISHTP
tristate "ChromeOS Embedded Controller (ISHTP)"
depends on MFD_CROS_EC
depends on CROS_EC
depends on INTEL_ISH_HID
help
If you say Y here, you get support for talking to the ChromeOS EC
@@ -87,7 +110,7 @@ config CROS_EC_ISHTP
config CROS_EC_SPI
tristate "ChromeOS Embedded Controller (SPI)"
depends on MFD_CROS_EC && SPI
depends on CROS_EC && SPI
---help---
If you say Y here, you get support for talking to the ChromeOS EC
@@ -97,7 +120,7 @@ config CROS_EC_SPI
config CROS_EC_LPC
tristate "ChromeOS Embedded Controller (LPC)"
depends on MFD_CROS_EC && ACPI && (X86 || COMPILE_TEST)
depends on 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, including the LPC Microchip EC (MEC) variant.
@@ -123,10 +146,21 @@ config CROS_KBD_LED_BACKLIGHT
To compile this driver as a module, choose M here: the
module will be called cros_kbd_led_backlight.
config CROS_EC_CHARDEV
tristate "ChromeOS EC miscdevice"
depends on MFD_CROS_EC_DEV
default MFD_CROS_EC_DEV
help
This driver adds file operations support to talk with the
ChromeOS EC from userspace via a character device.
To compile this driver as a module, choose M here: the
module will be called cros_ec_chardev.
config CROS_EC_LIGHTBAR
tristate "Chromebook Pixel's lightbar support"
depends on MFD_CROS_EC_CHARDEV
default MFD_CROS_EC_CHARDEV
depends on MFD_CROS_EC_DEV
default MFD_CROS_EC_DEV
help
This option exposes the Chromebook Pixel's lightbar to
userspace.
@@ -136,8 +170,8 @@ config CROS_EC_LIGHTBAR
config CROS_EC_VBC
tristate "ChromeOS EC vboot context support"
depends on MFD_CROS_EC_CHARDEV && OF
default MFD_CROS_EC_CHARDEV
depends on MFD_CROS_EC_DEV && OF
default MFD_CROS_EC_DEV
help
This option exposes the ChromeOS EC vboot context nvram to
userspace.
@@ -147,8 +181,8 @@ config CROS_EC_VBC
config CROS_EC_DEBUGFS
tristate "Export ChromeOS EC internals in DebugFS"
depends on MFD_CROS_EC_CHARDEV && DEBUG_FS
default MFD_CROS_EC_CHARDEV
depends on MFD_CROS_EC_DEV && DEBUG_FS
default MFD_CROS_EC_DEV
help
This option exposes the ChromeOS EC device internals to
userspace.
@@ -158,8 +192,8 @@ config CROS_EC_DEBUGFS
config CROS_EC_SYSFS
tristate "ChromeOS EC control and information through sysfs"
depends on MFD_CROS_EC_CHARDEV && SYSFS
default MFD_CROS_EC_CHARDEV
depends on MFD_CROS_EC_DEV && SYSFS
default MFD_CROS_EC_DEV
help
This option exposes some sysfs attributes to control and get
information from ChromeOS EC.

View File

@@ -6,6 +6,7 @@ CFLAGS_cros_ec_trace.o:= -I$(src)
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) += cros_ec.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
@@ -14,6 +15,7 @@ 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
obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_chardev.o
obj-$(CONFIG_CROS_EC_LIGHTBAR) += cros_ec_lightbar.o
obj-$(CONFIG_CROS_EC_VBC) += cros_ec_vbc.o
obj-$(CONFIG_CROS_EC_DEBUGFS) += cros_ec_debugfs.o

View File

@@ -838,18 +838,14 @@ static void chromeos_laptop_destroy(const struct chromeos_laptop *cros_laptop)
i2c_dev = &cros_laptop->i2c_peripherals[i];
info = &i2c_dev->board_info;
if (i2c_dev->client)
i2c_unregister_device(i2c_dev->client);
if (info->properties)
property_entries_free(info->properties);
i2c_unregister_device(i2c_dev->client);
property_entries_free(info->properties);
}
for (i = 0; i < cros_laptop->num_acpi_peripherals; i++) {
acpi_dev = &cros_laptop->acpi_peripherals[i];
if (acpi_dev->properties)
property_entries_free(acpi_dev->properties);
property_entries_free(acpi_dev->properties);
}
kfree(cros_laptop->i2c_peripherals);

View File

@@ -47,6 +47,7 @@ static __maybe_unused int chromeos_tbmc_resume(struct device *dev)
static void chromeos_tbmc_notify(struct acpi_device *adev, u32 event)
{
acpi_pm_wakeup_event(&adev->dev);
switch (event) {
case 0x80:
chromeos_tbmc_query_switch(adev, adev->driver_data);
@@ -90,6 +91,7 @@ static int chromeos_tbmc_add(struct acpi_device *adev)
dev_err(dev, "cannot register input device\n");
return ret;
}
device_init_wakeup(dev, true);
return 0;
}

View File

@@ -0,0 +1,281 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ChromeOS EC multi-function device
*
* Copyright (C) 2012 Google, Inc
*
* The ChromeOS EC multi function device is used to mux all the requests
* to the EC device for its multiple features: keyboard controller,
* battery charging and regulator control, firmware update.
*/
#include <linux/of_platform.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/suspend.h>
#include <asm/unaligned.h>
#define CROS_EC_DEV_EC_INDEX 0
#define CROS_EC_DEV_PD_INDEX 1
static struct cros_ec_platform ec_p = {
.ec_name = CROS_EC_DEV_NAME,
.cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_EC_INDEX),
};
static struct cros_ec_platform pd_p = {
.ec_name = CROS_EC_DEV_PD_NAME,
.cmd_offset = EC_CMD_PASSTHRU_OFFSET(CROS_EC_DEV_PD_INDEX),
};
static irqreturn_t ec_irq_thread(int irq, void *data)
{
struct cros_ec_device *ec_dev = data;
bool wake_event = true;
int ret;
ret = cros_ec_get_next_event(ec_dev, &wake_event);
/*
* Signal only if wake host events or any interrupt if
* cros_ec_get_next_event() returned an error (default value for
* wake_event is true)
*/
if (wake_event && device_may_wakeup(ec_dev->dev))
pm_wakeup_event(ec_dev->dev, 0);
if (ret > 0)
blocking_notifier_call_chain(&ec_dev->event_notifier,
0, ec_dev);
return IRQ_HANDLED;
}
static int cros_ec_sleep_event(struct cros_ec_device *ec_dev, u8 sleep_event)
{
int ret;
struct {
struct cros_ec_command msg;
union {
struct ec_params_host_sleep_event req0;
struct ec_params_host_sleep_event_v1 req1;
struct ec_response_host_sleep_event_v1 resp1;
} u;
} __packed buf;
memset(&buf, 0, sizeof(buf));
if (ec_dev->host_sleep_v1) {
buf.u.req1.sleep_event = sleep_event;
buf.u.req1.suspend_params.sleep_timeout_ms =
EC_HOST_SLEEP_TIMEOUT_DEFAULT;
buf.msg.outsize = sizeof(buf.u.req1);
if ((sleep_event == HOST_SLEEP_EVENT_S3_RESUME) ||
(sleep_event == HOST_SLEEP_EVENT_S0IX_RESUME))
buf.msg.insize = sizeof(buf.u.resp1);
buf.msg.version = 1;
} else {
buf.u.req0.sleep_event = sleep_event;
buf.msg.outsize = sizeof(buf.u.req0);
}
buf.msg.command = EC_CMD_HOST_SLEEP_EVENT;
ret = cros_ec_cmd_xfer(ec_dev, &buf.msg);
/* 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)) {
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;
}
int cros_ec_register(struct cros_ec_device *ec_dev)
{
struct device *dev = ec_dev->dev;
int err = 0;
BLOCKING_INIT_NOTIFIER_HEAD(&ec_dev->event_notifier);
ec_dev->max_request = sizeof(struct ec_params_hello);
ec_dev->max_response = sizeof(struct ec_response_get_protocol_info);
ec_dev->max_passthru = 0;
ec_dev->din = devm_kzalloc(dev, ec_dev->din_size, GFP_KERNEL);
if (!ec_dev->din)
return -ENOMEM;
ec_dev->dout = devm_kzalloc(dev, ec_dev->dout_size, GFP_KERNEL);
if (!ec_dev->dout)
return -ENOMEM;
mutex_init(&ec_dev->lock);
err = cros_ec_query_all(ec_dev);
if (err) {
dev_err(dev, "Cannot identify the EC: error %d\n", err);
return err;
}
if (ec_dev->irq) {
err = devm_request_threaded_irq(dev, ec_dev->irq, NULL,
ec_irq_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT,
"chromeos-ec", ec_dev);
if (err) {
dev_err(dev, "Failed to request IRQ %d: %d",
ec_dev->irq, err);
return err;
}
}
/* Register a platform device for the main EC instance */
ec_dev->ec = platform_device_register_data(ec_dev->dev, "cros-ec-dev",
PLATFORM_DEVID_AUTO, &ec_p,
sizeof(struct cros_ec_platform));
if (IS_ERR(ec_dev->ec)) {
dev_err(ec_dev->dev,
"Failed to create CrOS EC platform device\n");
return PTR_ERR(ec_dev->ec);
}
if (ec_dev->max_passthru) {
/*
* Register a platform device for the PD behind the main EC.
* We make the following assumptions:
* - behind an EC, we have a pd
* - only one device added.
* - the EC is responsive at init time (it is not true for a
* sensor hub).
*/
ec_dev->pd = platform_device_register_data(ec_dev->dev,
"cros-ec-dev",
PLATFORM_DEVID_AUTO, &pd_p,
sizeof(struct cros_ec_platform));
if (IS_ERR(ec_dev->pd)) {
dev_err(ec_dev->dev,
"Failed to create CrOS PD platform device\n");
platform_device_unregister(ec_dev->ec);
return PTR_ERR(ec_dev->pd);
}
}
if (IS_ENABLED(CONFIG_OF) && dev->of_node) {
err = devm_of_platform_populate(dev);
if (err) {
platform_device_unregister(ec_dev->pd);
platform_device_unregister(ec_dev->ec);
dev_err(dev, "Failed to register sub-devices\n");
return err;
}
}
/*
* Clear sleep event - this will fail harmlessly on platforms that
* don't implement the sleep event host command.
*/
err = cros_ec_sleep_event(ec_dev, 0);
if (err < 0)
dev_dbg(ec_dev->dev, "Error %d clearing sleep event to ec",
err);
dev_info(dev, "Chrome EC device registered\n");
return 0;
}
EXPORT_SYMBOL(cros_ec_register);
int cros_ec_unregister(struct cros_ec_device *ec_dev)
{
if (ec_dev->pd)
platform_device_unregister(ec_dev->pd);
platform_device_unregister(ec_dev->ec);
return 0;
}
EXPORT_SYMBOL(cros_ec_unregister);
#ifdef CONFIG_PM_SLEEP
int cros_ec_suspend(struct cros_ec_device *ec_dev)
{
struct device *dev = ec_dev->dev;
int ret;
u8 sleep_event;
sleep_event = (!IS_ENABLED(CONFIG_ACPI) || pm_suspend_via_firmware()) ?
HOST_SLEEP_EVENT_S3_SUSPEND :
HOST_SLEEP_EVENT_S0IX_SUSPEND;
ret = cros_ec_sleep_event(ec_dev, sleep_event);
if (ret < 0)
dev_dbg(ec_dev->dev, "Error %d sending suspend event to ec",
ret);
if (device_may_wakeup(dev))
ec_dev->wake_enabled = !enable_irq_wake(ec_dev->irq);
disable_irq(ec_dev->irq);
ec_dev->was_wake_device = ec_dev->wake_enabled;
ec_dev->suspended = true;
return 0;
}
EXPORT_SYMBOL(cros_ec_suspend);
static void cros_ec_report_events_during_suspend(struct cros_ec_device *ec_dev)
{
while (ec_dev->mkbp_event_supported &&
cros_ec_get_next_event(ec_dev, NULL) > 0)
blocking_notifier_call_chain(&ec_dev->event_notifier,
1, ec_dev);
}
int cros_ec_resume(struct cros_ec_device *ec_dev)
{
int ret;
u8 sleep_event;
ec_dev->suspended = false;
enable_irq(ec_dev->irq);
sleep_event = (!IS_ENABLED(CONFIG_ACPI) || pm_suspend_via_firmware()) ?
HOST_SLEEP_EVENT_S3_RESUME :
HOST_SLEEP_EVENT_S0IX_RESUME;
ret = cros_ec_sleep_event(ec_dev, sleep_event);
if (ret < 0)
dev_dbg(ec_dev->dev, "Error %d sending resume event to ec",
ret);
if (ec_dev->wake_enabled) {
disable_irq_wake(ec_dev->irq);
ec_dev->wake_enabled = 0;
}
/*
* Let the mfd devices know about events that occur during
* suspend. This way the clients know what to do with them.
*/
cros_ec_report_events_during_suspend(ec_dev);
return 0;
}
EXPORT_SYMBOL(cros_ec_resume);
#endif
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ChromeOS EC core driver");

View File

@@ -0,0 +1,419 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Miscellaneous character driver for ChromeOS Embedded Controller
*
* Copyright 2014 Google, Inc.
* Copyright 2019 Google LLC
*
* This file is a rework and part of the code is ported from
* drivers/mfd/cros_ec_dev.c that was originally written by
* Bill Richardson.
*/
#include <linux/init.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/mfd/cros_ec.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/platform_data/cros_ec_chardev.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#define DRV_NAME "cros-ec-chardev"
/* Arbitrary bounded size for the event queue */
#define CROS_MAX_EVENT_LEN PAGE_SIZE
struct chardev_data {
struct cros_ec_dev *ec_dev;
struct miscdevice misc;
};
struct chardev_priv {
struct cros_ec_dev *ec_dev;
struct notifier_block notifier;
wait_queue_head_t wait_event;
unsigned long event_mask;
struct list_head events;
size_t event_len;
};
struct ec_event {
struct list_head node;
size_t size;
u8 event_type;
u8 data[0];
};
static int ec_get_version(struct cros_ec_dev *ec, char *str, int maxlen)
{
static const char * const current_image_name[] = {
"unknown", "read-only", "read-write", "invalid",
};
struct ec_response_get_version *resp;
struct cros_ec_command *msg;
int ret;
msg = kzalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL);
if (!msg)
return -ENOMEM;
msg->command = EC_CMD_GET_VERSION + ec->cmd_offset;
msg->insize = sizeof(*resp);
ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg);
if (ret < 0) {
snprintf(str, maxlen,
"Unknown EC version, returned error: %d\n",
msg->result);
goto exit;
}
resp = (struct ec_response_get_version *)msg->data;
if (resp->current_image >= ARRAY_SIZE(current_image_name))
resp->current_image = 3; /* invalid */
snprintf(str, maxlen, "%s\n%s\n%s\n%s\n", CROS_EC_DEV_VERSION,
resp->version_string_ro, resp->version_string_rw,
current_image_name[resp->current_image]);
ret = 0;
exit:
kfree(msg);
return ret;
}
static int cros_ec_chardev_mkbp_event(struct notifier_block *nb,
unsigned long queued_during_suspend,
void *_notify)
{
struct chardev_priv *priv = container_of(nb, struct chardev_priv,
notifier);
struct cros_ec_device *ec_dev = priv->ec_dev->ec_dev;
struct ec_event *event;
unsigned long event_bit = 1 << ec_dev->event_data.event_type;
int total_size = sizeof(*event) + ec_dev->event_size;
if (!(event_bit & priv->event_mask) ||
(priv->event_len + total_size) > CROS_MAX_EVENT_LEN)
return NOTIFY_DONE;
event = kzalloc(total_size, GFP_KERNEL);
if (!event)
return NOTIFY_DONE;
event->size = ec_dev->event_size;
event->event_type = ec_dev->event_data.event_type;
memcpy(event->data, &ec_dev->event_data.data, ec_dev->event_size);
spin_lock(&priv->wait_event.lock);
list_add_tail(&event->node, &priv->events);
priv->event_len += total_size;
wake_up_locked(&priv->wait_event);
spin_unlock(&priv->wait_event.lock);
return NOTIFY_OK;
}
static struct ec_event *cros_ec_chardev_fetch_event(struct chardev_priv *priv,
bool fetch, bool block)
{
struct ec_event *event;
int err;
spin_lock(&priv->wait_event.lock);
if (!block && list_empty(&priv->events)) {
event = ERR_PTR(-EWOULDBLOCK);
goto out;
}
if (!fetch) {
event = NULL;
goto out;
}
err = wait_event_interruptible_locked(priv->wait_event,
!list_empty(&priv->events));
if (err) {
event = ERR_PTR(err);
goto out;
}
event = list_first_entry(&priv->events, struct ec_event, node);
list_del(&event->node);
priv->event_len -= sizeof(*event) + event->size;
out:
spin_unlock(&priv->wait_event.lock);
return event;
}
/*
* Device file ops
*/
static int cros_ec_chardev_open(struct inode *inode, struct file *filp)
{
struct miscdevice *mdev = filp->private_data;
struct cros_ec_dev *ec_dev = dev_get_drvdata(mdev->parent);
struct chardev_priv *priv;
int ret;
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->ec_dev = ec_dev;
filp->private_data = priv;
INIT_LIST_HEAD(&priv->events);
init_waitqueue_head(&priv->wait_event);
nonseekable_open(inode, filp);
priv->notifier.notifier_call = cros_ec_chardev_mkbp_event;
ret = blocking_notifier_chain_register(&ec_dev->ec_dev->event_notifier,
&priv->notifier);
if (ret) {
dev_err(ec_dev->dev, "failed to register event notifier\n");
kfree(priv);
}
return ret;
}
static __poll_t cros_ec_chardev_poll(struct file *filp, poll_table *wait)
{
struct chardev_priv *priv = filp->private_data;
poll_wait(filp, &priv->wait_event, wait);
if (list_empty(&priv->events))
return 0;
return EPOLLIN | EPOLLRDNORM;
}
static ssize_t cros_ec_chardev_read(struct file *filp, char __user *buffer,
size_t length, loff_t *offset)
{
char msg[sizeof(struct ec_response_get_version) +
sizeof(CROS_EC_DEV_VERSION)];
struct chardev_priv *priv = filp->private_data;
struct cros_ec_dev *ec_dev = priv->ec_dev;
size_t count;
int ret;
if (priv->event_mask) { /* queued MKBP event */
struct ec_event *event;
event = cros_ec_chardev_fetch_event(priv, length != 0,
!(filp->f_flags & O_NONBLOCK));
if (IS_ERR(event))
return PTR_ERR(event);
/*
* length == 0 is special - no IO is done but we check
* for error conditions.
*/
if (length == 0)
return 0;
/* The event is 1 byte of type plus the payload */
count = min(length, event->size + 1);
ret = copy_to_user(buffer, &event->event_type, count);
kfree(event);
if (ret) /* the copy failed */
return -EFAULT;
*offset = count;
return count;
}
/*
* Legacy behavior if no event mask is defined
*/
if (*offset != 0)
return 0;
ret = ec_get_version(ec_dev, msg, sizeof(msg));
if (ret)
return ret;
count = min(length, strlen(msg));
if (copy_to_user(buffer, msg, count))
return -EFAULT;
*offset = count;
return count;
}
static int cros_ec_chardev_release(struct inode *inode, struct file *filp)
{
struct chardev_priv *priv = filp->private_data;
struct cros_ec_dev *ec_dev = priv->ec_dev;
struct ec_event *event, *e;
blocking_notifier_chain_unregister(&ec_dev->ec_dev->event_notifier,
&priv->notifier);
list_for_each_entry_safe(event, e, &priv->events, node) {
list_del(&event->node);
kfree(event);
}
kfree(priv);
return 0;
}
/*
* Ioctls
*/
static long cros_ec_chardev_ioctl_xcmd(struct cros_ec_dev *ec, void __user *arg)
{
struct cros_ec_command *s_cmd;
struct cros_ec_command u_cmd;
long ret;
if (copy_from_user(&u_cmd, arg, sizeof(u_cmd)))
return -EFAULT;
if (u_cmd.outsize > EC_MAX_MSG_BYTES ||
u_cmd.insize > EC_MAX_MSG_BYTES)
return -EINVAL;
s_cmd = kmalloc(sizeof(*s_cmd) + max(u_cmd.outsize, u_cmd.insize),
GFP_KERNEL);
if (!s_cmd)
return -ENOMEM;
if (copy_from_user(s_cmd, arg, sizeof(*s_cmd) + u_cmd.outsize)) {
ret = -EFAULT;
goto exit;
}
if (u_cmd.outsize != s_cmd->outsize ||
u_cmd.insize != s_cmd->insize) {
ret = -EINVAL;
goto exit;
}
s_cmd->command += ec->cmd_offset;
ret = cros_ec_cmd_xfer(ec->ec_dev, s_cmd);
/* Only copy data to userland if data was received. */
if (ret < 0)
goto exit;
if (copy_to_user(arg, s_cmd, sizeof(*s_cmd) + s_cmd->insize))
ret = -EFAULT;
exit:
kfree(s_cmd);
return ret;
}
static long cros_ec_chardev_ioctl_readmem(struct cros_ec_dev *ec,
void __user *arg)
{
struct cros_ec_device *ec_dev = ec->ec_dev;
struct cros_ec_readmem s_mem = { };
long num;
/* Not every platform supports direct reads */
if (!ec_dev->cmd_readmem)
return -ENOTTY;
if (copy_from_user(&s_mem, arg, sizeof(s_mem)))
return -EFAULT;
num = ec_dev->cmd_readmem(ec_dev, s_mem.offset, s_mem.bytes,
s_mem.buffer);
if (num <= 0)
return num;
if (copy_to_user((void __user *)arg, &s_mem, sizeof(s_mem)))
return -EFAULT;
return num;
}
static long cros_ec_chardev_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct chardev_priv *priv = filp->private_data;
struct cros_ec_dev *ec = priv->ec_dev;
if (_IOC_TYPE(cmd) != CROS_EC_DEV_IOC)
return -ENOTTY;
switch (cmd) {
case CROS_EC_DEV_IOCXCMD:
return cros_ec_chardev_ioctl_xcmd(ec, (void __user *)arg);
case CROS_EC_DEV_IOCRDMEM:
return cros_ec_chardev_ioctl_readmem(ec, (void __user *)arg);
case CROS_EC_DEV_IOCEVENTMASK:
priv->event_mask = arg;
return 0;
}
return -ENOTTY;
}
static const struct file_operations chardev_fops = {
.open = cros_ec_chardev_open,
.poll = cros_ec_chardev_poll,
.read = cros_ec_chardev_read,
.release = cros_ec_chardev_release,
.unlocked_ioctl = cros_ec_chardev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = cros_ec_chardev_ioctl,
#endif
};
static int cros_ec_chardev_probe(struct platform_device *pdev)
{
struct cros_ec_dev *ec_dev = dev_get_drvdata(pdev->dev.parent);
struct cros_ec_platform *ec_platform = dev_get_platdata(ec_dev->dev);
struct chardev_data *data;
/* Create a char device: we want to create it anew */
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->ec_dev = ec_dev;
data->misc.minor = MISC_DYNAMIC_MINOR;
data->misc.fops = &chardev_fops;
data->misc.name = ec_platform->ec_name;
data->misc.parent = pdev->dev.parent;
dev_set_drvdata(&pdev->dev, data);
return misc_register(&data->misc);
}
static int cros_ec_chardev_remove(struct platform_device *pdev)
{
struct chardev_data *data = dev_get_drvdata(&pdev->dev);
misc_deregister(&data->misc);
return 0;
}
static struct platform_driver cros_ec_chardev_driver = {
.driver = {
.name = DRV_NAME,
},
.probe = cros_ec_chardev_probe,
.remove = cros_ec_chardev_remove,
};
module_platform_driver(cros_ec_chardev_driver);
MODULE_ALIAS("platform:" DRV_NAME);
MODULE_AUTHOR("Enric Balletbo i Serra <enric.balletbo@collabora.com>");
MODULE_DESCRIPTION("ChromeOS EC Miscellaneous Character Driver");
MODULE_LICENSE("GPL");

View File

@@ -8,9 +8,10 @@
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/mfd/cros_ec.h>
#include <linux/mfd/cros_ec_commands.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/poll.h>
#include <linux/sched.h>

View File

@@ -9,8 +9,8 @@
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/mfd/cros_ec.h>
#include <linux/mfd/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
@@ -307,6 +307,13 @@ static int cros_ec_i2c_probe(struct i2c_client *client,
return 0;
}
static int cros_ec_i2c_remove(struct i2c_client *client)
{
struct cros_ec_device *ec_dev = i2c_get_clientdata(client);
return cros_ec_unregister(ec_dev);
}
#ifdef CONFIG_PM_SLEEP
static int cros_ec_i2c_suspend(struct device *dev)
{
@@ -357,6 +364,7 @@ static struct i2c_driver cros_ec_driver = {
.pm = &cros_ec_i2c_pm_ops,
},
.probe = cros_ec_i2c_probe,
.remove = cros_ec_i2c_remove,
.id_table = cros_ec_i2c_id,
};

View File

@@ -8,11 +8,10 @@
// (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/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/intel-ish-client-if.h>
/*

View File

@@ -9,8 +9,9 @@
#include <linux/fs.h>
#include <linux/kobject.h>
#include <linux/mfd/cros_ec.h>
#include <linux/mfd/cros_ec_commands.h>
#include <linux/module.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/types.h>

View File

@@ -16,9 +16,9 @@
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/mfd/cros_ec.h>
#include <linux/mfd/cros_ec_commands.h>
#include <linux/module.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/printk.h>
#include <linux/suspend.h>
@@ -421,6 +421,7 @@ static int cros_ec_lpc_probe(struct platform_device *pdev)
static int cros_ec_lpc_remove(struct platform_device *pdev)
{
struct cros_ec_device *ec_dev = platform_get_drvdata(pdev);
struct acpi_device *adev;
adev = ACPI_COMPANION(&pdev->dev);
@@ -428,7 +429,7 @@ static int cros_ec_lpc_remove(struct platform_device *pdev)
acpi_remove_notify_handler(adev->handle, ACPI_ALL_NOTIFY,
cros_ec_lpc_acpi_notify);
return 0;
return cros_ec_unregister(ec_dev);
}
static const struct acpi_device_id cros_ec_lpc_acpi_device_ids[] = {

View File

@@ -3,10 +3,11 @@
//
// Copyright (C) 2015 Google, Inc
#include <linux/mfd/cros_ec.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/slab.h>
#include <asm/unaligned.h>

View File

@@ -6,9 +6,9 @@
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mfd/cros_ec.h>
#include <linux/mfd/cros_ec_commands.h>
#include <linux/of.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
@@ -41,6 +41,7 @@ struct cros_ec_rpmsg {
struct rpmsg_device *rpdev;
struct completion xfer_ack;
struct work_struct host_event_work;
struct rpmsg_endpoint *ept;
};
/**
@@ -72,7 +73,6 @@ static int cros_ec_pkt_xfer_rpmsg(struct cros_ec_device *ec_dev,
struct cros_ec_command *ec_msg)
{
struct cros_ec_rpmsg *ec_rpmsg = ec_dev->priv;
struct rpmsg_device *rpdev = ec_rpmsg->rpdev;
struct ec_host_response *response;
unsigned long timeout;
int len;
@@ -85,7 +85,7 @@ static int cros_ec_pkt_xfer_rpmsg(struct cros_ec_device *ec_dev,
dev_dbg(ec_dev->dev, "prepared, len=%d\n", len);
reinit_completion(&ec_rpmsg->xfer_ack);
ret = rpmsg_send(rpdev->ept, ec_dev->dout, len);
ret = rpmsg_send(ec_rpmsg->ept, ec_dev->dout, len);
if (ret) {
dev_err(ec_dev->dev, "rpmsg send failed\n");
return ret;
@@ -196,11 +196,24 @@ static int cros_ec_rpmsg_callback(struct rpmsg_device *rpdev, void *data,
return 0;
}
static struct rpmsg_endpoint *
cros_ec_rpmsg_create_ept(struct rpmsg_device *rpdev)
{
struct rpmsg_channel_info chinfo = {};
strscpy(chinfo.name, rpdev->id.name, RPMSG_NAME_SIZE);
chinfo.src = rpdev->src;
chinfo.dst = RPMSG_ADDR_ANY;
return rpmsg_create_ept(rpdev, cros_ec_rpmsg_callback, NULL, chinfo);
}
static int cros_ec_rpmsg_probe(struct rpmsg_device *rpdev)
{
struct device *dev = &rpdev->dev;
struct cros_ec_rpmsg *ec_rpmsg;
struct cros_ec_device *ec_dev;
int ret;
ec_dev = devm_kzalloc(dev, sizeof(*ec_dev), GFP_KERNEL);
if (!ec_dev)
@@ -225,7 +238,18 @@ static int cros_ec_rpmsg_probe(struct rpmsg_device *rpdev)
INIT_WORK(&ec_rpmsg->host_event_work,
cros_ec_rpmsg_host_event_function);
return cros_ec_register(ec_dev);
ec_rpmsg->ept = cros_ec_rpmsg_create_ept(rpdev);
if (!ec_rpmsg->ept)
return -ENOMEM;
ret = cros_ec_register(ec_dev);
if (ret < 0) {
rpmsg_destroy_ept(ec_rpmsg->ept);
cancel_work_sync(&ec_rpmsg->host_event_work);
return ret;
}
return 0;
}
static void cros_ec_rpmsg_remove(struct rpmsg_device *rpdev)
@@ -233,9 +257,30 @@ static void cros_ec_rpmsg_remove(struct rpmsg_device *rpdev)
struct cros_ec_device *ec_dev = dev_get_drvdata(&rpdev->dev);
struct cros_ec_rpmsg *ec_rpmsg = ec_dev->priv;
cros_ec_unregister(ec_dev);
rpmsg_destroy_ept(ec_rpmsg->ept);
cancel_work_sync(&ec_rpmsg->host_event_work);
}
#ifdef CONFIG_PM_SLEEP
static int cros_ec_rpmsg_suspend(struct device *dev)
{
struct cros_ec_device *ec_dev = dev_get_drvdata(dev);
return cros_ec_suspend(ec_dev);
}
static int cros_ec_rpmsg_resume(struct device *dev)
{
struct cros_ec_device *ec_dev = dev_get_drvdata(dev);
return cros_ec_resume(ec_dev);
}
#endif
static SIMPLE_DEV_PM_OPS(cros_ec_rpmsg_pm_ops, cros_ec_rpmsg_suspend,
cros_ec_rpmsg_resume);
static const struct of_device_id cros_ec_rpmsg_of_match[] = {
{ .compatible = "google,cros-ec-rpmsg", },
{ }
@@ -246,10 +291,10 @@ static struct rpmsg_driver cros_ec_driver_rpmsg = {
.drv = {
.name = "cros-ec-rpmsg",
.of_match_table = cros_ec_rpmsg_of_match,
.pm = &cros_ec_rpmsg_pm_ops,
},
.probe = cros_ec_rpmsg_probe,
.remove = cros_ec_rpmsg_remove,
.callback = cros_ec_rpmsg_callback,
};
module_rpmsg_driver(cros_ec_driver_rpmsg);

View File

@@ -6,9 +6,9 @@
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mfd/cros_ec.h>
#include <linux/mfd/cros_ec_commands.h>
#include <linux/of.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spi/spi.h>
@@ -785,6 +785,13 @@ static int cros_ec_spi_probe(struct spi_device *spi)
return 0;
}
static int cros_ec_spi_remove(struct spi_device *spi)
{
struct cros_ec_device *ec_dev = spi_get_drvdata(spi);
return cros_ec_unregister(ec_dev);
}
#ifdef CONFIG_PM_SLEEP
static int cros_ec_spi_suspend(struct device *dev)
{
@@ -823,6 +830,7 @@ static struct spi_driver cros_ec_driver_spi = {
.pm = &cros_ec_spi_pm_ops,
},
.probe = cros_ec_spi_probe,
.remove = cros_ec_spi_remove,
.id_table = cros_ec_spi_id,
};

View File

@@ -9,8 +9,9 @@
#include <linux/fs.h>
#include <linux/kobject.h>
#include <linux/mfd/cros_ec.h>
#include <linux/mfd/cros_ec_commands.h>
#include <linux/module.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/printk.h>
#include <linux/slab.h>

View File

@@ -6,7 +6,7 @@
#define TRACE_SYMBOL(a) {a, #a}
// Generate the list using the following script:
// sed -n 's/^#define \(EC_CMD_[[:alnum:]_]*\)\s.*/\tTRACE_SYMBOL(\1), \\/p' include/linux/mfd/cros_ec_commands.h
// sed -n 's/^#define \(EC_CMD_[[:alnum:]_]*\)\s.*/\tTRACE_SYMBOL(\1), \\/p' include/linux/platform_data/cros_ec_commands.h
#define EC_CMDS \
TRACE_SYMBOL(EC_CMD_PROTO_VERSION), \
TRACE_SYMBOL(EC_CMD_HELLO), \

View File

@@ -11,8 +11,10 @@
#if !defined(_CROS_EC_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
#define _CROS_EC_TRACE_H_
#include <linux/bits.h>
#include <linux/types.h>
#include <linux/mfd/cros_ec.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/tracepoint.h>

View File

@@ -7,8 +7,9 @@
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/mfd/cros_ec.h>
#include <linux/mfd/cros_ec_commands.h>
#include <linux/module.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/slab.h>
#define DRV_NAME "cros-ec-vbc"

View File

@@ -6,10 +6,11 @@
*/
#include <linux/ktime.h>
#include <linux/math64.h>
#include <linux/mfd/cros_ec.h>
#include <linux/mfd/cros_ec_commands.h>
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/rtc.h>
@@ -209,6 +210,9 @@ static int cros_usbpd_logger_probe(struct platform_device *pd)
/* Retrieve PD event logs periodically */
INIT_DELAYED_WORK(&logger->log_work, cros_usbpd_log_check);
logger->log_workqueue = create_singlethread_workqueue("cros_usbpd_log");
if (!logger->log_workqueue)
return -ENOMEM;
queue_delayed_work(logger->log_workqueue, &logger->log_work,
CROS_USBPD_LOG_UPDATE_DELAY);

View File

@@ -9,7 +9,7 @@
* 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
* of the allowlisted commands, and that no extraneous data is
* being transmitted to the EC. The response is passed directly
* back to the reader with no modification.
*
@@ -59,21 +59,10 @@ static DEFINE_IDA(telem_ida);
#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 WILCO_EC_TELEM_GET_BATT_PPID_INFO 0x8A
#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.
@@ -122,6 +111,32 @@ struct telem_args_get_batt_ext_info {
u8 var_args[5];
} __packed;
struct telem_args_get_batt_ppid_info {
u8 always1; /* Should always be 1 */
} __packed;
/**
* 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;
union {
u8 buf[TELEM_ARGS_SIZE_MAX];
struct telem_args_get_log get_log;
struct telem_args_get_version get_version;
struct telem_args_get_fan_info get_fan_info;
struct telem_args_get_diag_info get_diag_info;
struct telem_args_get_temp_info get_temp_info;
struct telem_args_get_temp_read get_temp_read;
struct telem_args_get_batt_ext_info get_batt_ext_info;
struct telem_args_get_batt_ppid_info get_batt_ppid_info;
} args;
} __packed;
/**
* check_telem_request() - Ensure that a request from userspace is valid.
* @rq: Request buffer copied from userspace.
@@ -133,7 +148,7 @@ struct telem_args_get_batt_ext_info {
* 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.
* 2. The supplied command code is one of the allowlisted 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,
@@ -146,25 +161,31 @@ static int check_telem_request(struct wilco_ec_telem_request *rq,
switch (rq->command) {
case WILCO_EC_TELEM_GET_LOG:
max_size += sizeof(struct telem_args_get_log);
max_size += sizeof(rq->args.get_log);
break;
case WILCO_EC_TELEM_GET_VERSION:
max_size += sizeof(struct telem_args_get_version);
max_size += sizeof(rq->args.get_version);
break;
case WILCO_EC_TELEM_GET_FAN_INFO:
max_size += sizeof(struct telem_args_get_fan_info);
max_size += sizeof(rq->args.get_fan_info);
break;
case WILCO_EC_TELEM_GET_DIAG_INFO:
max_size += sizeof(struct telem_args_get_diag_info);
max_size += sizeof(rq->args.get_diag_info);
break;
case WILCO_EC_TELEM_GET_TEMP_INFO:
max_size += sizeof(struct telem_args_get_temp_info);
max_size += sizeof(rq->args.get_temp_info);
break;
case WILCO_EC_TELEM_GET_TEMP_READ:
max_size += sizeof(struct telem_args_get_temp_read);
max_size += sizeof(rq->args.get_temp_read);
break;
case WILCO_EC_TELEM_GET_BATT_EXT_INFO:
max_size += sizeof(struct telem_args_get_batt_ext_info);
max_size += sizeof(rq->args.get_batt_ext_info);
break;
case WILCO_EC_TELEM_GET_BATT_PPID_INFO:
if (rq->args.get_batt_ppid_info.always1 != 1)
return -EINVAL;
max_size += sizeof(rq->args.get_batt_ppid_info);
break;
default:
return -EINVAL;
@@ -250,6 +271,7 @@ static ssize_t telem_write(struct file *filp, const char __user *buf,
if (count > sizeof(sess_data->request))
return -EMSGSIZE;
memset(&sess_data->request, 0, sizeof(sess_data->request));
if (copy_from_user(&sess_data->request, buf, count))
return -EFAULT;
ret = check_telem_request(&sess_data->request, count);