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