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/slab.h> 33 #include <linux/stat.h> 34 #include <linux/types.h> 35 #include <linux/uaccess.h> 36 37 #define to_cros_ec_dev(dev) container_of(dev, struct cros_ec_dev, class_dev) 38 39 /* Accessor functions */ 40 41 static ssize_t reboot_show(struct device *dev, 42 struct device_attribute *attr, char *buf) 43 { 44 int count = 0; 45 46 count += scnprintf(buf + count, PAGE_SIZE - count, 47 "ro|rw|cancel|cold|disable-jump|hibernate"); 48 count += scnprintf(buf + count, PAGE_SIZE - count, 49 " [at-shutdown]\n"); 50 return count; 51 } 52 53 static ssize_t reboot_store(struct device *dev, 54 struct device_attribute *attr, 55 const char *buf, size_t count) 56 { 57 static const struct { 58 const char * const str; 59 uint8_t cmd; 60 uint8_t flags; 61 } words[] = { 62 {"cancel", EC_REBOOT_CANCEL, 0}, 63 {"ro", EC_REBOOT_JUMP_RO, 0}, 64 {"rw", EC_REBOOT_JUMP_RW, 0}, 65 {"cold", EC_REBOOT_COLD, 0}, 66 {"disable-jump", EC_REBOOT_DISABLE_JUMP, 0}, 67 {"hibernate", EC_REBOOT_HIBERNATE, 0}, 68 {"at-shutdown", -1, EC_REBOOT_FLAG_ON_AP_SHUTDOWN}, 69 }; 70 struct cros_ec_command *msg; 71 struct ec_params_reboot_ec *param; 72 int got_cmd = 0, offset = 0; 73 int i; 74 int ret; 75 struct cros_ec_dev *ec = to_cros_ec_dev(dev); 76 77 msg = kmalloc(sizeof(*msg) + sizeof(*param), GFP_KERNEL); 78 if (!msg) 79 return -ENOMEM; 80 81 param = (struct ec_params_reboot_ec *)msg->data; 82 83 param->flags = 0; 84 while (1) { 85 /* Find word to start scanning */ 86 while (buf[offset] && isspace(buf[offset])) 87 offset++; 88 if (!buf[offset]) 89 break; 90 91 for (i = 0; i < ARRAY_SIZE(words); i++) { 92 if (!strncasecmp(words[i].str, buf+offset, 93 strlen(words[i].str))) { 94 if (words[i].flags) { 95 param->flags |= words[i].flags; 96 } else { 97 param->cmd = words[i].cmd; 98 got_cmd = 1; 99 } 100 break; 101 } 102 } 103 104 /* On to the next word, if any */ 105 while (buf[offset] && !isspace(buf[offset])) 106 offset++; 107 } 108 109 if (!got_cmd) { 110 count = -EINVAL; 111 goto exit; 112 } 113 114 msg->version = 0; 115 msg->command = EC_CMD_REBOOT_EC + ec->cmd_offset; 116 msg->outsize = sizeof(*param); 117 msg->insize = 0; 118 ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); 119 if (ret < 0) 120 count = ret; 121 exit: 122 kfree(msg); 123 return count; 124 } 125 126 static ssize_t version_show(struct device *dev, 127 struct device_attribute *attr, char *buf) 128 { 129 static const char * const image_names[] = {"unknown", "RO", "RW"}; 130 struct ec_response_get_version *r_ver; 131 struct ec_response_get_chip_info *r_chip; 132 struct ec_response_board_version *r_board; 133 struct cros_ec_command *msg; 134 int ret; 135 int count = 0; 136 struct cros_ec_dev *ec = to_cros_ec_dev(dev); 137 138 msg = kmalloc(sizeof(*msg) + EC_HOST_PARAM_SIZE, GFP_KERNEL); 139 if (!msg) 140 return -ENOMEM; 141 142 /* Get versions. RW may change. */ 143 msg->version = 0; 144 msg->command = EC_CMD_GET_VERSION + ec->cmd_offset; 145 msg->insize = sizeof(*r_ver); 146 msg->outsize = 0; 147 ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); 148 if (ret < 0) { 149 count = ret; 150 goto exit; 151 } 152 r_ver = (struct ec_response_get_version *)msg->data; 153 /* Strings should be null-terminated, but let's be sure. */ 154 r_ver->version_string_ro[sizeof(r_ver->version_string_ro) - 1] = '\0'; 155 r_ver->version_string_rw[sizeof(r_ver->version_string_rw) - 1] = '\0'; 156 count += scnprintf(buf + count, PAGE_SIZE - count, 157 "RO version: %s\n", r_ver->version_string_ro); 158 count += scnprintf(buf + count, PAGE_SIZE - count, 159 "RW version: %s\n", r_ver->version_string_rw); 160 count += scnprintf(buf + count, PAGE_SIZE - count, 161 "Firmware copy: %s\n", 162 (r_ver->current_image < ARRAY_SIZE(image_names) ? 163 image_names[r_ver->current_image] : "?")); 164 165 /* Get build info. */ 166 msg->command = EC_CMD_GET_BUILD_INFO + ec->cmd_offset; 167 msg->insize = EC_HOST_PARAM_SIZE; 168 ret = cros_ec_cmd_xfer(ec->ec_dev, msg); 169 if (ret < 0) 170 count += scnprintf(buf + count, PAGE_SIZE - count, 171 "Build info: XFER ERROR %d\n", ret); 172 else if (msg->result != EC_RES_SUCCESS) 173 count += scnprintf(buf + count, PAGE_SIZE - count, 174 "Build info: EC error %d\n", msg->result); 175 else { 176 msg->data[EC_HOST_PARAM_SIZE - 1] = '\0'; 177 count += scnprintf(buf + count, PAGE_SIZE - count, 178 "Build info: %s\n", msg->data); 179 } 180 181 /* Get chip info. */ 182 msg->command = EC_CMD_GET_CHIP_INFO + ec->cmd_offset; 183 msg->insize = sizeof(*r_chip); 184 ret = cros_ec_cmd_xfer(ec->ec_dev, msg); 185 if (ret < 0) 186 count += scnprintf(buf + count, PAGE_SIZE - count, 187 "Chip info: XFER ERROR %d\n", ret); 188 else if (msg->result != EC_RES_SUCCESS) 189 count += scnprintf(buf + count, PAGE_SIZE - count, 190 "Chip info: EC error %d\n", msg->result); 191 else { 192 r_chip = (struct ec_response_get_chip_info *)msg->data; 193 194 r_chip->vendor[sizeof(r_chip->vendor) - 1] = '\0'; 195 r_chip->name[sizeof(r_chip->name) - 1] = '\0'; 196 r_chip->revision[sizeof(r_chip->revision) - 1] = '\0'; 197 count += scnprintf(buf + count, PAGE_SIZE - count, 198 "Chip vendor: %s\n", r_chip->vendor); 199 count += scnprintf(buf + count, PAGE_SIZE - count, 200 "Chip name: %s\n", r_chip->name); 201 count += scnprintf(buf + count, PAGE_SIZE - count, 202 "Chip revision: %s\n", r_chip->revision); 203 } 204 205 /* Get board version */ 206 msg->command = EC_CMD_GET_BOARD_VERSION + ec->cmd_offset; 207 msg->insize = sizeof(*r_board); 208 ret = cros_ec_cmd_xfer(ec->ec_dev, msg); 209 if (ret < 0) 210 count += scnprintf(buf + count, PAGE_SIZE - count, 211 "Board version: XFER ERROR %d\n", ret); 212 else if (msg->result != EC_RES_SUCCESS) 213 count += scnprintf(buf + count, PAGE_SIZE - count, 214 "Board version: EC error %d\n", msg->result); 215 else { 216 r_board = (struct ec_response_board_version *)msg->data; 217 218 count += scnprintf(buf + count, PAGE_SIZE - count, 219 "Board version: %d\n", 220 r_board->board_version); 221 } 222 223 exit: 224 kfree(msg); 225 return count; 226 } 227 228 static ssize_t flashinfo_show(struct device *dev, 229 struct device_attribute *attr, char *buf) 230 { 231 struct ec_response_flash_info *resp; 232 struct cros_ec_command *msg; 233 int ret; 234 struct cros_ec_dev *ec = to_cros_ec_dev(dev); 235 236 msg = kmalloc(sizeof(*msg) + sizeof(*resp), GFP_KERNEL); 237 if (!msg) 238 return -ENOMEM; 239 240 /* The flash info shouldn't ever change, but ask each time anyway. */ 241 msg->version = 0; 242 msg->command = EC_CMD_FLASH_INFO + ec->cmd_offset; 243 msg->insize = sizeof(*resp); 244 msg->outsize = 0; 245 ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); 246 if (ret < 0) 247 goto exit; 248 249 resp = (struct ec_response_flash_info *)msg->data; 250 251 ret = scnprintf(buf, PAGE_SIZE, 252 "FlashSize %d\nWriteSize %d\n" 253 "EraseSize %d\nProtectSize %d\n", 254 resp->flash_size, resp->write_block_size, 255 resp->erase_block_size, resp->protect_block_size); 256 exit: 257 kfree(msg); 258 return ret; 259 } 260 261 /* Keyboard wake angle control */ 262 static ssize_t kb_wake_angle_show(struct device *dev, 263 struct device_attribute *attr, char *buf) 264 { 265 struct cros_ec_dev *ec = to_cros_ec_dev(dev); 266 struct ec_response_motion_sense *resp; 267 struct ec_params_motion_sense *param; 268 struct cros_ec_command *msg; 269 int ret; 270 271 msg = kmalloc(sizeof(*msg) + EC_HOST_PARAM_SIZE, GFP_KERNEL); 272 if (!msg) 273 return -ENOMEM; 274 275 param = (struct ec_params_motion_sense *)msg->data; 276 msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset; 277 msg->version = 2; 278 param->cmd = MOTIONSENSE_CMD_KB_WAKE_ANGLE; 279 param->kb_wake_angle.data = EC_MOTION_SENSE_NO_VALUE; 280 msg->outsize = sizeof(*param); 281 msg->insize = sizeof(*resp); 282 283 ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); 284 if (ret < 0) 285 goto exit; 286 287 resp = (struct ec_response_motion_sense *)msg->data; 288 ret = scnprintf(buf, PAGE_SIZE, "%d\n", resp->kb_wake_angle.ret); 289 exit: 290 kfree(msg); 291 return ret; 292 } 293 294 static ssize_t kb_wake_angle_store(struct device *dev, 295 struct device_attribute *attr, 296 const char *buf, size_t count) 297 { 298 struct cros_ec_dev *ec = to_cros_ec_dev(dev); 299 struct ec_params_motion_sense *param; 300 struct cros_ec_command *msg; 301 u16 angle; 302 int ret; 303 304 ret = kstrtou16(buf, 0, &angle); 305 if (ret) 306 return ret; 307 308 msg = kmalloc(sizeof(*msg) + EC_HOST_PARAM_SIZE, GFP_KERNEL); 309 if (!msg) 310 return -ENOMEM; 311 312 param = (struct ec_params_motion_sense *)msg->data; 313 msg->command = EC_CMD_MOTION_SENSE_CMD + ec->cmd_offset; 314 msg->version = 2; 315 param->cmd = MOTIONSENSE_CMD_KB_WAKE_ANGLE; 316 param->kb_wake_angle.data = angle; 317 msg->outsize = sizeof(*param); 318 msg->insize = sizeof(struct ec_response_motion_sense); 319 320 ret = cros_ec_cmd_xfer_status(ec->ec_dev, msg); 321 kfree(msg); 322 if (ret < 0) 323 return ret; 324 return count; 325 } 326 327 /* Module initialization */ 328 329 static DEVICE_ATTR_RW(reboot); 330 static DEVICE_ATTR_RO(version); 331 static DEVICE_ATTR_RO(flashinfo); 332 static DEVICE_ATTR_RW(kb_wake_angle); 333 334 static struct attribute *__ec_attrs[] = { 335 &dev_attr_kb_wake_angle.attr, 336 &dev_attr_reboot.attr, 337 &dev_attr_version.attr, 338 &dev_attr_flashinfo.attr, 339 NULL, 340 }; 341 342 static umode_t cros_ec_ctrl_visible(struct kobject *kobj, 343 struct attribute *a, int n) 344 { 345 struct device *dev = container_of(kobj, struct device, kobj); 346 struct cros_ec_dev *ec = to_cros_ec_dev(dev); 347 348 if (a == &dev_attr_kb_wake_angle.attr && !ec->has_kb_wake_angle) 349 return 0; 350 351 return a->mode; 352 } 353 354 struct attribute_group cros_ec_attr_group = { 355 .attrs = __ec_attrs, 356 .is_visible = cros_ec_ctrl_visible, 357 }; 358 EXPORT_SYMBOL(cros_ec_attr_group); 359 360 MODULE_LICENSE("GPL"); 361 MODULE_DESCRIPTION("ChromeOS EC control driver"); 362