1/* 2 * cros_ec_sysfs - expose the Chrome OS EC through sysfs 3 * 4 * Copyright (C) 2014 Google, Inc. 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20#define pr_fmt(fmt) "cros_ec_sysfs: " fmt 21 22#include <linux/ctype.h> 23#include <linux/delay.h> 24#include <linux/device.h> 25#include <linux/fs.h> 26#include <linux/kobject.h> 27#include <linux/mfd/cros_ec.h> 28#include <linux/mfd/cros_ec_commands.h> 29#include <linux/module.h> 30#include <linux/platform_device.h> 31#include <linux/printk.h> 32#include <linux/stat.h> 33#include <linux/types.h> 34#include <linux/uaccess.h> 35 36#include "cros_ec_dev.h" 37 38/* Accessor functions */ 39 40static ssize_t show_ec_reboot(struct device *dev, 41 struct device_attribute *attr, char *buf) 42{ 43 int count = 0; 44 45 count += scnprintf(buf + count, PAGE_SIZE - count, 46 "ro|rw|cancel|cold|disable-jump|hibernate"); 47 count += scnprintf(buf + count, PAGE_SIZE - count, 48 " [at-shutdown]\n"); 49 return count; 50} 51 52static ssize_t store_ec_reboot(struct device *dev, 53 struct device_attribute *attr, 54 const char *buf, size_t count) 55{ 56 static const struct { 57 const char * const str; 58 uint8_t cmd; 59 uint8_t flags; 60 } words[] = { 61 {"cancel", EC_REBOOT_CANCEL, 0}, 62 {"ro", EC_REBOOT_JUMP_RO, 0}, 63 {"rw", EC_REBOOT_JUMP_RW, 0}, 64 {"cold", EC_REBOOT_COLD, 0}, 65 {"disable-jump", EC_REBOOT_DISABLE_JUMP, 0}, 66 {"hibernate", EC_REBOOT_HIBERNATE, 0}, 67 {"at-shutdown", -1, EC_REBOOT_FLAG_ON_AP_SHUTDOWN}, 68 }; 69 struct cros_ec_command msg = { 0 }; 70 struct ec_params_reboot_ec *param = 71 (struct ec_params_reboot_ec *)msg.outdata; 72 int got_cmd = 0, offset = 0; 73 int i; 74 int ret; 75 struct cros_ec_device *ec = dev_get_drvdata(dev); 76 77 param->flags = 0; 78 while (1) { 79 /* Find word to start scanning */ 80 while (buf[offset] && isspace(buf[offset])) 81 offset++; 82 if (!buf[offset]) 83 break; 84 85 for (i = 0; i < ARRAY_SIZE(words); i++) { 86 if (!strncasecmp(words[i].str, buf+offset, 87 strlen(words[i].str))) { 88 if (words[i].flags) { 89 param->flags |= words[i].flags; 90 } else { 91 param->cmd = words[i].cmd; 92 got_cmd = 1; 93 } 94 break; 95 } 96 } 97 98 /* On to the next word, if any */ 99 while (buf[offset] && !isspace(buf[offset])) 100 offset++; 101 } 102 103 if (!got_cmd) 104 return -EINVAL; 105 106 msg.command = EC_CMD_REBOOT_EC; 107 msg.outsize = sizeof(param); 108 ret = cros_ec_cmd_xfer(ec, &msg); 109 if (ret < 0) 110 return ret; 111 if (msg.result != EC_RES_SUCCESS) { 112 dev_dbg(ec->dev, "EC result %d\n", msg.result); 113 return -EINVAL; 114 } 115 116 return count; 117} 118 119static ssize_t show_ec_version(struct device *dev, 120 struct device_attribute *attr, char *buf) 121{ 122 static const char * const image_names[] = {"unknown", "RO", "RW"}; 123 struct ec_response_get_version *r_ver; 124 struct ec_response_get_chip_info *r_chip; 125 struct ec_response_board_version *r_board; 126 struct cros_ec_command msg = { 0 }; 127 int ret; 128 int count = 0; 129 struct cros_ec_device *ec = dev_get_drvdata(dev); 130 131 /* Get versions. RW may change. */ 132 msg.command = EC_CMD_GET_VERSION; 133 msg.insize = sizeof(*r_ver); 134 ret = cros_ec_cmd_xfer(ec, &msg); 135 if (ret < 0) 136 return ret; 137 if (msg.result != EC_RES_SUCCESS) 138 return scnprintf(buf, PAGE_SIZE, 139 "ERROR: EC returned %d\n", msg.result); 140 141 r_ver = (struct ec_response_get_version *)msg.indata; 142 /* Strings should be null-terminated, but let's be sure. */ 143 r_ver->version_string_ro[sizeof(r_ver->version_string_ro) - 1] = '\0'; 144 r_ver->version_string_rw[sizeof(r_ver->version_string_rw) - 1] = '\0'; 145 count += scnprintf(buf + count, PAGE_SIZE - count, 146 "RO version: %s\n", r_ver->version_string_ro); 147 count += scnprintf(buf + count, PAGE_SIZE - count, 148 "RW version: %s\n", r_ver->version_string_rw); 149 count += scnprintf(buf + count, PAGE_SIZE - count, 150 "Firmware copy: %s\n", 151 (r_ver->current_image < ARRAY_SIZE(image_names) ? 152 image_names[r_ver->current_image] : "?")); 153 154 /* Get build info. */ 155 msg.command = EC_CMD_GET_BUILD_INFO; 156 msg.insize = sizeof(msg.indata); 157 ret = cros_ec_cmd_xfer(ec, &msg); 158 if (ret < 0) 159 count += scnprintf(buf + count, PAGE_SIZE - count, 160 "Build info: XFER ERROR %d\n", ret); 161 else if (msg.result != EC_RES_SUCCESS) 162 count += scnprintf(buf + count, PAGE_SIZE - count, 163 "Build info: EC error %d\n", msg.result); 164 else { 165 msg.indata[sizeof(msg.indata) - 1] = '\0'; 166 count += scnprintf(buf + count, PAGE_SIZE - count, 167 "Build info: %s\n", msg.indata); 168 } 169 170 /* Get chip info. */ 171 msg.command = EC_CMD_GET_CHIP_INFO; 172 msg.insize = sizeof(*r_chip); 173 ret = cros_ec_cmd_xfer(ec, &msg); 174 if (ret < 0) 175 count += scnprintf(buf + count, PAGE_SIZE - count, 176 "Chip info: XFER ERROR %d\n", ret); 177 else if (msg.result != EC_RES_SUCCESS) 178 count += scnprintf(buf + count, PAGE_SIZE - count, 179 "Chip info: EC error %d\n", msg.result); 180 else { 181 r_chip = (struct ec_response_get_chip_info *)msg.indata; 182 183 r_chip->vendor[sizeof(r_chip->vendor) - 1] = '\0'; 184 r_chip->name[sizeof(r_chip->name) - 1] = '\0'; 185 r_chip->revision[sizeof(r_chip->revision) - 1] = '\0'; 186 count += scnprintf(buf + count, PAGE_SIZE - count, 187 "Chip vendor: %s\n", r_chip->vendor); 188 count += scnprintf(buf + count, PAGE_SIZE - count, 189 "Chip name: %s\n", r_chip->name); 190 count += scnprintf(buf + count, PAGE_SIZE - count, 191 "Chip revision: %s\n", r_chip->revision); 192 } 193 194 /* Get board version */ 195 msg.command = EC_CMD_GET_BOARD_VERSION; 196 msg.insize = sizeof(*r_board); 197 ret = cros_ec_cmd_xfer(ec, &msg); 198 if (ret < 0) 199 count += scnprintf(buf + count, PAGE_SIZE - count, 200 "Board version: XFER ERROR %d\n", ret); 201 else if (msg.result != EC_RES_SUCCESS) 202 count += scnprintf(buf + count, PAGE_SIZE - count, 203 "Board version: EC error %d\n", msg.result); 204 else { 205 r_board = (struct ec_response_board_version *)msg.indata; 206 207 count += scnprintf(buf + count, PAGE_SIZE - count, 208 "Board version: %d\n", 209 r_board->board_version); 210 } 211 212 return count; 213} 214 215static ssize_t show_ec_flashinfo(struct device *dev, 216 struct device_attribute *attr, char *buf) 217{ 218 struct ec_response_flash_info *resp; 219 struct cros_ec_command msg = { 0 }; 220 int ret; 221 struct cros_ec_device *ec = dev_get_drvdata(dev); 222 223 /* The flash info shouldn't ever change, but ask each time anyway. */ 224 msg.command = EC_CMD_FLASH_INFO; 225 msg.insize = sizeof(*resp); 226 ret = cros_ec_cmd_xfer(ec, &msg); 227 if (ret < 0) 228 return ret; 229 if (msg.result != EC_RES_SUCCESS) 230 return scnprintf(buf, PAGE_SIZE, 231 "ERROR: EC returned %d\n", msg.result); 232 233 resp = (struct ec_response_flash_info *)msg.indata; 234 235 return scnprintf(buf, PAGE_SIZE, 236 "FlashSize %d\nWriteSize %d\n" 237 "EraseSize %d\nProtectSize %d\n", 238 resp->flash_size, resp->write_block_size, 239 resp->erase_block_size, resp->protect_block_size); 240} 241 242/* Module initialization */ 243 244static DEVICE_ATTR(reboot, S_IWUSR | S_IRUGO, show_ec_reboot, store_ec_reboot); 245static DEVICE_ATTR(version, S_IRUGO, show_ec_version, NULL); 246static DEVICE_ATTR(flashinfo, S_IRUGO, show_ec_flashinfo, NULL); 247 248static struct attribute *__ec_attrs[] = { 249 &dev_attr_reboot.attr, 250 &dev_attr_version.attr, 251 &dev_attr_flashinfo.attr, 252 NULL, 253}; 254 255static struct attribute_group ec_attr_group = { 256 .attrs = __ec_attrs, 257}; 258 259void ec_dev_sysfs_init(struct cros_ec_device *ec) 260{ 261 int error; 262 263 error = sysfs_create_group(&ec->vdev->kobj, &ec_attr_group); 264 if (error) 265 pr_warn("failed to create group: %d\n", error); 266} 267 268void ec_dev_sysfs_remove(struct cros_ec_device *ec) 269{ 270 sysfs_remove_group(&ec->vdev->kobj, &ec_attr_group); 271} 272