Re: [PATCH 2/3] mfd: cros_ec: add debugfs, console log file

From: Guenter Roeck
Date: Tue Aug 23 2016 - 17:17:59 EST


On Mon, Aug 22, 2016 at 9:34 PM, Nicolas Boichat <drinkcat@xxxxxxxxxxxx> wrote:
> From: Eric Caruso <ejcaruso@xxxxxxxxxxxx>
>
> If the EC supports the new CONSOLE_READ command type, then we
> place a console_log file in debugfs for that EC device which allows
> us to grab EC logs. The kernel will poll every 10 seconds for the
> log and keep its own buffer, but userspace should grab this and
> write it out to some logs which actually get rotated.
>
> Signed-off-by: Eric Caruso <ejcaruso@xxxxxxxxxxxx>
> Signed-off-by: Nicolas Boichat <drinkcat@xxxxxxxxxxxx>

Reviewed-by: Guenter Roeck <groeck@xxxxxxxxxxxx>

> ---
> drivers/platform/chrome/Makefile | 3 +-
> drivers/platform/chrome/cros_ec_debugfs.c | 347 ++++++++++++++++++++++++++++++
> drivers/platform/chrome/cros_ec_debugfs.h | 27 +++
> drivers/platform/chrome/cros_ec_dev.c | 7 +
> include/linux/mfd/cros_ec.h | 4 +
> 5 files changed, 387 insertions(+), 1 deletion(-)
> create mode 100644 drivers/platform/chrome/cros_ec_debugfs.c
> create mode 100644 drivers/platform/chrome/cros_ec_debugfs.h
>
> diff --git a/drivers/platform/chrome/Makefile b/drivers/platform/chrome/Makefile
> index 4f34627..3870afe 100644
> --- a/drivers/platform/chrome/Makefile
> +++ b/drivers/platform/chrome/Makefile
> @@ -2,7 +2,8 @@
> obj-$(CONFIG_CHROMEOS_LAPTOP) += chromeos_laptop.o
> obj-$(CONFIG_CHROMEOS_PSTORE) += chromeos_pstore.o
> cros_ec_devs-objs := cros_ec_dev.o cros_ec_sysfs.o \
> - cros_ec_lightbar.o cros_ec_vbc.o
> + cros_ec_lightbar.o cros_ec_vbc.o \
> + cros_ec_debugfs.o
> obj-$(CONFIG_CROS_EC_CHARDEV) += cros_ec_devs.o
> obj-$(CONFIG_CROS_EC_LPC) += cros_ec_lpc.o
> obj-$(CONFIG_CROS_EC_PROTO) += cros_ec_proto.o
> diff --git a/drivers/platform/chrome/cros_ec_debugfs.c b/drivers/platform/chrome/cros_ec_debugfs.c
> new file mode 100644
> index 0000000..225f936
> --- /dev/null
> +++ b/drivers/platform/chrome/cros_ec_debugfs.c
> @@ -0,0 +1,347 @@
> +/*
> + * cros_ec_debugfs - debug logs for Chrome OS EC
> + *
> + * Copyright 2015 Google, Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program. If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include <linux/circ_buf.h>
> +#include <linux/debugfs.h>
> +#include <linux/delay.h>
> +#include <linux/fs.h>
> +#include <linux/mfd/cros_ec.h>
> +#include <linux/mfd/cros_ec_commands.h>
> +#include <linux/mutex.h>
> +#include <linux/poll.h>
> +#include <linux/sched.h>
> +#include <linux/slab.h>
> +#include <linux/wait.h>
> +
> +#include "cros_ec_dev.h"
> +#include "cros_ec_debugfs.h"
> +
> +#define LOG_SHIFT 14
> +#define LOG_SIZE (1 << LOG_SHIFT)
> +#define LOG_POLL_SEC 10
> +
> +#define CIRC_ADD(idx, size, value) (((idx) + (value)) & ((size) - 1))
> +
> +/* struct cros_ec_debugfs - ChromeOS EC debugging information
> + *
> + * @ec: EC device this debugfs information belongs to
> + * @dir: dentry for debugfs files
> + * @log_buffer: circular buffer for console log information
> + * @read_msg: preallocated EC command and buffer to read console log
> + * @log_mutex: mutex to protect circular buffer
> + * @log_wq: waitqueue for log readers
> + * @log_poll_work: recurring task to poll EC for new console log data
> + */
> +struct cros_ec_debugfs {
> + struct cros_ec_dev *ec;
> + struct dentry *dir;
> + struct circ_buf log_buffer;
> + struct cros_ec_command *read_msg;
> + struct mutex log_mutex;
> + wait_queue_head_t log_wq;
> + struct delayed_work log_poll_work;
> +};
> +
> +/*
> + * We need to make sure that the EC log buffer on the UART is large enough,
> + * so that it is unlikely enough to overlow within LOG_POLL_SEC.
> + */
> +static void cros_ec_console_log_work(struct work_struct *__work)
> +{
> + struct cros_ec_debugfs *debug_info =
> + container_of(to_delayed_work(__work),
> + struct cros_ec_debugfs,
> + log_poll_work);
> + struct cros_ec_dev *ec = debug_info->ec;
> + struct circ_buf *cb = &debug_info->log_buffer;
> + struct cros_ec_command snapshot_msg = {
> + .command = EC_CMD_CONSOLE_SNAPSHOT + ec->cmd_offset,
> + };
> +
> + struct ec_params_console_read_v1 *read_params =
> + (struct ec_params_console_read_v1 *)debug_info->read_msg->data;
> + uint8_t *ec_buffer = (uint8_t *)debug_info->read_msg->data;
> + int idx;
> + int buf_space;
> + int ret;
> +
> + ret = cros_ec_cmd_xfer(ec->ec_dev, &snapshot_msg);
> + if (ret < 0) {
> + dev_err(ec->dev, "EC communication failed\n");
> + goto resched;
> + }
> + if (snapshot_msg.result != EC_RES_SUCCESS) {
> + dev_err(ec->dev, "EC failed to snapshot the console log\n");
> + goto resched;
> + }
> +
> + /* Loop until we have read everything, or there's an error. */
> + mutex_lock(&debug_info->log_mutex);
> + buf_space = CIRC_SPACE(cb->head, cb->tail, LOG_SIZE);
> +
> + while (1) {
> + if (!buf_space) {
> + dev_info_once(ec->dev,
> + "Some logs may have been dropped...\n");
> + break;
> + }
> +
> + memset(read_params, '\0', sizeof(*read_params));
> + read_params->subcmd = CONSOLE_READ_RECENT;
> + ret = cros_ec_cmd_xfer(ec->ec_dev, debug_info->read_msg);
> + if (ret < 0) {
> + dev_err(ec->dev, "EC communication failed\n");
> + break;
> + }
> + if (debug_info->read_msg->result != EC_RES_SUCCESS) {
> + dev_err(ec->dev,
> + "EC failed to read the console log\n");
> + break;
> + }
> +
> + /* If the buffer is empty, we're done here. */
> + if (ret == 0 || ec_buffer[0] == '\0')
> + break;
> +
> + idx = 0;
> + while (idx < ret && ec_buffer[idx] != '\0' && buf_space > 0) {
> + cb->buf[cb->head] = ec_buffer[idx];
> + cb->head = CIRC_ADD(cb->head, LOG_SIZE, 1);
> + idx++;
> + buf_space--;
> + }
> +
> + wake_up(&debug_info->log_wq);
> + }
> +
> + mutex_unlock(&debug_info->log_mutex);
> +
> +resched:
> + schedule_delayed_work(&debug_info->log_poll_work,
> + msecs_to_jiffies(LOG_POLL_SEC * 1000));
> +}
> +
> +static int cros_ec_console_log_open(struct inode *inode, struct file *file)
> +{
> + file->private_data = inode->i_private;
> +
> + return nonseekable_open(inode, file);
> +}
> +
> +static ssize_t cros_ec_console_log_read(struct file *file, char __user *buf,
> + size_t count, loff_t *ppos)
> +{
> + struct cros_ec_debugfs *debug_info = file->private_data;
> + struct circ_buf *cb = &debug_info->log_buffer;
> + ssize_t ret;
> +
> + mutex_lock(&debug_info->log_mutex);
> +
> + while (!CIRC_CNT(cb->head, cb->tail, LOG_SIZE)) {
> + if (file->f_flags & O_NONBLOCK) {
> + ret = -EAGAIN;
> + goto error;
> + }
> +
> + mutex_unlock(&debug_info->log_mutex);
> +
> + ret = wait_event_interruptible(debug_info->log_wq,
> + CIRC_CNT(cb->head, cb->tail, LOG_SIZE));
> + if (ret < 0)
> + return ret;
> +
> + mutex_lock(&debug_info->log_mutex);
> + }
> +
> + /* Only copy until the end of the circular buffer, and let userspace
> + * retry to get the rest of the data.
> + */
> + ret = min_t(size_t, CIRC_CNT_TO_END(cb->head, cb->tail, LOG_SIZE),
> + count);
> +
> + if (copy_to_user(buf, cb->buf + cb->tail, ret)) {
> + ret = -EFAULT;
> + goto error;
> + }
> +
> + cb->tail = CIRC_ADD(cb->tail, LOG_SIZE, ret);
> +
> +error:
> + mutex_unlock(&debug_info->log_mutex);
> + return ret;
> +}
> +
> +static unsigned int cros_ec_console_log_poll(struct file *file,
> + poll_table *wait)
> +{
> + struct cros_ec_debugfs *debug_info = file->private_data;
> + unsigned int mask = 0;
> +
> + poll_wait(file, &debug_info->log_wq, wait);
> +
> + mutex_lock(&debug_info->log_mutex);
> + if (CIRC_CNT(debug_info->log_buffer.head,
> + debug_info->log_buffer.tail,
> + LOG_SIZE))
> + mask |= POLLIN | POLLRDNORM;
> + mutex_unlock(&debug_info->log_mutex);
> +
> + return mask;
> +}
> +
> +static int cros_ec_console_log_release(struct inode *inode, struct file *file)
> +{
> + return 0;
> +}
> +
> +const struct file_operations cros_ec_console_log_fops = {
> + .owner = THIS_MODULE,
> + .open = cros_ec_console_log_open,
> + .read = cros_ec_console_log_read,
> + .llseek = no_llseek,
> + .poll = cros_ec_console_log_poll,
> + .release = cros_ec_console_log_release,
> +};
> +
> +static int ec_read_version_supported(struct cros_ec_dev *ec)
> +{
> + struct ec_params_get_cmd_versions_v1 *params;
> + struct ec_response_get_cmd_versions *response;
> + int ret;
> +
> + struct cros_ec_command *msg;
> +
> + msg = kzalloc(sizeof(*msg) + max(sizeof(params), sizeof(response)),
> + GFP_KERNEL);
> + if (!msg)
> + return 0;
> +
> + msg->command = EC_CMD_GET_CMD_VERSIONS + ec->cmd_offset;
> + msg->outsize = sizeof(params);
> + msg->insize = sizeof(response);
> +
> + params = (struct ec_params_get_cmd_versions_v1 *)msg->data;
> + params->cmd = EC_CMD_CONSOLE_READ;
> + response = (struct ec_response_get_cmd_versions *)msg->data;
> +
> + ret = cros_ec_cmd_xfer(ec->ec_dev, msg) >= 0 &&
> + msg->result == EC_RES_SUCCESS &&
> + (response->version_mask & EC_VER_MASK(1));
> +
> + kfree(msg);
> +
> + return ret;
> +}
> +
> +static int cros_ec_create_console_log(struct cros_ec_debugfs *debug_info)
> +{
> + struct cros_ec_dev *ec = debug_info->ec;
> + char *buf;
> + int read_params_size;
> + int read_response_size;
> +
> + if (!ec_read_version_supported(ec)) {
> + dev_warn(ec->dev,
> + "device does not support reading the console log\n");
> + return 0;
> + }
> +
> + buf = devm_kzalloc(ec->dev, LOG_SIZE, GFP_KERNEL);
> + if (!buf)
> + return -ENOMEM;
> +
> + read_params_size = sizeof(struct ec_params_console_read_v1);
> + read_response_size = ec->ec_dev->max_response;
> + debug_info->read_msg = devm_kzalloc(ec->dev,
> + sizeof(*debug_info->read_msg) +
> + max(read_params_size, read_response_size), GFP_KERNEL);
> + if (!debug_info->read_msg)
> + return -ENOMEM;
> +
> + debug_info->read_msg->version = 1;
> + debug_info->read_msg->command = EC_CMD_CONSOLE_READ + ec->cmd_offset;
> + debug_info->read_msg->outsize = read_params_size;
> + debug_info->read_msg->insize = read_response_size;
> +
> + debug_info->log_buffer.buf = buf;
> + debug_info->log_buffer.head = 0;
> + debug_info->log_buffer.tail = 0;
> +
> + mutex_init(&debug_info->log_mutex);
> + init_waitqueue_head(&debug_info->log_wq);
> +
> + if (!debugfs_create_file("console_log",
> + S_IFREG | S_IRUGO,
> + debug_info->dir,
> + debug_info,
> + &cros_ec_console_log_fops))
> + return -ENOMEM;
> +
> + INIT_DELAYED_WORK(&debug_info->log_poll_work,
> + cros_ec_console_log_work);
> + schedule_delayed_work(&debug_info->log_poll_work, 0);
> +
> + return 0;
> +}
> +
> +static void cros_ec_cleanup_console_log(struct cros_ec_debugfs *debug_info)
> +{
> + if (debug_info->log_buffer.buf) {
> + cancel_delayed_work_sync(&debug_info->log_poll_work);
> + mutex_destroy(&debug_info->log_mutex);
> + }
> +}
> +
> +int cros_ec_debugfs_init(struct cros_ec_dev *ec)
> +{
> + struct cros_ec_platform *ec_platform = dev_get_platdata(ec->dev);
> + const char *name = ec_platform->ec_name;
> + struct cros_ec_debugfs *debug_info;
> + int ret;
> +
> + debug_info = devm_kzalloc(ec->dev, sizeof(*debug_info), GFP_KERNEL);
> + if (!debug_info)
> + return -ENOMEM;
> +
> + debug_info->ec = ec;
> + debug_info->dir = debugfs_create_dir(name, NULL);
> + if (!debug_info->dir)
> + return -ENOMEM;
> +
> + ret = cros_ec_create_console_log(debug_info);
> + if (ret)
> + goto remove_debugfs;
> +
> + ec->debug_info = debug_info;
> +
> + return 0;
> +
> +remove_debugfs:
> + debugfs_remove_recursive(debug_info->dir);
> + return ret;
> +}
> +
> +void cros_ec_debugfs_remove(struct cros_ec_dev *ec)
> +{
> + if (!ec->debug_info)
> + return;
> +
> + debugfs_remove_recursive(ec->debug_info->dir);
> + cros_ec_cleanup_console_log(ec->debug_info);
> +}
> diff --git a/drivers/platform/chrome/cros_ec_debugfs.h b/drivers/platform/chrome/cros_ec_debugfs.h
> new file mode 100644
> index 0000000..1ff3a50
> --- /dev/null
> +++ b/drivers/platform/chrome/cros_ec_debugfs.h
> @@ -0,0 +1,27 @@
> +/*
> + * Copyright 2015 Google, Inc.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program. If not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#ifndef _DRV_CROS_EC_DEBUGFS_H_
> +#define _DRV_CROS_EC_DEBUGFS_H_
> +
> +#include "cros_ec_dev.h"
> +
> +/* debugfs stuff */
> +int cros_ec_debugfs_init(struct cros_ec_dev *ec);
> +void cros_ec_debugfs_remove(struct cros_ec_dev *ec);
> +
> +#endif /* _DRV_CROS_EC_DEBUGFS_H_ */
> diff --git a/drivers/platform/chrome/cros_ec_dev.c b/drivers/platform/chrome/cros_ec_dev.c
> index 8abd80d..7d51f69 100644
> --- a/drivers/platform/chrome/cros_ec_dev.c
> +++ b/drivers/platform/chrome/cros_ec_dev.c
> @@ -23,6 +23,7 @@
> #include <linux/slab.h>
> #include <linux/uaccess.h>
>
> +#include "cros_ec_debugfs.h"
> #include "cros_ec_dev.h"
>
> /* Device variables */
> @@ -282,6 +283,9 @@ static int ec_device_probe(struct platform_device *pdev)
> goto dev_reg_failed;
> }
>
> + if (cros_ec_debugfs_init(ec))
> + dev_warn(dev, "failed to create debugfs directory\n");
> +
> return 0;
>
> dev_reg_failed:
> @@ -296,6 +300,9 @@ cdev_add_failed:
> static int ec_device_remove(struct platform_device *pdev)
> {
> struct cros_ec_dev *ec = dev_get_drvdata(&pdev->dev);
> +
> + cros_ec_debugfs_remove(ec);
> +
> cdev_del(&ec->cdev);
> device_unregister(&ec->class_dev);
> return 0;
> diff --git a/include/linux/mfd/cros_ec.h b/include/linux/mfd/cros_ec.h
> index d641a18..e7001a7 100644
> --- a/include/linux/mfd/cros_ec.h
> +++ b/include/linux/mfd/cros_ec.h
> @@ -151,6 +151,8 @@ struct cros_ec_platform {
> u16 cmd_offset;
> };
>
> +struct cros_ec_debugfs;
> +
> /*
> * struct cros_ec_dev - ChromeOS EC device entry point
> *
> @@ -158,6 +160,7 @@ struct cros_ec_platform {
> * @cdev: Character device structure in /dev
> * @ec_dev: cros_ec_device structure to talk to the physical device
> * @dev: pointer to the platform device
> + * @debug_info: cros_ec_debugfs structure for debugging information
> * @cmd_offset: offset to apply for each command.
> */
> struct cros_ec_dev {
> @@ -165,6 +168,7 @@ struct cros_ec_dev {
> struct cdev cdev;
> struct cros_ec_device *ec_dev;
> struct device *dev;
> + struct cros_ec_debugfs *debug_info;
> u16 cmd_offset;
> };
>
> --
> 2.8.0.rc3.226.g39d4020
>