mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2025-12-27 12:21:22 -05:00
Pull drm updates from Dave Airlie:
"There was a rather late merge of a new color pipeline feature, that
some userspace projects are blocked on, and has seen a lot of work in
amdgpu. This should have seen some time in -next. There is additional
support for this for Intel, that if it arrives in the next day or two
I'll pass it on in another pull request and you can decide if you want
to take it.
Highlights:
- Arm Ethos NPU accelerator driver
- new DRM color pipeline support
- amdgpu will now run discrete SI/CIK cards instead of radeon, which
enables vulkan support in userspace
- msm gets gen8 gpu support
- initial Xe3P support in xe
Full detail summary:
New driver:
- Arm Ethos-U65/U85 accel driver
Core:
- support the drm color pipeline in vkms/amdgfx
- add support for drm colorop pipeline
- add COLOR PIPELINE plane property
- add DRM_CLIENT_CAP_PLANE_COLOR_PIPELINE
- throttle dirty worker with vblank
- use drm_for_each_bridge_in_chain_scoped in drm's bridge code
- Ensure drm_client_modeset tests are enabled in UML
- add simulated vblank interrupt - use in drivers
- dumb buffer sizing helper
- move freeing of drm client memory to driver
- crtc sharpness strength property
- stop using system_wq in scheduler/drivers
- support emergency restore in drm-client
Rust:
- make slice::as_flattened usable on all supported rustc
- add FromBytes::from_bytes_prefix() method
- remove redundant device ptr from Rust GEM object
- Change how AlwaysRefCounted is implemented for GEM objects
gpuvm:
- Add deferred vm_bo cleanup to GPUVM (for rust)
atomic:
- cleanup and improve state handling interfaces
buddy:
- optimize block management
dma-buf:
- heaps: Create heap per CMA reserved location
- improve userspace documentation
dp:
- add POST_LT_ADJ_REQ training sequence
- DPCD dSC quirk for synaptics panamera devices
- helpers to query branch DSC max throughput
ttm:
- Rename ttm_bo_put to ttm_bo_fini
- allow page protection flags on risc-v
- rework pipelined eviction fence handling
amdgpu:
- enable amdgpu by default for SI/CI dGPUs
- enable DC by default on SI
- refactor CIK/SI enablement
- add ABM KMS property
- Re-enable DM idle optimizations
- DC Analog encoders support
- Powerplay fixes for fiji/iceland
- Enable DC on bonaire by default
- HMM cleanup
- Add new RAS framework
- DML2.1 updates
- YCbCr420 fixes
- DC FP fixes
- DMUB fixes
- LTTPR fixes
- DTBCLK fixes
- DMU cursor offload handling
- Userq validation improvements
- Unify shutdown callback handling
- Suspend improvements
- Power limit code cleanup
- SR-IOV fixes
- AUX backlight fixes
- DCN 3.5 fixes
- HDMI compliance fixes
- DCN 4.0.1 cursor updates
- DCN interrupt fix
- DC KMS full update improvements
- Add additional HDCP traces
- DCN 3.2 fixes
- DP MST fixes
- Add support for new SR-IOV mailbox interface
- UQ reset support
- HDP flush rework
- VCE1 support
amdkfd:
- HMM cleanups
- Relax checks on save area overallocations
- Fix GPU mappings after prefetch
radeon:
- refactor CIK/SI enablement
xe:
- Initial Xe3P support
- panic support on VRAM for display
- fix stolen size check
- Loosen used tracking restriction
- New SR-IOV debugfs structure and debugfs updates
- Hide the GPU madvise flag behind a VM_BIND flag
- Always expose VRAM provisioning data on discrete GPUs
- Allow VRAM mappings for userptr when used with SVM
- Allow pinning of p2p dma-buf
- Use per-tile debugfs where appropriate
- Add documentation for Execution Queues
- PF improvements
- VF migration recovery redesign work
- User / Kernel VRAM partitioning
- Update Tile-based messages
- Allow configfs to disable specific GT types
- VF provisioning and migration improvements
- use SVM range helpers in PT layer
- Initial CRI support
- access VF registers using dedicated MMIO view
- limit number of jobs per exec queue
- add sriov_admin sysfs tree
- more crescent island specific support
- debugfs residency counter
- SRIOV migration work
- runtime registers for GFX 35
i915:
- add initial Xe3p_LPD display version 35 support
- Enable LNL+ content adaptive sharpness filter
- Use optimized VRR guardband
- Enable Xe3p LT PHY
- enable FBC support for Xe3p_LPD display
- add display 30.02 firmware support
- refactor SKL+ watermark latency setup
- refactor fbdev handling
- call i915/xe runtime PM via function pointers
- refactor i915/xe stolen memory/display interfaces
- use display version instead of gfx version in display code
- extend i915_display_info with Type-C port details
- lots of display cleanups/refactorings
- set O_LARGEFILE in __create_shmem
- skuip guc communication warning on reset
- fix time conversions
- defeature DRRS on LNL+
- refactor intel_frontbuffer split between i915/xe/display
- convert inteL_rom interfaces to struct drm_device
- unify display register polling interfaces
- aovid lock inversion when pinning to GGTT on CHV/BXT+VTD
panel:
- Add KD116N3730A08/A12, chromebook mt8189
- JT101TM023, LQ079L1SX01,
- GLD070WX3-SL01 MIPI DSI
- Samsung LTL106AL0, Samsung LTL106AL01
- Raystar RFF500F-AWH-DNN
- Winstar WF70A8SYJHLNGA
- Wanchanglong w552946aaa
- Samsung SOFEF00
- Lenovo X13s panel
- ilitek-ili9881c - add rpi 5" support
- visionx-rm69299 - add backlight support
- edp - support AUI B116XAN02.0
bridge:
- improve ref counting
- ti-sn65dsi86 - add support for DP mode with HPD
- synopsis: support CEC, init timer with correct freq
- ASL CS5263 DP-to-HDMI bridge support
nova-core:
- introduce bitfield! macro
- introduce safe integer converters
- GSP inits to fully booted state on Ampere
- Use more future-proof register for GPU identification
nova-drm:
- select NOVA_CORE
- 64-bit only
nouveau:
- improve reclocking on tegra 186+
- add large page and compression support
msm:
- GPU:
- Gen8 support: A840 (Kaanapali) and X2-85 (Glymur)
- A612 support
- MDSS:
- Added support for Glymur and QCS8300 platforms
- DPU:
- Enabled Quad-Pipe support, unlocking higher resolutions support
- Added support for Glymur platform
- Documented DPU on QCS8300 platform as supported
- DisplayPort:
- Added support for Glymur platform
- Added support lame remapping inside DP block
- Documented DisplayPort controller on QCS8300 and SM6150/QCS615
as supported
tegra:
- NVJPG driver
panfrost:
- display JM contexts over debugfs
- export JM contexts to userspace
- improve error and job handling
panthor:
- support custom ASN_HASH for mt8196
- support mali-G1 GPU
- flush shmem write before mapping buffers uncached
- make timeout per-queue instead of per-job
mediatek:
- MT8195/88 HDMIv2/DDCv2 support
rockchip:
- dsi: add support for RK3368
amdxdna:
- enhance runtime PM
- last hardware error reading uapi
- support firmware debug output
- add resource and telemetry data uapi
- preemption support
imx:
- add driver for HDMI TX Parallel audio interface
ivpu:
- add support for user-managed preemption buffer
- add userptr support
- update JSM firware API to 3.33.0
- add better alloc/free warnings
- fix page fault in unbind all bos
- rework bind/unbind of imported buffers
- enable MCA ECC signalling
- split fw runtime and global memory buffers
- add fdinfo memory statistics
tidss:
- convert to drm logging
- logging cleanup
ast:
- refactor generation init paths
- add per chip generation detect_tx_chip
- set quirks for each chip model
atmel-hlcdc:
- set LCDC_ATTRE register in plane disable
- set correct values for plane scaler
solomon:
- use drm helper for get_modes and move_valid
sitronix:
- fix output position when clearing screens
qaic:
- support dma-buf exports
- support new firmware's READ_DATA implementation
- sahara AIC200 image table update
- add sysfs support
- add coredump support
- add uevents support
- PM support
sun4i:
- layer refactors to decouple plane from output
- improve DE33 support
vc4:
- switch to generic CEC helpers
komeda:
- use drm_ logging functions
vkms:
- configfs support for display configuration
vgem:
- fix fence timer deadlock
etnaviv:
- add HWDB entry for GC8000 Nano Ultra VIP r6205"
* tag 'drm-next-2025-12-03' of https://gitlab.freedesktop.org/drm/kernel: (1869 commits)
Revert "drm/amd: Skip power ungate during suspend for VPE"
drm/amdgpu: use common defines for HUB faults
drm/amdgpu/gmc12: add amdgpu_vm_handle_fault() handling
drm/amdgpu/gmc11: add amdgpu_vm_handle_fault() handling
drm/amdgpu: use static ids for ACP platform devs
drm/amdgpu/sdma6: Update SDMA 6.0.3 FW version to include UMQ protected-fence fix
drm/amdgpu: Forward VMID reservation errors
drm/amdgpu/gmc8: Delegate VM faults to soft IRQ handler ring
drm/amdgpu/gmc7: Delegate VM faults to soft IRQ handler ring
drm/amdgpu/gmc6: Delegate VM faults to soft IRQ handler ring
drm/amdgpu/gmc6: Cache VM fault info
drm/amdgpu/gmc6: Don't print MC client as it's unknown
drm/amdgpu/cz_ih: Enable soft IRQ handler ring
drm/amdgpu/tonga_ih: Enable soft IRQ handler ring
drm/amdgpu/iceland_ih: Enable soft IRQ handler ring
drm/amdgpu/cik_ih: Enable soft IRQ handler ring
drm/amdgpu/si_ih: Enable soft IRQ handler ring
drm/amd/display: fix typo in display_mode_core_structs.h
drm/amd/display: fix Smart Power OLED not working after S4
drm/amd/display: Move RGB-type check for audio sync to DCE HW sequence
...
3451 lines
84 KiB
C
3451 lines
84 KiB
C
/*
|
|
* linux/drivers/video/fbcon.c -- Low level frame buffer based console driver
|
|
*
|
|
* Copyright (C) 1995 Geert Uytterhoeven
|
|
*
|
|
*
|
|
* This file is based on the original Amiga console driver (amicon.c):
|
|
*
|
|
* Copyright (C) 1993 Hamish Macdonald
|
|
* Greg Harp
|
|
* Copyright (C) 1994 David Carter [carter@compsci.bristol.ac.uk]
|
|
*
|
|
* with work by William Rucklidge (wjr@cs.cornell.edu)
|
|
* Geert Uytterhoeven
|
|
* Jes Sorensen (jds@kom.auc.dk)
|
|
* Martin Apel
|
|
*
|
|
* and on the original Atari console driver (atacon.c):
|
|
*
|
|
* Copyright (C) 1993 Bjoern Brauel
|
|
* Roman Hodek
|
|
*
|
|
* with work by Guenther Kelleter
|
|
* Martin Schaller
|
|
* Andreas Schwab
|
|
*
|
|
* Hardware cursor support added by Emmanuel Marty (core@ggi-project.org)
|
|
* Smart redraw scrolling, arbitrary font width support, 512char font support
|
|
* and software scrollback added by
|
|
* Jakub Jelinek (jj@ultra.linux.cz)
|
|
*
|
|
* Random hacking by Martin Mares <mj@ucw.cz>
|
|
*
|
|
* 2001 - Documented with DocBook
|
|
* - Brad Douglas <brad@neruo.com>
|
|
*
|
|
* The low level operations for the various display memory organizations are
|
|
* now in separate source files.
|
|
*
|
|
* Currently the following organizations are supported:
|
|
*
|
|
* o afb Amiga bitplanes
|
|
* o cfb{2,4,8,16,24,32} Packed pixels
|
|
* o ilbm Amiga interleaved bitplanes
|
|
* o iplan2p[248] Atari interleaved bitplanes
|
|
* o mfb Monochrome
|
|
* o vga VGA characters/attributes
|
|
*
|
|
* To do:
|
|
*
|
|
* - Implement 16 plane mode (iplan2p16)
|
|
*
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License. See the file COPYING in the main directory of this archive for
|
|
* more details.
|
|
*/
|
|
|
|
#include <linux/export.h>
|
|
#include <linux/module.h>
|
|
#include <linux/types.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/delay.h> /* MSch: for IRQ probe */
|
|
#include <linux/console.h>
|
|
#include <linux/string.h>
|
|
#include <linux/kd.h>
|
|
#include <linux/panic.h>
|
|
#include <linux/pci.h>
|
|
#include <linux/printk.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/fb.h>
|
|
#include <linux/fbcon.h>
|
|
#include <linux/vt_kern.h>
|
|
#include <linux/selection.h>
|
|
#include <linux/font.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/init.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/crc32.h> /* For counting font checksums */
|
|
#include <linux/uaccess.h>
|
|
#include <linux/vga_switcheroo.h>
|
|
#include <asm/irq.h>
|
|
|
|
#include "fbcon.h"
|
|
#include "fbcon_rotate.h"
|
|
#include "fb_internal.h"
|
|
|
|
/*
|
|
* FIXME: Locking
|
|
*
|
|
* - fbcon state itself is protected by the console_lock, and the code does a
|
|
* pretty good job at making sure that lock is held everywhere it's needed.
|
|
*
|
|
* - fbcon doesn't bother with fb_lock/unlock at all. This is buggy, since it
|
|
* means concurrent access to the same fbdev from both fbcon and userspace
|
|
* will blow up. To fix this all fbcon calls from fbmem.c need to be moved out
|
|
* of fb_lock/unlock protected sections, since otherwise we'll recurse and
|
|
* deadlock eventually. Aside: Due to these deadlock issues the fbdev code in
|
|
* fbmem.c cannot use locking asserts, and there's lots of callers which get
|
|
* the rules wrong, e.g. fbsysfs.c entirely missed fb_lock/unlock calls too.
|
|
*/
|
|
|
|
enum {
|
|
FBCON_LOGO_CANSHOW = -1, /* the logo can be shown */
|
|
FBCON_LOGO_DRAW = -2, /* draw the logo to a console */
|
|
FBCON_LOGO_DONTSHOW = -3 /* do not show the logo */
|
|
};
|
|
|
|
static struct fbcon_display fb_display[MAX_NR_CONSOLES];
|
|
|
|
static struct fb_info *fbcon_registered_fb[FB_MAX];
|
|
static int fbcon_num_registered_fb;
|
|
|
|
#define fbcon_for_each_registered_fb(i) \
|
|
for (i = 0; WARN_CONSOLE_UNLOCKED(), i < FB_MAX; i++) \
|
|
if (!fbcon_registered_fb[i]) {} else
|
|
|
|
static signed char con2fb_map[MAX_NR_CONSOLES];
|
|
static signed char con2fb_map_boot[MAX_NR_CONSOLES];
|
|
|
|
static struct fb_info *fbcon_info_from_console(int console)
|
|
{
|
|
signed char fb;
|
|
WARN_CONSOLE_UNLOCKED();
|
|
|
|
fb = con2fb_map[console];
|
|
if (fb < 0 || fb >= ARRAY_SIZE(fbcon_registered_fb))
|
|
return NULL;
|
|
|
|
return fbcon_registered_fb[fb];
|
|
}
|
|
|
|
static int logo_lines;
|
|
/* logo_shown is an index to vc_cons when >= 0; otherwise follows FBCON_LOGO
|
|
enums. */
|
|
static int logo_shown = FBCON_LOGO_CANSHOW;
|
|
/* console mappings */
|
|
static unsigned int first_fb_vc;
|
|
static unsigned int last_fb_vc = MAX_NR_CONSOLES - 1;
|
|
static bool fbcon_is_default = true;
|
|
static int primary_device = -1;
|
|
static bool fbcon_has_console_bind;
|
|
|
|
#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY
|
|
static int map_override;
|
|
|
|
static inline void fbcon_map_override(void)
|
|
{
|
|
map_override = 1;
|
|
}
|
|
#else
|
|
static inline void fbcon_map_override(void)
|
|
{
|
|
}
|
|
#endif /* CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY */
|
|
|
|
#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER
|
|
static bool deferred_takeover = true;
|
|
#else
|
|
#define deferred_takeover false
|
|
#endif
|
|
|
|
/* font data */
|
|
static char fontname[40];
|
|
|
|
/* current fb_info */
|
|
static int info_idx = -1;
|
|
|
|
/* console rotation */
|
|
static int initial_rotation = -1;
|
|
static int margin_color;
|
|
|
|
static const struct consw fb_con;
|
|
|
|
#define advance_row(p, delta) (unsigned short *)((unsigned long)(p) + (delta) * vc->vc_size_row)
|
|
|
|
static bool fbcon_cursor_blink = true;
|
|
|
|
#define divides(a, b) ((!(a) || (b)%(a)) ? 0 : 1)
|
|
|
|
/*
|
|
* Interface used by the world
|
|
*/
|
|
|
|
static void fbcon_clear_margins(struct vc_data *vc, int bottom_only);
|
|
static void fbcon_set_palette(struct vc_data *vc, const unsigned char *table);
|
|
|
|
/*
|
|
* Internal routines
|
|
*/
|
|
static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var,
|
|
int unit);
|
|
static void fbcon_redraw_move(struct vc_data *vc, struct fbcon_display *p,
|
|
int line, int count, int dy);
|
|
static void fbcon_modechanged(struct fb_info *info);
|
|
static void fbcon_set_all_vcs(struct fb_info *info);
|
|
|
|
static struct device *fbcon_device;
|
|
|
|
#ifdef CONFIG_FRAMEBUFFER_CONSOLE_ROTATION
|
|
static inline void fbcon_set_rotation(struct fb_info *info)
|
|
{
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
if (!(info->flags & FBINFO_MISC_TILEBLITTING) &&
|
|
par->p->con_rotate < 4)
|
|
par->rotate = par->p->con_rotate;
|
|
else
|
|
par->rotate = 0;
|
|
}
|
|
|
|
static void fbcon_rotate(struct fb_info *info, u32 rotate)
|
|
{
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
struct fb_info *fb_info;
|
|
|
|
if (!par || par->currcon == -1)
|
|
return;
|
|
|
|
fb_info = fbcon_info_from_console(par->currcon);
|
|
|
|
if (info == fb_info) {
|
|
struct fbcon_display *p = &fb_display[par->currcon];
|
|
|
|
if (rotate < 4)
|
|
p->con_rotate = rotate;
|
|
else
|
|
p->con_rotate = 0;
|
|
|
|
fbcon_modechanged(info);
|
|
}
|
|
}
|
|
|
|
static void fbcon_rotate_all(struct fb_info *info, u32 rotate)
|
|
{
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
struct vc_data *vc;
|
|
struct fbcon_display *p;
|
|
int i;
|
|
|
|
if (!par || par->currcon < 0 || rotate > 3)
|
|
return;
|
|
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
vc = vc_cons[i].d;
|
|
if (!vc || vc->vc_mode != KD_TEXT ||
|
|
fbcon_info_from_console(i) != info)
|
|
continue;
|
|
|
|
p = &fb_display[vc->vc_num];
|
|
p->con_rotate = rotate;
|
|
}
|
|
|
|
fbcon_set_all_vcs(info);
|
|
}
|
|
#else
|
|
static inline void fbcon_set_rotation(struct fb_info *info)
|
|
{
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
par->rotate = FB_ROTATE_UR;
|
|
}
|
|
|
|
static void fbcon_rotate(struct fb_info *info, u32 rotate)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static void fbcon_rotate_all(struct fb_info *info, u32 rotate)
|
|
{
|
|
return;
|
|
}
|
|
#endif /* CONFIG_FRAMEBUFFER_CONSOLE_ROTATION */
|
|
|
|
static void fbcon_set_bitops(struct fbcon_par *par)
|
|
{
|
|
switch (par->rotate) {
|
|
default:
|
|
fallthrough;
|
|
case FB_ROTATE_UR:
|
|
fbcon_set_bitops_ur(par);
|
|
break;
|
|
case FB_ROTATE_CW:
|
|
fbcon_set_bitops_cw(par);
|
|
break;
|
|
case FB_ROTATE_UD:
|
|
fbcon_set_bitops_ud(par);
|
|
break;
|
|
case FB_ROTATE_CCW:
|
|
fbcon_set_bitops_ccw(par);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int fbcon_get_rotate(struct fb_info *info)
|
|
{
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
return (par) ? par->rotate : 0;
|
|
}
|
|
|
|
static bool fbcon_skip_panic(struct fb_info *info)
|
|
{
|
|
return (info->skip_panic && unlikely(panic_in_progress()));
|
|
}
|
|
|
|
static inline bool fbcon_is_active(struct vc_data *vc, struct fb_info *info)
|
|
{
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
return info->state == FBINFO_STATE_RUNNING &&
|
|
vc->vc_mode == KD_TEXT && !par->graphics && !fbcon_skip_panic(info);
|
|
}
|
|
|
|
static int get_color(struct vc_data *vc, struct fb_info *info,
|
|
u16 c, bool is_fg)
|
|
{
|
|
int depth = fb_get_color_depth(&info->var, &info->fix);
|
|
int color = 0;
|
|
|
|
if (console_blanked) {
|
|
unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
|
|
|
|
c = vc->vc_video_erase_char & charmask;
|
|
}
|
|
|
|
if (depth != 1)
|
|
color = (is_fg) ? attr_fgcol((vc->vc_hi_font_mask) ? 9 : 8, c)
|
|
: attr_bgcol((vc->vc_hi_font_mask) ? 13 : 12, c);
|
|
|
|
switch (depth) {
|
|
case 1:
|
|
{
|
|
int col = mono_col(info);
|
|
/* 0 or 1 */
|
|
int fg = (info->fix.visual != FB_VISUAL_MONO01) ? col : 0;
|
|
int bg = (info->fix.visual != FB_VISUAL_MONO01) ? 0 : col;
|
|
|
|
if (console_blanked)
|
|
fg = bg;
|
|
|
|
color = (is_fg) ? fg : bg;
|
|
break;
|
|
}
|
|
case 2:
|
|
/*
|
|
* Scale down 16-colors to 4 colors. Default 4-color palette
|
|
* is grayscale. However, simply dividing the values by 4
|
|
* will not work, as colors 1, 2 and 3 will be scaled-down
|
|
* to zero rendering them invisible. So empirically convert
|
|
* colors to a sane 4-level grayscale.
|
|
*/
|
|
switch (color) {
|
|
case 0:
|
|
color = 0; /* black */
|
|
break;
|
|
case 1 ... 6:
|
|
color = 2; /* white */
|
|
break;
|
|
case 7 ... 8:
|
|
color = 1; /* gray */
|
|
break;
|
|
default:
|
|
color = 3; /* intense white */
|
|
break;
|
|
}
|
|
break;
|
|
case 3:
|
|
/*
|
|
* Last 8 entries of default 16-color palette is a more intense
|
|
* version of the first 8 (i.e., same chrominance, different
|
|
* luminance).
|
|
*/
|
|
color &= 7;
|
|
break;
|
|
}
|
|
|
|
|
|
return color;
|
|
}
|
|
|
|
static int get_fg_color(struct vc_data *vc, struct fb_info *info, u16 c)
|
|
{
|
|
return get_color(vc, info, c, true);
|
|
}
|
|
|
|
static int get_bg_color(struct vc_data *vc, struct fb_info *info, u16 c)
|
|
{
|
|
return get_color(vc, info, c, false);
|
|
}
|
|
|
|
static void fb_flashcursor(struct work_struct *work)
|
|
{
|
|
struct fbcon_par *par = container_of(work, struct fbcon_par, cursor_work.work);
|
|
struct fb_info *info;
|
|
struct vc_data *vc = NULL;
|
|
int c;
|
|
bool enable;
|
|
int ret;
|
|
|
|
/* FIXME: we should sort out the unbind locking instead */
|
|
/* instead we just fail to flash the cursor if we can't get
|
|
* the lock instead of blocking fbcon deinit */
|
|
ret = console_trylock();
|
|
if (ret == 0)
|
|
return;
|
|
|
|
/* protected by console_lock */
|
|
info = par->info;
|
|
|
|
if (par->currcon != -1)
|
|
vc = vc_cons[par->currcon].d;
|
|
|
|
if (!vc || !con_is_visible(vc) ||
|
|
fbcon_info_from_console(vc->vc_num) != info ||
|
|
vc->vc_deccm != 1) {
|
|
console_unlock();
|
|
return;
|
|
}
|
|
|
|
c = scr_readw((u16 *) vc->vc_pos);
|
|
enable = par->cursor_flash && !par->cursor_state.enable;
|
|
par->bitops->cursor(vc, info, enable,
|
|
get_fg_color(vc, info, c),
|
|
get_bg_color(vc, info, c));
|
|
console_unlock();
|
|
|
|
queue_delayed_work(system_power_efficient_wq, &par->cursor_work,
|
|
par->cur_blink_jiffies);
|
|
}
|
|
|
|
static void fbcon_add_cursor_work(struct fb_info *info)
|
|
{
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
if (fbcon_cursor_blink)
|
|
queue_delayed_work(system_power_efficient_wq, &par->cursor_work,
|
|
par->cur_blink_jiffies);
|
|
}
|
|
|
|
static void fbcon_del_cursor_work(struct fb_info *info)
|
|
{
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
cancel_delayed_work_sync(&par->cursor_work);
|
|
}
|
|
|
|
#ifndef MODULE
|
|
static int __init fb_console_setup(char *this_opt)
|
|
{
|
|
char *options;
|
|
int i, j;
|
|
|
|
if (!this_opt || !*this_opt)
|
|
return 1;
|
|
|
|
while ((options = strsep(&this_opt, ",")) != NULL) {
|
|
if (!strncmp(options, "font:", 5)) {
|
|
strscpy(fontname, options + 5, sizeof(fontname));
|
|
continue;
|
|
}
|
|
|
|
if (!strncmp(options, "scrollback:", 11)) {
|
|
pr_warn("Ignoring scrollback size option\n");
|
|
continue;
|
|
}
|
|
|
|
if (!strncmp(options, "map:", 4)) {
|
|
options += 4;
|
|
if (*options) {
|
|
for (i = 0, j = 0; i < MAX_NR_CONSOLES; i++) {
|
|
if (!options[j])
|
|
j = 0;
|
|
con2fb_map_boot[i] =
|
|
(options[j++]-'0') % FB_MAX;
|
|
}
|
|
|
|
fbcon_map_override();
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!strncmp(options, "vc:", 3)) {
|
|
options += 3;
|
|
if (*options)
|
|
first_fb_vc = simple_strtoul(options, &options, 10) - 1;
|
|
if (first_fb_vc >= MAX_NR_CONSOLES)
|
|
first_fb_vc = 0;
|
|
if (*options++ == '-')
|
|
last_fb_vc = simple_strtoul(options, &options, 10) - 1;
|
|
if (last_fb_vc < first_fb_vc || last_fb_vc >= MAX_NR_CONSOLES)
|
|
last_fb_vc = MAX_NR_CONSOLES - 1;
|
|
fbcon_is_default = false;
|
|
continue;
|
|
}
|
|
|
|
if (!strncmp(options, "rotate:", 7)) {
|
|
options += 7;
|
|
if (*options)
|
|
initial_rotation = simple_strtoul(options, &options, 0);
|
|
if (initial_rotation > 3)
|
|
initial_rotation = 0;
|
|
continue;
|
|
}
|
|
|
|
if (!strncmp(options, "margin:", 7)) {
|
|
options += 7;
|
|
if (*options)
|
|
margin_color = simple_strtoul(options, &options, 0);
|
|
continue;
|
|
}
|
|
#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER
|
|
if (!strcmp(options, "nodefer")) {
|
|
deferred_takeover = false;
|
|
continue;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_LOGO
|
|
if (!strncmp(options, "logo-pos:", 9)) {
|
|
options += 9;
|
|
if (!strcmp(options, "center"))
|
|
fb_center_logo = true;
|
|
continue;
|
|
}
|
|
|
|
if (!strncmp(options, "logo-count:", 11)) {
|
|
options += 11;
|
|
if (*options)
|
|
fb_logo_count = simple_strtol(options, &options, 0);
|
|
continue;
|
|
}
|
|
#endif
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
__setup("fbcon=", fb_console_setup);
|
|
#endif
|
|
|
|
static int search_fb_in_map(int idx)
|
|
{
|
|
int i, retval = 0;
|
|
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
if (con2fb_map[i] == idx) {
|
|
retval = 1;
|
|
break;
|
|
}
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
static int search_for_mapped_con(void)
|
|
{
|
|
int i, retval = 0;
|
|
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
if (con2fb_map[i] != -1) {
|
|
retval = 1;
|
|
break;
|
|
}
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
static int do_fbcon_takeover(int show_logo)
|
|
{
|
|
int err, i;
|
|
|
|
if (!fbcon_num_registered_fb)
|
|
return -ENODEV;
|
|
|
|
if (!show_logo)
|
|
logo_shown = FBCON_LOGO_DONTSHOW;
|
|
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++)
|
|
con2fb_map[i] = info_idx;
|
|
|
|
err = do_take_over_console(&fb_con, first_fb_vc, last_fb_vc,
|
|
fbcon_is_default);
|
|
|
|
if (err) {
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++)
|
|
con2fb_map[i] = -1;
|
|
info_idx = -1;
|
|
} else {
|
|
fbcon_has_console_bind = true;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
#ifdef MODULE
|
|
static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
|
|
int cols, int rows, int new_cols, int new_rows)
|
|
{
|
|
logo_shown = FBCON_LOGO_DONTSHOW;
|
|
}
|
|
#else
|
|
static void fbcon_prepare_logo(struct vc_data *vc, struct fb_info *info,
|
|
int cols, int rows, int new_cols, int new_rows)
|
|
{
|
|
/* Need to make room for the logo */
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
int cnt, erase = vc->vc_video_erase_char, step;
|
|
unsigned short *save = NULL, *r, *q;
|
|
int logo_height;
|
|
|
|
if (info->fbops->owner) {
|
|
logo_shown = FBCON_LOGO_DONTSHOW;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* remove underline attribute from erase character
|
|
* if black and white framebuffer.
|
|
*/
|
|
if (fb_get_color_depth(&info->var, &info->fix) == 1)
|
|
erase &= ~0x400;
|
|
logo_height = fb_prepare_logo(info, par->rotate);
|
|
logo_lines = DIV_ROUND_UP(logo_height, vc->vc_font.height);
|
|
q = (unsigned short *) (vc->vc_origin +
|
|
vc->vc_size_row * rows);
|
|
step = logo_lines * cols;
|
|
for (r = q - logo_lines * cols; r < q; r++)
|
|
if (scr_readw(r) != vc->vc_video_erase_char)
|
|
break;
|
|
if (r != q && new_rows >= rows + logo_lines) {
|
|
save = kmalloc(array3_size(logo_lines, new_cols, 2),
|
|
GFP_KERNEL);
|
|
if (save) {
|
|
int i = min(cols, new_cols);
|
|
scr_memsetw(save, erase, array3_size(logo_lines, new_cols, 2));
|
|
r = q - step;
|
|
for (cnt = 0; cnt < logo_lines; cnt++, r += i)
|
|
scr_memcpyw(save + cnt * new_cols, r, 2 * i);
|
|
r = q;
|
|
}
|
|
}
|
|
if (r == q) {
|
|
/* We can scroll screen down */
|
|
r = q - step - cols;
|
|
for (cnt = rows - logo_lines; cnt > 0; cnt--) {
|
|
scr_memcpyw(r + step, r, vc->vc_size_row);
|
|
r -= cols;
|
|
}
|
|
if (!save) {
|
|
int lines;
|
|
if (vc->state.y + logo_lines >= rows)
|
|
lines = rows - vc->state.y - 1;
|
|
else
|
|
lines = logo_lines;
|
|
vc->state.y += lines;
|
|
vc->vc_pos += lines * vc->vc_size_row;
|
|
}
|
|
}
|
|
scr_memsetw((unsigned short *) vc->vc_origin,
|
|
erase,
|
|
vc->vc_size_row * logo_lines);
|
|
|
|
if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) {
|
|
fbcon_clear_margins(vc, 0);
|
|
update_screen(vc);
|
|
}
|
|
|
|
if (save) {
|
|
q = (unsigned short *) (vc->vc_origin +
|
|
vc->vc_size_row *
|
|
rows);
|
|
scr_memcpyw(q, save, array3_size(logo_lines, new_cols, 2));
|
|
vc->state.y += logo_lines;
|
|
vc->vc_pos += logo_lines * vc->vc_size_row;
|
|
kfree(save);
|
|
}
|
|
|
|
if (logo_shown == FBCON_LOGO_DONTSHOW)
|
|
return;
|
|
|
|
if (logo_lines > vc->vc_bottom) {
|
|
logo_shown = FBCON_LOGO_CANSHOW;
|
|
pr_info("fbcon: disable boot-logo (boot-logo bigger than screen).\n");
|
|
} else {
|
|
logo_shown = FBCON_LOGO_DRAW;
|
|
vc->vc_top = logo_lines;
|
|
}
|
|
}
|
|
#endif /* MODULE */
|
|
|
|
#ifdef CONFIG_FB_TILEBLITTING
|
|
static void set_blitting_type(struct vc_data *vc, struct fb_info *info)
|
|
{
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
par->p = &fb_display[vc->vc_num];
|
|
|
|
if ((info->flags & FBINFO_MISC_TILEBLITTING))
|
|
fbcon_set_tileops(vc, info);
|
|
else {
|
|
fbcon_set_rotation(info);
|
|
fbcon_set_bitops(par);
|
|
}
|
|
}
|
|
|
|
static int fbcon_invalid_charcount(struct fb_info *info, unsigned charcount)
|
|
{
|
|
int err = 0;
|
|
|
|
if (info->flags & FBINFO_MISC_TILEBLITTING &&
|
|
info->tileops->fb_get_tilemax(info) < charcount)
|
|
err = 1;
|
|
|
|
return err;
|
|
}
|
|
#else
|
|
static void set_blitting_type(struct vc_data *vc, struct fb_info *info)
|
|
{
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
info->flags &= ~FBINFO_MISC_TILEBLITTING;
|
|
par->p = &fb_display[vc->vc_num];
|
|
fbcon_set_rotation(info);
|
|
fbcon_set_bitops(par);
|
|
}
|
|
|
|
static int fbcon_invalid_charcount(struct fb_info *info, unsigned charcount)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
#endif /* CONFIG_MISC_TILEBLITTING */
|
|
|
|
static void fbcon_release(struct fb_info *info)
|
|
{
|
|
lock_fb_info(info);
|
|
if (info->fbops->fb_release)
|
|
info->fbops->fb_release(info, 0);
|
|
unlock_fb_info(info);
|
|
|
|
module_put(info->fbops->owner);
|
|
|
|
if (info->fbcon_par) {
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
fbcon_del_cursor_work(info);
|
|
kfree(par->cursor_state.mask);
|
|
kfree(par->cursor_data);
|
|
kfree(par->cursor_src);
|
|
kfree(par->fontbuffer);
|
|
kfree(info->fbcon_par);
|
|
info->fbcon_par = NULL;
|
|
}
|
|
}
|
|
|
|
static int fbcon_open(struct fb_info *info)
|
|
{
|
|
struct fbcon_par *par;
|
|
|
|
if (!try_module_get(info->fbops->owner))
|
|
return -ENODEV;
|
|
|
|
lock_fb_info(info);
|
|
if (info->fbops->fb_open &&
|
|
info->fbops->fb_open(info, 0)) {
|
|
unlock_fb_info(info);
|
|
module_put(info->fbops->owner);
|
|
return -ENODEV;
|
|
}
|
|
unlock_fb_info(info);
|
|
|
|
par = kzalloc(sizeof(*par), GFP_KERNEL);
|
|
if (!par) {
|
|
fbcon_release(info);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
INIT_DELAYED_WORK(&par->cursor_work, fb_flashcursor);
|
|
par->info = info;
|
|
info->fbcon_par = par;
|
|
par->cur_blink_jiffies = HZ / 5;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int con2fb_acquire_newinfo(struct vc_data *vc, struct fb_info *info,
|
|
int unit)
|
|
{
|
|
int err;
|
|
|
|
err = fbcon_open(info);
|
|
if (err)
|
|
return err;
|
|
|
|
if (vc)
|
|
set_blitting_type(vc, info);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void con2fb_release_oldinfo(struct vc_data *vc, struct fb_info *oldinfo,
|
|
struct fb_info *newinfo)
|
|
{
|
|
int ret;
|
|
|
|
fbcon_release(oldinfo);
|
|
|
|
/*
|
|
If oldinfo and newinfo are driving the same hardware,
|
|
the fb_release() method of oldinfo may attempt to
|
|
restore the hardware state. This will leave the
|
|
newinfo in an undefined state. Thus, a call to
|
|
fb_set_par() may be needed for the newinfo.
|
|
*/
|
|
if (newinfo && newinfo->fbops->fb_set_par) {
|
|
ret = newinfo->fbops->fb_set_par(newinfo);
|
|
|
|
if (ret)
|
|
printk(KERN_ERR "con2fb_release_oldinfo: "
|
|
"detected unhandled fb_set_par error, "
|
|
"error code %d\n", ret);
|
|
}
|
|
}
|
|
|
|
static void con2fb_init_display(struct vc_data *vc, struct fb_info *info,
|
|
int unit, int show_logo)
|
|
{
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
int ret;
|
|
|
|
par->currcon = fg_console;
|
|
|
|
if (info->fbops->fb_set_par && !par->initialized) {
|
|
ret = info->fbops->fb_set_par(info);
|
|
|
|
if (ret)
|
|
printk(KERN_ERR "con2fb_init_display: detected "
|
|
"unhandled fb_set_par error, "
|
|
"error code %d\n", ret);
|
|
}
|
|
|
|
par->initialized = true;
|
|
par->graphics = 0;
|
|
fbcon_set_disp(info, &info->var, unit);
|
|
|
|
if (show_logo) {
|
|
struct vc_data *fg_vc = vc_cons[fg_console].d;
|
|
struct fb_info *fg_info =
|
|
fbcon_info_from_console(fg_console);
|
|
|
|
fbcon_prepare_logo(fg_vc, fg_info, fg_vc->vc_cols,
|
|
fg_vc->vc_rows, fg_vc->vc_cols,
|
|
fg_vc->vc_rows);
|
|
}
|
|
|
|
if (fg_console != unit)
|
|
update_screen(vc_cons[fg_console].d);
|
|
}
|
|
|
|
/**
|
|
* set_con2fb_map - map console to frame buffer device
|
|
* @unit: virtual console number to map
|
|
* @newidx: frame buffer index to map virtual console to
|
|
* @user: user request
|
|
*
|
|
* Maps a virtual console @unit to a frame buffer device
|
|
* @newidx.
|
|
*
|
|
* This should be called with the console lock held.
|
|
*/
|
|
static int set_con2fb_map(int unit, int newidx, int user)
|
|
{
|
|
struct vc_data *vc = vc_cons[unit].d;
|
|
int oldidx = con2fb_map[unit];
|
|
struct fb_info *info = fbcon_registered_fb[newidx];
|
|
struct fb_info *oldinfo = NULL;
|
|
int err = 0, show_logo;
|
|
|
|
WARN_CONSOLE_UNLOCKED();
|
|
|
|
if (oldidx == newidx)
|
|
return 0;
|
|
|
|
if (!info)
|
|
return -EINVAL;
|
|
|
|
if (!search_for_mapped_con() || !con_is_bound(&fb_con)) {
|
|
info_idx = newidx;
|
|
return do_fbcon_takeover(0);
|
|
}
|
|
|
|
if (oldidx != -1)
|
|
oldinfo = fbcon_registered_fb[oldidx];
|
|
|
|
if (!search_fb_in_map(newidx)) {
|
|
err = con2fb_acquire_newinfo(vc, info, unit);
|
|
if (err)
|
|
return err;
|
|
|
|
fbcon_add_cursor_work(info);
|
|
} else if (vc) {
|
|
set_blitting_type(vc, info);
|
|
}
|
|
|
|
con2fb_map[unit] = newidx;
|
|
|
|
/*
|
|
* If old fb is not mapped to any of the consoles,
|
|
* fbcon should release it.
|
|
*/
|
|
if (oldinfo && !search_fb_in_map(oldidx))
|
|
con2fb_release_oldinfo(vc, oldinfo, info);
|
|
|
|
show_logo = (fg_console == 0 && !user &&
|
|
logo_shown != FBCON_LOGO_DONTSHOW);
|
|
|
|
con2fb_map_boot[unit] = newidx;
|
|
con2fb_init_display(vc, info, unit, show_logo);
|
|
|
|
if (!search_fb_in_map(info_idx))
|
|
info_idx = newidx;
|
|
|
|
return err;
|
|
}
|
|
|
|
/*
|
|
* Low Level Operations
|
|
*/
|
|
/* NOTE: fbcon cannot be __init: it may be called from do_take_over_console later */
|
|
static int var_to_display(struct fbcon_display *disp,
|
|
struct fb_var_screeninfo *var,
|
|
struct fb_info *info)
|
|
{
|
|
disp->xres_virtual = var->xres_virtual;
|
|
disp->yres_virtual = var->yres_virtual;
|
|
disp->bits_per_pixel = var->bits_per_pixel;
|
|
disp->grayscale = var->grayscale;
|
|
disp->nonstd = var->nonstd;
|
|
disp->accel_flags = var->accel_flags;
|
|
disp->height = var->height;
|
|
disp->width = var->width;
|
|
disp->red = var->red;
|
|
disp->green = var->green;
|
|
disp->blue = var->blue;
|
|
disp->transp = var->transp;
|
|
disp->rotate = var->rotate;
|
|
disp->mode = fb_match_mode(var, &info->modelist);
|
|
if (disp->mode == NULL)
|
|
/* This should not happen */
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
static void display_to_var(struct fb_var_screeninfo *var,
|
|
struct fbcon_display *disp)
|
|
{
|
|
fb_videomode_to_var(var, disp->mode);
|
|
var->xres_virtual = disp->xres_virtual;
|
|
var->yres_virtual = disp->yres_virtual;
|
|
var->bits_per_pixel = disp->bits_per_pixel;
|
|
var->grayscale = disp->grayscale;
|
|
var->nonstd = disp->nonstd;
|
|
var->accel_flags = disp->accel_flags;
|
|
var->height = disp->height;
|
|
var->width = disp->width;
|
|
var->red = disp->red;
|
|
var->green = disp->green;
|
|
var->blue = disp->blue;
|
|
var->transp = disp->transp;
|
|
var->rotate = disp->rotate;
|
|
}
|
|
|
|
static const char *fbcon_startup(void)
|
|
{
|
|
static const char display_desc[] = "frame buffer device";
|
|
struct fbcon_display *p = &fb_display[fg_console];
|
|
struct vc_data *vc = vc_cons[fg_console].d;
|
|
const struct font_desc *font = NULL;
|
|
struct fb_info *info = NULL;
|
|
struct fbcon_par *par;
|
|
int rows, cols;
|
|
|
|
/*
|
|
* If fbcon_num_registered_fb is zero, this is a call for the dummy part.
|
|
* The frame buffer devices weren't initialized yet.
|
|
*/
|
|
if (!fbcon_num_registered_fb || info_idx == -1)
|
|
return display_desc;
|
|
/*
|
|
* Instead of blindly using fbcon_registered_fb[0], we use info_idx, set by
|
|
* fbcon_fb_registered();
|
|
*/
|
|
info = fbcon_registered_fb[info_idx];
|
|
if (!info)
|
|
return NULL;
|
|
|
|
if (fbcon_open(info))
|
|
return NULL;
|
|
|
|
par = info->fbcon_par;
|
|
par->currcon = -1;
|
|
par->graphics = 1;
|
|
par->cur_rotate = -1;
|
|
|
|
p->con_rotate = initial_rotation;
|
|
if (p->con_rotate == -1)
|
|
p->con_rotate = info->fbcon_rotate_hint;
|
|
if (p->con_rotate == -1)
|
|
p->con_rotate = FB_ROTATE_UR;
|
|
|
|
set_blitting_type(vc, info);
|
|
|
|
/* Setup default font */
|
|
if (!p->fontdata) {
|
|
if (!fontname[0] || !(font = find_font(fontname)))
|
|
font = get_default_font(info->var.xres,
|
|
info->var.yres,
|
|
info->pixmap.blit_x,
|
|
info->pixmap.blit_y);
|
|
vc->vc_font.width = font->width;
|
|
vc->vc_font.height = font->height;
|
|
vc->vc_font.data = (void *)(p->fontdata = font->data);
|
|
vc->vc_font.charcount = font->charcount;
|
|
}
|
|
|
|
cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
|
|
rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
|
|
cols /= vc->vc_font.width;
|
|
rows /= vc->vc_font.height;
|
|
vc_resize(vc, cols, rows);
|
|
|
|
pr_debug("mode: %s\n", info->fix.id);
|
|
pr_debug("visual: %d\n", info->fix.visual);
|
|
pr_debug("res: %dx%d-%d\n", info->var.xres,
|
|
info->var.yres,
|
|
info->var.bits_per_pixel);
|
|
|
|
fbcon_add_cursor_work(info);
|
|
return display_desc;
|
|
}
|
|
|
|
static void fbcon_init(struct vc_data *vc, bool init)
|
|
{
|
|
struct fb_info *info;
|
|
struct fbcon_par *par;
|
|
struct vc_data **default_mode = vc->vc_display_fg;
|
|
struct vc_data *svc = *default_mode;
|
|
struct fbcon_display *t, *p = &fb_display[vc->vc_num];
|
|
int logo = 1, new_rows, new_cols, rows, cols;
|
|
int ret;
|
|
|
|
if (WARN_ON(info_idx == -1))
|
|
return;
|
|
|
|
if (con2fb_map[vc->vc_num] == -1)
|
|
con2fb_map[vc->vc_num] = info_idx;
|
|
|
|
info = fbcon_info_from_console(vc->vc_num);
|
|
|
|
if (logo_shown < 0 && console_loglevel <= CONSOLE_LOGLEVEL_QUIET)
|
|
logo_shown = FBCON_LOGO_DONTSHOW;
|
|
|
|
if (vc != svc || logo_shown == FBCON_LOGO_DONTSHOW ||
|
|
(info->fix.type == FB_TYPE_TEXT))
|
|
logo = 0;
|
|
|
|
if (var_to_display(p, &info->var, info))
|
|
return;
|
|
|
|
if (!info->fbcon_par)
|
|
con2fb_acquire_newinfo(vc, info, vc->vc_num);
|
|
|
|
/* If we are not the first console on this
|
|
fb, copy the font from that console */
|
|
t = &fb_display[fg_console];
|
|
if (!p->fontdata) {
|
|
if (t->fontdata) {
|
|
struct vc_data *fvc = vc_cons[fg_console].d;
|
|
|
|
vc->vc_font.data = (void *)(p->fontdata =
|
|
fvc->vc_font.data);
|
|
vc->vc_font.width = fvc->vc_font.width;
|
|
vc->vc_font.height = fvc->vc_font.height;
|
|
vc->vc_font.charcount = fvc->vc_font.charcount;
|
|
p->userfont = t->userfont;
|
|
|
|
if (p->userfont)
|
|
REFCOUNT(p->fontdata)++;
|
|
} else {
|
|
const struct font_desc *font = NULL;
|
|
|
|
if (!fontname[0] || !(font = find_font(fontname)))
|
|
font = get_default_font(info->var.xres,
|
|
info->var.yres,
|
|
info->pixmap.blit_x,
|
|
info->pixmap.blit_y);
|
|
vc->vc_font.width = font->width;
|
|
vc->vc_font.height = font->height;
|
|
vc->vc_font.data = (void *)(p->fontdata = font->data);
|
|
vc->vc_font.charcount = font->charcount;
|
|
}
|
|
}
|
|
|
|
vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1);
|
|
vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
|
|
if (vc->vc_font.charcount == 256) {
|
|
vc->vc_hi_font_mask = 0;
|
|
} else {
|
|
vc->vc_hi_font_mask = 0x100;
|
|
if (vc->vc_can_do_color)
|
|
vc->vc_complement_mask <<= 1;
|
|
}
|
|
|
|
if (!*svc->uni_pagedict_loc)
|
|
con_set_default_unimap(svc);
|
|
if (!*vc->uni_pagedict_loc)
|
|
con_copy_unimap(vc, svc);
|
|
|
|
par = info->fbcon_par;
|
|
par->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms);
|
|
|
|
p->con_rotate = initial_rotation;
|
|
if (p->con_rotate == -1)
|
|
p->con_rotate = info->fbcon_rotate_hint;
|
|
if (p->con_rotate == -1)
|
|
p->con_rotate = FB_ROTATE_UR;
|
|
|
|
set_blitting_type(vc, info);
|
|
|
|
cols = vc->vc_cols;
|
|
rows = vc->vc_rows;
|
|
new_cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
|
|
new_rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
|
|
new_cols /= vc->vc_font.width;
|
|
new_rows /= vc->vc_font.height;
|
|
|
|
/*
|
|
* We must always set the mode. The mode of the previous console
|
|
* driver could be in the same resolution but we are using different
|
|
* hardware so we have to initialize the hardware.
|
|
*
|
|
* We need to do it in fbcon_init() to prevent screen corruption.
|
|
*/
|
|
if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) {
|
|
if (info->fbops->fb_set_par && !par->initialized) {
|
|
ret = info->fbops->fb_set_par(info);
|
|
|
|
if (ret)
|
|
printk(KERN_ERR "fbcon_init: detected "
|
|
"unhandled fb_set_par error, "
|
|
"error code %d\n", ret);
|
|
}
|
|
|
|
par->initialized = true;
|
|
}
|
|
|
|
par->graphics = 0;
|
|
|
|
#ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION
|
|
if ((info->flags & FBINFO_HWACCEL_COPYAREA) &&
|
|
!(info->flags & FBINFO_HWACCEL_DISABLED))
|
|
p->scrollmode = SCROLL_MOVE;
|
|
else /* default to something safe */
|
|
p->scrollmode = SCROLL_REDRAW;
|
|
#endif
|
|
|
|
/*
|
|
* ++guenther: console.c:vc_allocate() relies on initializing
|
|
* vc_{cols,rows}, but we must not set those if we are only
|
|
* resizing the console.
|
|
*/
|
|
if (init) {
|
|
vc->vc_cols = new_cols;
|
|
vc->vc_rows = new_rows;
|
|
} else
|
|
vc_resize(vc, new_cols, new_rows);
|
|
|
|
if (logo)
|
|
fbcon_prepare_logo(vc, info, cols, rows, new_cols, new_rows);
|
|
|
|
if (par->bitops->rotate_font && par->bitops->rotate_font(info, vc)) {
|
|
par->rotate = FB_ROTATE_UR;
|
|
set_blitting_type(vc, info);
|
|
}
|
|
|
|
par->p = &fb_display[fg_console];
|
|
}
|
|
|
|
static void fbcon_free_font(struct fbcon_display *p)
|
|
{
|
|
if (p->userfont && p->fontdata && (--REFCOUNT(p->fontdata) == 0))
|
|
kfree(p->fontdata - FONT_EXTRA_WORDS * sizeof(int));
|
|
p->fontdata = NULL;
|
|
p->userfont = 0;
|
|
}
|
|
|
|
static void set_vc_hi_font(struct vc_data *vc, bool set);
|
|
|
|
static void fbcon_release_all(void)
|
|
{
|
|
struct fb_info *info;
|
|
int i, j, mapped;
|
|
|
|
fbcon_for_each_registered_fb(i) {
|
|
mapped = 0;
|
|
info = fbcon_registered_fb[i];
|
|
|
|
for (j = first_fb_vc; j <= last_fb_vc; j++) {
|
|
if (con2fb_map[j] == i) {
|
|
mapped = 1;
|
|
con2fb_map[j] = -1;
|
|
}
|
|
}
|
|
|
|
if (mapped)
|
|
fbcon_release(info);
|
|
}
|
|
}
|
|
|
|
static void fbcon_deinit(struct vc_data *vc)
|
|
{
|
|
struct fbcon_display *p = &fb_display[vc->vc_num];
|
|
struct fb_info *info;
|
|
struct fbcon_par *par;
|
|
int idx;
|
|
|
|
fbcon_free_font(p);
|
|
idx = con2fb_map[vc->vc_num];
|
|
|
|
if (idx == -1)
|
|
goto finished;
|
|
|
|
info = fbcon_registered_fb[idx];
|
|
|
|
if (!info)
|
|
goto finished;
|
|
|
|
par = info->fbcon_par;
|
|
|
|
if (!par)
|
|
goto finished;
|
|
|
|
if (con_is_visible(vc))
|
|
fbcon_del_cursor_work(info);
|
|
|
|
par->initialized = false;
|
|
finished:
|
|
|
|
fbcon_free_font(p);
|
|
vc->vc_font.data = NULL;
|
|
|
|
if (vc->vc_hi_font_mask && vc->vc_screenbuf)
|
|
set_vc_hi_font(vc, false);
|
|
|
|
if (!con_is_bound(&fb_con))
|
|
fbcon_release_all();
|
|
|
|
if (vc->vc_num == logo_shown)
|
|
logo_shown = FBCON_LOGO_CANSHOW;
|
|
|
|
return;
|
|
}
|
|
|
|
/* ====================================================================== */
|
|
|
|
/* fbcon_XXX routines - interface used by the world
|
|
*
|
|
* This system is now divided into two levels because of complications
|
|
* caused by hardware scrolling. Top level functions:
|
|
*
|
|
* fbcon_bmove(), fbcon_clear(), fbcon_putc(), fbcon_clear_margins()
|
|
*
|
|
* handles y values in range [0, scr_height-1] that correspond to real
|
|
* screen positions. y_wrap shift means that first line of bitmap may be
|
|
* anywhere on this display. These functions convert lineoffsets to
|
|
* bitmap offsets and deal with the wrap-around case by splitting blits.
|
|
*
|
|
* fbcon_bmove_physical_8() -- These functions fast implementations
|
|
* fbcon_clear_physical_8() -- of original fbcon_XXX fns.
|
|
* fbcon_putc_physical_8() -- (font width != 8) may be added later
|
|
*
|
|
* WARNING:
|
|
*
|
|
* At the moment fbcon_putc() cannot blit across vertical wrap boundary
|
|
* Implies should only really hardware scroll in rows. Only reason for
|
|
* restriction is simplicity & efficiency at the moment.
|
|
*/
|
|
|
|
static void __fbcon_clear(struct vc_data *vc, unsigned int sy, unsigned int sx,
|
|
unsigned int height, unsigned int width)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
int fg, bg;
|
|
struct fbcon_display *p = &fb_display[vc->vc_num];
|
|
u_int y_break;
|
|
|
|
if (!fbcon_is_active(vc, info))
|
|
return;
|
|
|
|
if (!height || !width)
|
|
return;
|
|
|
|
if (sy < vc->vc_top && vc->vc_top == logo_lines) {
|
|
vc->vc_top = 0;
|
|
/*
|
|
* If the font dimensions are not an integral of the display
|
|
* dimensions then the par->clear below won't end up clearing
|
|
* the margins. Call clear_margins here in case the logo
|
|
* bitmap stretched into the margin area.
|
|
*/
|
|
fbcon_clear_margins(vc, 0);
|
|
}
|
|
|
|
fg = get_color(vc, info, vc->vc_video_erase_char, 1);
|
|
bg = get_color(vc, info, vc->vc_video_erase_char, 0);
|
|
/* Split blits that cross physical y_wrap boundary */
|
|
|
|
y_break = p->vrows - p->yscroll;
|
|
if (sy < y_break && sy + height - 1 >= y_break) {
|
|
u_int b = y_break - sy;
|
|
par->bitops->clear(vc, info, real_y(p, sy), sx, b, width, fg, bg);
|
|
par->bitops->clear(vc, info, real_y(p, sy + b), sx, height - b,
|
|
width, fg, bg);
|
|
} else
|
|
par->bitops->clear(vc, info, real_y(p, sy), sx, height, width, fg, bg);
|
|
}
|
|
|
|
static void fbcon_clear(struct vc_data *vc, unsigned int sy, unsigned int sx,
|
|
unsigned int width)
|
|
{
|
|
__fbcon_clear(vc, sy, sx, 1, width);
|
|
}
|
|
|
|
static void fbcon_putcs(struct vc_data *vc, const u16 *s, unsigned int count,
|
|
unsigned int ypos, unsigned int xpos)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_display *p = &fb_display[vc->vc_num];
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
if (fbcon_is_active(vc, info))
|
|
par->bitops->putcs(vc, info, s, count, real_y(p, ypos), xpos,
|
|
get_fg_color(vc, info, scr_readw(s)),
|
|
get_bg_color(vc, info, scr_readw(s)));
|
|
}
|
|
|
|
static void fbcon_clear_margins(struct vc_data *vc, int bottom_only)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
if (fbcon_is_active(vc, info))
|
|
par->bitops->clear_margins(vc, info, margin_color, bottom_only);
|
|
}
|
|
|
|
static void fbcon_cursor(struct vc_data *vc, bool enable)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
int c = scr_readw((u16 *) vc->vc_pos);
|
|
|
|
par->cur_blink_jiffies = msecs_to_jiffies(vc->vc_cur_blink_ms);
|
|
|
|
if (!fbcon_is_active(vc, info) || vc->vc_deccm != 1)
|
|
return;
|
|
|
|
if (vc->vc_cursor_type & CUR_SW)
|
|
fbcon_del_cursor_work(info);
|
|
else
|
|
fbcon_add_cursor_work(info);
|
|
|
|
par->cursor_flash = enable;
|
|
|
|
if (!par->bitops->cursor)
|
|
return;
|
|
|
|
par->bitops->cursor(vc, info, enable,
|
|
get_fg_color(vc, info, c),
|
|
get_bg_color(vc, info, c));
|
|
}
|
|
|
|
static int scrollback_phys_max = 0;
|
|
static int scrollback_max = 0;
|
|
static int scrollback_current = 0;
|
|
|
|
static void fbcon_set_disp(struct fb_info *info, struct fb_var_screeninfo *var,
|
|
int unit)
|
|
{
|
|
struct fbcon_display *p, *t;
|
|
struct vc_data **default_mode, *vc;
|
|
struct vc_data *svc;
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
int rows, cols;
|
|
unsigned long ret = 0;
|
|
|
|
p = &fb_display[unit];
|
|
|
|
if (var_to_display(p, var, info))
|
|
return;
|
|
|
|
vc = vc_cons[unit].d;
|
|
|
|
if (!vc)
|
|
return;
|
|
|
|
default_mode = vc->vc_display_fg;
|
|
svc = *default_mode;
|
|
t = &fb_display[svc->vc_num];
|
|
|
|
if (!vc->vc_font.data) {
|
|
vc->vc_font.data = (void *)(p->fontdata = t->fontdata);
|
|
vc->vc_font.width = (*default_mode)->vc_font.width;
|
|
vc->vc_font.height = (*default_mode)->vc_font.height;
|
|
vc->vc_font.charcount = (*default_mode)->vc_font.charcount;
|
|
p->userfont = t->userfont;
|
|
if (p->userfont)
|
|
REFCOUNT(p->fontdata)++;
|
|
}
|
|
|
|
var->activate = FB_ACTIVATE_NOW;
|
|
info->var.activate = var->activate;
|
|
var->yoffset = info->var.yoffset;
|
|
var->xoffset = info->var.xoffset;
|
|
fb_set_var(info, var);
|
|
par->var = info->var;
|
|
vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1);
|
|
vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
|
|
if (vc->vc_font.charcount == 256) {
|
|
vc->vc_hi_font_mask = 0;
|
|
} else {
|
|
vc->vc_hi_font_mask = 0x100;
|
|
if (vc->vc_can_do_color)
|
|
vc->vc_complement_mask <<= 1;
|
|
}
|
|
|
|
if (!*svc->uni_pagedict_loc)
|
|
con_set_default_unimap(svc);
|
|
if (!*vc->uni_pagedict_loc)
|
|
con_copy_unimap(vc, svc);
|
|
|
|
cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
|
|
rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
|
|
cols /= vc->vc_font.width;
|
|
rows /= vc->vc_font.height;
|
|
ret = vc_resize(vc, cols, rows);
|
|
|
|
if (con_is_visible(vc) && !ret)
|
|
update_screen(vc);
|
|
}
|
|
|
|
static __inline__ void ywrap_up(struct vc_data *vc, int count)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
struct fbcon_display *p = &fb_display[vc->vc_num];
|
|
|
|
p->yscroll += count;
|
|
if (p->yscroll >= p->vrows) /* Deal with wrap */
|
|
p->yscroll -= p->vrows;
|
|
par->var.xoffset = 0;
|
|
par->var.yoffset = p->yscroll * vc->vc_font.height;
|
|
par->var.vmode |= FB_VMODE_YWRAP;
|
|
par->bitops->update_start(info);
|
|
scrollback_max += count;
|
|
if (scrollback_max > scrollback_phys_max)
|
|
scrollback_max = scrollback_phys_max;
|
|
scrollback_current = 0;
|
|
}
|
|
|
|
static __inline__ void ywrap_down(struct vc_data *vc, int count)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
struct fbcon_display *p = &fb_display[vc->vc_num];
|
|
|
|
p->yscroll -= count;
|
|
if (p->yscroll < 0) /* Deal with wrap */
|
|
p->yscroll += p->vrows;
|
|
par->var.xoffset = 0;
|
|
par->var.yoffset = p->yscroll * vc->vc_font.height;
|
|
par->var.vmode |= FB_VMODE_YWRAP;
|
|
par->bitops->update_start(info);
|
|
scrollback_max -= count;
|
|
if (scrollback_max < 0)
|
|
scrollback_max = 0;
|
|
scrollback_current = 0;
|
|
}
|
|
|
|
static __inline__ void ypan_up(struct vc_data *vc, int count)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_display *p = &fb_display[vc->vc_num];
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
p->yscroll += count;
|
|
if (p->yscroll > p->vrows - vc->vc_rows) {
|
|
par->bitops->bmove(vc, info, p->vrows - vc->vc_rows,
|
|
0, 0, 0, vc->vc_rows, vc->vc_cols);
|
|
p->yscroll -= p->vrows - vc->vc_rows;
|
|
}
|
|
|
|
par->var.xoffset = 0;
|
|
par->var.yoffset = p->yscroll * vc->vc_font.height;
|
|
par->var.vmode &= ~FB_VMODE_YWRAP;
|
|
par->bitops->update_start(info);
|
|
fbcon_clear_margins(vc, 1);
|
|
scrollback_max += count;
|
|
if (scrollback_max > scrollback_phys_max)
|
|
scrollback_max = scrollback_phys_max;
|
|
scrollback_current = 0;
|
|
}
|
|
|
|
static __inline__ void ypan_up_redraw(struct vc_data *vc, int t, int count)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
struct fbcon_display *p = &fb_display[vc->vc_num];
|
|
|
|
p->yscroll += count;
|
|
|
|
if (p->yscroll > p->vrows - vc->vc_rows) {
|
|
p->yscroll -= p->vrows - vc->vc_rows;
|
|
fbcon_redraw_move(vc, p, t + count, vc->vc_rows - count, t);
|
|
}
|
|
|
|
par->var.xoffset = 0;
|
|
par->var.yoffset = p->yscroll * vc->vc_font.height;
|
|
par->var.vmode &= ~FB_VMODE_YWRAP;
|
|
par->bitops->update_start(info);
|
|
fbcon_clear_margins(vc, 1);
|
|
scrollback_max += count;
|
|
if (scrollback_max > scrollback_phys_max)
|
|
scrollback_max = scrollback_phys_max;
|
|
scrollback_current = 0;
|
|
}
|
|
|
|
static __inline__ void ypan_down(struct vc_data *vc, int count)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_display *p = &fb_display[vc->vc_num];
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
p->yscroll -= count;
|
|
if (p->yscroll < 0) {
|
|
par->bitops->bmove(vc, info, 0, 0, p->vrows - vc->vc_rows,
|
|
0, vc->vc_rows, vc->vc_cols);
|
|
p->yscroll += p->vrows - vc->vc_rows;
|
|
}
|
|
|
|
par->var.xoffset = 0;
|
|
par->var.yoffset = p->yscroll * vc->vc_font.height;
|
|
par->var.vmode &= ~FB_VMODE_YWRAP;
|
|
par->bitops->update_start(info);
|
|
fbcon_clear_margins(vc, 1);
|
|
scrollback_max -= count;
|
|
if (scrollback_max < 0)
|
|
scrollback_max = 0;
|
|
scrollback_current = 0;
|
|
}
|
|
|
|
static __inline__ void ypan_down_redraw(struct vc_data *vc, int t, int count)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
struct fbcon_display *p = &fb_display[vc->vc_num];
|
|
|
|
p->yscroll -= count;
|
|
|
|
if (p->yscroll < 0) {
|
|
p->yscroll += p->vrows - vc->vc_rows;
|
|
fbcon_redraw_move(vc, p, t, vc->vc_rows - count, t + count);
|
|
}
|
|
|
|
par->var.xoffset = 0;
|
|
par->var.yoffset = p->yscroll * vc->vc_font.height;
|
|
par->var.vmode &= ~FB_VMODE_YWRAP;
|
|
par->bitops->update_start(info);
|
|
fbcon_clear_margins(vc, 1);
|
|
scrollback_max -= count;
|
|
if (scrollback_max < 0)
|
|
scrollback_max = 0;
|
|
scrollback_current = 0;
|
|
}
|
|
|
|
static void fbcon_redraw_move(struct vc_data *vc, struct fbcon_display *p,
|
|
int line, int count, int dy)
|
|
{
|
|
unsigned short *s = (unsigned short *)
|
|
(vc->vc_origin + vc->vc_size_row * line);
|
|
|
|
while (count--) {
|
|
unsigned short *start = s;
|
|
unsigned short *le = advance_row(s, 1);
|
|
unsigned short c;
|
|
int x = 0;
|
|
unsigned short attr = 1;
|
|
|
|
do {
|
|
c = scr_readw(s);
|
|
if (attr != (c & 0xff00)) {
|
|
attr = c & 0xff00;
|
|
if (s > start) {
|
|
fbcon_putcs(vc, start, s - start,
|
|
dy, x);
|
|
x += s - start;
|
|
start = s;
|
|
}
|
|
}
|
|
console_conditional_schedule();
|
|
s++;
|
|
} while (s < le);
|
|
if (s > start)
|
|
fbcon_putcs(vc, start, s - start, dy, x);
|
|
console_conditional_schedule();
|
|
dy++;
|
|
}
|
|
}
|
|
|
|
static void fbcon_redraw_blit(struct vc_data *vc, struct fb_info *info,
|
|
struct fbcon_display *p, int line, int count, int ycount)
|
|
{
|
|
int offset = ycount * vc->vc_cols;
|
|
unsigned short *d = (unsigned short *)
|
|
(vc->vc_origin + vc->vc_size_row * line);
|
|
unsigned short *s = d + offset;
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
while (count--) {
|
|
unsigned short *start = s;
|
|
unsigned short *le = advance_row(s, 1);
|
|
unsigned short c;
|
|
int x = 0;
|
|
|
|
do {
|
|
c = scr_readw(s);
|
|
|
|
if (c == scr_readw(d)) {
|
|
if (s > start) {
|
|
par->bitops->bmove(vc, info, line + ycount, x,
|
|
line, x, 1, s - start);
|
|
x += s - start + 1;
|
|
start = s + 1;
|
|
} else {
|
|
x++;
|
|
start++;
|
|
}
|
|
}
|
|
|
|
scr_writew(c, d);
|
|
console_conditional_schedule();
|
|
s++;
|
|
d++;
|
|
} while (s < le);
|
|
if (s > start)
|
|
par->bitops->bmove(vc, info, line + ycount, x, line, x, 1,
|
|
s - start);
|
|
console_conditional_schedule();
|
|
if (ycount > 0)
|
|
line++;
|
|
else {
|
|
line--;
|
|
/* NOTE: We subtract two lines from these pointers */
|
|
s -= vc->vc_size_row;
|
|
d -= vc->vc_size_row;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void fbcon_redraw(struct vc_data *vc, int line, int count, int offset)
|
|
{
|
|
unsigned short *d = (unsigned short *)
|
|
(vc->vc_origin + vc->vc_size_row * line);
|
|
unsigned short *s = d + offset;
|
|
|
|
while (count--) {
|
|
unsigned short *start = s;
|
|
unsigned short *le = advance_row(s, 1);
|
|
unsigned short c;
|
|
int x = 0;
|
|
unsigned short attr = 1;
|
|
|
|
do {
|
|
c = scr_readw(s);
|
|
if (attr != (c & 0xff00)) {
|
|
attr = c & 0xff00;
|
|
if (s > start) {
|
|
fbcon_putcs(vc, start, s - start,
|
|
line, x);
|
|
x += s - start;
|
|
start = s;
|
|
}
|
|
}
|
|
if (c == scr_readw(d)) {
|
|
if (s > start) {
|
|
fbcon_putcs(vc, start, s - start,
|
|
line, x);
|
|
x += s - start + 1;
|
|
start = s + 1;
|
|
} else {
|
|
x++;
|
|
start++;
|
|
}
|
|
}
|
|
scr_writew(c, d);
|
|
console_conditional_schedule();
|
|
s++;
|
|
d++;
|
|
} while (s < le);
|
|
if (s > start)
|
|
fbcon_putcs(vc, start, s - start, line, x);
|
|
console_conditional_schedule();
|
|
if (offset > 0)
|
|
line++;
|
|
else {
|
|
line--;
|
|
/* NOTE: We subtract two lines from these pointers */
|
|
s -= vc->vc_size_row;
|
|
d -= vc->vc_size_row;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void fbcon_bmove_rec(struct vc_data *vc, struct fbcon_display *p, int sy, int sx,
|
|
int dy, int dx, int height, int width, u_int y_break)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
u_int b;
|
|
|
|
if (sy < y_break && sy + height > y_break) {
|
|
b = y_break - sy;
|
|
if (dy < sy) { /* Avoid trashing self */
|
|
fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width,
|
|
y_break);
|
|
fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx,
|
|
height - b, width, y_break);
|
|
} else {
|
|
fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx,
|
|
height - b, width, y_break);
|
|
fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width,
|
|
y_break);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (dy < y_break && dy + height > y_break) {
|
|
b = y_break - dy;
|
|
if (dy < sy) { /* Avoid trashing self */
|
|
fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width,
|
|
y_break);
|
|
fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx,
|
|
height - b, width, y_break);
|
|
} else {
|
|
fbcon_bmove_rec(vc, p, sy + b, sx, dy + b, dx,
|
|
height - b, width, y_break);
|
|
fbcon_bmove_rec(vc, p, sy, sx, dy, dx, b, width,
|
|
y_break);
|
|
}
|
|
return;
|
|
}
|
|
par->bitops->bmove(vc, info, real_y(p, sy), sx, real_y(p, dy), dx,
|
|
height, width);
|
|
}
|
|
|
|
static void fbcon_bmove(struct vc_data *vc, int sy, int sx, int dy, int dx,
|
|
int height, int width)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_display *p = &fb_display[vc->vc_num];
|
|
|
|
if (!fbcon_is_active(vc, info))
|
|
return;
|
|
|
|
if (!width || !height)
|
|
return;
|
|
|
|
/* Split blits that cross physical y_wrap case.
|
|
* Pathological case involves 4 blits, better to use recursive
|
|
* code rather than unrolled case
|
|
*
|
|
* Recursive invocations don't need to erase the cursor over and
|
|
* over again, so we use fbcon_bmove_rec()
|
|
*/
|
|
fbcon_bmove_rec(vc, p, sy, sx, dy, dx, height, width,
|
|
p->vrows - p->yscroll);
|
|
}
|
|
|
|
static bool fbcon_scroll(struct vc_data *vc, unsigned int t, unsigned int b,
|
|
enum con_scroll dir, unsigned int count)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_display *p = &fb_display[vc->vc_num];
|
|
int scroll_partial = info->flags & FBINFO_PARTIAL_PAN_OK;
|
|
|
|
if (!fbcon_is_active(vc, info))
|
|
return true;
|
|
|
|
fbcon_cursor(vc, false);
|
|
|
|
/*
|
|
* ++Geert: Only use ywrap/ypan if the console is in text mode
|
|
* ++Andrew: Only use ypan on hardware text mode when scrolling the
|
|
* whole screen (prevents flicker).
|
|
*/
|
|
|
|
switch (dir) {
|
|
case SM_UP:
|
|
if (count > vc->vc_rows) /* Maximum realistic size */
|
|
count = vc->vc_rows;
|
|
switch (fb_scrollmode(p)) {
|
|
case SCROLL_MOVE:
|
|
fbcon_redraw_blit(vc, info, p, t, b - t - count,
|
|
count);
|
|
__fbcon_clear(vc, b - count, 0, count, vc->vc_cols);
|
|
scr_memsetw((unsigned short *) (vc->vc_origin +
|
|
vc->vc_size_row *
|
|
(b - count)),
|
|
vc->vc_video_erase_char,
|
|
vc->vc_size_row * count);
|
|
return true;
|
|
|
|
case SCROLL_WRAP_MOVE:
|
|
if (b - t - count > 3 * vc->vc_rows >> 2) {
|
|
if (t > 0)
|
|
fbcon_bmove(vc, 0, 0, count, 0, t,
|
|
vc->vc_cols);
|
|
ywrap_up(vc, count);
|
|
if (vc->vc_rows - b > 0)
|
|
fbcon_bmove(vc, b - count, 0, b, 0,
|
|
vc->vc_rows - b,
|
|
vc->vc_cols);
|
|
} else if (info->flags & FBINFO_READS_FAST)
|
|
fbcon_bmove(vc, t + count, 0, t, 0,
|
|
b - t - count, vc->vc_cols);
|
|
else
|
|
goto redraw_up;
|
|
__fbcon_clear(vc, b - count, 0, count, vc->vc_cols);
|
|
break;
|
|
|
|
case SCROLL_PAN_REDRAW:
|
|
if ((p->yscroll + count <=
|
|
2 * (p->vrows - vc->vc_rows))
|
|
&& ((!scroll_partial && (b - t == vc->vc_rows))
|
|
|| (scroll_partial
|
|
&& (b - t - count >
|
|
3 * vc->vc_rows >> 2)))) {
|
|
if (t > 0)
|
|
fbcon_redraw_move(vc, p, 0, t, count);
|
|
ypan_up_redraw(vc, t, count);
|
|
if (vc->vc_rows - b > 0)
|
|
fbcon_redraw_move(vc, p, b,
|
|
vc->vc_rows - b, b);
|
|
} else
|
|
fbcon_redraw_move(vc, p, t + count, b - t - count, t);
|
|
__fbcon_clear(vc, b - count, 0, count, vc->vc_cols);
|
|
break;
|
|
|
|
case SCROLL_PAN_MOVE:
|
|
if ((p->yscroll + count <=
|
|
2 * (p->vrows - vc->vc_rows))
|
|
&& ((!scroll_partial && (b - t == vc->vc_rows))
|
|
|| (scroll_partial
|
|
&& (b - t - count >
|
|
3 * vc->vc_rows >> 2)))) {
|
|
if (t > 0)
|
|
fbcon_bmove(vc, 0, 0, count, 0, t,
|
|
vc->vc_cols);
|
|
ypan_up(vc, count);
|
|
if (vc->vc_rows - b > 0)
|
|
fbcon_bmove(vc, b - count, 0, b, 0,
|
|
vc->vc_rows - b,
|
|
vc->vc_cols);
|
|
} else if (info->flags & FBINFO_READS_FAST)
|
|
fbcon_bmove(vc, t + count, 0, t, 0,
|
|
b - t - count, vc->vc_cols);
|
|
else
|
|
goto redraw_up;
|
|
__fbcon_clear(vc, b - count, 0, count, vc->vc_cols);
|
|
break;
|
|
|
|
case SCROLL_REDRAW:
|
|
redraw_up:
|
|
fbcon_redraw(vc, t, b - t - count,
|
|
count * vc->vc_cols);
|
|
__fbcon_clear(vc, b - count, 0, count, vc->vc_cols);
|
|
scr_memsetw((unsigned short *) (vc->vc_origin +
|
|
vc->vc_size_row *
|
|
(b - count)),
|
|
vc->vc_video_erase_char,
|
|
vc->vc_size_row * count);
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case SM_DOWN:
|
|
if (count > vc->vc_rows) /* Maximum realistic size */
|
|
count = vc->vc_rows;
|
|
switch (fb_scrollmode(p)) {
|
|
case SCROLL_MOVE:
|
|
fbcon_redraw_blit(vc, info, p, b - 1, b - t - count,
|
|
-count);
|
|
__fbcon_clear(vc, t, 0, count, vc->vc_cols);
|
|
scr_memsetw((unsigned short *) (vc->vc_origin +
|
|
vc->vc_size_row *
|
|
t),
|
|
vc->vc_video_erase_char,
|
|
vc->vc_size_row * count);
|
|
return true;
|
|
|
|
case SCROLL_WRAP_MOVE:
|
|
if (b - t - count > 3 * vc->vc_rows >> 2) {
|
|
if (vc->vc_rows - b > 0)
|
|
fbcon_bmove(vc, b, 0, b - count, 0,
|
|
vc->vc_rows - b,
|
|
vc->vc_cols);
|
|
ywrap_down(vc, count);
|
|
if (t > 0)
|
|
fbcon_bmove(vc, count, 0, 0, 0, t,
|
|
vc->vc_cols);
|
|
} else if (info->flags & FBINFO_READS_FAST)
|
|
fbcon_bmove(vc, t, 0, t + count, 0,
|
|
b - t - count, vc->vc_cols);
|
|
else
|
|
goto redraw_down;
|
|
__fbcon_clear(vc, t, 0, count, vc->vc_cols);
|
|
break;
|
|
|
|
case SCROLL_PAN_MOVE:
|
|
if ((count - p->yscroll <= p->vrows - vc->vc_rows)
|
|
&& ((!scroll_partial && (b - t == vc->vc_rows))
|
|
|| (scroll_partial
|
|
&& (b - t - count >
|
|
3 * vc->vc_rows >> 2)))) {
|
|
if (vc->vc_rows - b > 0)
|
|
fbcon_bmove(vc, b, 0, b - count, 0,
|
|
vc->vc_rows - b,
|
|
vc->vc_cols);
|
|
ypan_down(vc, count);
|
|
if (t > 0)
|
|
fbcon_bmove(vc, count, 0, 0, 0, t,
|
|
vc->vc_cols);
|
|
} else if (info->flags & FBINFO_READS_FAST)
|
|
fbcon_bmove(vc, t, 0, t + count, 0,
|
|
b - t - count, vc->vc_cols);
|
|
else
|
|
goto redraw_down;
|
|
__fbcon_clear(vc, t, 0, count, vc->vc_cols);
|
|
break;
|
|
|
|
case SCROLL_PAN_REDRAW:
|
|
if ((count - p->yscroll <= p->vrows - vc->vc_rows)
|
|
&& ((!scroll_partial && (b - t == vc->vc_rows))
|
|
|| (scroll_partial
|
|
&& (b - t - count >
|
|
3 * vc->vc_rows >> 2)))) {
|
|
if (vc->vc_rows - b > 0)
|
|
fbcon_redraw_move(vc, p, b, vc->vc_rows - b,
|
|
b - count);
|
|
ypan_down_redraw(vc, t, count);
|
|
if (t > 0)
|
|
fbcon_redraw_move(vc, p, count, t, 0);
|
|
} else
|
|
fbcon_redraw_move(vc, p, t, b - t - count, t + count);
|
|
__fbcon_clear(vc, t, 0, count, vc->vc_cols);
|
|
break;
|
|
|
|
case SCROLL_REDRAW:
|
|
redraw_down:
|
|
fbcon_redraw(vc, b - 1, b - t - count,
|
|
-count * vc->vc_cols);
|
|
__fbcon_clear(vc, t, 0, count, vc->vc_cols);
|
|
scr_memsetw((unsigned short *) (vc->vc_origin +
|
|
vc->vc_size_row *
|
|
t),
|
|
vc->vc_video_erase_char,
|
|
vc->vc_size_row * count);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
static void updatescrollmode_accel(struct fbcon_display *p,
|
|
struct fb_info *info,
|
|
struct vc_data *vc)
|
|
{
|
|
#ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
int cap = info->flags;
|
|
u16 t = 0;
|
|
int ypan = FBCON_SWAP(par->rotate, info->fix.ypanstep, info->fix.xpanstep);
|
|
int ywrap = FBCON_SWAP(par->rotate, info->fix.ywrapstep, t);
|
|
int yres = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
|
|
int vyres = FBCON_SWAP(par->rotate, info->var.yres_virtual, info->var.xres_virtual);
|
|
int good_pan = (cap & FBINFO_HWACCEL_YPAN) &&
|
|
divides(ypan, vc->vc_font.height) && vyres > yres;
|
|
int good_wrap = (cap & FBINFO_HWACCEL_YWRAP) &&
|
|
divides(ywrap, vc->vc_font.height) &&
|
|
divides(vc->vc_font.height, vyres) &&
|
|
divides(vc->vc_font.height, yres);
|
|
int reading_fast = cap & FBINFO_READS_FAST;
|
|
int fast_copyarea = (cap & FBINFO_HWACCEL_COPYAREA) &&
|
|
!(cap & FBINFO_HWACCEL_DISABLED);
|
|
int fast_imageblit = (cap & FBINFO_HWACCEL_IMAGEBLIT) &&
|
|
!(cap & FBINFO_HWACCEL_DISABLED);
|
|
|
|
if (good_wrap || good_pan) {
|
|
if (reading_fast || fast_copyarea)
|
|
p->scrollmode = good_wrap ?
|
|
SCROLL_WRAP_MOVE : SCROLL_PAN_MOVE;
|
|
else
|
|
p->scrollmode = good_wrap ? SCROLL_REDRAW :
|
|
SCROLL_PAN_REDRAW;
|
|
} else {
|
|
if (reading_fast || (fast_copyarea && !fast_imageblit))
|
|
p->scrollmode = SCROLL_MOVE;
|
|
else
|
|
p->scrollmode = SCROLL_REDRAW;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void updatescrollmode(struct fbcon_display *p,
|
|
struct fb_info *info,
|
|
struct vc_data *vc)
|
|
{
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
int fh = vc->vc_font.height;
|
|
int yres = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
|
|
int vyres = FBCON_SWAP(par->rotate, info->var.yres_virtual, info->var.xres_virtual);
|
|
|
|
p->vrows = vyres/fh;
|
|
if (yres > (fh * (vc->vc_rows + 1)))
|
|
p->vrows -= (yres - (fh * vc->vc_rows)) / fh;
|
|
if ((yres % fh) && (vyres % fh < yres % fh))
|
|
p->vrows--;
|
|
|
|
/* update scrollmode in case hardware acceleration is used */
|
|
updatescrollmode_accel(p, info, vc);
|
|
}
|
|
|
|
#define PITCH(w) (((w) + 7) >> 3)
|
|
#define CALC_FONTSZ(h, p, c) ((h) * (p) * (c)) /* size = height * pitch * charcount */
|
|
|
|
static int fbcon_resize(struct vc_data *vc, unsigned int width,
|
|
unsigned int height, bool from_user)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
struct fbcon_display *p = &fb_display[vc->vc_num];
|
|
struct fb_var_screeninfo var = info->var;
|
|
int x_diff, y_diff, virt_w, virt_h, virt_fw, virt_fh;
|
|
|
|
if (p->userfont && FNTSIZE(vc->vc_font.data)) {
|
|
int size;
|
|
int pitch = PITCH(vc->vc_font.width);
|
|
|
|
/*
|
|
* If user font, ensure that a possible change to user font
|
|
* height or width will not allow a font data out-of-bounds access.
|
|
* NOTE: must use original charcount in calculation as font
|
|
* charcount can change and cannot be used to determine the
|
|
* font data allocated size.
|
|
*/
|
|
if (pitch <= 0)
|
|
return -EINVAL;
|
|
size = CALC_FONTSZ(vc->vc_font.height, pitch, vc->vc_font.charcount);
|
|
if (size > FNTSIZE(vc->vc_font.data))
|
|
return -EINVAL;
|
|
}
|
|
|
|
virt_w = FBCON_SWAP(par->rotate, width, height);
|
|
virt_h = FBCON_SWAP(par->rotate, height, width);
|
|
virt_fw = FBCON_SWAP(par->rotate, vc->vc_font.width, vc->vc_font.height);
|
|
virt_fh = FBCON_SWAP(par->rotate, vc->vc_font.height, vc->vc_font.width);
|
|
var.xres = virt_w * virt_fw;
|
|
var.yres = virt_h * virt_fh;
|
|
x_diff = info->var.xres - var.xres;
|
|
y_diff = info->var.yres - var.yres;
|
|
if (x_diff < 0 || x_diff > virt_fw ||
|
|
y_diff < 0 || y_diff > virt_fh) {
|
|
const struct fb_videomode *mode;
|
|
|
|
pr_debug("attempting resize %ix%i\n", var.xres, var.yres);
|
|
mode = fb_find_best_mode(&var, &info->modelist);
|
|
if (mode == NULL)
|
|
return -EINVAL;
|
|
display_to_var(&var, p);
|
|
fb_videomode_to_var(&var, mode);
|
|
|
|
if (virt_w > var.xres/virt_fw || virt_h > var.yres/virt_fh)
|
|
return -EINVAL;
|
|
|
|
pr_debug("resize now %ix%i\n", var.xres, var.yres);
|
|
if (con_is_visible(vc) && vc->vc_mode == KD_TEXT) {
|
|
var.activate = FB_ACTIVATE_NOW |
|
|
FB_ACTIVATE_FORCE;
|
|
fb_set_var(info, &var);
|
|
}
|
|
var_to_display(p, &info->var, info);
|
|
par->var = info->var;
|
|
}
|
|
updatescrollmode(p, info, vc);
|
|
return 0;
|
|
}
|
|
|
|
static bool fbcon_switch(struct vc_data *vc)
|
|
{
|
|
struct fb_info *info, *old_info = NULL;
|
|
struct fbcon_par *par;
|
|
struct fbcon_display *p = &fb_display[vc->vc_num];
|
|
struct fb_var_screeninfo var;
|
|
int i, ret, prev_console;
|
|
|
|
info = fbcon_info_from_console(vc->vc_num);
|
|
par = info->fbcon_par;
|
|
|
|
if (logo_shown >= 0) {
|
|
struct vc_data *conp2 = vc_cons[logo_shown].d;
|
|
|
|
if (conp2->vc_top == logo_lines
|
|
&& conp2->vc_bottom == conp2->vc_rows)
|
|
conp2->vc_top = 0;
|
|
logo_shown = FBCON_LOGO_CANSHOW;
|
|
}
|
|
|
|
prev_console = par->currcon;
|
|
if (prev_console != -1)
|
|
old_info = fbcon_info_from_console(prev_console);
|
|
/*
|
|
* FIXME: If we have multiple fbdev's loaded, we need to
|
|
* update all info->currcon. Perhaps, we can place this
|
|
* in a centralized structure, but this might break some
|
|
* drivers.
|
|
*
|
|
* info->currcon = vc->vc_num;
|
|
*/
|
|
fbcon_for_each_registered_fb(i) {
|
|
if (fbcon_registered_fb[i]->fbcon_par) {
|
|
struct fbcon_par *par = fbcon_registered_fb[i]->fbcon_par;
|
|
|
|
par->currcon = vc->vc_num;
|
|
}
|
|
}
|
|
memset(&var, 0, sizeof(struct fb_var_screeninfo));
|
|
display_to_var(&var, p);
|
|
var.activate = FB_ACTIVATE_NOW;
|
|
|
|
/*
|
|
* make sure we don't unnecessarily trip the memcmp()
|
|
* in fb_set_var()
|
|
*/
|
|
info->var.activate = var.activate;
|
|
var.vmode |= info->var.vmode & ~FB_VMODE_MASK;
|
|
fb_set_var(info, &var);
|
|
par->var = info->var;
|
|
|
|
if (old_info != NULL && (old_info != info ||
|
|
info->flags & FBINFO_MISC_ALWAYS_SETPAR)) {
|
|
if (info->fbops->fb_set_par) {
|
|
ret = info->fbops->fb_set_par(info);
|
|
|
|
if (ret)
|
|
printk(KERN_ERR "fbcon_switch: detected "
|
|
"unhandled fb_set_par error, "
|
|
"error code %d\n", ret);
|
|
}
|
|
|
|
if (old_info != info)
|
|
fbcon_del_cursor_work(old_info);
|
|
}
|
|
|
|
if (!fbcon_is_active(vc, info) || par->blank_state != FB_BLANK_UNBLANK)
|
|
fbcon_del_cursor_work(info);
|
|
else
|
|
fbcon_add_cursor_work(info);
|
|
|
|
set_blitting_type(vc, info);
|
|
par->cursor_reset = 1;
|
|
|
|
if (par->bitops->rotate_font && par->bitops->rotate_font(info, vc)) {
|
|
par->rotate = FB_ROTATE_UR;
|
|
set_blitting_type(vc, info);
|
|
}
|
|
|
|
vc->vc_can_do_color = (fb_get_color_depth(&info->var, &info->fix)!=1);
|
|
vc->vc_complement_mask = vc->vc_can_do_color ? 0x7700 : 0x0800;
|
|
|
|
if (vc->vc_font.charcount > 256)
|
|
vc->vc_complement_mask <<= 1;
|
|
|
|
updatescrollmode(p, info, vc);
|
|
|
|
switch (fb_scrollmode(p)) {
|
|
case SCROLL_WRAP_MOVE:
|
|
scrollback_phys_max = p->vrows - vc->vc_rows;
|
|
break;
|
|
case SCROLL_PAN_MOVE:
|
|
case SCROLL_PAN_REDRAW:
|
|
scrollback_phys_max = p->vrows - 2 * vc->vc_rows;
|
|
if (scrollback_phys_max < 0)
|
|
scrollback_phys_max = 0;
|
|
break;
|
|
default:
|
|
scrollback_phys_max = 0;
|
|
break;
|
|
}
|
|
|
|
scrollback_max = 0;
|
|
scrollback_current = 0;
|
|
|
|
if (fbcon_is_active(vc, info)) {
|
|
par->var.xoffset = par->var.yoffset = p->yscroll = 0;
|
|
par->bitops->update_start(info);
|
|
}
|
|
|
|
fbcon_set_palette(vc, color_table);
|
|
fbcon_clear_margins(vc, 0);
|
|
|
|
if (logo_shown == FBCON_LOGO_DRAW) {
|
|
|
|
logo_shown = fg_console;
|
|
fb_show_logo(info, par->rotate);
|
|
update_region(vc,
|
|
vc->vc_origin + vc->vc_size_row * vc->vc_top,
|
|
vc->vc_size_row * (vc->vc_bottom -
|
|
vc->vc_top) / 2);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void fbcon_generic_blank(struct vc_data *vc, struct fb_info *info,
|
|
int blank)
|
|
{
|
|
if (blank) {
|
|
unsigned short charmask = vc->vc_hi_font_mask ?
|
|
0x1ff : 0xff;
|
|
unsigned short oldc;
|
|
|
|
oldc = vc->vc_video_erase_char;
|
|
vc->vc_video_erase_char &= charmask;
|
|
__fbcon_clear(vc, 0, 0, vc->vc_rows, vc->vc_cols);
|
|
vc->vc_video_erase_char = oldc;
|
|
}
|
|
}
|
|
|
|
static bool fbcon_blank(struct vc_data *vc, enum vesa_blank_mode blank,
|
|
bool mode_switch)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
if (mode_switch) {
|
|
struct fb_var_screeninfo var = info->var;
|
|
|
|
par->graphics = 1;
|
|
|
|
if (!blank) {
|
|
var.activate = FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE |
|
|
FB_ACTIVATE_KD_TEXT;
|
|
fb_set_var(info, &var);
|
|
par->graphics = 0;
|
|
par->var = info->var;
|
|
}
|
|
}
|
|
|
|
if (fbcon_is_active(vc, info)) {
|
|
if (par->blank_state != blank) {
|
|
par->blank_state = blank;
|
|
fbcon_cursor(vc, !blank);
|
|
par->cursor_flash = (!blank);
|
|
|
|
if (fb_blank(info, blank))
|
|
fbcon_generic_blank(vc, info, blank);
|
|
}
|
|
|
|
if (!blank)
|
|
update_screen(vc);
|
|
}
|
|
|
|
if (mode_switch || !fbcon_is_active(vc, info) || par->blank_state != FB_BLANK_UNBLANK)
|
|
fbcon_del_cursor_work(info);
|
|
else
|
|
fbcon_add_cursor_work(info);
|
|
|
|
return false;
|
|
}
|
|
|
|
static void fbcon_debug_enter(struct vc_data *vc)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
par->save_graphics = par->graphics;
|
|
par->graphics = 0;
|
|
if (info->fbops->fb_debug_enter)
|
|
info->fbops->fb_debug_enter(info);
|
|
fbcon_set_palette(vc, color_table);
|
|
}
|
|
|
|
static void fbcon_debug_leave(struct vc_data *vc)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
par->graphics = par->save_graphics;
|
|
if (info->fbops->fb_debug_leave)
|
|
info->fbops->fb_debug_leave(info);
|
|
}
|
|
|
|
static int fbcon_get_font(struct vc_data *vc, struct console_font *font, unsigned int vpitch)
|
|
{
|
|
u8 *fontdata = vc->vc_font.data;
|
|
u8 *data = font->data;
|
|
int i, j;
|
|
|
|
font->width = vc->vc_font.width;
|
|
font->height = vc->vc_font.height;
|
|
if (font->height > vpitch)
|
|
return -ENOSPC;
|
|
font->charcount = vc->vc_hi_font_mask ? 512 : 256;
|
|
if (!font->data)
|
|
return 0;
|
|
|
|
if (font->width <= 8) {
|
|
j = vc->vc_font.height;
|
|
if (font->charcount * j > FNTSIZE(fontdata))
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < font->charcount; i++) {
|
|
memcpy(data, fontdata, j);
|
|
memset(data + j, 0, vpitch - j);
|
|
data += vpitch;
|
|
fontdata += j;
|
|
}
|
|
} else if (font->width <= 16) {
|
|
j = vc->vc_font.height * 2;
|
|
if (font->charcount * j > FNTSIZE(fontdata))
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < font->charcount; i++) {
|
|
memcpy(data, fontdata, j);
|
|
memset(data + j, 0, 2*vpitch - j);
|
|
data += 2*vpitch;
|
|
fontdata += j;
|
|
}
|
|
} else if (font->width <= 24) {
|
|
if (font->charcount * (vc->vc_font.height * sizeof(u32)) > FNTSIZE(fontdata))
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < font->charcount; i++) {
|
|
for (j = 0; j < vc->vc_font.height; j++) {
|
|
*data++ = fontdata[0];
|
|
*data++ = fontdata[1];
|
|
*data++ = fontdata[2];
|
|
fontdata += sizeof(u32);
|
|
}
|
|
memset(data, 0, 3 * (vpitch - j));
|
|
data += 3 * (vpitch - j);
|
|
}
|
|
} else {
|
|
j = vc->vc_font.height * 4;
|
|
if (font->charcount * j > FNTSIZE(fontdata))
|
|
return -EINVAL;
|
|
|
|
for (i = 0; i < font->charcount; i++) {
|
|
memcpy(data, fontdata, j);
|
|
memset(data + j, 0, 4 * vpitch - j);
|
|
data += 4 * vpitch;
|
|
fontdata += j;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* set/clear vc_hi_font_mask and update vc attrs accordingly */
|
|
static void set_vc_hi_font(struct vc_data *vc, bool set)
|
|
{
|
|
if (!set) {
|
|
vc->vc_hi_font_mask = 0;
|
|
if (vc->vc_can_do_color) {
|
|
vc->vc_complement_mask >>= 1;
|
|
vc->vc_s_complement_mask >>= 1;
|
|
}
|
|
|
|
/* ++Edmund: reorder the attribute bits */
|
|
if (vc->vc_can_do_color) {
|
|
unsigned short *cp =
|
|
(unsigned short *) vc->vc_origin;
|
|
int count = vc->vc_screenbuf_size / 2;
|
|
unsigned short c;
|
|
for (; count > 0; count--, cp++) {
|
|
c = scr_readw(cp);
|
|
scr_writew(((c & 0xfe00) >> 1) |
|
|
(c & 0xff), cp);
|
|
}
|
|
c = vc->vc_video_erase_char;
|
|
vc->vc_video_erase_char =
|
|
((c & 0xfe00) >> 1) | (c & 0xff);
|
|
vc->vc_attr >>= 1;
|
|
}
|
|
} else {
|
|
vc->vc_hi_font_mask = 0x100;
|
|
if (vc->vc_can_do_color) {
|
|
vc->vc_complement_mask <<= 1;
|
|
vc->vc_s_complement_mask <<= 1;
|
|
}
|
|
|
|
/* ++Edmund: reorder the attribute bits */
|
|
{
|
|
unsigned short *cp =
|
|
(unsigned short *) vc->vc_origin;
|
|
int count = vc->vc_screenbuf_size / 2;
|
|
unsigned short c;
|
|
for (; count > 0; count--, cp++) {
|
|
unsigned short newc;
|
|
c = scr_readw(cp);
|
|
if (vc->vc_can_do_color)
|
|
newc =
|
|
((c & 0xff00) << 1) | (c &
|
|
0xff);
|
|
else
|
|
newc = c & ~0x100;
|
|
scr_writew(newc, cp);
|
|
}
|
|
c = vc->vc_video_erase_char;
|
|
if (vc->vc_can_do_color) {
|
|
vc->vc_video_erase_char =
|
|
((c & 0xff00) << 1) | (c & 0xff);
|
|
vc->vc_attr <<= 1;
|
|
} else
|
|
vc->vc_video_erase_char = c & ~0x100;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int fbcon_do_set_font(struct vc_data *vc, int w, int h, int charcount,
|
|
const u8 * data, int userfont)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
struct fbcon_display *p = &fb_display[vc->vc_num];
|
|
int resize, ret, old_userfont, old_width, old_height, old_charcount;
|
|
u8 *old_data = vc->vc_font.data;
|
|
|
|
resize = (w != vc->vc_font.width) || (h != vc->vc_font.height);
|
|
vc->vc_font.data = (void *)(p->fontdata = data);
|
|
old_userfont = p->userfont;
|
|
if ((p->userfont = userfont))
|
|
REFCOUNT(data)++;
|
|
|
|
old_width = vc->vc_font.width;
|
|
old_height = vc->vc_font.height;
|
|
old_charcount = vc->vc_font.charcount;
|
|
|
|
vc->vc_font.width = w;
|
|
vc->vc_font.height = h;
|
|
vc->vc_font.charcount = charcount;
|
|
if (vc->vc_hi_font_mask && charcount == 256)
|
|
set_vc_hi_font(vc, false);
|
|
else if (!vc->vc_hi_font_mask && charcount == 512)
|
|
set_vc_hi_font(vc, true);
|
|
|
|
if (resize) {
|
|
int cols, rows;
|
|
|
|
cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
|
|
rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
|
|
cols /= w;
|
|
rows /= h;
|
|
ret = vc_resize(vc, cols, rows);
|
|
if (ret)
|
|
goto err_out;
|
|
} else if (con_is_visible(vc)
|
|
&& vc->vc_mode == KD_TEXT) {
|
|
fbcon_clear_margins(vc, 0);
|
|
update_screen(vc);
|
|
}
|
|
|
|
if (old_userfont && (--REFCOUNT(old_data) == 0))
|
|
kfree(old_data - FONT_EXTRA_WORDS * sizeof(int));
|
|
return 0;
|
|
|
|
err_out:
|
|
p->fontdata = old_data;
|
|
vc->vc_font.data = old_data;
|
|
|
|
if (userfont) {
|
|
p->userfont = old_userfont;
|
|
if (--REFCOUNT(data) == 0)
|
|
kfree(data - FONT_EXTRA_WORDS * sizeof(int));
|
|
}
|
|
|
|
vc->vc_font.width = old_width;
|
|
vc->vc_font.height = old_height;
|
|
vc->vc_font.charcount = old_charcount;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* User asked to set font; we are guaranteed that charcount does not exceed 512
|
|
* but lets not assume that, since charcount of 512 is small for unicode support.
|
|
*/
|
|
|
|
static int fbcon_set_font(struct vc_data *vc, const struct console_font *font,
|
|
unsigned int vpitch, unsigned int flags)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
unsigned charcount = font->charcount;
|
|
int w = font->width;
|
|
int h = font->height;
|
|
int size, alloc_size;
|
|
int i, csum;
|
|
u8 *new_data, *data = font->data;
|
|
int pitch = PITCH(font->width);
|
|
|
|
/* Is there a reason why fbconsole couldn't handle any charcount >256?
|
|
* If not this check should be changed to charcount < 256 */
|
|
if (charcount != 256 && charcount != 512)
|
|
return -EINVAL;
|
|
|
|
/* font bigger than screen resolution ? */
|
|
if (w > FBCON_SWAP(info->var.rotate, info->var.xres, info->var.yres) ||
|
|
h > FBCON_SWAP(info->var.rotate, info->var.yres, info->var.xres))
|
|
return -EINVAL;
|
|
|
|
if (font->width > FB_MAX_BLIT_WIDTH || font->height > FB_MAX_BLIT_HEIGHT)
|
|
return -EINVAL;
|
|
|
|
/* Make sure drawing engine can handle the font */
|
|
if (!test_bit(font->width - 1, info->pixmap.blit_x) ||
|
|
!test_bit(font->height - 1, info->pixmap.blit_y))
|
|
return -EINVAL;
|
|
|
|
/* Make sure driver can handle the font length */
|
|
if (fbcon_invalid_charcount(info, charcount))
|
|
return -EINVAL;
|
|
|
|
/* Check for integer overflow in font size calculation */
|
|
if (check_mul_overflow(h, pitch, &size) ||
|
|
check_mul_overflow(size, charcount, &size))
|
|
return -EINVAL;
|
|
|
|
/* Check for overflow in allocation size calculation */
|
|
if (check_add_overflow(FONT_EXTRA_WORDS * sizeof(int), size, &alloc_size))
|
|
return -EINVAL;
|
|
|
|
new_data = kmalloc(alloc_size, GFP_USER);
|
|
|
|
if (!new_data)
|
|
return -ENOMEM;
|
|
|
|
memset(new_data, 0, FONT_EXTRA_WORDS * sizeof(int));
|
|
|
|
new_data += FONT_EXTRA_WORDS * sizeof(int);
|
|
FNTSIZE(new_data) = size;
|
|
REFCOUNT(new_data) = 0; /* usage counter */
|
|
for (i=0; i< charcount; i++) {
|
|
memcpy(new_data + i*h*pitch, data + i*vpitch*pitch, h*pitch);
|
|
}
|
|
|
|
/* Since linux has a nice crc32 function use it for counting font
|
|
* checksums. */
|
|
csum = crc32(0, new_data, size);
|
|
|
|
FNTSUM(new_data) = csum;
|
|
/* Check if the same font is on some other console already */
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
struct vc_data *tmp = vc_cons[i].d;
|
|
|
|
if (fb_display[i].userfont &&
|
|
fb_display[i].fontdata &&
|
|
FNTSUM(fb_display[i].fontdata) == csum &&
|
|
FNTSIZE(fb_display[i].fontdata) == size &&
|
|
tmp->vc_font.width == w &&
|
|
!memcmp(fb_display[i].fontdata, new_data, size)) {
|
|
kfree(new_data - FONT_EXTRA_WORDS * sizeof(int));
|
|
new_data = (u8 *)fb_display[i].fontdata;
|
|
break;
|
|
}
|
|
}
|
|
return fbcon_do_set_font(vc, font->width, font->height, charcount, new_data, 1);
|
|
}
|
|
|
|
static int fbcon_set_def_font(struct vc_data *vc, struct console_font *font,
|
|
const char *name)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
const struct font_desc *f;
|
|
|
|
if (!name)
|
|
f = get_default_font(info->var.xres, info->var.yres,
|
|
info->pixmap.blit_x, info->pixmap.blit_y);
|
|
else if (!(f = find_font(name)))
|
|
return -ENOENT;
|
|
|
|
font->width = f->width;
|
|
font->height = f->height;
|
|
return fbcon_do_set_font(vc, f->width, f->height, f->charcount, f->data, 0);
|
|
}
|
|
|
|
static u16 palette_red[16];
|
|
static u16 palette_green[16];
|
|
static u16 palette_blue[16];
|
|
|
|
static struct fb_cmap palette_cmap = {
|
|
0, 16, palette_red, palette_green, palette_blue, NULL
|
|
};
|
|
|
|
static void fbcon_set_palette(struct vc_data *vc, const unsigned char *table)
|
|
{
|
|
struct fb_info *info = fbcon_info_from_console(vc->vc_num);
|
|
int i, j, k, depth;
|
|
u8 val;
|
|
|
|
if (!fbcon_is_active(vc, info))
|
|
return;
|
|
|
|
if (!con_is_visible(vc))
|
|
return;
|
|
|
|
depth = fb_get_color_depth(&info->var, &info->fix);
|
|
if (depth > 3) {
|
|
for (i = j = 0; i < 16; i++) {
|
|
k = table[i];
|
|
val = vc->vc_palette[j++];
|
|
palette_red[k] = (val << 8) | val;
|
|
val = vc->vc_palette[j++];
|
|
palette_green[k] = (val << 8) | val;
|
|
val = vc->vc_palette[j++];
|
|
palette_blue[k] = (val << 8) | val;
|
|
}
|
|
palette_cmap.len = 16;
|
|
palette_cmap.start = 0;
|
|
/*
|
|
* If framebuffer is capable of less than 16 colors,
|
|
* use default palette of fbcon.
|
|
*/
|
|
} else
|
|
fb_copy_cmap(fb_default_cmap(1 << depth), &palette_cmap);
|
|
|
|
fb_set_cmap(&palette_cmap, info);
|
|
}
|
|
|
|
/* As we might be inside of softback, we may work with non-contiguous buffer,
|
|
that's why we have to use a separate routine. */
|
|
static void fbcon_invert_region(struct vc_data *vc, u16 * p, int cnt)
|
|
{
|
|
while (cnt--) {
|
|
u16 a = scr_readw(p);
|
|
if (!vc->vc_can_do_color)
|
|
a ^= 0x0800;
|
|
else if (vc->vc_hi_font_mask == 0x100)
|
|
a = ((a) & 0x11ff) | (((a) & 0xe000) >> 4) |
|
|
(((a) & 0x0e00) << 4);
|
|
else
|
|
a = ((a) & 0x88ff) | (((a) & 0x7000) >> 4) |
|
|
(((a) & 0x0700) << 4);
|
|
scr_writew(a, p++);
|
|
}
|
|
}
|
|
|
|
void fbcon_suspended(struct fb_info *info)
|
|
{
|
|
struct vc_data *vc = NULL;
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
if (!par || par->currcon < 0)
|
|
return;
|
|
vc = vc_cons[par->currcon].d;
|
|
|
|
/* Clear cursor, restore saved data */
|
|
fbcon_cursor(vc, false);
|
|
}
|
|
|
|
void fbcon_resumed(struct fb_info *info)
|
|
{
|
|
struct vc_data *vc;
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
|
|
if (!par || par->currcon < 0)
|
|
return;
|
|
vc = vc_cons[par->currcon].d;
|
|
|
|
update_screen(vc);
|
|
}
|
|
|
|
static void fbcon_modechanged(struct fb_info *info)
|
|
{
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
struct vc_data *vc;
|
|
struct fbcon_display *p;
|
|
int rows, cols;
|
|
|
|
if (!par || par->currcon < 0)
|
|
return;
|
|
vc = vc_cons[par->currcon].d;
|
|
if (vc->vc_mode != KD_TEXT ||
|
|
fbcon_info_from_console(par->currcon) != info)
|
|
return;
|
|
|
|
p = &fb_display[vc->vc_num];
|
|
set_blitting_type(vc, info);
|
|
|
|
if (con_is_visible(vc)) {
|
|
var_to_display(p, &info->var, info);
|
|
cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
|
|
rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
|
|
cols /= vc->vc_font.width;
|
|
rows /= vc->vc_font.height;
|
|
vc_resize(vc, cols, rows);
|
|
updatescrollmode(p, info, vc);
|
|
scrollback_max = 0;
|
|
scrollback_current = 0;
|
|
|
|
if (fbcon_is_active(vc, info)) {
|
|
par->var.xoffset = par->var.yoffset = p->yscroll = 0;
|
|
par->bitops->update_start(info);
|
|
}
|
|
|
|
fbcon_set_palette(vc, color_table);
|
|
update_screen(vc);
|
|
}
|
|
}
|
|
|
|
static void fbcon_set_all_vcs(struct fb_info *info)
|
|
{
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
struct vc_data *vc;
|
|
struct fbcon_display *p;
|
|
int i, rows, cols, fg = -1;
|
|
|
|
if (!par || par->currcon < 0)
|
|
return;
|
|
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
vc = vc_cons[i].d;
|
|
if (!vc || vc->vc_mode != KD_TEXT ||
|
|
fbcon_info_from_console(i) != info)
|
|
continue;
|
|
|
|
if (con_is_visible(vc)) {
|
|
fg = i;
|
|
continue;
|
|
}
|
|
|
|
p = &fb_display[vc->vc_num];
|
|
set_blitting_type(vc, info);
|
|
var_to_display(p, &info->var, info);
|
|
cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
|
|
rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
|
|
cols /= vc->vc_font.width;
|
|
rows /= vc->vc_font.height;
|
|
vc_resize(vc, cols, rows);
|
|
}
|
|
|
|
if (fg != -1)
|
|
fbcon_modechanged(info);
|
|
}
|
|
|
|
|
|
void fbcon_update_vcs(struct fb_info *info, bool all)
|
|
{
|
|
if (all)
|
|
fbcon_set_all_vcs(info);
|
|
else
|
|
fbcon_modechanged(info);
|
|
}
|
|
EXPORT_SYMBOL(fbcon_update_vcs);
|
|
|
|
/* let fbcon check if it supports a new screen resolution */
|
|
int fbcon_modechange_possible(struct fb_info *info, struct fb_var_screeninfo *var)
|
|
{
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
struct vc_data *vc;
|
|
unsigned int i;
|
|
|
|
WARN_CONSOLE_UNLOCKED();
|
|
|
|
if (!par)
|
|
return 0;
|
|
|
|
/* prevent setting a screen size which is smaller than font size */
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
vc = vc_cons[i].d;
|
|
if (!vc || vc->vc_mode != KD_TEXT ||
|
|
fbcon_info_from_console(i) != info)
|
|
continue;
|
|
|
|
if (vc->vc_font.width > FBCON_SWAP(var->rotate, var->xres, var->yres) ||
|
|
vc->vc_font.height > FBCON_SWAP(var->rotate, var->yres, var->xres))
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(fbcon_modechange_possible);
|
|
|
|
int fbcon_mode_deleted(struct fb_info *info,
|
|
struct fb_videomode *mode)
|
|
{
|
|
struct fb_info *fb_info;
|
|
struct fbcon_display *p;
|
|
int i, j, found = 0;
|
|
|
|
/* before deletion, ensure that mode is not in use */
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
j = con2fb_map[i];
|
|
if (j == -1)
|
|
continue;
|
|
fb_info = fbcon_registered_fb[j];
|
|
if (fb_info != info)
|
|
continue;
|
|
p = &fb_display[i];
|
|
if (!p || !p->mode)
|
|
continue;
|
|
if (fb_mode_is_equal(p->mode, mode)) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
static void fbcon_delete_mode(struct fb_videomode *m)
|
|
{
|
|
struct fbcon_display *p;
|
|
|
|
for (int i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
p = &fb_display[i];
|
|
if (p->mode == m)
|
|
p->mode = NULL;
|
|
}
|
|
}
|
|
|
|
void fbcon_delete_modelist(struct list_head *head)
|
|
{
|
|
struct fb_modelist *modelist;
|
|
|
|
list_for_each_entry(modelist, head, list)
|
|
fbcon_delete_mode(&modelist->mode);
|
|
}
|
|
|
|
#ifdef CONFIG_VT_HW_CONSOLE_BINDING
|
|
static void fbcon_unbind(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = do_unbind_con_driver(&fb_con, first_fb_vc, last_fb_vc,
|
|
fbcon_is_default);
|
|
|
|
if (!ret)
|
|
fbcon_has_console_bind = false;
|
|
}
|
|
#else
|
|
static inline void fbcon_unbind(void) {}
|
|
#endif /* CONFIG_VT_HW_CONSOLE_BINDING */
|
|
|
|
void fbcon_fb_unbind(struct fb_info *info)
|
|
{
|
|
int i, new_idx = -1;
|
|
int idx = info->node;
|
|
|
|
console_lock();
|
|
|
|
if (!fbcon_has_console_bind) {
|
|
console_unlock();
|
|
return;
|
|
}
|
|
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
if (con2fb_map[i] != idx &&
|
|
con2fb_map[i] != -1) {
|
|
new_idx = con2fb_map[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (new_idx != -1) {
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
if (con2fb_map[i] == idx)
|
|
set_con2fb_map(i, new_idx, 0);
|
|
}
|
|
} else {
|
|
struct fb_info *info = fbcon_registered_fb[idx];
|
|
|
|
/* This is sort of like set_con2fb_map, except it maps
|
|
* the consoles to no device and then releases the
|
|
* oldinfo to free memory and cancel the cursor blink
|
|
* timer. I can imagine this just becoming part of
|
|
* set_con2fb_map where new_idx is -1
|
|
*/
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
if (con2fb_map[i] == idx) {
|
|
con2fb_map[i] = -1;
|
|
if (!search_fb_in_map(idx)) {
|
|
con2fb_release_oldinfo(vc_cons[i].d,
|
|
info, NULL);
|
|
}
|
|
}
|
|
}
|
|
fbcon_unbind();
|
|
}
|
|
|
|
console_unlock();
|
|
}
|
|
|
|
void fbcon_fb_unregistered(struct fb_info *info)
|
|
{
|
|
int i, idx;
|
|
|
|
console_lock();
|
|
|
|
if (info->device && dev_is_pci(info->device))
|
|
vga_switcheroo_client_fb_set(to_pci_dev(info->device), NULL);
|
|
|
|
fbcon_registered_fb[info->node] = NULL;
|
|
fbcon_num_registered_fb--;
|
|
|
|
if (deferred_takeover) {
|
|
console_unlock();
|
|
return;
|
|
}
|
|
|
|
idx = info->node;
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
if (con2fb_map[i] == idx)
|
|
con2fb_map[i] = -1;
|
|
}
|
|
|
|
if (idx == info_idx) {
|
|
info_idx = -1;
|
|
|
|
fbcon_for_each_registered_fb(i) {
|
|
info_idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (info_idx != -1) {
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
if (con2fb_map[i] == -1)
|
|
con2fb_map[i] = info_idx;
|
|
}
|
|
}
|
|
|
|
if (primary_device == idx)
|
|
primary_device = -1;
|
|
|
|
if (!fbcon_num_registered_fb)
|
|
do_unregister_con_driver(&fb_con);
|
|
console_unlock();
|
|
}
|
|
|
|
void fbcon_remap_all(struct fb_info *info)
|
|
{
|
|
int i, idx = info->node;
|
|
|
|
console_lock();
|
|
if (deferred_takeover) {
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++)
|
|
con2fb_map_boot[i] = idx;
|
|
fbcon_map_override();
|
|
console_unlock();
|
|
return;
|
|
}
|
|
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++)
|
|
set_con2fb_map(i, idx, 0);
|
|
|
|
if (con_is_bound(&fb_con)) {
|
|
printk(KERN_INFO "fbcon: Remapping primary device, "
|
|
"fb%i, to tty %i-%i\n", idx,
|
|
first_fb_vc + 1, last_fb_vc + 1);
|
|
info_idx = idx;
|
|
}
|
|
console_unlock();
|
|
}
|
|
|
|
#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DETECT_PRIMARY
|
|
static void fbcon_select_primary(struct fb_info *info)
|
|
{
|
|
if (!map_override && primary_device == -1 &&
|
|
video_is_primary_device(info->device)) {
|
|
int i;
|
|
|
|
printk(KERN_INFO "fbcon: %s (fb%i) is primary device\n",
|
|
info->fix.id, info->node);
|
|
primary_device = info->node;
|
|
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++)
|
|
con2fb_map_boot[i] = primary_device;
|
|
|
|
if (con_is_bound(&fb_con)) {
|
|
printk(KERN_INFO "fbcon: Remapping primary device, "
|
|
"fb%i, to tty %i-%i\n", info->node,
|
|
first_fb_vc + 1, last_fb_vc + 1);
|
|
info_idx = primary_device;
|
|
}
|
|
}
|
|
|
|
}
|
|
#else
|
|
static inline void fbcon_select_primary(struct fb_info *info)
|
|
{
|
|
return;
|
|
}
|
|
#endif /* CONFIG_FRAMEBUFFER_DETECT_PRIMARY */
|
|
|
|
static bool lockless_register_fb;
|
|
module_param_named_unsafe(lockless_register_fb, lockless_register_fb, bool, 0400);
|
|
MODULE_PARM_DESC(lockless_register_fb,
|
|
"Lockless framebuffer registration for debugging [default=off]");
|
|
|
|
/* called with console_lock held */
|
|
static int do_fb_registered(struct fb_info *info)
|
|
{
|
|
int ret = 0, i, idx;
|
|
|
|
WARN_CONSOLE_UNLOCKED();
|
|
|
|
fbcon_registered_fb[info->node] = info;
|
|
fbcon_num_registered_fb++;
|
|
|
|
idx = info->node;
|
|
fbcon_select_primary(info);
|
|
|
|
if (deferred_takeover) {
|
|
pr_info("fbcon: Deferring console take-over\n");
|
|
return 0;
|
|
}
|
|
|
|
if (info_idx == -1) {
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
if (con2fb_map_boot[i] == idx) {
|
|
info_idx = idx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (info_idx != -1)
|
|
ret = do_fbcon_takeover(1);
|
|
} else {
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
if (con2fb_map_boot[i] == idx)
|
|
set_con2fb_map(i, idx, 0);
|
|
}
|
|
}
|
|
|
|
/* Set the fb info for vga_switcheroo clients. Does nothing otherwise. */
|
|
if (info->device && dev_is_pci(info->device))
|
|
vga_switcheroo_client_fb_set(to_pci_dev(info->device), info);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int fbcon_fb_registered(struct fb_info *info)
|
|
{
|
|
int ret;
|
|
|
|
if (!lockless_register_fb)
|
|
console_lock();
|
|
else
|
|
atomic_inc(&ignore_console_lock_warning);
|
|
|
|
ret = do_fb_registered(info);
|
|
|
|
if (!lockless_register_fb)
|
|
console_unlock();
|
|
else
|
|
atomic_dec(&ignore_console_lock_warning);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void fbcon_fb_blanked(struct fb_info *info, int blank)
|
|
{
|
|
struct fbcon_par *par = info->fbcon_par;
|
|
struct vc_data *vc;
|
|
|
|
if (!par || par->currcon < 0)
|
|
return;
|
|
|
|
vc = vc_cons[par->currcon].d;
|
|
if (vc->vc_mode != KD_TEXT || fbcon_info_from_console(par->currcon) != info)
|
|
return;
|
|
|
|
if (con_is_visible(vc)) {
|
|
if (blank)
|
|
do_blank_screen(0);
|
|
else
|
|
do_unblank_screen(0);
|
|
}
|
|
par->blank_state = blank;
|
|
}
|
|
|
|
void fbcon_new_modelist(struct fb_info *info)
|
|
{
|
|
int i;
|
|
struct vc_data *vc;
|
|
struct fb_var_screeninfo var;
|
|
const struct fb_videomode *mode;
|
|
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
if (fbcon_info_from_console(i) != info)
|
|
continue;
|
|
if (!fb_display[i].mode)
|
|
continue;
|
|
vc = vc_cons[i].d;
|
|
display_to_var(&var, &fb_display[i]);
|
|
mode = fb_find_nearest_mode(fb_display[i].mode,
|
|
&info->modelist);
|
|
fb_videomode_to_var(&var, mode);
|
|
fbcon_set_disp(info, &var, vc->vc_num);
|
|
}
|
|
}
|
|
|
|
void fbcon_get_requirement(struct fb_info *info,
|
|
struct fb_blit_caps *caps)
|
|
{
|
|
struct vc_data *vc;
|
|
|
|
if (caps->flags) {
|
|
int i, charcnt;
|
|
|
|
for (i = first_fb_vc; i <= last_fb_vc; i++) {
|
|
vc = vc_cons[i].d;
|
|
if (vc && vc->vc_mode == KD_TEXT &&
|
|
info->node == con2fb_map[i]) {
|
|
set_bit(vc->vc_font.width - 1, caps->x);
|
|
set_bit(vc->vc_font.height - 1, caps->y);
|
|
charcnt = vc->vc_font.charcount;
|
|
if (caps->len < charcnt)
|
|
caps->len = charcnt;
|
|
}
|
|
}
|
|
} else {
|
|
vc = vc_cons[fg_console].d;
|
|
|
|
if (vc && vc->vc_mode == KD_TEXT &&
|
|
info->node == con2fb_map[fg_console]) {
|
|
bitmap_zero(caps->x, FB_MAX_BLIT_WIDTH);
|
|
set_bit(vc->vc_font.width - 1, caps->x);
|
|
bitmap_zero(caps->y, FB_MAX_BLIT_HEIGHT);
|
|
set_bit(vc->vc_font.height - 1, caps->y);
|
|
caps->len = vc->vc_font.charcount;
|
|
}
|
|
}
|
|
}
|
|
|
|
int fbcon_set_con2fb_map_ioctl(void __user *argp)
|
|
{
|
|
struct fb_con2fbmap con2fb;
|
|
int ret;
|
|
|
|
if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
|
|
return -EFAULT;
|
|
if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
|
|
return -EINVAL;
|
|
if (con2fb.framebuffer >= FB_MAX)
|
|
return -EINVAL;
|
|
if (!fbcon_registered_fb[con2fb.framebuffer])
|
|
request_module("fb%d", con2fb.framebuffer);
|
|
if (!fbcon_registered_fb[con2fb.framebuffer]) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
console_lock();
|
|
ret = set_con2fb_map(con2fb.console - 1,
|
|
con2fb.framebuffer, 1);
|
|
console_unlock();
|
|
|
|
return ret;
|
|
}
|
|
|
|
int fbcon_get_con2fb_map_ioctl(void __user *argp)
|
|
{
|
|
struct fb_con2fbmap con2fb;
|
|
|
|
if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
|
|
return -EFAULT;
|
|
if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
|
|
return -EINVAL;
|
|
|
|
console_lock();
|
|
con2fb.framebuffer = con2fb_map[con2fb.console - 1];
|
|
console_unlock();
|
|
|
|
return copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0;
|
|
}
|
|
|
|
/*
|
|
* The console `switch' structure for the frame buffer based console
|
|
*/
|
|
|
|
static const struct consw fb_con = {
|
|
.owner = THIS_MODULE,
|
|
.con_startup = fbcon_startup,
|
|
.con_init = fbcon_init,
|
|
.con_deinit = fbcon_deinit,
|
|
.con_clear = fbcon_clear,
|
|
.con_putcs = fbcon_putcs,
|
|
.con_cursor = fbcon_cursor,
|
|
.con_scroll = fbcon_scroll,
|
|
.con_switch = fbcon_switch,
|
|
.con_blank = fbcon_blank,
|
|
.con_font_set = fbcon_set_font,
|
|
.con_font_get = fbcon_get_font,
|
|
.con_font_default = fbcon_set_def_font,
|
|
.con_set_palette = fbcon_set_palette,
|
|
.con_invert_region = fbcon_invert_region,
|
|
.con_resize = fbcon_resize,
|
|
.con_debug_enter = fbcon_debug_enter,
|
|
.con_debug_leave = fbcon_debug_leave,
|
|
};
|
|
|
|
static ssize_t rotate_store(struct device *device,
|
|
struct device_attribute *attr, const char *buf,
|
|
size_t count)
|
|
{
|
|
struct fb_info *info;
|
|
int rotate, idx;
|
|
char **last = NULL;
|
|
|
|
console_lock();
|
|
idx = con2fb_map[fg_console];
|
|
|
|
if (idx == -1 || fbcon_registered_fb[idx] == NULL)
|
|
goto err;
|
|
|
|
info = fbcon_registered_fb[idx];
|
|
rotate = simple_strtoul(buf, last, 0);
|
|
fbcon_rotate(info, rotate);
|
|
err:
|
|
console_unlock();
|
|
return count;
|
|
}
|
|
|
|
static ssize_t rotate_all_store(struct device *device,
|
|
struct device_attribute *attr,const char *buf,
|
|
size_t count)
|
|
{
|
|
struct fb_info *info;
|
|
int rotate, idx;
|
|
char **last = NULL;
|
|
|
|
console_lock();
|
|
idx = con2fb_map[fg_console];
|
|
|
|
if (idx == -1 || fbcon_registered_fb[idx] == NULL)
|
|
goto err;
|
|
|
|
info = fbcon_registered_fb[idx];
|
|
rotate = simple_strtoul(buf, last, 0);
|
|
fbcon_rotate_all(info, rotate);
|
|
err:
|
|
console_unlock();
|
|
return count;
|
|
}
|
|
|
|
static ssize_t rotate_show(struct device *device,
|
|
struct device_attribute *attr,char *buf)
|
|
{
|
|
struct fb_info *info;
|
|
int rotate = 0, idx;
|
|
|
|
console_lock();
|
|
idx = con2fb_map[fg_console];
|
|
|
|
if (idx == -1 || fbcon_registered_fb[idx] == NULL)
|
|
goto err;
|
|
|
|
info = fbcon_registered_fb[idx];
|
|
rotate = fbcon_get_rotate(info);
|
|
err:
|
|
console_unlock();
|
|
return sysfs_emit(buf, "%d\n", rotate);
|
|
}
|
|
|
|
static ssize_t cursor_blink_show(struct device *device,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct fb_info *info;
|
|
struct fbcon_par *par;
|
|
int idx, blink = -1;
|
|
|
|
console_lock();
|
|
idx = con2fb_map[fg_console];
|
|
|
|
if (idx == -1 || fbcon_registered_fb[idx] == NULL)
|
|
goto err;
|
|
|
|
info = fbcon_registered_fb[idx];
|
|
par = info->fbcon_par;
|
|
|
|
if (!par)
|
|
goto err;
|
|
|
|
blink = delayed_work_pending(&par->cursor_work);
|
|
err:
|
|
console_unlock();
|
|
return sysfs_emit(buf, "%d\n", blink);
|
|
}
|
|
|
|
static ssize_t cursor_blink_store(struct device *device,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct fb_info *info;
|
|
char **last = NULL;
|
|
bool blink;
|
|
int idx;
|
|
|
|
console_lock();
|
|
idx = con2fb_map[fg_console];
|
|
|
|
if (idx == -1 || fbcon_registered_fb[idx] == NULL)
|
|
goto err;
|
|
|
|
info = fbcon_registered_fb[idx];
|
|
|
|
if (!info->fbcon_par)
|
|
goto err;
|
|
|
|
blink = simple_strtoul(buf, last, 0);
|
|
|
|
if (blink) {
|
|
fbcon_cursor_blink = true;
|
|
fbcon_add_cursor_work(info);
|
|
} else {
|
|
fbcon_cursor_blink = false;
|
|
fbcon_del_cursor_work(info);
|
|
}
|
|
|
|
err:
|
|
console_unlock();
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(cursor_blink);
|
|
static DEVICE_ATTR_RW(rotate);
|
|
static DEVICE_ATTR_WO(rotate_all);
|
|
|
|
static struct attribute *fbcon_device_attrs[] = {
|
|
&dev_attr_cursor_blink.attr,
|
|
&dev_attr_rotate.attr,
|
|
&dev_attr_rotate_all.attr,
|
|
NULL
|
|
};
|
|
|
|
ATTRIBUTE_GROUPS(fbcon_device);
|
|
|
|
#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER
|
|
static void fbcon_register_existing_fbs(struct work_struct *work)
|
|
{
|
|
int i;
|
|
|
|
console_lock();
|
|
|
|
deferred_takeover = false;
|
|
logo_shown = FBCON_LOGO_DONTSHOW;
|
|
|
|
fbcon_for_each_registered_fb(i)
|
|
do_fb_registered(fbcon_registered_fb[i]);
|
|
|
|
console_unlock();
|
|
}
|
|
|
|
static struct notifier_block fbcon_output_nb;
|
|
static DECLARE_WORK(fbcon_deferred_takeover_work, fbcon_register_existing_fbs);
|
|
|
|
static int fbcon_output_notifier(struct notifier_block *nb,
|
|
unsigned long action, void *data)
|
|
{
|
|
WARN_CONSOLE_UNLOCKED();
|
|
|
|
pr_info("fbcon: Taking over console\n");
|
|
|
|
dummycon_unregister_output_notifier(&fbcon_output_nb);
|
|
|
|
/* We may get called in atomic context */
|
|
schedule_work(&fbcon_deferred_takeover_work);
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
#endif
|
|
|
|
static void fbcon_start(void)
|
|
{
|
|
WARN_CONSOLE_UNLOCKED();
|
|
|
|
#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER
|
|
if (conswitchp != &dummy_con)
|
|
deferred_takeover = false;
|
|
|
|
if (deferred_takeover) {
|
|
fbcon_output_nb.notifier_call = fbcon_output_notifier;
|
|
dummycon_register_output_notifier(&fbcon_output_nb);
|
|
return;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void __init fb_console_init(void)
|
|
{
|
|
int i;
|
|
|
|
console_lock();
|
|
fbcon_device = device_create_with_groups(fb_class, NULL,
|
|
MKDEV(0, 0), NULL,
|
|
fbcon_device_groups, "fbcon");
|
|
|
|
if (IS_ERR(fbcon_device)) {
|
|
printk(KERN_WARNING "Unable to create device "
|
|
"for fbcon; errno = %ld\n",
|
|
PTR_ERR(fbcon_device));
|
|
fbcon_device = NULL;
|
|
}
|
|
|
|
for (i = 0; i < MAX_NR_CONSOLES; i++)
|
|
con2fb_map[i] = -1;
|
|
|
|
fbcon_start();
|
|
console_unlock();
|
|
}
|
|
|
|
#ifdef MODULE
|
|
|
|
void __exit fb_console_exit(void)
|
|
{
|
|
#ifdef CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER
|
|
console_lock();
|
|
if (deferred_takeover)
|
|
dummycon_unregister_output_notifier(&fbcon_output_nb);
|
|
console_unlock();
|
|
|
|
cancel_work_sync(&fbcon_deferred_takeover_work);
|
|
#endif
|
|
|
|
console_lock();
|
|
device_destroy(fb_class, MKDEV(0, 0));
|
|
|
|
do_unregister_con_driver(&fb_con);
|
|
console_unlock();
|
|
}
|
|
#endif
|