From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1757668AbcHWVR5 (ORCPT ); Tue, 23 Aug 2016 17:17:57 -0400 Received: from mail-qk0-f175.google.com ([209.85.220.175]:35154 "EHLO mail-qk0-f175.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1756561AbcHWVRy (ORCPT ); Tue, 23 Aug 2016 17:17:54 -0400 MIME-Version: 1.0 In-Reply-To: <1471926859-21263-3-git-send-email-drinkcat@chromium.org> References: <1471926859-21263-1-git-send-email-drinkcat@chromium.org> <1471926859-21263-3-git-send-email-drinkcat@chromium.org> From: Guenter Roeck Date: Tue, 23 Aug 2016 14:17:52 -0700 Message-ID: Subject: Re: [PATCH 2/3] mfd: cros_ec: add debugfs, console log file To: Nicolas Boichat Cc: Lee Jones , Gwendal Grignou , ejcaruso@chromium.org, Olof Johansson , linux-kernel , Guenter Roeck Content-Type: text/plain; charset=UTF-8 Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org On Mon, Aug 22, 2016 at 9:34 PM, Nicolas Boichat wrote: > From: Eric Caruso > > 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 > Signed-off-by: Nicolas Boichat Reviewed-by: Guenter Roeck > --- > 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 . > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > +#include > + > +#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 . > + */ > + > +#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 > #include > > +#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 >