123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493 |
- /*
- * 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>
- #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
- * @panicinfo_blob: panicinfo debugfs blob
- */
- struct cros_ec_debugfs {
- struct cros_ec_dev *ec;
- struct dentry *dir;
- /* EC log */
- 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;
- /* EC panicinfo */
- struct debugfs_blob_wrapper panicinfo_blob;
- };
- /*
- * 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 __poll_t cros_ec_console_log_poll(struct file *file,
- poll_table *wait)
- {
- struct cros_ec_debugfs *debug_info = file->private_data;
- __poll_t 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 |= EPOLLIN | EPOLLRDNORM;
- mutex_unlock(&debug_info->log_mutex);
- return mask;
- }
- static int cros_ec_console_log_release(struct inode *inode, struct file *file)
- {
- return 0;
- }
- static ssize_t cros_ec_pdinfo_read(struct file *file,
- char __user *user_buf,
- size_t count,
- loff_t *ppos)
- {
- char read_buf[EC_USB_PD_MAX_PORTS * 40], *p = read_buf;
- 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 msg;
- union {
- struct ec_response_usb_pd_control_v1 resp;
- struct ec_params_usb_pd_control params;
- };
- } __packed ec_buf;
- struct cros_ec_command *msg;
- struct ec_response_usb_pd_control_v1 *resp;
- struct ec_params_usb_pd_control *params;
- int i;
- msg = &ec_buf.msg;
- params = (struct ec_params_usb_pd_control *)msg->data;
- resp = (struct ec_response_usb_pd_control_v1 *)msg->data;
- msg->command = EC_CMD_USB_PD_CONTROL;
- msg->version = 1;
- msg->insize = sizeof(*resp);
- msg->outsize = sizeof(*params);
- /*
- * Read status from all PD ports until failure, typically caused
- * by attempting to read status on a port that doesn't exist.
- */
- for (i = 0; i < EC_USB_PD_MAX_PORTS; ++i) {
- params->port = i;
- params->role = 0;
- params->mux = 0;
- params->swap = 0;
- if (cros_ec_cmd_xfer_status(ec_dev, msg) < 0)
- break;
- p += scnprintf(p, sizeof(read_buf) + read_buf - p,
- "p%d: %s en:%.2x role:%.2x pol:%.2x\n", i,
- resp->state, resp->enabled, resp->role,
- resp->polarity);
- }
- return simple_read_from_buffer(user_buf, count, ppos,
- read_buf, p - read_buf);
- }
- 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,
- };
- const struct file_operations cros_ec_pdinfo_fops = {
- .owner = THIS_MODULE,
- .open = simple_open,
- .read = cros_ec_pdinfo_read,
- .llseek = default_llseek,
- };
- 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 | 0444,
- 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);
- }
- }
- static int cros_ec_create_panicinfo(struct cros_ec_debugfs *debug_info)
- {
- struct cros_ec_device *ec_dev = debug_info->ec->ec_dev;
- int ret;
- struct cros_ec_command *msg;
- int insize;
- insize = ec_dev->max_response;
- msg = devm_kzalloc(debug_info->ec->dev,
- sizeof(*msg) + insize, GFP_KERNEL);
- if (!msg)
- return -ENOMEM;
- msg->command = EC_CMD_GET_PANIC_INFO;
- msg->insize = insize;
- ret = cros_ec_cmd_xfer(ec_dev, msg);
- if (ret < 0) {
- dev_warn(debug_info->ec->dev, "Cannot read panicinfo.\n");
- ret = 0;
- goto free;
- }
- /* No panic data */
- if (ret == 0)
- goto free;
- debug_info->panicinfo_blob.data = msg->data;
- debug_info->panicinfo_blob.size = ret;
- if (!debugfs_create_blob("panicinfo",
- S_IFREG | 0444,
- debug_info->dir,
- &debug_info->panicinfo_blob)) {
- ret = -ENOMEM;
- goto free;
- }
- return 0;
- free:
- devm_kfree(debug_info->ec->dev, msg);
- return ret;
- }
- static int cros_ec_create_pdinfo(struct cros_ec_debugfs *debug_info)
- {
- if (!debugfs_create_file("pdinfo", 0444, debug_info->dir, debug_info,
- &cros_ec_pdinfo_fops))
- return -ENOMEM;
- return 0;
- }
- 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_panicinfo(debug_info);
- if (ret)
- goto remove_debugfs;
- ret = cros_ec_create_console_log(debug_info);
- if (ret)
- goto remove_debugfs;
- ret = cros_ec_create_pdinfo(debug_info);
- if (ret)
- goto remove_debugfs;
- ec->debug_info = debug_info;
- return 0;
- remove_debugfs:
- debugfs_remove_recursive(debug_info->dir);
- return ret;
- }
- EXPORT_SYMBOL(cros_ec_debugfs_init);
- 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);
- }
- EXPORT_SYMBOL(cros_ec_debugfs_remove);
- void cros_ec_debugfs_suspend(struct cros_ec_dev *ec)
- {
- /*
- * cros_ec_debugfs_init() failures are non-fatal; it's also possible
- * that we initted things but decided that console log wasn't supported.
- * We'll use the same set of checks that cros_ec_debugfs_remove() +
- * cros_ec_cleanup_console_log() end up using to handle those cases.
- */
- if (ec->debug_info && ec->debug_info->log_buffer.buf)
- cancel_delayed_work_sync(&ec->debug_info->log_poll_work);
- }
- EXPORT_SYMBOL(cros_ec_debugfs_suspend);
- void cros_ec_debugfs_resume(struct cros_ec_dev *ec)
- {
- if (ec->debug_info && ec->debug_info->log_buffer.buf)
- schedule_delayed_work(&ec->debug_info->log_poll_work, 0);
- }
- EXPORT_SYMBOL(cros_ec_debugfs_resume);
|