Commit | Line | Data |
---|---|---|
6fa3eb70 S |
1 | /* |
2 | * kernel/power/tuxonice_sysfs.c | |
3 | * | |
4 | * Copyright (C) 2002-2010 Nigel Cunningham (nigel at tuxonice net) | |
5 | * | |
6 | * This file is released under the GPLv2. | |
7 | * | |
8 | * This file contains support for sysfs entries for tuning TuxOnIce. | |
9 | * | |
10 | * We have a generic handler that deals with the most common cases, and | |
11 | * hooks for special handlers to use. | |
12 | */ | |
13 | ||
14 | #include <linux/suspend.h> | |
15 | ||
16 | #include "tuxonice_sysfs.h" | |
17 | #include "tuxonice.h" | |
18 | #include "tuxonice_storage.h" | |
19 | #include "tuxonice_alloc.h" | |
20 | ||
21 | static int toi_sysfs_initialised; | |
22 | ||
23 | static void toi_initialise_sysfs(void); | |
24 | ||
25 | static struct toi_sysfs_data sysfs_params[]; | |
26 | ||
27 | #define to_sysfs_data(_attr) container_of(_attr, struct toi_sysfs_data, attr) | |
28 | ||
29 | static void toi_main_wrapper(void) | |
30 | { | |
31 | toi_try_hibernate(); | |
32 | } | |
33 | ||
34 | static ssize_t toi_attr_show(struct kobject *kobj, struct attribute *attr, char *page) | |
35 | { | |
36 | struct toi_sysfs_data *sysfs_data = to_sysfs_data(attr); | |
37 | int len = 0; | |
38 | int full_prep = sysfs_data->flags & SYSFS_NEEDS_SM_FOR_READ; | |
39 | ||
40 | if (full_prep && toi_start_anything(0)) | |
41 | return -EBUSY; | |
42 | ||
43 | if (sysfs_data->flags & SYSFS_NEEDS_SM_FOR_READ) | |
44 | toi_prepare_usm(); | |
45 | ||
46 | switch (sysfs_data->type) { | |
47 | case TOI_SYSFS_DATA_CUSTOM: | |
48 | len = (sysfs_data->data.special.read_sysfs) ? | |
49 | (sysfs_data->data.special.read_sysfs) (page, PAGE_SIZE) | |
50 | : 0; | |
51 | break; | |
52 | case TOI_SYSFS_DATA_BIT: | |
53 | len = sprintf(page, "%d\n", | |
54 | -test_bit(sysfs_data->data.bit.bit, sysfs_data->data.bit.bit_vector)); | |
55 | break; | |
56 | case TOI_SYSFS_DATA_INTEGER: | |
57 | len = sprintf(page, "%d\n", *(sysfs_data->data.integer.variable)); | |
58 | break; | |
59 | case TOI_SYSFS_DATA_LONG: | |
60 | len = sprintf(page, "%ld\n", *(sysfs_data->data.a_long.variable)); | |
61 | break; | |
62 | case TOI_SYSFS_DATA_UL: | |
63 | len = sprintf(page, "%lu\n", *(sysfs_data->data.ul.variable)); | |
64 | break; | |
65 | case TOI_SYSFS_DATA_STRING: | |
66 | len = sprintf(page, "%s\n", sysfs_data->data.string.variable); | |
67 | break; | |
68 | } | |
69 | ||
70 | if (sysfs_data->flags & SYSFS_NEEDS_SM_FOR_READ) | |
71 | toi_cleanup_usm(); | |
72 | ||
73 | if (full_prep) | |
74 | toi_finish_anything(0); | |
75 | ||
76 | return len; | |
77 | } | |
78 | ||
79 | #define BOUND(_variable, _type) do { \ | |
80 | if (*_variable < sysfs_data->data._type.minimum) \ | |
81 | *_variable = sysfs_data->data._type.minimum; \ | |
82 | else if (*_variable > sysfs_data->data._type.maximum) \ | |
83 | *_variable = sysfs_data->data._type.maximum; \ | |
84 | } while (0) | |
85 | ||
86 | static ssize_t toi_attr_store(struct kobject *kobj, struct attribute *attr, | |
87 | const char *my_buf, size_t count) | |
88 | { | |
89 | int assigned_temp_buffer = 0, result = count; | |
90 | struct toi_sysfs_data *sysfs_data = to_sysfs_data(attr); | |
91 | ||
92 | if (toi_start_anything((sysfs_data->flags & SYSFS_HIBERNATE_OR_RESUME))) | |
93 | return -EBUSY; | |
94 | ||
95 | ((char *)my_buf)[count] = 0; | |
96 | ||
97 | if (sysfs_data->flags & SYSFS_NEEDS_SM_FOR_WRITE) | |
98 | toi_prepare_usm(); | |
99 | ||
100 | switch (sysfs_data->type) { | |
101 | case TOI_SYSFS_DATA_CUSTOM: | |
102 | if (sysfs_data->data.special.write_sysfs) | |
103 | result = (sysfs_data->data.special.write_sysfs) (my_buf, count); | |
104 | break; | |
105 | case TOI_SYSFS_DATA_BIT: | |
106 | { | |
107 | unsigned long value; | |
108 | result = strict_strtoul(my_buf, 0, &value); | |
109 | if (result) | |
110 | break; | |
111 | if (value) | |
112 | set_bit(sysfs_data->data.bit.bit, | |
113 | (sysfs_data->data.bit.bit_vector)); | |
114 | else | |
115 | clear_bit(sysfs_data->data.bit.bit, | |
116 | (sysfs_data->data.bit.bit_vector)); | |
117 | } | |
118 | break; | |
119 | case TOI_SYSFS_DATA_INTEGER: | |
120 | { | |
121 | long temp; | |
122 | result = strict_strtol(my_buf, 0, &temp); | |
123 | if (result) | |
124 | break; | |
125 | *(sysfs_data->data.integer.variable) = (int)temp; | |
126 | BOUND(sysfs_data->data.integer.variable, integer); | |
127 | break; | |
128 | } | |
129 | case TOI_SYSFS_DATA_LONG: | |
130 | { | |
131 | long *variable = sysfs_data->data.a_long.variable; | |
132 | result = strict_strtol(my_buf, 0, variable); | |
133 | if (result) | |
134 | break; | |
135 | BOUND(variable, a_long); | |
136 | break; | |
137 | } | |
138 | case TOI_SYSFS_DATA_UL: | |
139 | { | |
140 | unsigned long *variable = sysfs_data->data.ul.variable; | |
141 | result = strict_strtoul(my_buf, 0, variable); | |
142 | if (result) | |
143 | break; | |
144 | BOUND(variable, ul); | |
145 | break; | |
146 | } | |
147 | break; | |
148 | case TOI_SYSFS_DATA_STRING: | |
149 | { | |
150 | int copy_len = count; | |
151 | char *variable = sysfs_data->data.string.variable; | |
152 | ||
153 | if (sysfs_data->data.string.max_length && | |
154 | (copy_len > sysfs_data->data.string.max_length)) | |
155 | copy_len = sysfs_data->data.string.max_length; | |
156 | ||
157 | if (!variable) { | |
158 | variable = (char *)toi_get_zeroed_page(31, TOI_ATOMIC_GFP); | |
159 | sysfs_data->data.string.variable = variable; | |
160 | assigned_temp_buffer = 1; | |
161 | } | |
162 | strncpy(variable, my_buf, copy_len); | |
163 | if (copy_len && my_buf[copy_len - 1] == '\n') | |
164 | variable[count - 1] = 0; | |
165 | variable[count] = 0; | |
166 | } | |
167 | break; | |
168 | } | |
169 | ||
170 | if (!result) | |
171 | result = count; | |
172 | ||
173 | /* Side effect routine? */ | |
174 | if (result == count && sysfs_data->write_side_effect) | |
175 | sysfs_data->write_side_effect(); | |
176 | ||
177 | /* Free temporary buffers */ | |
178 | if (assigned_temp_buffer) { | |
179 | toi_free_page(31, (unsigned long)sysfs_data->data.string.variable); | |
180 | sysfs_data->data.string.variable = NULL; | |
181 | } | |
182 | ||
183 | if (sysfs_data->flags & SYSFS_NEEDS_SM_FOR_WRITE) | |
184 | toi_cleanup_usm(); | |
185 | ||
186 | toi_finish_anything(sysfs_data->flags & SYSFS_HIBERNATE_OR_RESUME); | |
187 | ||
188 | return result; | |
189 | } | |
190 | ||
191 | static struct sysfs_ops toi_sysfs_ops = { | |
192 | .show = &toi_attr_show, | |
193 | .store = &toi_attr_store, | |
194 | }; | |
195 | ||
196 | static struct kobj_type toi_ktype = { | |
197 | .sysfs_ops = &toi_sysfs_ops, | |
198 | }; | |
199 | ||
200 | struct kobject *tuxonice_kobj; | |
201 | ||
202 | /* Non-module sysfs entries. | |
203 | * | |
204 | * This array contains entries that are automatically registered at | |
205 | * boot. Modules and the console code register their own entries separately. | |
206 | */ | |
207 | ||
208 | static struct toi_sysfs_data sysfs_params[] = { | |
209 | SYSFS_CUSTOM("do_hibernate", SYSFS_WRITEONLY, NULL, NULL, | |
210 | SYSFS_HIBERNATING, toi_main_wrapper), | |
211 | SYSFS_CUSTOM("do_resume", SYSFS_WRITEONLY, NULL, NULL, | |
212 | SYSFS_RESUMING, toi_try_resume) | |
213 | }; | |
214 | ||
215 | void remove_toi_sysdir(struct kobject *kobj) | |
216 | { | |
217 | if (!kobj) | |
218 | return; | |
219 | ||
220 | kobject_put(kobj); | |
221 | } | |
222 | ||
223 | struct kobject *make_toi_sysdir(char *name) | |
224 | { | |
225 | struct kobject *kobj = kobject_create_and_add(name, tuxonice_kobj); | |
226 | ||
227 | if (!kobj) { | |
228 | printk(KERN_INFO "TuxOnIce: Can't allocate kobject for sysfs " "dir!\n"); | |
229 | return NULL; | |
230 | } | |
231 | ||
232 | kobj->ktype = &toi_ktype; | |
233 | ||
234 | return kobj; | |
235 | } | |
236 | ||
237 | /* toi_register_sysfs_file | |
238 | * | |
239 | * Helper for registering a new /sysfs/tuxonice entry. | |
240 | */ | |
241 | ||
242 | int toi_register_sysfs_file(struct kobject *kobj, struct toi_sysfs_data *toi_sysfs_data) | |
243 | { | |
244 | int result; | |
245 | ||
246 | if (!toi_sysfs_initialised) | |
247 | toi_initialise_sysfs(); | |
248 | ||
249 | result = sysfs_create_file(kobj, &toi_sysfs_data->attr); | |
250 | if (result) | |
251 | printk(KERN_INFO "TuxOnIce: sysfs_create_file for %s " | |
252 | "returned %d.\n", toi_sysfs_data->attr.name, result); | |
253 | kobj->ktype = &toi_ktype; | |
254 | ||
255 | return result; | |
256 | } | |
257 | EXPORT_SYMBOL_GPL(toi_register_sysfs_file); | |
258 | ||
259 | /* toi_unregister_sysfs_file | |
260 | * | |
261 | * Helper for removing unwanted /sys/power/tuxonice entries. | |
262 | * | |
263 | */ | |
264 | void toi_unregister_sysfs_file(struct kobject *kobj, struct toi_sysfs_data *toi_sysfs_data) | |
265 | { | |
266 | sysfs_remove_file(kobj, &toi_sysfs_data->attr); | |
267 | } | |
268 | EXPORT_SYMBOL_GPL(toi_unregister_sysfs_file); | |
269 | ||
270 | void toi_cleanup_sysfs(void) | |
271 | { | |
272 | int i, numfiles = sizeof(sysfs_params) / sizeof(struct toi_sysfs_data); | |
273 | ||
274 | if (!toi_sysfs_initialised) | |
275 | return; | |
276 | ||
277 | for (i = 0; i < numfiles; i++) | |
278 | toi_unregister_sysfs_file(tuxonice_kobj, &sysfs_params[i]); | |
279 | ||
280 | kobject_put(tuxonice_kobj); | |
281 | toi_sysfs_initialised = 0; | |
282 | } | |
283 | ||
284 | /* toi_initialise_sysfs | |
285 | * | |
286 | * Initialise the /sysfs/tuxonice directory. | |
287 | */ | |
288 | ||
289 | static void toi_initialise_sysfs(void) | |
290 | { | |
291 | int i; | |
292 | int numfiles = sizeof(sysfs_params) / sizeof(struct toi_sysfs_data); | |
293 | ||
294 | if (toi_sysfs_initialised) | |
295 | return; | |
296 | ||
297 | /* Make our TuxOnIce directory a child of /sys/power */ | |
298 | tuxonice_kobj = kobject_create_and_add("tuxonice", power_kobj); | |
299 | if (!tuxonice_kobj) | |
300 | return; | |
301 | ||
302 | toi_sysfs_initialised = 1; | |
303 | ||
304 | for (i = 0; i < numfiles; i++) | |
305 | toi_register_sysfs_file(tuxonice_kobj, &sysfs_params[i]); | |
306 | } | |
307 | ||
308 | int toi_sysfs_init(void) | |
309 | { | |
310 | toi_initialise_sysfs(); | |
311 | return 0; | |
312 | } | |
313 | ||
314 | void toi_sysfs_exit(void) | |
315 | { | |
316 | toi_cleanup_sysfs(); | |
317 | } |