1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * fbsysfs.c - framebuffer device class and attributes |
4 | * |
5 | * Copyright (c) 2004 James Simmons <jsimmons@infradead.org> |
6 | */ |
7 | |
8 | #include <linux/console.h> |
9 | #include <linux/fb.h> |
10 | #include <linux/fbcon.h> |
11 | #include <linux/major.h> |
12 | |
13 | #include "fb_internal.h" |
14 | |
15 | #define FB_SYSFS_FLAG_ATTR 1 |
16 | |
17 | static int activate(struct fb_info *fb_info, struct fb_var_screeninfo *var) |
18 | { |
19 | int err; |
20 | |
21 | var->activate |= FB_ACTIVATE_FORCE; |
22 | console_lock(); |
23 | lock_fb_info(info: fb_info); |
24 | err = fb_set_var(info: fb_info, var); |
25 | if (!err) |
26 | fbcon_update_vcs(info: fb_info, all: var->activate & FB_ACTIVATE_ALL); |
27 | unlock_fb_info(info: fb_info); |
28 | console_unlock(); |
29 | if (err) |
30 | return err; |
31 | return 0; |
32 | } |
33 | |
34 | static int mode_string(char *buf, unsigned int offset, |
35 | const struct fb_videomode *mode) |
36 | { |
37 | char m = 'U'; |
38 | char v = 'p'; |
39 | |
40 | if (mode->flag & FB_MODE_IS_DETAILED) |
41 | m = 'D'; |
42 | if (mode->flag & FB_MODE_IS_VESA) |
43 | m = 'V'; |
44 | if (mode->flag & FB_MODE_IS_STANDARD) |
45 | m = 'S'; |
46 | |
47 | if (mode->vmode & FB_VMODE_INTERLACED) |
48 | v = 'i'; |
49 | if (mode->vmode & FB_VMODE_DOUBLE) |
50 | v = 'd'; |
51 | |
52 | return snprintf(buf: &buf[offset], PAGE_SIZE - offset, fmt: "%c:%dx%d%c-%d\n" , |
53 | m, mode->xres, mode->yres, v, mode->refresh); |
54 | } |
55 | |
56 | static ssize_t store_mode(struct device *device, struct device_attribute *attr, |
57 | const char *buf, size_t count) |
58 | { |
59 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
60 | char mstr[100]; |
61 | struct fb_var_screeninfo var; |
62 | struct fb_modelist *modelist; |
63 | struct fb_videomode *mode; |
64 | size_t i; |
65 | int err; |
66 | |
67 | memset(&var, 0, sizeof(var)); |
68 | |
69 | list_for_each_entry(modelist, &fb_info->modelist, list) { |
70 | mode = &modelist->mode; |
71 | i = mode_string(buf: mstr, offset: 0, mode); |
72 | if (strncmp(mstr, buf, max(count, i)) == 0) { |
73 | |
74 | var = fb_info->var; |
75 | fb_videomode_to_var(var: &var, mode); |
76 | if ((err = activate(fb_info, var: &var))) |
77 | return err; |
78 | fb_info->mode = mode; |
79 | return count; |
80 | } |
81 | } |
82 | return -EINVAL; |
83 | } |
84 | |
85 | static ssize_t show_mode(struct device *device, struct device_attribute *attr, |
86 | char *buf) |
87 | { |
88 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
89 | |
90 | if (!fb_info->mode) |
91 | return 0; |
92 | |
93 | return mode_string(buf, offset: 0, mode: fb_info->mode); |
94 | } |
95 | |
96 | static ssize_t store_modes(struct device *device, |
97 | struct device_attribute *attr, |
98 | const char *buf, size_t count) |
99 | { |
100 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
101 | LIST_HEAD(old_list); |
102 | int i = count / sizeof(struct fb_videomode); |
103 | |
104 | if (i * sizeof(struct fb_videomode) != count) |
105 | return -EINVAL; |
106 | |
107 | console_lock(); |
108 | lock_fb_info(info: fb_info); |
109 | |
110 | list_splice(list: &fb_info->modelist, head: &old_list); |
111 | fb_videomode_to_modelist(modedb: (const struct fb_videomode *)buf, num: i, |
112 | head: &fb_info->modelist); |
113 | if (fb_new_modelist(info: fb_info)) { |
114 | fb_destroy_modelist(head: &fb_info->modelist); |
115 | list_splice(list: &old_list, head: &fb_info->modelist); |
116 | } else |
117 | fb_destroy_modelist(head: &old_list); |
118 | |
119 | unlock_fb_info(info: fb_info); |
120 | console_unlock(); |
121 | |
122 | return 0; |
123 | } |
124 | |
125 | static ssize_t show_modes(struct device *device, struct device_attribute *attr, |
126 | char *buf) |
127 | { |
128 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
129 | unsigned int i; |
130 | struct fb_modelist *modelist; |
131 | const struct fb_videomode *mode; |
132 | |
133 | i = 0; |
134 | list_for_each_entry(modelist, &fb_info->modelist, list) { |
135 | mode = &modelist->mode; |
136 | i += mode_string(buf, offset: i, mode); |
137 | } |
138 | return i; |
139 | } |
140 | |
141 | static ssize_t store_bpp(struct device *device, struct device_attribute *attr, |
142 | const char *buf, size_t count) |
143 | { |
144 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
145 | struct fb_var_screeninfo var; |
146 | char ** last = NULL; |
147 | int err; |
148 | |
149 | var = fb_info->var; |
150 | var.bits_per_pixel = simple_strtoul(buf, last, 0); |
151 | if ((err = activate(fb_info, var: &var))) |
152 | return err; |
153 | return count; |
154 | } |
155 | |
156 | static ssize_t show_bpp(struct device *device, struct device_attribute *attr, |
157 | char *buf) |
158 | { |
159 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
160 | return sysfs_emit(buf, fmt: "%d\n" , fb_info->var.bits_per_pixel); |
161 | } |
162 | |
163 | static ssize_t store_rotate(struct device *device, |
164 | struct device_attribute *attr, |
165 | const char *buf, size_t count) |
166 | { |
167 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
168 | struct fb_var_screeninfo var; |
169 | char **last = NULL; |
170 | int err; |
171 | |
172 | var = fb_info->var; |
173 | var.rotate = simple_strtoul(buf, last, 0); |
174 | |
175 | if ((err = activate(fb_info, var: &var))) |
176 | return err; |
177 | |
178 | return count; |
179 | } |
180 | |
181 | |
182 | static ssize_t show_rotate(struct device *device, |
183 | struct device_attribute *attr, char *buf) |
184 | { |
185 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
186 | |
187 | return sysfs_emit(buf, fmt: "%d\n" , fb_info->var.rotate); |
188 | } |
189 | |
190 | static ssize_t store_virtual(struct device *device, |
191 | struct device_attribute *attr, |
192 | const char *buf, size_t count) |
193 | { |
194 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
195 | struct fb_var_screeninfo var; |
196 | char *last = NULL; |
197 | int err; |
198 | |
199 | var = fb_info->var; |
200 | var.xres_virtual = simple_strtoul(buf, &last, 0); |
201 | last++; |
202 | if (last - buf >= count) |
203 | return -EINVAL; |
204 | var.yres_virtual = simple_strtoul(last, &last, 0); |
205 | |
206 | if ((err = activate(fb_info, var: &var))) |
207 | return err; |
208 | return count; |
209 | } |
210 | |
211 | static ssize_t show_virtual(struct device *device, |
212 | struct device_attribute *attr, char *buf) |
213 | { |
214 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
215 | return sysfs_emit(buf, fmt: "%d,%d\n" , fb_info->var.xres_virtual, |
216 | fb_info->var.yres_virtual); |
217 | } |
218 | |
219 | static ssize_t show_stride(struct device *device, |
220 | struct device_attribute *attr, char *buf) |
221 | { |
222 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
223 | return sysfs_emit(buf, fmt: "%d\n" , fb_info->fix.line_length); |
224 | } |
225 | |
226 | static ssize_t store_blank(struct device *device, |
227 | struct device_attribute *attr, |
228 | const char *buf, size_t count) |
229 | { |
230 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
231 | char *last = NULL; |
232 | int err, arg; |
233 | |
234 | arg = simple_strtoul(buf, &last, 0); |
235 | console_lock(); |
236 | err = fb_blank(info: fb_info, blank: arg); |
237 | /* might again call into fb_blank */ |
238 | fbcon_fb_blanked(info: fb_info, blank: arg); |
239 | console_unlock(); |
240 | if (err < 0) |
241 | return err; |
242 | return count; |
243 | } |
244 | |
245 | static ssize_t show_blank(struct device *device, |
246 | struct device_attribute *attr, char *buf) |
247 | { |
248 | // struct fb_info *fb_info = dev_get_drvdata(device); |
249 | return 0; |
250 | } |
251 | |
252 | static ssize_t store_console(struct device *device, |
253 | struct device_attribute *attr, |
254 | const char *buf, size_t count) |
255 | { |
256 | // struct fb_info *fb_info = dev_get_drvdata(device); |
257 | return 0; |
258 | } |
259 | |
260 | static ssize_t show_console(struct device *device, |
261 | struct device_attribute *attr, char *buf) |
262 | { |
263 | // struct fb_info *fb_info = dev_get_drvdata(device); |
264 | return 0; |
265 | } |
266 | |
267 | static ssize_t store_cursor(struct device *device, |
268 | struct device_attribute *attr, |
269 | const char *buf, size_t count) |
270 | { |
271 | // struct fb_info *fb_info = dev_get_drvdata(device); |
272 | return 0; |
273 | } |
274 | |
275 | static ssize_t show_cursor(struct device *device, |
276 | struct device_attribute *attr, char *buf) |
277 | { |
278 | // struct fb_info *fb_info = dev_get_drvdata(device); |
279 | return 0; |
280 | } |
281 | |
282 | static ssize_t store_pan(struct device *device, |
283 | struct device_attribute *attr, |
284 | const char *buf, size_t count) |
285 | { |
286 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
287 | struct fb_var_screeninfo var; |
288 | char *last = NULL; |
289 | int err; |
290 | |
291 | var = fb_info->var; |
292 | var.xoffset = simple_strtoul(buf, &last, 0); |
293 | last++; |
294 | if (last - buf >= count) |
295 | return -EINVAL; |
296 | var.yoffset = simple_strtoul(last, &last, 0); |
297 | |
298 | console_lock(); |
299 | err = fb_pan_display(info: fb_info, var: &var); |
300 | console_unlock(); |
301 | |
302 | if (err < 0) |
303 | return err; |
304 | return count; |
305 | } |
306 | |
307 | static ssize_t show_pan(struct device *device, |
308 | struct device_attribute *attr, char *buf) |
309 | { |
310 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
311 | return sysfs_emit(buf, fmt: "%d,%d\n" , fb_info->var.xoffset, |
312 | fb_info->var.yoffset); |
313 | } |
314 | |
315 | static ssize_t show_name(struct device *device, |
316 | struct device_attribute *attr, char *buf) |
317 | { |
318 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
319 | |
320 | return sysfs_emit(buf, fmt: "%s\n" , fb_info->fix.id); |
321 | } |
322 | |
323 | static ssize_t store_fbstate(struct device *device, |
324 | struct device_attribute *attr, |
325 | const char *buf, size_t count) |
326 | { |
327 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
328 | u32 state; |
329 | char *last = NULL; |
330 | |
331 | state = simple_strtoul(buf, &last, 0); |
332 | |
333 | console_lock(); |
334 | lock_fb_info(info: fb_info); |
335 | |
336 | fb_set_suspend(info: fb_info, state: (int)state); |
337 | |
338 | unlock_fb_info(info: fb_info); |
339 | console_unlock(); |
340 | |
341 | return count; |
342 | } |
343 | |
344 | static ssize_t show_fbstate(struct device *device, |
345 | struct device_attribute *attr, char *buf) |
346 | { |
347 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
348 | return sysfs_emit(buf, fmt: "%d\n" , fb_info->state); |
349 | } |
350 | |
351 | #if IS_ENABLED(CONFIG_FB_BACKLIGHT) |
352 | static ssize_t store_bl_curve(struct device *device, |
353 | struct device_attribute *attr, |
354 | const char *buf, size_t count) |
355 | { |
356 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
357 | u8 tmp_curve[FB_BACKLIGHT_LEVELS]; |
358 | unsigned int i; |
359 | |
360 | /* Some drivers don't use framebuffer_alloc(), but those also |
361 | * don't have backlights. |
362 | */ |
363 | if (!fb_info || !fb_info->bl_dev) |
364 | return -ENODEV; |
365 | |
366 | if (count != (FB_BACKLIGHT_LEVELS / 8 * 24)) |
367 | return -EINVAL; |
368 | |
369 | for (i = 0; i < (FB_BACKLIGHT_LEVELS / 8); ++i) |
370 | if (sscanf(&buf[i * 24], |
371 | "%2hhx %2hhx %2hhx %2hhx %2hhx %2hhx %2hhx %2hhx\n" , |
372 | &tmp_curve[i * 8 + 0], |
373 | &tmp_curve[i * 8 + 1], |
374 | &tmp_curve[i * 8 + 2], |
375 | &tmp_curve[i * 8 + 3], |
376 | &tmp_curve[i * 8 + 4], |
377 | &tmp_curve[i * 8 + 5], |
378 | &tmp_curve[i * 8 + 6], |
379 | &tmp_curve[i * 8 + 7]) != 8) |
380 | return -EINVAL; |
381 | |
382 | /* If there has been an error in the input data, we won't |
383 | * reach this loop. |
384 | */ |
385 | mutex_lock(&fb_info->bl_curve_mutex); |
386 | for (i = 0; i < FB_BACKLIGHT_LEVELS; ++i) |
387 | fb_info->bl_curve[i] = tmp_curve[i]; |
388 | mutex_unlock(lock: &fb_info->bl_curve_mutex); |
389 | |
390 | return count; |
391 | } |
392 | |
393 | static ssize_t show_bl_curve(struct device *device, |
394 | struct device_attribute *attr, char *buf) |
395 | { |
396 | struct fb_info *fb_info = dev_get_drvdata(dev: device); |
397 | ssize_t len = 0; |
398 | unsigned int i; |
399 | |
400 | /* Some drivers don't use framebuffer_alloc(), but those also |
401 | * don't have backlights. |
402 | */ |
403 | if (!fb_info || !fb_info->bl_dev) |
404 | return -ENODEV; |
405 | |
406 | mutex_lock(&fb_info->bl_curve_mutex); |
407 | for (i = 0; i < FB_BACKLIGHT_LEVELS; i += 8) |
408 | len += scnprintf(buf: &buf[len], PAGE_SIZE - len, fmt: "%8ph\n" , |
409 | fb_info->bl_curve + i); |
410 | mutex_unlock(lock: &fb_info->bl_curve_mutex); |
411 | |
412 | return len; |
413 | } |
414 | #endif |
415 | |
416 | /* When cmap is added back in it should be a binary attribute |
417 | * not a text one. Consideration should also be given to converting |
418 | * fbdev to use configfs instead of sysfs */ |
419 | static struct device_attribute device_attrs[] = { |
420 | __ATTR(bits_per_pixel, S_IRUGO|S_IWUSR, show_bpp, store_bpp), |
421 | __ATTR(blank, S_IRUGO|S_IWUSR, show_blank, store_blank), |
422 | __ATTR(console, S_IRUGO|S_IWUSR, show_console, store_console), |
423 | __ATTR(cursor, S_IRUGO|S_IWUSR, show_cursor, store_cursor), |
424 | __ATTR(mode, S_IRUGO|S_IWUSR, show_mode, store_mode), |
425 | __ATTR(modes, S_IRUGO|S_IWUSR, show_modes, store_modes), |
426 | __ATTR(pan, S_IRUGO|S_IWUSR, show_pan, store_pan), |
427 | __ATTR(virtual_size, S_IRUGO|S_IWUSR, show_virtual, store_virtual), |
428 | __ATTR(name, S_IRUGO, show_name, NULL), |
429 | __ATTR(stride, S_IRUGO, show_stride, NULL), |
430 | __ATTR(rotate, S_IRUGO|S_IWUSR, show_rotate, store_rotate), |
431 | __ATTR(state, S_IRUGO|S_IWUSR, show_fbstate, store_fbstate), |
432 | #if IS_ENABLED(CONFIG_FB_BACKLIGHT) |
433 | __ATTR(bl_curve, S_IRUGO|S_IWUSR, show_bl_curve, store_bl_curve), |
434 | #endif |
435 | }; |
436 | |
437 | static int fb_init_device(struct fb_info *fb_info) |
438 | { |
439 | int i, error = 0; |
440 | |
441 | dev_set_drvdata(dev: fb_info->dev, data: fb_info); |
442 | |
443 | fb_info->class_flag |= FB_SYSFS_FLAG_ATTR; |
444 | |
445 | for (i = 0; i < ARRAY_SIZE(device_attrs); i++) { |
446 | error = device_create_file(device: fb_info->dev, entry: &device_attrs[i]); |
447 | |
448 | if (error) |
449 | break; |
450 | } |
451 | |
452 | if (error) { |
453 | while (--i >= 0) |
454 | device_remove_file(dev: fb_info->dev, attr: &device_attrs[i]); |
455 | fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR; |
456 | } |
457 | |
458 | return 0; |
459 | } |
460 | |
461 | static void fb_cleanup_device(struct fb_info *fb_info) |
462 | { |
463 | unsigned int i; |
464 | |
465 | if (fb_info->class_flag & FB_SYSFS_FLAG_ATTR) { |
466 | for (i = 0; i < ARRAY_SIZE(device_attrs); i++) |
467 | device_remove_file(dev: fb_info->dev, attr: &device_attrs[i]); |
468 | |
469 | fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR; |
470 | } |
471 | } |
472 | |
473 | int fb_device_create(struct fb_info *fb_info) |
474 | { |
475 | int node = fb_info->node; |
476 | dev_t devt = MKDEV(FB_MAJOR, node); |
477 | int ret; |
478 | |
479 | fb_info->dev = device_create(cls: fb_class, parent: fb_info->device, devt, NULL, fmt: "fb%d" , node); |
480 | if (IS_ERR(ptr: fb_info->dev)) { |
481 | /* Not fatal */ |
482 | ret = PTR_ERR(ptr: fb_info->dev); |
483 | pr_warn("Unable to create device for framebuffer %d; error %d\n" , node, ret); |
484 | fb_info->dev = NULL; |
485 | } else { |
486 | fb_init_device(fb_info); |
487 | } |
488 | |
489 | return 0; |
490 | } |
491 | |
492 | void fb_device_destroy(struct fb_info *fb_info) |
493 | { |
494 | dev_t devt = MKDEV(FB_MAJOR, fb_info->node); |
495 | |
496 | if (!fb_info->dev) |
497 | return; |
498 | |
499 | fb_cleanup_device(fb_info); |
500 | device_destroy(cls: fb_class, devt); |
501 | fb_info->dev = NULL; |
502 | } |
503 | |