503 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * fbsysfs.c - framebuffer device class and attributes4 *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 116 17static 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(fb_info);24 err = fb_set_var(fb_info, var);25 if (!err)26 fbcon_update_vcs(fb_info, var->activate & FB_ACTIVATE_ALL);27 unlock_fb_info(fb_info);28 console_unlock();29 if (err)30 return err;31 return 0;32}33 34static 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[offset], PAGE_SIZE - offset, "%c:%dx%d%c-%d\n",53 m, mode->xres, mode->yres, v, mode->refresh);54}55 56static 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(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(mstr, 0, mode);72 if (strncmp(mstr, buf, max(count, i)) == 0) {73 74 var = fb_info->var;75 fb_videomode_to_var(&var, mode);76 if ((err = activate(fb_info, &var)))77 return err;78 fb_info->mode = mode;79 return count;80 }81 }82 return -EINVAL;83}84 85static ssize_t show_mode(struct device *device, struct device_attribute *attr,86 char *buf)87{88 struct fb_info *fb_info = dev_get_drvdata(device);89 90 if (!fb_info->mode)91 return 0;92 93 return mode_string(buf, 0, fb_info->mode);94}95 96static 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(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(fb_info);109 110 list_splice(&fb_info->modelist, &old_list);111 fb_videomode_to_modelist((const struct fb_videomode *)buf, i,112 &fb_info->modelist);113 if (fb_new_modelist(fb_info)) {114 fb_destroy_modelist(&fb_info->modelist);115 list_splice(&old_list, &fb_info->modelist);116 } else117 fb_destroy_modelist(&old_list);118 119 unlock_fb_info(fb_info);120 console_unlock();121 122 return 0;123}124 125static ssize_t show_modes(struct device *device, struct device_attribute *attr,126 char *buf)127{128 struct fb_info *fb_info = dev_get_drvdata(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, i, mode);137 }138 return i;139}140 141static 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(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)))152 return err;153 return count;154}155 156static ssize_t show_bpp(struct device *device, struct device_attribute *attr,157 char *buf)158{159 struct fb_info *fb_info = dev_get_drvdata(device);160 return sysfs_emit(buf, "%d\n", fb_info->var.bits_per_pixel);161}162 163static 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(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)))176 return err;177 178 return count;179}180 181 182static ssize_t show_rotate(struct device *device,183 struct device_attribute *attr, char *buf)184{185 struct fb_info *fb_info = dev_get_drvdata(device);186 187 return sysfs_emit(buf, "%d\n", fb_info->var.rotate);188}189 190static 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(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)))207 return err;208 return count;209}210 211static ssize_t show_virtual(struct device *device,212 struct device_attribute *attr, char *buf)213{214 struct fb_info *fb_info = dev_get_drvdata(device);215 return sysfs_emit(buf, "%d,%d\n", fb_info->var.xres_virtual,216 fb_info->var.yres_virtual);217}218 219static ssize_t show_stride(struct device *device,220 struct device_attribute *attr, char *buf)221{222 struct fb_info *fb_info = dev_get_drvdata(device);223 return sysfs_emit(buf, "%d\n", fb_info->fix.line_length);224}225 226static 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(device);231 char *last = NULL;232 int err, arg;233 234 arg = simple_strtoul(buf, &last, 0);235 console_lock();236 err = fb_blank(fb_info, arg);237 /* might again call into fb_blank */238 fbcon_fb_blanked(fb_info, arg);239 console_unlock();240 if (err < 0)241 return err;242 return count;243}244 245static 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 252static 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 260static 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 267static 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 275static 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 282static 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(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(fb_info, &var);300 console_unlock();301 302 if (err < 0)303 return err;304 return count;305}306 307static ssize_t show_pan(struct device *device,308 struct device_attribute *attr, char *buf)309{310 struct fb_info *fb_info = dev_get_drvdata(device);311 return sysfs_emit(buf, "%d,%d\n", fb_info->var.xoffset,312 fb_info->var.yoffset);313}314 315static ssize_t show_name(struct device *device,316 struct device_attribute *attr, char *buf)317{318 struct fb_info *fb_info = dev_get_drvdata(device);319 320 return sysfs_emit(buf, "%s\n", fb_info->fix.id);321}322 323static 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(device);328 u32 state;329 char *last = NULL;330 331 state = simple_strtoul(buf, &last, 0);332 333 console_lock();334 lock_fb_info(fb_info);335 336 fb_set_suspend(fb_info, (int)state);337 338 unlock_fb_info(fb_info);339 console_unlock();340 341 return count;342}343 344static ssize_t show_fbstate(struct device *device,345 struct device_attribute *attr, char *buf)346{347 struct fb_info *fb_info = dev_get_drvdata(device);348 return sysfs_emit(buf, "%d\n", fb_info->state);349}350 351#if IS_ENABLED(CONFIG_FB_BACKLIGHT)352static 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(device);357 u8 tmp_curve[FB_BACKLIGHT_LEVELS];358 unsigned int i;359 360 /* Some drivers don't use framebuffer_alloc(), but those also361 * 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't383 * 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(&fb_info->bl_curve_mutex);389 390 return count;391}392 393static 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(device);397 ssize_t len = 0;398 unsigned int i;399 400 /* Some drivers don't use framebuffer_alloc(), but those also401 * 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[len], PAGE_SIZE - len, "%8ph\n",409 fb_info->bl_curve + i);410 mutex_unlock(&fb_info->bl_curve_mutex);411 412 return len;413}414#endif415 416/* When cmap is added back in it should be a binary attribute417 * not a text one. Consideration should also be given to converting418 * fbdev to use configfs instead of sysfs */419static 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#endif435};436 437static int fb_init_device(struct fb_info *fb_info)438{439 int i, error = 0;440 441 dev_set_drvdata(fb_info->dev, 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(fb_info->dev, &device_attrs[i]);447 448 if (error)449 break;450 }451 452 if (error) {453 while (--i >= 0)454 device_remove_file(fb_info->dev, &device_attrs[i]);455 fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR;456 }457 458 return 0;459}460 461static 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(fb_info->dev, &device_attrs[i]);468 469 fb_info->class_flag &= ~FB_SYSFS_FLAG_ATTR;470 }471}472 473int 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(fb_class, fb_info->device, devt, NULL, "fb%d", node);480 if (IS_ERR(fb_info->dev)) {481 /* Not fatal */482 ret = PTR_ERR(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 492void 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(fb_class, devt);501 fb_info->dev = NULL;502}503