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