text: // SPDX-License-Identifier: GPL-2.0
  /*
   * debugfs attributes for Wilco EC
   *
   * Copyright 2019 Google LLC
   *
   * See Documentation/ABI/testing/debugfs-wilco-ec for usage.
   */
  
  #include <linux/ctype.h>
  #include <linux/debugfs.h>
  #include <linux/fs.h>
  #include <linux/module.h>
  #include <linux/platform_data/wilco-ec.h>
  #include <linux/platform_device.h>
  
  #define DRV_NAME "wilco-ec-debugfs"
  
  /* 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];
  	u8 formatted_data[FORMATTED_BUFFER_SIZE];
  };
  static struct wilco_ec_debugfs *debug_info;
  
  /**
   * parse_hex_sentence() - Convert a ascii hex representation into byte array.
   * @in: Input buffer of ascii.
   * @isize: Length of input buffer.
   * @out: Output buffer.
   * @osize: Length of output buffer, e.g. max number of bytes to parse.
   *
   * An valid input is a series of ascii hexadecimal numbers, separated by spaces.
   * An example valid input is
   * "   00 f2 0    000076 6 0  ff"
   *
   * If an individual "word" within the hex sentence is longer than MAX_WORD_SIZE,
   * then the sentence is illegal, and parsing will fail.
   *
   * Return: Number of bytes parsed, or negative error code on failure.
   */
  static int parse_hex_sentence(const char *in, int isize, u8 *out, int osize)
  {
  	int n_parsed = 0;
  	int word_start = 0;
  	int word_end;
  	int word_len;
  	/* Temp buffer for holding a "word" of chars that represents one byte */
  	#define MAX_WORD_SIZE 16
  	char tmp[MAX_WORD_SIZE + 1];
  	u8 byte;
  
  	while (word_start < isize && n_parsed < osize) {
  		/* Find the start of the next word */
  		while (word_start < isize && isspace(in[word_start]))
  			word_start++;
  		 /* reached the end of the input before next word? */
  		if (word_start >= isize)
  			break;
  
  		/* Find the end of this word */
  		word_end = word_start;
  		while (word_end < isize && !isspace(in[word_end]))
  			word_end++;
  
  		/* Copy to a tmp NULL terminated string */
  		word_len = word_end - word_start;
  		if (word_len > MAX_WORD_SIZE)
  			return -EINVAL;
  		memcpy(tmp, in + word_start, word_len);
  		tmp[word_len] = '\0';
  
  		/*
  		 * Convert from hex string, place in output. If fails to parse,
  		 * just return -EINVAL because specific error code is only
  		 * relevant for this one word, returning it would be confusing.
  		 */
  		if (kstrtou8(tmp, 16, &byte))
  			return -EINVAL;
  		out[n_parsed++] = byte;
  
  		word_start = word_end;
  	}
  	return n_parsed;
  }
  
  /* The message type takes up two bytes*/
  #define TYPE_AND_DATA_SIZE ((EC_MAILBOX_DATA_SIZE) + 2)
  
  static ssize_t raw_write(struct file *file, const char __user *user_buf,
  			 size_t count, loff_t *ppos)
  {
  	char *buf = debug_info->formatted_data;
  	struct wilco_ec_message msg;
  	u8 request_data[TYPE_AND_DATA_SIZE];
  	ssize_t kcount;
  	int ret;
  
  	if (count > FORMATTED_BUFFER_SIZE)
  		return -EINVAL;
  
  	kcount = simple_write_to_buffer(buf, FORMATTED_BUFFER_SIZE, ppos,
  					user_buf, count);
  	if (kcount < 0)
  		return kcount;
  
  	ret = parse_hex_sentence(buf, kcount, request_data, TYPE_AND_DATA_SIZE);
  	if (ret < 0)
  		return ret;
  	/* Need at least two bytes for message type and one byte of data */
  	if (ret < 3)
  		return -EINVAL;
  
  	msg.type = request_data[0] << 8 | request_data[1];
  	msg.flags = 0;
  	msg.request_data = request_data + 2;
  	msg.request_size = ret - 2;
  	memset(debug_info->raw_data, 0, sizeof(debug_info->raw_data));
  	msg.response_data = debug_info->raw_data;
  	msg.response_size = EC_MAILBOX_DATA_SIZE;
  
  	ret = wilco_ec_mailbox(debug_info->ec, &msg);
  	if (ret < 0)
  		return ret;
  	debug_info->response_size = ret;
  
  	return count;
  }
  
  static ssize_t raw_read(struct file *file, char __user *user_buf, size_t count,
  			loff_t *ppos)
  {
  	int fmt_len = 0;
  
  	if (debug_info->response_size) {
  		fmt_len = hex_dump_to_buffer(debug_info->raw_data,
  					     debug_info->response_size,
  					     16, 1, debug_info->formatted_data,
  					     sizeof(debug_info->formatted_data),
  					     true);
  		/* Only return response the first time it is read */
  		debug_info->response_size = 0;
  	}
  
  	return simple_read_from_buffer(user_buf, count, ppos,
  				       debug_info->formatted_data, fmt_len);
  }
  
  static const struct file_operations fops_raw = {
  	.owner = THIS_MODULE,
  	.read = raw_read,
  	.write = raw_write,
  	.llseek = no_llseek,
  };
  
  #define CMD_KB_CHROME		0x88
  #define SUB_CMD_H1_GPIO		0x0A
  #define SUB_CMD_TEST_EVENT	0x0B
  
  struct ec_request {
  	u8 cmd;		/* Always CMD_KB_CHROME */
  	u8 reserved;
  	u8 sub_cmd;
  } __packed;
  
  struct ec_response {
  	u8 status;	/* 0 if allowed */
  	u8 val;
  } __packed;
  
  static int send_ec_cmd(struct wilco_ec_device *ec, u8 sub_cmd, u8 *out_val)
  {
  	struct ec_request rq;
  	struct ec_response rs;
  	struct wilco_ec_message msg;
  	int ret;
  
  	memset(&rq, 0, sizeof(rq));
  	rq.cmd = CMD_KB_CHROME;
  	rq.sub_cmd = sub_cmd;
  
  	memset(&msg, 0, sizeof(msg));
  	msg.type = WILCO_EC_MSG_LEGACY;
  	msg.request_data = &rq;
  	msg.request_size = sizeof(rq);
  	msg.response_data = &rs;
  	msg.response_size = sizeof(rs);
  	ret = wilco_ec_mailbox(ec, &msg);
  	if (ret < 0)
  		return ret;
  	if (rs.status)
  		return -EIO;
  
  	*out_val = rs.val;
  
  	return 0;
  }
  
  /**
   * h1_gpio_get() - Gets h1 gpio status.
   * @arg: The wilco EC device.
   * @val: BIT(0)=ENTRY_TO_FACT_MODE, BIT(1)=SPI_CHROME_SEL
   */
  static int h1_gpio_get(void *arg, u64 *val)
  {
  	int ret;
  
  	ret = send_ec_cmd(arg, SUB_CMD_H1_GPIO, (u8 *)val);
  	if (ret == 0)
  		*val &= 0xFF;
  	return ret;
  }
  
  DEFINE_DEBUGFS_ATTRIBUTE(fops_h1_gpio, h1_gpio_get, NULL, "0x%02llx\n");
  
  /**
   * test_event_set() - Sends command to EC to cause an EC test event.
   * @arg: The wilco EC device.
   * @val: unused.
   */
  static int test_event_set(void *arg, u64 val)
  {
  	u8 ret;
  
  	return send_ec_cmd(arg, SUB_CMD_TEST_EVENT, &ret);
  }
  
  /* Format is unused since it is only required for get method which is NULL */
  DEFINE_DEBUGFS_ATTRIBUTE(fops_test_event, NULL, test_event_set, "%llu\n");
  
  /**
   * wilco_ec_debugfs_probe() - Create the debugfs node
   * @pdev: The platform device, probably created in core.c
   *
   * Try to create a debugfs node. If it fails, then we don't want to change
   * behavior at all, this is for debugging after all. Just fail silently.
   *
   * Return: 0 always.
   */
  static int wilco_ec_debugfs_probe(struct platform_device *pdev)
  {
  	struct wilco_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
  
  	debug_info = devm_kzalloc(&pdev->dev, sizeof(*debug_info), GFP_KERNEL);
  	if (!debug_info)
  		return 0;
  	debug_info->ec = ec;
  	debug_info->dir = debugfs_create_dir("wilco_ec", NULL);
  	if (!debug_info->dir)
  		return 0;
  	debugfs_create_file("raw", 0644, debug_info->dir, NULL, &fops_raw);
  	debugfs_create_file("h1_gpio", 0444, debug_info->dir, ec,
  			    &fops_h1_gpio);
  	debugfs_create_file("test_event", 0200, debug_info->dir, ec,
  			    &fops_test_event);
  
  	return 0;
  }
  
  static int wilco_ec_debugfs_remove(struct platform_device *pdev)
  {
  	debugfs_remove_recursive(debug_info->dir);
  
  	return 0;
  }
  
  static struct platform_driver wilco_ec_debugfs_driver = {
  	.driver = {
  		.name = DRV_NAME,
  	},
  	.probe = wilco_ec_debugfs_probe,
  	.remove = wilco_ec_debugfs_remove,
  };
  
  module_platform_driver(wilco_ec_debugfs_driver);
  
  MODULE_ALIAS("platform:" DRV_NAME);
  MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
  MODULE_LICENSE("GPL v2");
  MODULE_DESCRIPTION("Wilco EC debugfs driver");
  