syzbot


ID Workflow Result Correct Bug Created Started Finished Revision Error
39761345-63fa-429c-8d69-cda650983c98 patching KASAN: vmalloc-out-of-bounds Write in imageblit (6) 2026/01/29 18:54 2026/01/29 19:46 2026/01/29 20:35 9f91b6e4a37d1ff41db8959be87cce846b2b5b47


KernelBranch:
master


KernelCommit:
63804fed149a6750ffd28610c5c1c98cce6bd377


KernelRepo:
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git


PatchDescription:
fbcon: Handle vc_resize() failure in fbcon_modechanged()

The syzbot reported a vmalloc-out-of-bounds issue in fb_write_offset().
The call trace shows the crash happens in cw_putcs() during a console
rotation.

Root cause analysis shows that fbcon_modechanged() calls
set_blitting_type() which updates par->rotate and par->bitops to the
new rotation settings. Then it calls vc_resize() to update the console
dimensions. However, it ignores the return value of vc_resize().

If vc_resize() fails (e.g. due to OOM), the console dimensions
(vc->vc_cols/rows) remain unchanged, but par->rotate and par->bitops
are already updated to the new rotation. This inconsistency leads to
cw_putcs() using the old (larger) dimensions with the new framebuffer
layout. For example, when rotating from landscape to portrait, the old
width is larger than the new width, causing cw_putcs() to calculate
offsets that exceed the framebuffer bounds.

Fix this by checking the return value of vc_resize(). If it fails,
revert the rotation settings (par->rotate and par->bitops) to their
previous state to ensure consistency.


PatchDiff:
diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 7be9e8653..4b7fbef76 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -2695,6 +2695,7 @@ static void fbcon_modechanged(struct fb_info *info)
 	struct vc_data *vc;
 	struct fbcon_display *p;
 	int rows, cols;
+	int old_rotate, err;
 
 	if (!par || par->currcon < 0)
 		return;
@@ -2704,6 +2705,7 @@ static void fbcon_modechanged(struct fb_info *info)
 		return;
 
 	p = &fb_display[vc->vc_num];
+	old_rotate = par->rotate;
 	set_blitting_type(vc, info);
 
 	if (con_is_visible(vc)) {
@@ -2712,7 +2714,14 @@ static void fbcon_modechanged(struct fb_info *info)
 		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);
+		err = vc_resize(vc, cols, rows);
+		if (err) {
+			if (!(info->flags & FBINFO_MISC_TILEBLITTING)) {
+				par->rotate = old_rotate;
+				fbcon_set_bitops(par);
+			}
+			return;
+		}
 		updatescrollmode(p, info, vc);
 		scrollback_max = 0;
 		scrollback_current = 0;


Recipients:
[map[Email:deller@gmx.de Name:Helge Deller To:true] map[Email:dri-devel@lists.freedesktop.org Name: To:true] map[Email:linux-fbdev@vger.kernel.org Name: To:true] map[Email:linux-kernel@vger.kernel.org Name: To:false] map[Email:simona@ffwll.ch Name:Simona Vetter To:true]]

Crash report:
RDX: 0000000000000007 RSI: 0000000000000000 RDI: 0000000000000003
RBP: 00007fff34de03f0 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001
R13: 00007fd0417e5fa0 R14: 00007fd0417e5fa0 R15: 0000000000000003
 </TASK>
==================================================================
BUG: KASAN: vmalloc-out-of-bounds in fb_write_offset drivers/video/fbdev/core/sysmem.h:30 [inline]
BUG: KASAN: vmalloc-out-of-bounds in fb_bitmap_2ppw drivers/video/fbdev/core/fb_imageblit.h:364 [inline]
BUG: KASAN: vmalloc-out-of-bounds in fb_bitmap_imageblit drivers/video/fbdev/core/fb_imageblit.h:462 [inline]
BUG: KASAN: vmalloc-out-of-bounds in fb_imageblit drivers/video/fbdev/core/fb_imageblit.h:492 [inline]
BUG: KASAN: vmalloc-out-of-bounds in sys_imageblit+0x1a6f/0x1e60 drivers/video/fbdev/core/sysimgblt.c:24
Write of size 8 at addr ffffc90003749fc0 by task syz.0.17/6037

CPU: 0 UID: 0 PID: 6037 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/25/2025
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x116/0x1f0 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0xcd/0x630 mm/kasan/report.c:482
 kasan_report+0xe0/0x110 mm/kasan/report.c:595
 fb_write_offset drivers/video/fbdev/core/sysmem.h:30 [inline]
 fb_bitmap_2ppw drivers/video/fbdev/core/fb_imageblit.h:364 [inline]
 fb_bitmap_imageblit drivers/video/fbdev/core/fb_imageblit.h:462 [inline]
 fb_imageblit drivers/video/fbdev/core/fb_imageblit.h:492 [inline]
 sys_imageblit+0x1a6f/0x1e60 drivers/video/fbdev/core/sysimgblt.c:24
 drm_fbdev_shmem_defio_imageblit+0x20/0x130 drivers/gpu/drm/drm_fbdev_shmem.c:38
 cw_putcs_aligned drivers/video/fbdev/core/fbcon_cw.c:110 [inline]
 cw_putcs+0x917/0xbb0 drivers/video/fbdev/core/fbcon_cw.c:158
 fbcon_putcs+0x387/0x450 drivers/video/fbdev/core/fbcon.c:1320
 do_update_region+0x2e9/0x3f0 drivers/tty/vt/vt.c:628
 redraw_screen+0x63f/0x760 drivers/tty/vt/vt.c:980
 fbcon_modechanged+0x456/0x6b0 drivers/video/fbdev/core/fbcon.c:2710
 fbcon_rotate drivers/video/fbdev/core/fbcon.c:228 [inline]
 rotate_store+0x258/0x2f0 drivers/video/fbdev/core/fbcon.c:3215
 dev_attr_store+0x58/0x80 drivers/base/core.c:2437
 sysfs_kf_write+0xf2/0x150 fs/sysfs/file.c:142
 kernfs_fop_write_iter+0x3af/0x570 fs/kernfs/file.c:352
 new_sync_write fs/read_write.c:593 [inline]
 vfs_write+0x7d3/0x11d0 fs/read_write.c:686
 ksys_write+0x12a/0x250 fs/read_write.c:738
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xcd/0xfa0 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fd04158f6c9
Code: ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 a8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fff34de0398 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007fd0417e5fa0 RCX: 00007fd04158f6c9
RDX: 0000000000000007 RSI: 0000000000000000 RDI: 0000000000000003
RBP: 00007fff34de03f0 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001
R13: 00007fd0417e5fa0 R14: 00007fd0417e5fa0 R15: 0000000000000003
 </TASK>

The buggy address belongs to a vmalloc virtual mapping
Memory state around the buggy address:
 ffffc90003749e80: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
 ffffc90003749f00: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
>ffffc90003749f80: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
                                           ^
 ffffc9000374a000: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
 ffffc9000374a080: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
==================================================================

Trajectory:
Seq Timestamp Type Name Duration
0/0 2026/01/29 19:46 flow patching
49m Results:
map[KernelBranch:master KernelCommit:63804fed149a6750ffd28610c5c1c98cce6bd377 KernelRepo:git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git PatchDescription:fbcon: Handle vc_resize() failure in fbcon_modechanged()

The syzbot reported a vmalloc-out-of-bounds issue in fb_write_offset().
The call trace shows the crash happens in cw_putcs() during a console
rotation.

Root cause analysis shows that fbcon_modechanged() calls
set_blitting_type() which updates par->rotate and par->bitops to the
new rotation settings. Then it calls vc_resize() to update the console
dimensions. However, it ignores the return value of vc_resize().

If vc_resize() fails (e.g. due to OOM), the console dimensions
(vc->vc_cols/rows) remain unchanged, but par->rotate and par->bitops
are already updated to the new rotation. This inconsistency leads to
cw_putcs() using the old (larger) dimensions with the new framebuffer
layout. For example, when rotating from landscape to portrait, the old
width is larger than the new width, causing cw_putcs() to calculate
offsets that exceed the framebuffer bounds.

Fix this by checking the return value of vc_resize(). If it fails,
revert the rotation settings (par->rotate and par->bitops) to their
previous state to ensure consistency. PatchDiff:diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 7be9e8653..4b7fbef76 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -2695,6 +2695,7 @@ static void fbcon_modechanged(struct fb_info *info)
 	struct vc_data *vc;
 	struct fbcon_display *p;
 	int rows, cols;
+	int old_rotate, err;
 
 	if (!par || par->currcon < 0)
 		return;
@@ -2704,6 +2705,7 @@ static void fbcon_modechanged(struct fb_info *info)
 		return;
 
 	p = &fb_display[vc->vc_num];
+	old_rotate = par->rotate;
 	set_blitting_type(vc, info);
 
 	if (con_is_visible(vc)) {
@@ -2712,7 +2714,14 @@ static void fbcon_modechanged(struct fb_info *info)
 		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);
+		err = vc_resize(vc, cols, rows);
+		if (err) {
+			if (!(info->flags & FBINFO_MISC_TILEBLITTING)) {
+				par->rotate = old_rotate;
+				fbcon_set_bitops(par);
+			}
+			return;
+		}
 		updatescrollmode(p, info, vc);
 		scrollback_max = 0;
 		scrollback_current = 0;
 Recipients:[map[Email:deller@gmx.de Name:Helge Deller To:true] map[Email:dri-devel@lists.freedesktop.org Name: To:true] map[Email:linux-fbdev@vger.kernel.org Name: To:true] map[Email:linux-kernel@vger.kernel.org Name: To:false] map[Email:simona@ffwll.ch Name:Simona Vetter To:true]]]

1/1 2026/01/29 19:46 action base-commit-picker
0m Results:
map[KernelBranch:master KernelCommit:63804fed149a6750ffd28610c5c1c98cce6bd377 KernelRepo:git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git]

2/1 2026/01/29 19:46 action kernel-checkouter
0m Results:
map[KernelSrc:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/src/8c64ca34582568f44cbf475e4f60a6f0b9d98e80]

3/1 2026/01/29 19:46 action kernel-builder
6m Results:
map[KernelObj:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/build/6337ba542adbc52c7e5ae329550055031eb93105]

4/1 2026/01/29 19:52 action crash-reproducer
1m Results:
map[BugTitle:KASAN: vmalloc-out-of-bounds Write in imageblit CrashReport:R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001
R13: 00007fffd2735b50 R14: 00007f26d9187b40 R15: 0000000000000003
 </TASK>
==================================================================
BUG: KASAN: vmalloc-out-of-bounds in fb_write_offset drivers/video/fbdev/core/sysmem.h:30 [inline]
BUG: KASAN: vmalloc-out-of-bounds in fb_bitmap_2ppw drivers/video/fbdev/core/fb_imageblit.h:364 [inline]
BUG: KASAN: vmalloc-out-of-bounds in fb_bitmap_imageblit drivers/video/fbdev/core/fb_imageblit.h:462 [inline]
BUG: KASAN: vmalloc-out-of-bounds in fb_imageblit drivers/video/fbdev/core/fb_imageblit.h:492 [inline]
BUG: KASAN: vmalloc-out-of-bounds in sys_imageblit+0xfed/0x1ea0 drivers/video/fbdev/core/sysimgblt.c:24
Write of size 8 at addr ffffc90002c223c0 by task syz-executor173/6014

CPU: 0 UID: 0 PID: 6014 Comm: syz-executor173 Not tainted syzkaller #1 PREEMPT(full) 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x10e/0x190 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0x17e/0x810 mm/kasan/report.c:482
 kasan_report+0x147/0x180 mm/kasan/report.c:595
 fb_write_offset drivers/video/fbdev/core/sysmem.h:30 [inline]
 fb_bitmap_2ppw drivers/video/fbdev/core/fb_imageblit.h:364 [inline]
 fb_bitmap_imageblit drivers/video/fbdev/core/fb_imageblit.h:462 [inline]
 fb_imageblit drivers/video/fbdev/core/fb_imageblit.h:492 [inline]
 sys_imageblit+0xfed/0x1ea0 drivers/video/fbdev/core/sysimgblt.c:24
 drm_fbdev_shmem_defio_imageblit+0x2c/0x110 drivers/gpu/drm/drm_fbdev_shmem.c:39
 cw_putcs_aligned drivers/video/fbdev/core/fbcon_cw.c:110 [inline]
 cw_putcs+0x15af/0x1880 drivers/video/fbdev/core/fbcon_cw.c:158
 fbcon_putcs+0x3bd/0x590 drivers/video/fbdev/core/fbcon.c:1343
 do_update_region+0x3a1/0x460 drivers/tty/vt/vt.c:628
 redraw_screen+0x91a/0xe90 drivers/tty/vt/vt.c:980
 fbcon_rotate drivers/video/fbdev/core/fbcon.c:231 [inline]
 rotate_store+0x33f/0x510 drivers/video/fbdev/core/fbcon.c:3237
 kernfs_fop_write_iter+0x3af/0x540 fs/kernfs/file.c:352
 new_sync_write fs/read_write.c:593 [inline]
 vfs_write+0x5d2/0xb40 fs/read_write.c:686
 ksys_write+0x149/0x250 fs/read_write.c:738
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f26d90f4749
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 d8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fffd2735b38 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007fffd2735b80 RCX: 00007f26d90f4749
RDX: 0000000000000007 RSI: 0000000000000000 RDI: 0000000000000003
RBP: 0000000000000001 R08: 0000000000000000 R09: 00007fffd2735b70
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001
R13: 00007fffd2735b50 R14: 00007f26d9187b40 R15: 0000000000000003
 </TASK>

The buggy address belongs to a vmalloc virtual mapping
Memory state around the buggy address:
 ffffc90002c22280: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
 ffffc90002c22300: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
>ffffc90002c22380: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
                                           ^
 ffffc90002c22400: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
 ffffc90002c22480: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
==================================================================
]

5/1 2026/01/29 19:54 action codesearch-prepare
22m Results:
map[Index:codesearch-index]

6/1 2026/01/29 20:16 agent debugger
5m Model:
gemini-3-pro-preview

Instruction:
You are an experienced Linux kernel developer tasked with debugging a kernel crash root cause.
You need to provide a detailed explanation of the root cause for another developer to be
able to write a fix for the bug based on your explanation.
Your final reply must contain only the explanation.

Prefer calling several tools at the same time to save round-trips.

Prompt:
The crash is:

R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001
R13: 00007fffd2735b50 R14: 00007f26d9187b40 R15: 0000000000000003
 </TASK>
==================================================================
BUG: KASAN: vmalloc-out-of-bounds in fb_write_offset drivers/video/fbdev/core/sysmem.h:30 [inline]
BUG: KASAN: vmalloc-out-of-bounds in fb_bitmap_2ppw drivers/video/fbdev/core/fb_imageblit.h:364 [inline]
BUG: KASAN: vmalloc-out-of-bounds in fb_bitmap_imageblit drivers/video/fbdev/core/fb_imageblit.h:462 [inline]
BUG: KASAN: vmalloc-out-of-bounds in fb_imageblit drivers/video/fbdev/core/fb_imageblit.h:492 [inline]
BUG: KASAN: vmalloc-out-of-bounds in sys_imageblit+0xfed/0x1ea0 drivers/video/fbdev/core/sysimgblt.c:24
Write of size 8 at addr ffffc90002c223c0 by task syz-executor173/6014

CPU: 0 UID: 0 PID: 6014 Comm: syz-executor173 Not tainted syzkaller #1 PREEMPT(full) 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x10e/0x190 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0x17e/0x810 mm/kasan/report.c:482
 kasan_report+0x147/0x180 mm/kasan/report.c:595
 fb_write_offset drivers/video/fbdev/core/sysmem.h:30 [inline]
 fb_bitmap_2ppw drivers/video/fbdev/core/fb_imageblit.h:364 [inline]
 fb_bitmap_imageblit drivers/video/fbdev/core/fb_imageblit.h:462 [inline]
 fb_imageblit drivers/video/fbdev/core/fb_imageblit.h:492 [inline]
 sys_imageblit+0xfed/0x1ea0 drivers/video/fbdev/core/sysimgblt.c:24
 drm_fbdev_shmem_defio_imageblit+0x2c/0x110 drivers/gpu/drm/drm_fbdev_shmem.c:39
 cw_putcs_aligned drivers/video/fbdev/core/fbcon_cw.c:110 [inline]
 cw_putcs+0x15af/0x1880 drivers/video/fbdev/core/fbcon_cw.c:158
 fbcon_putcs+0x3bd/0x590 drivers/video/fbdev/core/fbcon.c:1343
 do_update_region+0x3a1/0x460 drivers/tty/vt/vt.c:628
 redraw_screen+0x91a/0xe90 drivers/tty/vt/vt.c:980
 fbcon_rotate drivers/video/fbdev/core/fbcon.c:231 [inline]
 rotate_store+0x33f/0x510 drivers/video/fbdev/core/fbcon.c:3237
 kernfs_fop_write_iter+0x3af/0x540 fs/kernfs/file.c:352
 new_sync_write fs/read_write.c:593 [inline]
 vfs_write+0x5d2/0xb40 fs/read_write.c:686
 ksys_write+0x149/0x250 fs/read_write.c:738
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f26d90f4749
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 d8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fffd2735b38 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007fffd2735b80 RCX: 00007f26d90f4749
RDX: 0000000000000007 RSI: 0000000000000000 RDI: 0000000000000003
RBP: 0000000000000001 R08: 0000000000000000 R09: 00007fffd2735b70
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001
R13: 00007fffd2735b50 R14: 00007f26d9187b40 R15: 0000000000000003
 </TASK>

The buggy address belongs to a vmalloc virtual mapping
Memory state around the buggy address:
 ffffc90002c22280: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
 ffffc90002c22300: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
>ffffc90002c22380: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
                                           ^
 ffffc90002c22400: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
 ffffc90002c22480: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
==================================================================


Reply:
The root cause of the crash is a failure to handle the error return from `vc_resize` in `fbcon_modechanged`, leading to an inconsistency between the console's logical dimensions and the installed framebuffer operations (specifically rotation support).

Here is the step-by-step analysis:

1.  **Rotation Trigger**: The user (or syzkaller) initiates a console rotation via a sysfs write to `rotate` (or `rotate_all`), which calls `fbcon_rotate`.
2.  **State Update**: `fbcon_rotate` updates the requested rotation in `fb_display[vc->vc_num].con_rotate` (e.g., to `FB_ROTATE_CW` for 90-degree clockwise rotation) and calls `fbcon_modechanged`.
3.  **Ops Update**: `fbcon_modechanged` calls `set_blitting_type`, which updates the `fbcon_par->bitops` to the rotated versions (e.g., `cw_fbcon_bitops` containing `cw_putcs`) based on the new rotation.
4.  **Resize Attempt**: `fbcon_modechanged` calculates the new logical dimensions (`cols` and `rows`) for the rotated screen. It calls `vc_resize(vc, cols, rows)` to update the console's text buffer and dimensions (`vc->vc_cols`, `vc->vc_rows`).
5.  **Ignored Failure**: `vc_resize` can fail (e.g., due to memory allocation failure `ENOMEM`, which is common with syzkaller fault injection). **Critically, `fbcon_modechanged` ignores the return value of `vc_resize`.**
6.  **Inconsistent State**: If `vc_resize` fails, the console's `vc_cols` and `vc_rows` remain at their *old* (unrotated) values. However, the bit-blitting operations (`cw_putcs`) are already set to the *new* (rotated) mode.
    *   For example, if the screen was Landscape (wide), `vc_cols` is large.
    *   After rotating to Portrait (tall), `vc_cols` should be smaller.
    *   Due to the failure, `vc_cols` remains large (Landscape width).
7.  **Out-of-Bounds Calculation**: `fbcon_modechanged` proceeds to call `update_screen`, which eventually calls `cw_putcs`.
    *   `cw_putcs` iterates over the characters to be drawn. The iteration limits are based on the (incorrectly large) `vc_cols`.
    *   `cw_putcs` calculates the physical Y coordinate (`image.dy`) in the framebuffer corresponding to the logical column `xx`:
        `image.dy = xx * vc->vc_font.width;`
    *   Since `xx` can go up to the old `vc_cols`, `image.dy` can exceed the physical height (`yres`) of the framebuffer (because the old width is larger than the new height).
8.  **The Crash**: `cw_putcs` calls `fb_imageblit` -> `fb_bitmap_2ppw` -> `fb_write_offset`.
    *   `fb_imageblit` calculates the memory offset: `dst = base + image.dy * stride + image.dx * bpp`.
    *   Because `image.dy` is too large (out of physical bounds), the calculated address points past the end of the allocated vmalloc framebuffer.
    *   `fb_write_offset` attempts to write to this address, triggering the `KASAN: vmalloc-out-of-bounds`.

**Fix**: `fbcon_modechanged` must check the return value of `vc_resize`. If it fails, it should abort the update and potentially revert the rotation setting/ops to ensure the console state remains consistent.

7/2 2026/01/29 20:16 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 4414
							output: 58
							thoughts: 735
						

Thoughts:
**Root Cause Analysis of Kernel Crash: KASAN vmalloc-out-of-bounds in fb_write_offset**

Okay, so I've got a kernel crash, a `KASAN: vmalloc-out-of-bounds` error, specifically within `fb_write_offset`. The call trace tells the story: it's flowing from `fb_write_offset` through `fb_bitmap_2ppw`, `fb_bitmap_imageblit`, down to `rotate_store`.  The fact that `rotate_store` is at the end of this points towards the framebuffer console rotation as the trigger.  This is interesting.

The KASAN error, "vmalloc-out-of-bounds", with a write of size 8, strongly suggests a write operation exceeding the bounds of a vmalloc'd buffer. Given the context, this *likely* is the framebuffer memory itself.  I need to pinpoint *where* exactly this over-write is occurring during rotation.

My approach will be methodical. I need to understand the code flow, specifically how data is being written to the framebuffer during rotation.  I'll start by examining the relevant code files, using the paths provided.  I need to locate the definition of `fb_write_offset` (which is in `drivers/video/fbdev/core/sysmem.h`), `fb_bitmap_2ppw` and related functions (`drivers/video/fbdev/core/fb_imageblit.h`), and `cw_putcs_aligned` and `cw_putcs` (`drivers/video/fbdev/core/fbcon_cw.c`), and `fbcon_rotate` and `rotate_store` (`drivers/video/fbdev/core/fbcon.c`).

The `sysmem.h` path hints at this being related to the framebuffer in system memory.  That seems reasonable.  I should start by examining the source around the crash site.

I'll first examine `fb_write_offset` in `drivers/video/fbdev/core/sysmem.h` and then `fb_bitmap_2ppw` in `drivers/video/fbdev/core/fb_imageblit.h`. I'll use the available tools to quickly navigate the code and understand what each function does.

Wait, looking again, since these seem like headers, the critical code might be inline within those files. That's actually great – less hunting around.

Okay, the crash *is* indeed within these inline functions:
* `BUG: KASAN: vmalloc-out-of-bounds in fb_write_offset drivers/video/fbdev/core/sysmem.h:30 [inline]`
* `BUG: KASAN: vmalloc-out-of-bounds in fb_bitmap_2ppw drivers/video/fbdev/core/fb_imageblit.h:364 [inline]`

Now I will read the code around `fb_write_offset`, focusing on the parameters and how they are used. Then I will look at `fb_bitmap_2ppw`, the logic for bit blitting, to see what logic causes the over-write.



8/2 2026/01/29 20:16 tool read-file
0m Args:
map[File:drivers/video/fbdev/core/sysmem.h]

Results:
map[Contents:/* SPDX-License-Identifier: GPL-2.0-only
 *
 *	Virtual memory framebuffer access for drawing routines
 *
 *	Copyright (C) 2025 Zsolt Kajtar (soci@c64.rulez.org)
 */

/* keeps track of a bit address in framebuffer memory */
struct fb_address {
	void *address;
	int bits;
};

/* initialize the bit address pointer to the beginning of the frame buffer */
static inline struct fb_address fb_address_init(struct fb_info *p)
{
	void *base = p->screen_buffer;
	struct fb_address ptr;

	ptr.address = PTR_ALIGN_DOWN(base, BITS_PER_LONG / BITS_PER_BYTE);
	ptr.bits = (base - ptr.address) * BITS_PER_BYTE;
	return ptr;
}

/* framebuffer write access */
static inline void fb_write_offset(unsigned long val, int offset, const struct fb_address *dst)
{
	unsigned long *mem = dst->address;

	mem[offset] = val;
}

/* framebuffer read access */
static inline unsigned long fb_read_offset(int offset, const struct fb_address *src)
{
	unsigned long *mem = src->address;

	return mem[offset];
}
]

9/2 2026/01/29 20:16 tool read-file
0m Args:
map[File:drivers/video/fbdev/core/fb_imageblit.h]

Results:
map[Contents:/* SPDX-License-Identifier: GPL-2.0-only
 *
 *  Generic bitmap / 8 bpp image bitstreamer for packed pixel framebuffers
 *
 *	Rewritten by:
 *	Copyright (C)  2025 Zsolt Kajtar (soci@c64.rulez.org)
 *
 *	Based on previous work of:
 *	Copyright (C)  June 1999 James Simmons
 *	Anton Vorontsov <avorontsov@ru.mvista.com>
 *	Pavel Pisa <pisa@cmp.felk.cvut.cz>
 *	Antonino A. Daplas <adaplas@gmail.com>
 *	and others
 *
 * NOTES:
 *
 * Handles native and foreign byte order on both endians, standard and
 * reverse pixel order in a byte (<8 BPP), word length of 32/64 bits,
 * bits per pixel from 1 to the word length. Handles line lengths at byte
 * granularity while maintaining aligned accesses.
 *
 * Optimized routines for word aligned 1, 2, 4 pixel per word for high
 * bpp modes and 4 pixel at a time operation for low bpp.
 *
 * The color image is expected to be one byte per pixel, and values should
 * not exceed the bitdepth or the pseudo palette (if used).
 */
#include "fb_draw.h"

/* bitmap image iterator, one pixel at a time */
struct fb_bitmap_iter {
	const u8 *data;
	unsigned long colors[2];
	int width, i;
};

static bool fb_bitmap_image(void *iterator, unsigned long *pixels, int *bits)
{
	struct fb_bitmap_iter *iter = iterator;

	if (iter->i < iter->width) {
		int bit = ~iter->i & (BITS_PER_BYTE-1);
		int byte = iter->i++ / BITS_PER_BYTE;

		*pixels = iter->colors[(iter->data[byte] >> bit) & 1];
		return true;
	}
	iter->data += BITS_TO_BYTES(iter->width);
	iter->i = 0;
	return false;
}

/* color image iterator, one pixel at a time */
struct fb_color_iter {
	const u8 *data;
	const u32 *palette;
	struct fb_reverse reverse;
	int shift;
	int width, i;
};

static bool fb_color_image(void *iterator, unsigned long *pixels, int *bits)
{
	struct fb_color_iter *iter = iterator;

	if (iter->i < iter->width) {
		unsigned long color = iter->data[iter->i++];

		if (iter->palette)
			color = iter->palette[color];
		*pixels = color << iter->shift;
		if (iter->reverse.pixel)
			*pixels = fb_reverse_bits_long(*pixels);
		return true;
	}
	iter->data += iter->width;
	iter->i = 0;
	return false;
}

/* bitmap image iterator, 4 pixels at a time */
struct fb_bitmap4x_iter {
	const u8 *data;
	u32 fgxcolor, bgcolor;
	int width, i;
	const u32 *expand;
	int bpp;
	bool top;
};

static bool fb_bitmap4x_image(void *iterator, unsigned long *pixels, int *bits)
{
	struct fb_bitmap4x_iter *iter = iterator;
	u8 data;

	if (iter->i >= BITS_PER_BYTE/2) {
		iter->i -= BITS_PER_BYTE/2;
		iter->top = !iter->top;
		if (iter->top)
			data = *iter->data++ >> BITS_PER_BYTE/2;
		else
			data = iter->data[-1] & ((1 << BITS_PER_BYTE/2)-1);
	} else if (iter->i != 0) {
		*bits = iter->bpp * iter->i;
		if (iter->top)
			data = iter->data[-1] & ((1 << BITS_PER_BYTE/2)-1);
		else
			data = *iter->data++ >> BITS_PER_BYTE/2;
#ifndef __LITTLE_ENDIAN
		data >>= BITS_PER_BYTE/2 - iter->i;
#endif
		iter->i = 0;
	} else {
		*bits = iter->bpp * BITS_PER_BYTE/2;
		iter->i = iter->width;
		iter->top = false;
		return false;
	}
	*pixels = (iter->fgxcolor & iter->expand[data]) ^ iter->bgcolor;
#ifndef __LITTLE_ENDIAN
	*pixels <<= BITS_PER_LONG - *bits;
#endif
	return true;
}

/* draw a line a group of pixels at a time */
static __always_inline void fb_bitblit(bool (*get)(void *iter, unsigned long *pixels,
						   int *bits),
				       void *iter, int bits, struct fb_address *dst,
				       struct fb_reverse reverse)
{
	unsigned long pixels, val, mask, old;
	int offset = 0;
	int shift = dst->bits;

	if (shift) {
		old = fb_read_offset(0, dst);
		val = fb_reverse_long(old, reverse);
		val &= ~fb_right(~0UL, shift);
	} else {
		old = 0;
		val = 0;
	}

	while (get(iter, &pixels, &bits)) {
		val |= fb_right(pixels, shift);
		shift += bits;

		if (shift < BITS_PER_LONG)
			continue;

		val = fb_reverse_long(val, reverse);
		fb_write_offset(val, offset++, dst);
		shift &= BITS_PER_LONG - 1;
		val = !shift ? 0 : fb_left(pixels, bits - shift);
	}

	if (shift) {
		mask = ~fb_pixel_mask(shift, reverse);
		val = fb_reverse_long(val, reverse);
		if (offset || !dst->bits)
			old = fb_read_offset(offset, dst);
		fb_write_offset(fb_comp(val, old, mask), offset, dst);
	}
}

/* draw a color image a pixel at a time */
static inline void fb_color_imageblit(const struct fb_image *image, struct fb_address *dst,
				      unsigned int bits_per_line, const u32 *palette, int bpp,
				      struct fb_reverse reverse)
{
	struct fb_color_iter iter;
	u32 height;

	iter.data = (const u8 *)image->data;
	iter.palette = palette;
	iter.reverse = reverse;
#ifdef __LITTLE_ENDIAN
	if (reverse.pixel)
		iter.shift = BITS_PER_BYTE - bpp;
	else
		iter.shift = 0;
#else
	if (reverse.pixel)
		iter.shift = BITS_PER_LONG - BITS_PER_BYTE;
	else
		iter.shift = BITS_PER_LONG - bpp;
#endif
	iter.width = image->width;
	iter.i = 0;

	height = image->height;
	while (height--) {
		fb_bitblit(fb_color_image, &iter, bpp, dst, reverse);
		fb_address_forward(dst, bits_per_line);
	}
}

#ifdef __LITTLE_ENDIAN
#define FB_GEN(a, b) (((a)/8+(((a)&4)<<((b)-2)) \
		       +(((a)&2)<<((b)*2-1))+(((a)&1u)<<((b)*3)))*((1<<(b))-1))
#define FB_GEN1(a) ((a)/8+((a)&4)/2+((a)&2)*2+((a)&1)*8)
#else
#define FB_GEN(a, b) (((((a)/8)<<((b)*3))+(((a)&4)<<((b)*2-2)) \
		       +(((a)&2)<<(b-1))+((a)&1u))*((1<<(b))-1))
#define FB_GEN1(a) (a)
#endif

#define FB_GENx(a) { FB_GEN(0, (a)), FB_GEN(1, (a)), FB_GEN(2, (a)), FB_GEN(3, (a)),	\
	FB_GEN(4, (a)), FB_GEN(5, (a)), FB_GEN(6, (a)), FB_GEN(7, (a)),			\
	FB_GEN(8, (a)), FB_GEN(9, (a)), FB_GEN(10, (a)), FB_GEN(11, (a)),		\
	FB_GEN(12, (a)), FB_GEN(13, (a)), FB_GEN(14, (a)), FB_GEN(15, (a)) }

/* draw a 2 color image four pixels at a time (for 1-8 bpp only) */
static inline void fb_bitmap4x_imageblit(const struct fb_image *image, struct fb_address *dst,
					 unsigned long fgcolor, unsigned long bgcolor, int bpp,
					 unsigned int bits_per_line, struct fb_reverse reverse)
{
	static const u32 mul[BITS_PER_BYTE] = {
		0xf, 0x55, 0x249, 0x1111, 0x8421, 0x41041, 0x204081, 0x01010101
	};
	static const u32 expand[BITS_PER_BYTE][1 << 4] = {
		{
			FB_GEN1(0), FB_GEN1(1), FB_GEN1(2), FB_GEN1(3),
			FB_GEN1(4), FB_GEN1(5), FB_GEN1(6), FB_GEN1(7),
			FB_GEN1(8), FB_GEN1(9), FB_GEN1(10), FB_GEN1(11),
			FB_GEN1(12), FB_GEN1(13), FB_GEN1(14), FB_GEN1(15)
		},
		FB_GENx(2), FB_GENx(3), FB_GENx(4),
		FB_GENx(5), FB_GENx(6), FB_GENx(7), FB_GENx(8),
	};
	struct fb_bitmap4x_iter iter;
	u32 height;

	iter.data = (const u8 *)image->data;
	if (reverse.pixel) {
		fgcolor = fb_reverse_bits_long(fgcolor << (BITS_PER_BYTE - bpp));
		bgcolor = fb_reverse_bits_long(bgcolor << (BITS_PER_BYTE - bpp));
	}
	iter.fgxcolor = (fgcolor ^ bgcolor) * mul[bpp-1];
	iter.bgcolor = bgcolor * mul[bpp-1];
	iter.width = image->width;
	iter.i = image->width;
	iter.expand = expand[bpp-1];
	iter.bpp = bpp;
	iter.top = false;

	height = image->height;
	while (height--) {
		fb_bitblit(fb_bitmap4x_image, &iter, bpp * BITS_PER_BYTE/2, dst, reverse);
		fb_address_forward(dst, bits_per_line);
	}
}

/* draw a bitmap image 1 pixel at a time (for >8 bpp) */
static inline void fb_bitmap1x_imageblit(const struct fb_image *image, struct fb_address *dst,
					 unsigned long fgcolor, unsigned long bgcolor, int bpp,
					 unsigned int bits_per_line, struct fb_reverse reverse)
{
	struct fb_bitmap_iter iter;
	u32 height;

	iter.colors[0] = bgcolor;
	iter.colors[1] = fgcolor;
#ifndef __LITTLE_ENDIAN
	iter.colors[0] <<= BITS_PER_LONG - bpp;
	iter.colors[1] <<= BITS_PER_LONG - bpp;
#endif
	iter.data = (const u8 *)image->data;
	iter.width = image->width;
	iter.i = 0;

	height = image->height;
	while (height--) {
		fb_bitblit(fb_bitmap_image, &iter, bpp, dst, reverse);
		fb_address_forward(dst, bits_per_line);
	}
}

/* one pixel per word, 64/32 bpp blitting */
static inline void fb_bitmap_1ppw(const struct fb_image *image, struct fb_address *dst,
				  unsigned long fgcolor, unsigned long bgcolor,
				  int words_per_line, struct fb_reverse reverse)
{
	unsigned long tab[2];
	const u8 *src = (u8 *)image->data;
	int width = image->width;
	int offset;
	u32 height;

	if (reverse.byte) {
		tab[0] = swab_long(bgcolor);
		tab[1] = swab_long(fgcolor);
	} else {
		tab[0] = bgcolor;
		tab[1] = fgcolor;
	}

	height = image->height;
	while (height--) {
		for (offset = 0; offset + 8 <= width; offset += 8) {
			unsigned int srcbyte = *src++;

			fb_write_offset(tab[(srcbyte >> 7) & 1], offset + 0, dst);
			fb_write_offset(tab[(srcbyte >> 6) & 1], offset + 1, dst);
			fb_write_offset(tab[(srcbyte >> 5) & 1], offset + 2, dst);
			fb_write_offset(tab[(srcbyte >> 4) & 1], offset + 3, dst);
			fb_write_offset(tab[(srcbyte >> 3) & 1], offset + 4, dst);
			fb_write_offset(tab[(srcbyte >> 2) & 1], offset + 5, dst);
			fb_write_offset(tab[(srcbyte >> 1) & 1], offset + 6, dst);
			fb_write_offset(tab[(srcbyte >> 0) & 1], offset + 7, dst);
		}

		if (offset < width) {
			unsigned int srcbyte = *src++;

			while (offset < width) {
				fb_write_offset(tab[(srcbyte >> 7) & 1], offset, dst);
				srcbyte <<= 1;
				offset++;
			}
		}
		fb_address_move_long(dst, words_per_line);
	}
}

static inline unsigned long fb_pack(unsigned long left, unsigned long right, int bits)
{
#ifdef __LITTLE_ENDIAN
	return left | right << bits;
#else
	return right | left << bits;
#endif
}

/* aligned 32/16 bpp blitting */
static inline void fb_bitmap_2ppw(const struct fb_image *image, struct fb_address *dst,
				  unsigned long fgcolor, unsigned long bgcolor,
				  int words_per_line, struct fb_reverse reverse)
{
	unsigned long tab[4];
	const u8 *src = (u8 *)image->data;
	int width = image->width / 2;
	int offset;
	u32 height;

	tab[0] = fb_pack(bgcolor, bgcolor, BITS_PER_LONG/2);
	tab[1] = fb_pack(bgcolor, fgcolor, BITS_PER_LONG/2);
	tab[2] = fb_pack(fgcolor, bgcolor, BITS_PER_LONG/2);
	tab[3] = fb_pack(fgcolor, fgcolor, BITS_PER_LONG/2);

	if (reverse.byte) {
		tab[0] = swab_long(tab[0]);
		tab[1] = swab_long(tab[1]);
		tab[2] = swab_long(tab[2]);
		tab[3] = swab_long(tab[3]);
	}

	height = image->height;
	while (height--) {
		for (offset = 0; offset + 4 <= width; offset += 4) {
			unsigned int srcbyte = *src++;

			fb_write_offset(tab[(srcbyte >> 6) & 3], offset + 0, dst);
			fb_write_offset(tab[(srcbyte >> 4) & 3], offset + 1, dst);
			fb_write_offset(tab[(srcbyte >> 2) & 3], offset + 2, dst);
			fb_write_offset(tab[(srcbyte >> 0) & 3], offset + 3, dst);
		}

		if (offset < width) {
			unsigned int srcbyte = *src++;

			while (offset < width) {
				fb_write_offset(tab[(srcbyte >> 6) & 3], offset, dst);
				srcbyte <<= 2;
				offset++;
			}
		}
		fb_address_move_long(dst, words_per_line);
	}
}

#define FB_PATP(a, b) (((a)<<((b)*BITS_PER_LONG/4))*((1UL<<BITS_PER_LONG/4)-1UL))
#define FB_PAT4(a) (FB_PATP((a)&1, 0)|FB_PATP(((a)&2)/2, 1)| \
	FB_PATP(((a)&4)/4, 2)|FB_PATP(((a)&8)/8, 3))

/* aligned 16/8 bpp blitting */
static inline void fb_bitmap_4ppw(const struct fb_image *image, struct fb_address *dst,
				  unsigned long fgcolor, unsigned long bgcolor,
				  int words_per_line, struct fb_reverse reverse)
{
	static const unsigned long tab16_be[] = {
		0, FB_PAT4(1UL), FB_PAT4(2UL), FB_PAT4(3UL),
		FB_PAT4(4UL), FB_PAT4(5UL), FB_PAT4(6UL), FB_PAT4(7UL),
		FB_PAT4(8UL), FB_PAT4(9UL), FB_PAT4(10UL), FB_PAT4(11UL),
		FB_PAT4(12UL), FB_PAT4(13UL), FB_PAT4(14UL), ~0UL
	};
	static const unsigned long tab16_le[] = {
		0, FB_PAT4(8UL), FB_PAT4(4UL), FB_PAT4(12UL),
		FB_PAT4(2UL), FB_PAT4(10UL), FB_PAT4(6UL), FB_PAT4(14UL),
		FB_PAT4(1UL), FB_PAT4(9UL), FB_PAT4(5UL), FB_PAT4(13UL),
		FB_PAT4(3UL), FB_PAT4(11UL), FB_PAT4(7UL), ~0UL
	};
	const unsigned long *tab;
	const u8 *src = (u8 *)image->data;
	int width = image->width / 4;
	int offset;
	u32 height;

	fgcolor = fgcolor | fgcolor << BITS_PER_LONG/4;
	bgcolor = bgcolor | bgcolor << BITS_PER_LONG/4;
	fgcolor = fgcolor | fgcolor << BITS_PER_LONG/2;
	bgcolor = bgcolor | bgcolor << BITS_PER_LONG/2;
	fgcolor ^= bgcolor;

	if (BITS_PER_LONG/4 > BITS_PER_BYTE && reverse.byte) {
		fgcolor = swab_long(fgcolor);
		bgcolor = swab_long(bgcolor);
	}

#ifdef __LITTLE_ENDIAN
	tab = reverse.byte ? tab16_be : tab16_le;
#else
	tab = reverse.byte ? tab16_le : tab16_be;
#endif

	height = image->height;
	while (height--) {
		for (offset = 0; offset + 2 <= width; offset += 2, src++) {
			fb_write_offset((fgcolor & tab[*src >> 4]) ^ bgcolor, offset + 0, dst);
			fb_write_offset((fgcolor & tab[*src & 0xf]) ^ bgcolor, offset + 1, dst);
		}

		if (offset < width)
			fb_write_offset((fgcolor & tab[*src++ >> 4]) ^ bgcolor, offset, dst);

		fb_address_move_long(dst, words_per_line);
	}
}

static inline void fb_bitmap_imageblit(const struct fb_image *image, struct fb_address *dst,
				       unsigned int bits_per_line, const u32 *palette, int bpp,
				       struct fb_reverse reverse)
{
	unsigned long fgcolor, bgcolor;

	if (palette) {
		fgcolor = palette[image->fg_color];
		bgcolor = palette[image->bg_color];
	} else {
		fgcolor = image->fg_color;
		bgcolor = image->bg_color;
	}

	if (!dst->bits && !(bits_per_line & (BITS_PER_LONG-1))) {
		if (bpp == BITS_PER_LONG && BITS_PER_LONG == 32) {
			fb_bitmap_1ppw(image, dst, fgcolor, bgcolor,
				       bits_per_line / BITS_PER_LONG, reverse);
			return;
		}
		if (bpp == BITS_PER_LONG/2 && !(image->width & 1)) {
			fb_bitmap_2ppw(image, dst, fgcolor, bgcolor,
				       bits_per_line / BITS_PER_LONG, reverse);
			return;
		}
		if (bpp == BITS_PER_LONG/4 && !(image->width & 3)) {
			fb_bitmap_4ppw(image, dst, fgcolor, bgcolor,
				       bits_per_line / BITS_PER_LONG, reverse);
			return;
		}
	}

	if (bpp > 0 && bpp <= BITS_PER_BYTE)
		fb_bitmap4x_imageblit(image, dst, fgcolor, bgcolor, bpp,
				     bits_per_line, reverse);
	else if (bpp > BITS_PER_BYTE && bpp <= BITS_PER_LONG)
		fb_bitmap1x_imageblit(image, dst, fgcolor, bgcolor, bpp,
				     bits_per_line, reverse);
}

static inline void fb_imageblit(struct fb_info *p, const struct fb_image *image)
{
	int bpp = p->var.bits_per_pixel;
	unsigned int bits_per_line = BYTES_TO_BITS(p->fix.line_length);
	struct fb_address dst = fb_address_init(p);
	struct fb_reverse reverse = fb_reverse_init(p);
	const u32 *palette = fb_palette(p);

	fb_address_forward(&dst, image->dy * bits_per_line + image->dx * bpp);

	if (image->depth == 1)
		fb_bitmap_imageblit(image, &dst, bits_per_line, palette, bpp, reverse);
	else
		fb_color_imageblit(image, &dst, bits_per_line, palette, bpp, reverse);
}
]

10/2 2026/01/29 20:16 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 12026
							output: 30
							thoughts: 1381
						

Thoughts:
**Analyzing a Potential Framebuffer Write Out-of-Bounds**

Okay, let's break this down. We've got a crash, a `vmalloc-out-of-bounds` error, and it seems to be happening within the framebuffer code. `fb_write_offset` is the culprit. Looking at the code, `fb_write_offset` takes a value, an offset, and a `fb_address` struct. That struct contains a `void *` pointer (`address`) to the framebuffer memory and a `bits` field. The function then casts `address` to an `unsigned long *` and writes the value at the specified offset.  Essentially, it's performing a write to `*(unsigned long *)(dst->address + offset * sizeof(unsigned long))`.

The call chain goes from `fb_bitmap_2ppw` to `fb_write_offset`. `fb_bitmap_2ppw` seems to handle bitmap rendering, specifically when we're dealing with two pixels per word.  It iterates through the image's height and width, calculating offsets, and calling `fb_write_offset` to write pixel data. The `dst` pointer, which represents the destination address within the framebuffer, is updated using `fb_address_move_long` within the loop. The `words_per_line` parameter for this move comes from `fb_bitmap_imageblit` which, in turn, gets it as `p->fix.line_length * 8 / BITS_PER_LONG`.  Effectively, it's calculated as the line length in bytes divided by the size of a long.

The initial `dst` address is set up in `fb_imageblit`, where a `fb_address` structure is initialized, and then advanced by an offset determined by the image's `dx`, `dy`, `bpp`, and line length. So, the `dst` points to the starting location in the framebuffer where the image should be drawn.

The crucial point is that the crash happens during a write to memory address `ffffc90002c223c0`, a write of size 8 bytes, and KASAN is reporting poisoned memory around this address, specifically with the `f8` value, indicating an out-of-bounds or invalid memory region, a `KASAN_VMALLOC_INVALID`. Given that the framebuffer is vmalloced, this suggests an out-of-bounds write within the vmalloc'd region.

Now, there's a little bit of a puzzle in the call chain as reported by the trace. It seems `sys_imageblit` calls `drm_fbdev_shmem_defio_imageblit`, which then calls `cw_putcs`. This is peculiar because `drm_fbdev_shmem_defio_imageblit` is registered as an `imageblit` function. The key is in understanding how `cw_putcs` and `cw_putcs_aligned` fits into this. It appears `cw_putcs` calls `cw_putcs_aligned`, and `cw_putcs_aligned` in turn calls the `imageblit` operation provided by the framebuffer driver, which is where `drm_fbdev_shmem_defio_imageblit` comes in. This calls `sys_imageblit` via `fbcon`.

So, the issue is that something in the chain from the character writing, through the imageblit call, is providing invalid destination parameters to `fb_bitmap_2ppw`, resulting in an out-of-bounds write in `fb_write_offset`. Specifically, the offset calculation, or the way the `dst` is updated, leads to writing outside the bounds of the framebuffer memory. We need to focus on what values feed into the destination.



11/2 2026/01/29 20:16 tool read-file
0m Args:
map[File:drivers/video/fbdev/core/fbcon_cw.c]

Results:
map[Contents:/*
 *  linux/drivers/video/console/fbcon_ud.c -- Software Rotation - 90 degrees
 *
 *      Copyright (C) 2005 Antonino Daplas <adaplas @pol.net>
 *
 *  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/module.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/fb.h>
#include <linux/vt_kern.h>
#include <linux/console.h>
#include <asm/types.h>
#include "fbcon.h"
#include "fbcon_rotate.h"

/*
 * Rotation 90 degrees
 */

static void cw_update_attr(u8 *dst, u8 *src, int attribute,
				  struct vc_data *vc)
{
	int i, j, offset = (vc->vc_font.height < 10) ? 1 : 2;
	int width = (vc->vc_font.height + 7) >> 3;
	u8 c, msk = ~(0xff >> offset);

	for (i = 0; i < vc->vc_font.width; i++) {
		for (j = 0; j < width; j++) {
			c = *src;
			if (attribute & FBCON_ATTRIBUTE_UNDERLINE && !j)
				c |= msk;
			if (attribute & FBCON_ATTRIBUTE_BOLD && i)
				c |= *(src-width);
			if (attribute & FBCON_ATTRIBUTE_REVERSE)
				c = ~c;
			src++;
			*dst++ = c;
		}
	}
}


static void cw_bmove(struct vc_data *vc, struct fb_info *info, int sy,
		     int sx, int dy, int dx, int height, int width)
{
	struct fbcon_par *par = info->fbcon_par;
	struct fb_copyarea area;
	u32 vxres = GETVXRES(par->p, info);

	area.sx = vxres - ((sy + height) * vc->vc_font.height);
	area.sy = sx * vc->vc_font.width;
	area.dx = vxres - ((dy + height) * vc->vc_font.height);
	area.dy = dx * vc->vc_font.width;
	area.width = height * vc->vc_font.height;
	area.height  = width * vc->vc_font.width;

	info->fbops->fb_copyarea(info, &area);
}

static void cw_clear(struct vc_data *vc, struct fb_info *info, int sy,
		     int sx, int height, int width, int fg, int bg)
{
	struct fbcon_par *par = info->fbcon_par;
	struct fb_fillrect region;
	u32 vxres = GETVXRES(par->p, info);

	region.color = bg;
	region.dx = vxres - ((sy + height) * vc->vc_font.height);
	region.dy = sx *  vc->vc_font.width;
	region.height = width * vc->vc_font.width;
	region.width = height * vc->vc_font.height;
	region.rop = ROP_COPY;

	info->fbops->fb_fillrect(info, &region);
}

static inline void cw_putcs_aligned(struct vc_data *vc, struct fb_info *info,
				    const u16 *s, u32 attr, u32 cnt,
				    u32 d_pitch, u32 s_pitch, u32 cellsize,
				    struct fb_image *image, u8 *buf, u8 *dst)
{
	struct fbcon_par *par = info->fbcon_par;
	u16 charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
	u32 idx = (vc->vc_font.height + 7) >> 3;
	u8 *src;

	while (cnt--) {
		src = par->fontbuffer + (scr_readw(s++) & charmask) * cellsize;

		if (attr) {
			cw_update_attr(buf, src, attr, vc);
			src = buf;
		}

		if (likely(idx == 1))
			__fb_pad_aligned_buffer(dst, d_pitch, src, idx,
						vc->vc_font.width);
		else
			fb_pad_aligned_buffer(dst, d_pitch, src, idx,
					      vc->vc_font.width);

		dst += d_pitch * vc->vc_font.width;
	}

	info->fbops->fb_imageblit(info, image);
}

static void cw_putcs(struct vc_data *vc, struct fb_info *info,
		      const unsigned short *s, int count, int yy, int xx,
		      int fg, int bg)
{
	struct fb_image image;
	struct fbcon_par *par = info->fbcon_par;
	u32 width = (vc->vc_font.height + 7)/8;
	u32 cellsize = width * vc->vc_font.width;
	u32 maxcnt = info->pixmap.size/cellsize;
	u32 scan_align = info->pixmap.scan_align - 1;
	u32 buf_align = info->pixmap.buf_align - 1;
	u32 cnt, pitch, size;
	u32 attribute = get_attribute(info, scr_readw(s));
	u8 *dst, *buf = NULL;
	u32 vxres = GETVXRES(par->p, info);

	if (!par->fontbuffer)
		return;

	image.fg_color = fg;
	image.bg_color = bg;
	image.dx = vxres - ((yy + 1) * vc->vc_font.height);
	image.dy = xx * vc->vc_font.width;
	image.width = vc->vc_font.height;
	image.depth = 1;

	if (attribute) {
		buf = kmalloc(cellsize, GFP_KERNEL);
		if (!buf)
			return;
	}

	while (count) {
		if (count > maxcnt)
			cnt = maxcnt;
		else
			cnt = count;

		image.height = vc->vc_font.width * cnt;
		pitch = ((image.width + 7) >> 3) + scan_align;
		pitch &= ~scan_align;
		size = pitch * image.height + buf_align;
		size &= ~buf_align;
		dst = fb_get_buffer_offset(info, &info->pixmap, size);
		image.data = dst;
		cw_putcs_aligned(vc, info, s, attribute, cnt, pitch,
				 width, cellsize, &image, buf, dst);
		image.dy += image.height;
		count -= cnt;
		s += cnt;
	}

	/* buf is always NULL except when in monochrome mode, so in this case
	   it's a gain to check buf against NULL even though kfree() handles
	   NULL pointers just fine */
	if (unlikely(buf))
		kfree(buf);

}

static void cw_clear_margins(struct vc_data *vc, struct fb_info *info,
			     int color, int bottom_only)
{
	unsigned int cw = vc->vc_font.width;
	unsigned int ch = vc->vc_font.height;
	unsigned int rw = info->var.yres - (vc->vc_cols*cw);
	unsigned int bh = info->var.xres - (vc->vc_rows*ch);
	unsigned int rs = info->var.yres - rw;
	struct fb_fillrect region;

	region.color = color;
	region.rop = ROP_COPY;

	if ((int) rw > 0 && !bottom_only) {
		region.dx = 0;
		region.dy = info->var.yoffset + rs;
		region.height = rw;
		region.width = info->var.xres_virtual;
		info->fbops->fb_fillrect(info, &region);
	}

	if ((int) bh > 0) {
		region.dx = info->var.xoffset;
		region.dy = info->var.yoffset;
                region.height = info->var.yres;
                region.width = bh;
		info->fbops->fb_fillrect(info, &region);
	}
}

static void cw_cursor(struct vc_data *vc, struct fb_info *info, bool enable,
		      int fg, int bg)
{
	struct fb_cursor cursor;
	struct fbcon_par *par = info->fbcon_par;
	unsigned short charmask = vc->vc_hi_font_mask ? 0x1ff : 0xff;
	int w = (vc->vc_font.height + 7) >> 3, c;
	int y = real_y(par->p, vc->state.y);
	int attribute, use_sw = vc->vc_cursor_type & CUR_SW;
	int err = 1, dx, dy;
	char *src;
	u32 vxres = GETVXRES(par->p, info);

	if (!par->fontbuffer)
		return;

	cursor.set = 0;

 	c = scr_readw((u16 *) vc->vc_pos);
	attribute = get_attribute(info, c);
	src = par->fontbuffer + ((c & charmask) * (w * vc->vc_font.width));

	if (par->cursor_state.image.data != src ||
	    par->cursor_reset) {
		par->cursor_state.image.data = src;
		cursor.set |= FB_CUR_SETIMAGE;
	}

	if (attribute) {
		u8 *dst;

		dst = kmalloc_array(w, vc->vc_font.width, GFP_ATOMIC);
		if (!dst)
			return;
		kfree(par->cursor_data);
		par->cursor_data = dst;
		cw_update_attr(dst, src, attribute, vc);
		src = dst;
	}

	if (par->cursor_state.image.fg_color != fg ||
	    par->cursor_state.image.bg_color != bg ||
	    par->cursor_reset) {
		par->cursor_state.image.fg_color = fg;
		par->cursor_state.image.bg_color = bg;
		cursor.set |= FB_CUR_SETCMAP;
	}

	if (par->cursor_state.image.height != vc->vc_font.width ||
	    par->cursor_state.image.width != vc->vc_font.height ||
	    par->cursor_reset) {
		par->cursor_state.image.height = vc->vc_font.width;
		par->cursor_state.image.width = vc->vc_font.height;
		cursor.set |= FB_CUR_SETSIZE;
	}

	dx = vxres - ((y * vc->vc_font.height) + vc->vc_font.height);
	dy = vc->state.x * vc->vc_font.width;

	if (par->cursor_state.image.dx != dx ||
	    par->cursor_state.image.dy != dy ||
	    par->cursor_reset) {
		par->cursor_state.image.dx = dx;
		par->cursor_state.image.dy = dy;
		cursor.set |= FB_CUR_SETPOS;
	}

	if (par->cursor_state.hot.x || par->cursor_state.hot.y ||
	    par->cursor_reset) {
		par->cursor_state.hot.x = cursor.hot.y = 0;
		cursor.set |= FB_CUR_SETHOT;
	}

	if (cursor.set & FB_CUR_SETSIZE ||
	    vc->vc_cursor_type != par->p->cursor_shape ||
	    par->cursor_state.mask == NULL ||
	    par->cursor_reset) {
		char *tmp, *mask = kmalloc_array(w, vc->vc_font.width,
						 GFP_ATOMIC);
		int cur_height, size, i = 0;
		int width = (vc->vc_font.width + 7)/8;

		if (!mask)
			return;

		tmp = kmalloc_array(width, vc->vc_font.height, GFP_ATOMIC);

		if (!tmp) {
			kfree(mask);
			return;
		}

		kfree(par->cursor_state.mask);
		par->cursor_state.mask = mask;

		par->p->cursor_shape = vc->vc_cursor_type;
		cursor.set |= FB_CUR_SETSHAPE;

		switch (CUR_SIZE(par->p->cursor_shape)) {
		case CUR_NONE:
			cur_height = 0;
			break;
		case CUR_UNDERLINE:
			cur_height = (vc->vc_font.height < 10) ? 1 : 2;
			break;
		case CUR_LOWER_THIRD:
			cur_height = vc->vc_font.height/3;
			break;
		case CUR_LOWER_HALF:
			cur_height = vc->vc_font.height >> 1;
			break;
		case CUR_TWO_THIRDS:
			cur_height = (vc->vc_font.height << 1)/3;
			break;
		case CUR_BLOCK:
		default:
			cur_height = vc->vc_font.height;
			break;
		}

		size = (vc->vc_font.height - cur_height) * width;
		while (size--)
			tmp[i++] = 0;
		size = cur_height * width;
		while (size--)
			tmp[i++] = 0xff;
		memset(mask, 0, w * vc->vc_font.width);
		rotate_cw(tmp, mask, vc->vc_font.width, vc->vc_font.height);
		kfree(tmp);
	}

	par->cursor_state.enable = enable && !use_sw;

	cursor.image.data = src;
	cursor.image.fg_color = par->cursor_state.image.fg_color;
	cursor.image.bg_color = par->cursor_state.image.bg_color;
	cursor.image.dx = par->cursor_state.image.dx;
	cursor.image.dy = par->cursor_state.image.dy;
	cursor.image.height = par->cursor_state.image.height;
	cursor.image.width = par->cursor_state.image.width;
	cursor.hot.x = par->cursor_state.hot.x;
	cursor.hot.y = par->cursor_state.hot.y;
	cursor.mask = par->cursor_state.mask;
	cursor.enable = par->cursor_state.enable;
	cursor.image.depth = 1;
	cursor.rop = ROP_XOR;

	if (info->fbops->fb_cursor)
		err = info->fbops->fb_cursor(info, &cursor);

	if (err)
		soft_cursor(info, &cursor);

	par->cursor_reset = 0;
}

static int cw_update_start(struct fb_info *info)
{
	struct fbcon_par *par = info->fbcon_par;
	u32 vxres = GETVXRES(par->p, info);
	u32 xoffset;
	int err;

	xoffset = vxres - (info->var.xres + par->var.yoffset);
	par->var.yoffset = par->var.xoffset;
	par->var.xoffset = xoffset;
	err = fb_pan_display(info, &par->var);
	par->var.xoffset = info->var.xoffset;
	par->var.yoffset = info->var.yoffset;
	par->var.vmode = info->var.vmode;
	return err;
}

static const struct fbcon_bitops cw_fbcon_bitops = {
	.bmove = cw_bmove,
	.clear = cw_clear,
	.putcs = cw_putcs,
	.clear_margins = cw_clear_margins,
	.cursor = cw_cursor,
	.update_start = cw_update_start,
	.rotate_font = fbcon_rotate_font,
};

void fbcon_set_bitops_cw(struct fbcon_par *par)
{
	par->bitops = &cw_fbcon_bitops;
}
]

12/2 2026/01/29 20:16 llm debugger
2m Model:
gemini-3-pro-preview

Tokens:
							input: 18387
							output: 30
							thoughts: 12388
						

Thoughts:
**Analysis of a Framebuffer Rotation Crash**

Okay, here's what I've figured out. We're getting a `vmalloc-out-of-bounds` crash within the framebuffer console driver, specifically when the screen is rotated 90 degrees clockwise. The issue lies in how the driver calculates the offsets for drawing characters, especially when the virtual and physical resolutions are significantly different.

The core of the problem stems from the `GETVXRES` macro. In the `fbcon_cw.c` file (which handles the clockwise rotation), this macro currently returns `yres_virtual`, which is the total *height* of the *virtual* framebuffer (including scrollback), when the screen is rotated. However, for drawing operations, we need the *width* of the *physical* framebuffer (which becomes the *logical height* after rotation). We need `xres_virtual` here.

The crash occurs because the code in `cw_putcs` (and subsequently `fb_bitmap_2ppw` and `fb_write_offset`) uses this incorrect `vxres` value to calculate the physical X coordinate (`dx`) of where characters are drawn on the rotated screen. When we use `yres_virtual`, the calculation of `dx` produces very large values near the right edge of the *physical* framebuffer (which is the top of the rotated logical screen), which, when combined with logical height that can be large, results in offsets that exceed the allocated framebuffer memory, triggering the out-of-bounds write.

In short, when the `dx` calculation uses a large `yres_virtual`, it effectively shifts the drawing operation down multiple lines, and when it is added to the starting offset, it can push the writing location past the end of the allocated memory, particularly if the initial physical offset `dy` is already near the bottom of the virtual framebuffer.

The fix is simple: modify the `GETVXRES` macro in `fbcon_cw.c` to return `xres_virtual` instead of `yres_virtual`, or simply replace `vxres` with `info->var.xres_virtual` in the specific cases like `cw_putcs`, `cw_clear` and `cw_bmove`. This will ensure that the physical X coordinate is correctly calculated relative to the *physical* screen width, avoiding the out-of-bounds writes. This should resolve the crash. I'm going to look into this with an updated patch.



13/2 2026/01/29 20:19 tool read-file
0m Args:
map[File:drivers/video/fbdev/core/fbcon_rotate.h]

Results:
map[Contents:/*
 *  linux/drivers/video/console/fbcon_rotate.h -- Software Display Rotation
 *
 *	Copyright (C) 2005 Antonino Daplas <adaplas@pol.net>
 *
 *  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.
 */

#ifndef _FBCON_ROTATE_H
#define _FBCON_ROTATE_H

#define GETVYRES(s,i) ({                           \
        (fb_scrollmode(s) == SCROLL_REDRAW || fb_scrollmode(s) == SCROLL_MOVE) ? \
        (i)->var.yres : (i)->var.yres_virtual; })

#define GETVXRES(s,i) ({                           \
        (fb_scrollmode(s) == SCROLL_REDRAW || fb_scrollmode(s) == SCROLL_MOVE || !(i)->fix.xpanstep) ? \
        (i)->var.xres : (i)->var.xres_virtual; })


static inline int pattern_test_bit(u32 x, u32 y, u32 pitch, const char *pat)
{
	u32 tmp = (y * pitch) + x, index = tmp / 8,  bit = tmp % 8;

	pat +=index;
	return (*pat) & (0x80 >> bit);
}

static inline void pattern_set_bit(u32 x, u32 y, u32 pitch, char *pat)
{
	u32 tmp = (y * pitch) + x, index = tmp / 8, bit = tmp % 8;

	pat += index;

	(*pat) |= 0x80 >> bit;
}

static inline void rotate_ud(const char *in, char *out, u32 width, u32 height)
{
	int i, j;
	int shift = (8 - (width % 8)) & 7;

	width = (width + 7) & ~7;

	for (i = 0; i < height; i++) {
		for (j = 0; j < width - shift; j++) {
			if (pattern_test_bit(j, i, width, in))
				pattern_set_bit(width - (1 + j + shift),
						height - (1 + i),
						width, out);
		}

	}
}

static inline void rotate_cw(const char *in, char *out, u32 width, u32 height)
{
	int i, j, h = height, w = width;
	int shift = (8 - (height % 8)) & 7;

	width = (width + 7) & ~7;
	height = (height + 7) & ~7;

	for (i = 0; i < h; i++) {
		for (j = 0; j < w; j++) {
			if (pattern_test_bit(j, i, width, in))
				pattern_set_bit(height - 1 - i - shift, j,
						height, out);

		}
	}
}

static inline void rotate_ccw(const char *in, char *out, u32 width, u32 height)
{
	int i, j, h = height, w = width;
	int shift = (8 - (width % 8)) & 7;

	width = (width + 7) & ~7;
	height = (height + 7) & ~7;

	for (i = 0; i < h; i++) {
		for (j = 0; j < w; j++) {
			if (pattern_test_bit(j, i, width, in))
				pattern_set_bit(i, width - 1 - j - shift,
						height, out);
		}
	}
}

int fbcon_rotate_font(struct fb_info *info, struct vc_data *vc);

#if defined(CONFIG_FRAMEBUFFER_CONSOLE_ROTATION)
void fbcon_set_bitops_cw(struct fbcon_par *par);
void fbcon_set_bitops_ud(struct fbcon_par *par);
void fbcon_set_bitops_ccw(struct fbcon_par *par);
#else
static inline void fbcon_set_bitops_cw(struct fbcon_par *par)
{ }
static inline void fbcon_set_bitops_ud(struct fbcon_par *par)
{ }
static inline void fbcon_set_bitops_ccw(struct fbcon_par *par)
{ }
#endif

#endif
]

14/2 2026/01/29 20:19 llm debugger
0m Model:
gemini-3-pro-preview

Tokens:
							input: 32457
							output: 28
							thoughts: 801
						

Thoughts:
**Analyzing the `GETVXRES` Macro in the Framebuffer Console Driver**

Okay, so I was looking at how the `GETVXRES` macro is defined and used within the framebuffer console driver, specifically in relation to screen rotation. My initial impression was that the definition in `fbcon_cw.c` was overwriting the one in `fbcon_rotate.h`, and that it returned `yres_virtual`, which seemed wrong.

First, I examined the definition of `GETVXRES` in `fbcon_rotate.h`, which looks correct (returns `xres` or `xres_virtual` depending on scroll mode and xpanstep). I then went to check the definition in `fbcon_cw.c`, since I seemed to remember it being defined differently, possibly returning `yres_virtual`. I used `read-file` to view the source code.

I then did a search in the `fbcon_cw.c` output for `GETVXRES`, found it being *used* in the `cw_bmove` function. It seems that `GETVXRES(par->p, info)` is used, where `par` is `info->fbcon_par` and `info` is a `fb_info` structure, and it returns a `u32` value.

However, I realized that the output I got via `read-file` *did not* define `GETVXRES` within `fbcon_cw.c` itself. It *included* `fbcon_rotate.h`. This means `fbcon_cw.c` is using the `GETVXRES` definition from `fbcon_rotate.h` - which returns either `xres` or `xres_virtual`.

Therefore, my earlier assumption that the definition of `GETVXRES` was different in `fbcon_cw.c` or that it returned `yres_virtual` was incorrect. It must have been a false memory, or I misread something previously. Perhaps I had confused it with a similar concept, or mistakenly thought it was defined in a different header like `fbcon.h`. I need to revisit the `fbcon.h` include to be 100% sure.



15/2 2026/01/29 20:19 tool read-file
0m Args:
map[File:drivers/video/fbdev/core/fbcon.h]

Results:
map[Contents:/*
 *  linux/drivers/video/console/fbcon.h -- Low level frame buffer based console driver
 *
 *	Copyright (C) 1997 Geert Uytterhoeven
 *
 *  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.
 */

#ifndef _VIDEO_FBCON_H
#define _VIDEO_FBCON_H

#include <linux/types.h>
#include <linux/vt_buffer.h>
#include <linux/vt_kern.h>
#include <linux/workqueue.h>

#include <asm/io.h>

   /*
    *    This is the interface between the low-level console driver and the
    *    low-level frame buffer device
    */

struct fbcon_display {
    /* Filled in by the low-level console driver */
    const u_char *fontdata;
    int userfont;                   /* != 0 if fontdata kmalloc()ed */
#ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION
    u_short scrollmode;             /* Scroll Method, use fb_scrollmode() */
#endif
    u_short inverse;                /* != 0 text black on white as default */
    short yscroll;                  /* Hardware scrolling */
    int vrows;                      /* number of virtual rows */
    int cursor_shape;
    int con_rotate;
    u32 xres_virtual;
    u32 yres_virtual;
    u32 height;
    u32 width;
    u32 bits_per_pixel;
    u32 grayscale;
    u32 nonstd;
    u32 accel_flags;
    u32 rotate;
    struct fb_bitfield red;
    struct fb_bitfield green;
    struct fb_bitfield blue;
    struct fb_bitfield transp;
    const struct fb_videomode *mode;
};

struct fbcon_bitops {
	void (*bmove)(struct vc_data *vc, struct fb_info *info, int sy,
		      int sx, int dy, int dx, int height, int width);
	void (*clear)(struct vc_data *vc, struct fb_info *info, int sy,
		      int sx, int height, int width, int fb, int bg);
	void (*putcs)(struct vc_data *vc, struct fb_info *info,
		      const unsigned short *s, int count, int yy, int xx,
		      int fg, int bg);
	void (*clear_margins)(struct vc_data *vc, struct fb_info *info,
			      int color, int bottom_only);
	void (*cursor)(struct vc_data *vc, struct fb_info *info,
		       bool enable, int fg, int bg);
	int  (*update_start)(struct fb_info *info);
	int  (*rotate_font)(struct fb_info *info, struct vc_data *vc);
};

struct fbcon_par {
	struct fb_var_screeninfo var;  /* copy of the current fb_var_screeninfo */
	struct delayed_work cursor_work; /* Cursor timer */
	struct fb_cursor cursor_state;
	struct fbcon_display *p;
	struct fb_info *info;
        int    currcon;	                /* Current VC. */
	int    cur_blink_jiffies;
	int    cursor_flash;
	int    cursor_reset;
	int    blank_state;
	int    graphics;
	int    save_graphics; /* for debug enter/leave */
	bool   initialized;
	int    rotate;
	int    cur_rotate;
	char  *cursor_data;
	u8    *fontbuffer;
	u8    *fontdata;
	u8    *cursor_src;
	u32    cursor_size;
	u32    fd_size;

	const struct fbcon_bitops *bitops;
};

    /*
     *  Attribute Decoding
     */

/* Color */
#define attr_fgcol(fgshift,s)    \
	(((s) >> (fgshift)) & 0x0f)
#define attr_bgcol(bgshift,s)    \
	(((s) >> (bgshift)) & 0x0f)

/* Monochrome */
#define attr_bold(s) \
	((s) & 0x200)
#define attr_reverse(s) \
	((s) & 0x800)
#define attr_underline(s) \
	((s) & 0x400)
#define attr_blink(s) \
	((s) & 0x8000)

static inline int mono_col(const struct fb_info *info)
{
	__u32 max_len;
	max_len = max(info->var.green.length, info->var.red.length);
	max_len = max(info->var.blue.length, max_len);
	return (~(0xfff << max_len)) & 0xff;
}

    /*
     *  Scroll Method
     */

/* There are several methods fbcon can use to move text around the screen:
 *
 *                     Operation   Pan    Wrap
 *---------------------------------------------
 * SCROLL_MOVE         copyarea    No     No
 * SCROLL_PAN_MOVE     copyarea    Yes    No
 * SCROLL_WRAP_MOVE    copyarea    No     Yes
 * SCROLL_REDRAW       imageblit   No     No
 * SCROLL_PAN_REDRAW   imageblit   Yes    No
 * SCROLL_WRAP_REDRAW  imageblit   No     Yes
 *
 * (SCROLL_WRAP_REDRAW is not implemented yet)
 *
 * In general, fbcon will choose the best scrolling
 * method based on the rule below:
 *
 * Pan/Wrap > accel imageblit > accel copyarea >
 * soft imageblit > (soft copyarea)
 *
 * Exception to the rule: Pan + accel copyarea is
 * preferred over Pan + accel imageblit.
 *
 * The above is typical for PCI/AGP cards. Unless
 * overridden, fbcon will never use soft copyarea.
 *
 * If you need to override the above rule, set the
 * appropriate flags in fb_info->flags.  For example,
 * to prefer copyarea over imageblit, set
 * FBINFO_READS_FAST.
 *
 * Other notes:
 * + use the hardware engine to move the text
 *    (hw-accelerated copyarea() and fillrect())
 * + use hardware-supported panning on a large virtual screen
 * + amifb can not only pan, but also wrap the display by N lines
 *    (i.e. visible line i = physical line (i+N) % yres).
 * + read what's already rendered on the screen and
 *     write it in a different place (this is cfb_copyarea())
 * + re-render the text to the screen
 *
 * Whether to use wrapping or panning can only be figured out at
 * runtime (when we know whether our font height is a multiple
 * of the pan/wrap step)
 *
 */

#define SCROLL_MOVE	   0x001
#define SCROLL_PAN_MOVE	   0x002
#define SCROLL_WRAP_MOVE   0x003
#define SCROLL_REDRAW	   0x004
#define SCROLL_PAN_REDRAW  0x005

static inline u_short fb_scrollmode(struct fbcon_display *fb)
{
#ifdef CONFIG_FRAMEBUFFER_CONSOLE_LEGACY_ACCELERATION
	return fb->scrollmode;
#else
	/* hardcoded to SCROLL_REDRAW if acceleration was disabled. */
	return SCROLL_REDRAW;
#endif
}


#ifdef CONFIG_FB_TILEBLITTING
extern void fbcon_set_tileops(struct vc_data *vc, struct fb_info *info);
#endif
extern void fbcon_set_bitops_ur(struct fbcon_par *par);
extern int  soft_cursor(struct fb_info *info, struct fb_cursor *cursor);

#define FBCON_ATTRIBUTE_UNDERLINE 1
#define FBCON_ATTRIBUTE_REVERSE   2
#define FBCON_ATTRIBUTE_BOLD      4

static inline int real_y(struct fbcon_display *p, int ypos)
{
	int rows = p->vrows;

	ypos += p->yscroll;
	return ypos < rows ? ypos : ypos - rows;
}


static inline int get_attribute(struct fb_info *info, u16 c)
{
	int attribute = 0;

	if (fb_get_color_depth(&info->var, &info->fix) == 1) {
		if (attr_underline(c))
			attribute |= FBCON_ATTRIBUTE_UNDERLINE;
		if (attr_reverse(c))
			attribute |= FBCON_ATTRIBUTE_REVERSE;
		if (attr_bold(c))
			attribute |= FBCON_ATTRIBUTE_BOLD;
	}

	return attribute;
}

#define FBCON_SWAP(i,r,v) ({ \
        typeof(r) _r = (r);  \
        typeof(v) _v = (v);  \
        (void) (&_r == &_v); \
        (i == FB_ROTATE_UR || i == FB_ROTATE_UD) ? _r : _v; })

#endif /* _VIDEO_FBCON_H */
]

16/2 2026/01/29 20:19 llm debugger
1m Model:
gemini-3-pro-preview

Tokens:
							input: 36043
							output: 28
							thoughts: 6747
						

Thoughts:
**Analysis of Framebuffer Corruption during Rotation**

My investigation centers on a potential out-of-bounds write within the framebuffer during screen rotation. I've tracked the execution path from a sysfs write to `rotate_store`, which calls `fbcon_rotate`, and ultimately leads to `redraw_screen` and `do_update_region`, the latter redrawing the entire screen. The critical point seems to be the interaction between the `cw_putcs` function (used for character writing) and the `fb_bitmap_2ppw` function (for pixel blitting).

I've examined the `GETVXRES` macro used in `fbcon_rotate.h`, noting its dependence on the scroll mode. The macro selects between `xres` (physical width) and `xres_virtual` (virtual width). I hypothesized an issue with the horizontal coordinate (`dx`) calculation:  `dx = vxres - ((yy + 1) * height)`. If `xres_virtual` is used and hardware panning is enabled, `dx` is calculated using `xres_virtual` and could lead to large values. The intention of `dx` is to point the beginning of a line.

The stack trace reveals that the key function calls are `cw_putcs_aligned` -> `info->fbops->fb_imageblit` -> `fb_imageblit` -> `fb_bitmap_2ppw`. The crash occurs within `fb_bitmap_2ppw` and involves `fb_write_offset`. My focus is on where `offset` is calculated. It is crucial to examine the relationship between `dy`, `dx`, `width`, and `height` to see if they can overflow.

The `image` structure passed to `fb_imageblit` contains dimensions calculated by `cw_putcs`. `image.width` equals `vc->vc_font.height` and `image.height` equals `vc->vc_font.width * cnt`. `cnt` (count) can potentially be problematic, as it is clamped by `info->pixmap.size/cellsize`.  `image.dy` increments, potentially causing out-of-bounds writes.  This accumulation of `image.dy` as we iterate, might create problems for long writes. 

A likely cause is an integer overflow during address calculation, which occurs in `fb_address_forward`. Here, the calculated offset (derived from `image->dy`, `image->dx`, and bit depths) is added to the base framebuffer address. I observed that large values of `image->dy * bits_per_line` can lead to integer overflow, resulting in a negative offset and a potential backward jump in memory, causing OOB.

Additionally, incorrect calculation of the horizontal offset (`dx`) could also play a role.  While `dx` and `dy` should be within the bounds given correct row/col counts, the values of `cnt` and the state of the framebuffer could lead to an out-of-bounds `dy`.

However, the size reported in KASAN is "slightly past the end," rather than far beyond it, making it unlikely that we had full integer overflow. We were only out of bounds by a bit.

The fact that sysfs is being used to write the value that causes the rotation (and therefore, the crash) could indicate some invalid state being passed in by the user space, or a subtle bug in the calculation of `vc_rows`/`vc_cols` *during* or immediately *after* rotation, but before the redraw.



17/2 2026/01/29 20:20 tool read-file
0m Args:
map[File:drivers/video/fbdev/core/fbcon.c]

Results:
map[Contents:/*
 *  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
]

18/2 2026/01/29 20:20 llm debugger
1m Model:
gemini-3-pro-preview

Tokens:
							input: 78083
							output: 856
							thoughts: 5275
						

Thoughts:
**Analysis of the Framebuffer Rotation Crash**

Okay, here's what I've pieced together about this framebuffer rotation crash. The sequence of events triggered by `fbcon_rotate` seems to be the culprit. I can see the core problem lies in the handling (or rather, the *lack* of handling) of potential failures during the screen resize.

Here's my thought process:

1.  **Rotation Trigger:** The `fbcon_rotate` function is called, likely through a sysfs interface. This function sets a new rotation value (`p->con_rotate`). Importantly, it then calls `fbcon_modechanged`.
2.  **Mode Change and Blitting Type:** `fbcon_modechanged` is crucial. It begins by calling `set_blitting_type`. This function installs new bit-blitting operations based on the new rotation set earlier (like the CW, or clockwise, rotation in this case). It calls `fbcon_set_bitops`, and it looks like this is where the `cw_putcs` function, which handles the rotation, is installed. Then, `fbcon_modechanged` calls `vc_resize` to actually resize the virtual console.
3.  **Resize Failure (the Catch):** Here's the critical flaw. The code *ignores* the return value of `vc_resize`. If `vc_resize` fails (which it could due to memory allocation failure – easily injected by tools like syzkaller – or due to invalid dimensions), `vc->vc_cols` and `vc->vc_rows` are *not* updated. This is critical.
4.  **Inconsistent State:** Now we have a situation where the bit-blitting operations are set for a *rotated* screen (`cw_putcs` is active), but the console dimensions (columns and rows) still reflect the *unrotated* screen.
5.  **Drawing with Mismatched Data:** Later, potentially via `redraw_screen` called by `update_screen`, the system needs to draw to the console. It uses `do_update_region` that uses the still unrotated dimensions with a rotated set of blitting operations. It goes to draw characters using `cw_putcs`.
6.  **Out-of-Bounds Write:** Inside `cw_putcs`, the `dy` value (the vertical offset for drawing) is calculated. This calculation relies on `vc->vc_cols` (which hasn't been updated to rotated dimensions) and the character font height. Since the number of columns `vc->vc_cols` is larger than the actual new height after rotation, `dy` gets too large and leads to writing beyond the bounds of the framebuffer, which is why it ends up crashing inside `fb_bitmap_2ppw` or other similar blitting function.

Essentially, the crash happens because the code assumes the resize will always succeed. When it fails, we have new rotation operations, but still unrotated screen dimensions. When drawing, the coordinates based on a number of rotated characters do not match the old screen bounds.

**Root Cause:** The unhandled failure of `vc_resize` within `fbcon_modechanged` is the root cause. This leads to an inconsistent state where the blitting operations are rotated, but the console dimensions are not.

**The Fix:** The simplest fix would be to check the return value of `vc_resize` within `fbcon_modechanged`. If it fails, the code *must* revert the rotation (perhaps by resetting `p->con_rotate` or reinstalling the old bit-blitting operations) to maintain a consistent state.

In summary, the crux is a state inconsistency where the bit-blitting functions have been changed to reflect a rotated screen, but the console dimensions have not been updated due to a resize failure. The crash is triggered when `cw_putcs` and related blitting functions try to draw to coordinates that are calculated with the old, larger, and unrotated dimension. The crash occurs in the low-level `fb_write_offset` that writes to the framebuffer.



19/1 2026/01/29 20:21 action kernel-scratch-checkouter
0m Results:
map[KernelScratchSrc:/usr/local/google/home/dvyukov/syzkaller/agent/workdir/cache/tmp/tmp782918516]

20/1 2026/01/29 20:21 loop
11m
21/2 2026/01/29 20:21 iteration 0
11m
22/3 2026/01/29 20:21 agent patch-generator
3m Model:
gemini-3-pro-preview

Instruction:
You are an experienced Linux kernel developer tasked with creating a fix for a kernel bug.
Use the codeedit tool to do code edits.
Note: you will not see your changes when looking at the code using codesearch tools.
Your final reply should contain explanation of what you did in the patch and why.



Prefer calling several tools at the same time to save round-trips.

Prompt:
The crash that corresponds to the bug is:

R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001
R13: 00007fffd2735b50 R14: 00007f26d9187b40 R15: 0000000000000003
 </TASK>
==================================================================
BUG: KASAN: vmalloc-out-of-bounds in fb_write_offset drivers/video/fbdev/core/sysmem.h:30 [inline]
BUG: KASAN: vmalloc-out-of-bounds in fb_bitmap_2ppw drivers/video/fbdev/core/fb_imageblit.h:364 [inline]
BUG: KASAN: vmalloc-out-of-bounds in fb_bitmap_imageblit drivers/video/fbdev/core/fb_imageblit.h:462 [inline]
BUG: KASAN: vmalloc-out-of-bounds in fb_imageblit drivers/video/fbdev/core/fb_imageblit.h:492 [inline]
BUG: KASAN: vmalloc-out-of-bounds in sys_imageblit+0xfed/0x1ea0 drivers/video/fbdev/core/sysimgblt.c:24
Write of size 8 at addr ffffc90002c223c0 by task syz-executor173/6014

CPU: 0 UID: 0 PID: 6014 Comm: syz-executor173 Not tainted syzkaller #1 PREEMPT(full) 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x10e/0x190 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0x17e/0x810 mm/kasan/report.c:482
 kasan_report+0x147/0x180 mm/kasan/report.c:595
 fb_write_offset drivers/video/fbdev/core/sysmem.h:30 [inline]
 fb_bitmap_2ppw drivers/video/fbdev/core/fb_imageblit.h:364 [inline]
 fb_bitmap_imageblit drivers/video/fbdev/core/fb_imageblit.h:462 [inline]
 fb_imageblit drivers/video/fbdev/core/fb_imageblit.h:492 [inline]
 sys_imageblit+0xfed/0x1ea0 drivers/video/fbdev/core/sysimgblt.c:24
 drm_fbdev_shmem_defio_imageblit+0x2c/0x110 drivers/gpu/drm/drm_fbdev_shmem.c:39
 cw_putcs_aligned drivers/video/fbdev/core/fbcon_cw.c:110 [inline]
 cw_putcs+0x15af/0x1880 drivers/video/fbdev/core/fbcon_cw.c:158
 fbcon_putcs+0x3bd/0x590 drivers/video/fbdev/core/fbcon.c:1343
 do_update_region+0x3a1/0x460 drivers/tty/vt/vt.c:628
 redraw_screen+0x91a/0xe90 drivers/tty/vt/vt.c:980
 fbcon_rotate drivers/video/fbdev/core/fbcon.c:231 [inline]
 rotate_store+0x33f/0x510 drivers/video/fbdev/core/fbcon.c:3237
 kernfs_fop_write_iter+0x3af/0x540 fs/kernfs/file.c:352
 new_sync_write fs/read_write.c:593 [inline]
 vfs_write+0x5d2/0xb40 fs/read_write.c:686
 ksys_write+0x149/0x250 fs/read_write.c:738
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f26d90f4749
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 d8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fffd2735b38 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007fffd2735b80 RCX: 00007f26d90f4749
RDX: 0000000000000007 RSI: 0000000000000000 RDI: 0000000000000003
RBP: 0000000000000001 R08: 0000000000000000 R09: 00007fffd2735b70
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001
R13: 00007fffd2735b50 R14: 00007f26d9187b40 R15: 0000000000000003
 </TASK>

The buggy address belongs to a vmalloc virtual mapping
Memory state around the buggy address:
 ffffc90002c22280: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
 ffffc90002c22300: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
>ffffc90002c22380: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
                                           ^
 ffffc90002c22400: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
 ffffc90002c22480: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
==================================================================


The explanation of the root cause of the bug is:

The root cause of the crash is a failure to handle the error return from `vc_resize` in `fbcon_modechanged`, leading to an inconsistency between the console's logical dimensions and the installed framebuffer operations (specifically rotation support).

Here is the step-by-step analysis:

1.  **Rotation Trigger**: The user (or syzkaller) initiates a console rotation via a sysfs write to `rotate` (or `rotate_all`), which calls `fbcon_rotate`.
2.  **State Update**: `fbcon_rotate` updates the requested rotation in `fb_display[vc->vc_num].con_rotate` (e.g., to `FB_ROTATE_CW` for 90-degree clockwise rotation) and calls `fbcon_modechanged`.
3.  **Ops Update**: `fbcon_modechanged` calls `set_blitting_type`, which updates the `fbcon_par->bitops` to the rotated versions (e.g., `cw_fbcon_bitops` containing `cw_putcs`) based on the new rotation.
4.  **Resize Attempt**: `fbcon_modechanged` calculates the new logical dimensions (`cols` and `rows`) for the rotated screen. It calls `vc_resize(vc, cols, rows)` to update the console's text buffer and dimensions (`vc->vc_cols`, `vc->vc_rows`).
5.  **Ignored Failure**: `vc_resize` can fail (e.g., due to memory allocation failure `ENOMEM`, which is common with syzkaller fault injection). **Critically, `fbcon_modechanged` ignores the return value of `vc_resize`.**
6.  **Inconsistent State**: If `vc_resize` fails, the console's `vc_cols` and `vc_rows` remain at their *old* (unrotated) values. However, the bit-blitting operations (`cw_putcs`) are already set to the *new* (rotated) mode.
    *   For example, if the screen was Landscape (wide), `vc_cols` is large.
    *   After rotating to Portrait (tall), `vc_cols` should be smaller.
    *   Due to the failure, `vc_cols` remains large (Landscape width).
7.  **Out-of-Bounds Calculation**: `fbcon_modechanged` proceeds to call `update_screen`, which eventually calls `cw_putcs`.
    *   `cw_putcs` iterates over the characters to be drawn. The iteration limits are based on the (incorrectly large) `vc_cols`.
    *   `cw_putcs` calculates the physical Y coordinate (`image.dy`) in the framebuffer corresponding to the logical column `xx`:
        `image.dy = xx * vc->vc_font.width;`
    *   Since `xx` can go up to the old `vc_cols`, `image.dy` can exceed the physical height (`yres`) of the framebuffer (because the old width is larger than the new height).
8.  **The Crash**: `cw_putcs` calls `fb_imageblit` -> `fb_bitmap_2ppw` -> `fb_write_offset`.
    *   `fb_imageblit` calculates the memory offset: `dst = base + image.dy * stride + image.dx * bpp`.
    *   Because `image.dy` is too large (out of physical bounds), the calculated address points past the end of the allocated vmalloc framebuffer.
    *   `fb_write_offset` attempts to write to this address, triggering the `KASAN: vmalloc-out-of-bounds`.

**Fix**: `fbcon_modechanged` must check the return value of `vc_resize`. If it fails, it should abort the update and potentially revert the rotation setting/ops to ensure the console state remains consistent.



Reply:
The bug is caused by `fbcon_modechanged` ignoring the return value of `vc_resize`. When `vc_resize` fails (e.g., due to memory allocation failure), the console's logical dimensions (`vc_cols`, `vc_rows`) remain unchanged (e.g., large landscape width), but the framebuffer operations (`par->bitops`) are updated to the new rotation (e.g., portrait mode, which expects smaller width). This mismatch leads to out-of-bounds access in `cw_putcs` (or other rotated ops) when `update_screen` calls them with coordinates that are valid for the old dimensions but invalid for the new rotation.

The fix is to check the return value of `vc_resize` in `fbcon_modechanged`. If it fails, we must revert the changes to `par->rotate` and `par->bitops` to ensure the console state remains consistent with the (unchanged) dimensions.

Here is the patch:

```c
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;
	int old_rotate, err;

	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];
	old_rotate = par->rotate;
	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;
		err = vc_resize(vc, cols, rows);
		if (err) {
			if (!(info->flags & FBINFO_MISC_TILEBLITTING)) {
				par->rotate = old_rotate;
				fbcon_set_bitops(par);
			}
			return;
		}
		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);
	}
}
```

23/4 2026/01/29 20:21 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 5514
							output: 47
							thoughts: 316
						

Thoughts:
**Analyzing a Kernel Bug in the Framebuffer Console**

Okay, so I'm looking at a KASAN vmalloc-out-of-bounds triggered in `fb_write_offset`, apparently stemming from a bug in the framebuffer console code. The report points the finger at `drivers/video/fbdev/core/fbcon.c` and a problem with how `fbcon_modechanged` handles the return value of `vc_resize`. The explanation clearly outlines that if `vc_resize` fails, the console's dimension variables (`vc_cols`, `vc_rows`) don't get updated, but the operations pointers (`cw_putcs` and the like) *do* change to reflect a new rotation.

This immediately makes sense; it's a classic case of stale data. Essentially, we're ending up with calculations using old, potentially incorrect dimensions, while we're trying to write to the framebuffer with the *new* rotation settings. The example of landscape width vs portrait height clarifies the potential for out-of-bounds reads or writes. This seems quite likely to be related to mode setting and rotation.

My immediate focus needs to be on `drivers/video/fbdev/core/fbcon.c`. I need to zero in on `fbcon_modechanged` and see what's happening there with respect to `vc_resize`. Specifically, I need to understand *how* it's called and, critically, what's done with the return value. Is there any error checking? Is the result of `vc_resize` properly handled, or are we just blithely continuing?

I anticipate the fix is straightforward: adding the check for `vc_resize`'s return value. The report suggests it is missing.

Let me start by checking I have the right file. It's almost certain it's `drivers/video/fbdev/core/fbcon.c` as indicated in the traceback, but I need to make sure I don't miss something. I will read through this file and see if there are any other related pieces of information.



24/4 2026/01/29 20:21 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/video/fbdev/core/fbcon.c IncludeLines:true Name:fbcon_modechanged]

Results:
map[SourceCode:2692:	static void fbcon_modechanged(struct fb_info *info)
2693:	{
2694:		struct fbcon_par *par = info->fbcon_par;
2695:		struct vc_data *vc;
2696:		struct fbcon_display *p;
2697:		int rows, cols;
2698:	
2699:		if (!par || par->currcon < 0)
2700:			return;
2701:		vc = vc_cons[par->currcon].d;
2702:		if (vc->vc_mode != KD_TEXT ||
2703:		    fbcon_info_from_console(par->currcon) != info)
2704:			return;
2705:	
2706:		p = &fb_display[vc->vc_num];
2707:		set_blitting_type(vc, info);
2708:	
2709:		if (con_is_visible(vc)) {
2710:			var_to_display(p, &info->var, info);
2711:			cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
2712:			rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
2713:			cols /= vc->vc_font.width;
2714:			rows /= vc->vc_font.height;
2715:			vc_resize(vc, cols, rows);
2716:			updatescrollmode(p, info, vc);
2717:			scrollback_max = 0;
2718:			scrollback_current = 0;
2719:	
2720:			if (fbcon_is_active(vc, info)) {
2721:				par->var.xoffset = par->var.yoffset = p->yscroll = 0;
2722:				par->bitops->update_start(info);
2723:			}
2724:	
2725:			fbcon_set_palette(vc, color_table);
2726:			update_screen(vc);
2727:		}
2728:	}
 SourceFile:drivers/video/fbdev/core/fbcon.c]

25/4 2026/01/29 20:21 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 6955
							output: 48
							thoughts: 931
						

Thoughts:
**Analyzing the Rotation Bug in the Framebuffer Console**

Okay, so here's what's happening. The code flow is pretty clear: we call `set_blitting_type` to configure the operations, including the rotation-specific ones. Then, `vc_resize` is called, but its return value is ignored. Finally, `update_screen` triggers the blitting operations.

The core issue appears when `vc_resize` fails. This leaves `vc->vc_cols` and `vc->vc_rows` unchanged. Imagine we're rotating from landscape to portrait. In this scenario, `cols` (the desired new column count) would be *smaller* than the existing `vc->vc_cols`. Since the resize fails, `vc->vc_cols` remains large (the landscape value). The critical part is that `set_blitting_type` has *already* configured the operations for portrait mode, for example `cw_putcs`, and therefore the rotation.

Now, `cw_putcs` uses this outdated, large value of `vc->vc_cols` to calculate coordinates. Crucially, due to the rotation, the column value in portrait is actually a Y coordinate, not an X coordinate. We're now overwriting the screen at a much greater depth than the actual frame buffer height. Specifically, `cw_putcs` is calculating `image.dy = xx * vc->vc_font.width;` where `xx` goes up to the large, incorrect `vc->vc_cols` value. Since we are in portrait mode, what would have been the X coordinate for the landscape display becomes the Y coordinate in the rotated framebuffer. The result is out-of-bounds access.

Wait, let's make sure I've got this right. When we rotate, `info->var.xres` and `info->var.yres` *might* swap roles. The `cw_putcs` function does indeed define a clockwise rotation, so the X and Y dimensions do effectively change roles. Looking at the logic in `drivers/video/fbdev/core/fbcon_cw.c`, the actual coordinates are calculated like so:

```c
static void cw_putcs(struct vc_data *vc, struct fb_info *info,
		      const unsigned short *s, int count, int yy, int xx,
		      int fg, int bg)
{
    ...
    image.dy = xx * vc->vc_font.width;
    image.dx = info->var.xres - (yy * vc->vc_font.height + height);
    ...
}
```

So, in landscape, `xres` is large (e.g., 1920), `yres` is small (e.g., 1080). `vc_cols` is roughly `xres` divided by the font width. If we rotate to portrait, the `info->var` struct still describes the underlying *framebuffer*. The *physical* framebuffer dimensions don't usually change with software rotation, unless there's hardware rotation. Therefore, `info->var.xres` remains 1920, and `info->var.yres` remains 1080. But the logical console layout wants to be portrait. The `cols` calculation in `fbcon_modechanged` uses `FBCON_SWAP` to reflect this change. And because we are doing a clockwise rotation, `cols` becomes `yres` divided by font width (1080/font_w), and `rows` becomes `xres` divided by font height (1920/font_h).

If `vc_resize` fails, `vc->vc_cols` is still the *old* 1920/font_w. When `cw_putcs` is called, `xx` iterates up to this incorrect large value, 1920/font_w. Then `image.dy` will be at its maximum `(1920/font_w) * font_w` which evaluates to approximately 1920. But the physical `yres` (the height) of the screen is only 1080. So, `image.dy` (1920) is greater than 1080, causing an out-of-bounds access.

So, the fix is indeed to properly handle `vc_resize` failures. Now, we need to decide what the *right* handling is. Probably an early return would be appropriate but we also need to consider that `set_blitting_type` has already been called. Should we revert the blitting type? We need to look at `set_blitting_type` to


26/4 2026/01/29 20:22 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/video/fbdev/core/fbcon.c IncludeLines:true Name:set_blitting_type]

Results:
map[SourceCode: 692:	static void set_blitting_type(struct vc_data *vc, struct fb_info *info)
 693:	{
 694:		struct fbcon_par *par = info->fbcon_par;
 695:	
 696:		par->p = &fb_display[vc->vc_num];
 697:	
 698:		if ((info->flags & FBINFO_MISC_TILEBLITTING))
 699:			fbcon_set_tileops(vc, info);
 700:		else {
 701:			fbcon_set_rotation(info);
 702:			fbcon_set_bitops(par);
 703:		}
 704:	}
 SourceFile:drivers/video/fbdev/core/fbcon.c]

27/4 2026/01/29 20:22 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 9191
							output: 48
							thoughts: 593
						

Thoughts:
**Inconsistent State in Framebuffer Console Rotation**

Okay, let me break this down. I'm looking at the flow of rotation in the framebuffer console and I see a potential issue with the order of operations and error handling. I'm focusing on how `fbcon_set_rotation`, `fbcon_modechanged`, and `vc_resize` interact.

So, `fbcon_set_rotation` sets the rotation in `par->rotate`, or `p->con_rotate`, if the FB driver implements rotation. Then, `fbcon_modechanged` is called. The odd thing is that the rotation is set *before* mode changed is handled.

Within `fbcon_modechanged`, there's `set_blitting_type(vc, info)`. That function sets `par->bitops` which defines the blitting operations. And those operations are based on `par->rotate`, or `p->con_rotate`. Here's the catch: what happens if `vc_resize` fails *after* this happens?

If `vc_resize` fails, the `vc`'s dimensions are wrong. If I just return early from `fbcon_modechanged` on error, it means `par->bitops` will remain set to the rotated ops, but the virtual console `vc` is still unrotated. The `vc` and the framebuffer console now have inconsistent views of how the screen is rotated, which could lead to screen corruption. Subsequent calls to `update_screen` could then run into a problem.

Now, `fbcon_modechanged` *does* call `update_screen`. But, `fbcon_switch` and `redraw_screen` also call `update_screen`, so returning early isn't a silver bullet. The problem, as I understand it, is that after `vc_resize` fails, it now thinks the screen is landscape and `fbcon_par` believes it's portrait.

I think the solution is that I need to revert `set_blitting_type` if `vc_resize` fails. Or, at least, not set it until I know `vc_resize` succeeded! But that introduces a chicken and egg scenario.  `vc_resize` takes `cols` and `rows`, which are calculated based on the rotation. Where do I get the rotation from? I retrieve the rotation from `par->rotate` (which is updated from `p->con_rotate` in `fbcon_set_rotation`, which is called by `set_blitting_type`).

Wait a minute, `fbcon_modechanged` computes `cols`/`rows` using `par->rotate`.  So, `par->rotate` seems to be set *in* `set_blitting_type` -> `fbcon_set_rotation`. So `set_blitting_type` is responsible for setting the bitops *and* setting the rotation that defines the dimensions.
I need to go back and check the source of `fbcon_set_rotation`...



28/4 2026/01/29 20:22 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/video/fbdev/core/fbcon.c IncludeLines:true Name:fbcon_set_rotation]

Results:
map[SourceCode: 202:	static inline void fbcon_set_rotation(struct fb_info *info)
 203:	{
 204:		struct fbcon_par *par = info->fbcon_par;
 205:	
 206:		if (!(info->flags & FBINFO_MISC_TILEBLITTING) &&
 207:		    par->p->con_rotate < 4)
 208:			par->rotate = par->p->con_rotate;
 209:		else
 210:			par->rotate = 0;
 211:	}
 SourceFile:drivers/video/fbdev/core/fbcon.c]

29/4 2026/01/29 20:22 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 10666
							output: 23
							thoughts: 591
						

Thoughts:
**Analyzing the fbcon Rotation Process**

Okay, so here's what's going on in this framebuffer console rotation logic. The key seems to be how `par->rotate` gets updated in response to a requested console rotation, represented by `par->p->con_rotate`. `par->p` points to a `fb_display` structure for a specific virtual console (`vc`).

Here's my thought process on handling the failure case in `vc_resize`, which could occur due to memory allocation issues:

1.  **The flow:** `fbcon_rotate` sets `fb_display[vc->vc_num].con_rotate` and then calls `fbcon_modechanged`.  `fbcon_modechanged` is where the core work happens: `set_blitting_type` updates the important `par->rotate` (the *current* rotation setting) and `par->bitops` based on `p->con_rotate`. Then, it recalculates `cols` and `rows` based on the rotated dimensions and finally calls `vc_resize`.

2.  **The problem:** If `vc_resize` fails, the virtual console (`vc`) doesn't get resized. However, `par->rotate` has already been *changed*! This is a potential state inconsistency. The console might not be displaying what the user expects.

3.  **My initial instinct:** If `vc_resize` fails, I want to roll back the changes to `par->rotate` and potentially also `par->bitops` to revert to the previous working state. I want `par->rotate` and `bitops` to match the virtual console dimensions that are still valid.

4.  **The complication:** The tricky part is that `fbcon_rotate` already updated `p->con_rotate` *before* `fbcon_modechanged` is called. So, if I revert `par->rotate`, I create another inconsistency – `par->rotate` would no longer reflect `p->con_rotate`. The virtual console's *intended* rotation would be different from the frame buffer's rotation parameters.

5.  **A Better Solution:** The best place to handle the `vc_resize` failure is probably within `fbcon_modechanged` itself. If `vc_resize` fails, I need to restore a consistent state.

    *   Ideally, I'd revert `p->con_rotate` *back* to its original value, before `fbcon_rotate` changed it. But `fbcon_modechanged` doesn't have direct access to that previous value.
    *   Failing that, I must make sure that `par->rotate` matches what the `vc` *currently* is. If the `vc` is unchanged, that means the `par->rotate` needs to revert.

6.  **Digging deeper:** `fbcon_rotate` itself is pretty simple: it updates `fb_display[vc->vc_num].con_rotate` and calls `fbcon_modechanged`. If `fbcon_modechanged` fails to resize (because `vc_resize` fails), we're in a bad state. The user requested a rotation, but we didn't get it. This is a critical failure.

    ```c
    static void fbcon_rotate(struct fb_info *info, u32 rotate)
    {
        ...
        fb_display[vc->vc_num].con_rotate = rotate;
        fbcon_modechanged(info);
    }
    ```

7.  **The solution:** Because `fbcon_modechanged` is `void`, it can't signal a failure. I need to change the function signature of `fbcon_modechanged` to return an integer so that it can signal when an error occurred, and have `fbcon_rotate` check that error. Then `fbcon_rotate` can handle the fallback and revert. This is the cleanest solution: `fbcon_rotate` does the "user facing" rotation, but it only accepts that it is "rotated" if it successfully completes the change in frame buffer state.

8.  **Next steps:** Now, I need to look at the definition and callers of `fbcon_modechanged` to implement this change safely, and ensure that all error paths are correctly handled.



30/4 2026/01/29 20:22 tool codesearch-find-references
0m Args:
map[Name:fbcon_modechanged]

Results:
map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:fbcon_rotate SourceFile:drivers/video/fbdev/core/fbcon.c SourceLine:231] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:fbcon_set_all_vcs SourceFile:drivers/video/fbdev/core/fbcon.c SourceLine:2762] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:fbcon_update_vcs SourceFile:drivers/video/fbdev/core/fbcon.c SourceLine:2771]] TruncatedOutput:false]

31/4 2026/01/29 20:22 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 12402
							output: 48
							thoughts: 1284
						

Thoughts:
**Refining the Framebuffer Mode Change Logic**

Okay, so I'm looking at `fbcon_modechanged` and how it handles framebuffer mode changes, particularly rotation and resolution adjustments. I've got to make sure that the `bitops` structure, which handles the low-level framebuffer operations, stays consistent with the virtual console (`vc`) dimensions, even if `vc_resize` fails.

The main issue is this: `fbcon_modechanged` calculates new dimensions, resizes the virtual console, and updates the screen.  If `vc_resize` fails (which often means an out-of-memory situation), the `vc` dimensions remain at their *old* values. However, `par->rotate` (the active rotation) and `par->bitops` (the function pointers for low-level operations) *have already been changed* to the *new* rotation, potentially leading to a crash if the system tries to print anything. It'll be using the incorrect `bitops` with the outdated `vc` dimensions.

My immediate thought was to revert `par->bitops` and `par->rotate` if `vc_resize` fails.  But how do I determine the *previous* rotation to revert to? I can't just save `par->rotate` before `set_blitting_type` because that function reads from `p->con_rotate`. And that `p->con_rotate` value has already been updated by `fbcon_rotate` by the time we reach this function.

I realized I can't assume it's *just* a rotation issue; `fbcon_modechanged` is also used when valid `var` changes (resolution change). So I can't just revert a rotation.

The core of the problem is that the `bitops` and `vc` dimensions *must* be in sync. If `vc_resize` fails, the `vc` dimensions are valid for the *old* configuration. The `bitops` needs to match *that* old configuration.

So, I'm thinking I should rearrange the order of operations. Calculate `cols` and `rows` before setting the blitting type, try `vc_resize`, and *then* set the blitting type *only* if `vc_resize` succeeds.

Let's look at `fbcon_modechanged` again: it gets the `p` (display settings) and then sets the blitting type.  It's calling `var_to_display`, then calculating `cols` and `rows` which depend on `par->rotate`, and finally calls `vc_resize`.  The critical dependency is that `cols` and `rows` are computed *before* we can set the bitops for the correct rotation.

The issue is that `par->rotate` is set by `set_blitting_type` which, in turn, calls `fbcon_set_bitops(par)`. This is where the function pointers are actually set. I could temporarily set `par->rotate` without setting `bitops`, but the whole point is that I *need* the rotation info for the `cols`/`rows` calculation, but I *don't* want to install new `bitops` yet.

Can I move `set_blitting_type` *after* `vc_resize`? Then I need `par->rotate` set correctly before calculating `cols`/`rows`.

The `FBCON_SWAP` macro is used to swap `xres` and `yres` based on the rotation. It's safe if it has the right rotation set.  Okay, so it takes the rotation and the two resolution values, but it's *used* by the calculation of `cols` and `rows`, which are passed to `vc_resize`.

Wait a minute, `vc_resize` itself doesn't depend on `par->rotate`. It depends only on `cols` and `rows`. If I can calculate `cols` and `rows` using the *requested* rotation (`p->con_rotate`), I can try `vc_resize` first. And I can access that `p->con_rotate`.

This is the solution: calculate `cols` and `rows` using `p->con_rotate`. Call `vc_resize`.  If it succeeds, call `set_blitting_type`. If it fails, do *nothing* and the system keeps running with the old configuration.  No crashes. This looks like the right approach.



32/4 2026/01/29 20:22 tool codesearch-definition-source
0m Error:
requested entity does not exist

Args:
map[ContextFile:drivers/video/fbdev/core/fbcon.h IncludeLines:true Name:FBCON_SWAP]

Results:
map[SourceCode: SourceFile:]

33/4 2026/01/29 20:22 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 14717
							output: 49
							thoughts: 2522
						

Thoughts:
**My Analysis of the Framebuffer Rotation Bug**

Alright, let's break this down. My initial thought was to pinpoint the code responsible, possibly within `fbcon.c` or one of the included headers. The snippet showed `cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);` which is a clue. If `par->rotate` is just a flag/value, it influences the column swap based on rotation, but needs further investigation. I kept coming back to `fbcon_modechanged` and how it uses `par->rotate`, set within `set_blitting_type`.

My instinct is that if I can change the sequence or logic in `set_blitting_type` and call it later, after `vc_resize`.  This means I need to manually set `par->rotate` before `vc_resize`. `fbcon_set_rotation(info)` would be used for the setup, setting `par->rotate` based on `p->con_rotate`, and `fbcon_set_bitops(par)` would set the operations.

I considered splitting `set_blitting_type`, but realized it's static, which makes it tricky. If `vc_resize` fails, I want to revert `par->rotate` and `bitops`. The issue is, I won't have the old value of `p->con_rotate`, making direct reversion difficult. However, a `vc_resize` failure leaves `vc` with the old cols/rows, which corresponds to the old state.  I can't reliably deduce the old rotation from `vc->vc_cols` vs `info->var`...

My current thought is to simply check the return value of `vc_resize`. If it fails, I just return, *but* I must ensure that `bitops` aren't pointing to the new rotation operations.

By moving `set_blitting_type` after `vc_resize`, I ensure `par->rotate` is correct for `FBCON_SWAP`, calling `fbcon_set_rotation(info)` separately to calculate cols/rows, and then calling `vc_resize`. On success, I'd then call `set_blitting_type`. On failure, the crucial part is reversion.  If I change `par->rotate` via `fbcon_set_rotation(info)`, it then equals `p->con_rotate`. After a `vc_resize` failure, I am stuck with a new `par->rotate` but an old `vc` and the old `bitops`.

The question is, is it safe to have a new `par->rotate` but old `bitops`?  Do `bitops` functions use `par->rotate` internally? I checked `cw_putcs`. It doesn't seem to use `par->rotate` directly but assumes a coordinate system implied by the rotation. So if `bitops` are old (e.g., unrotated), but `par->rotate` is new (rotated), does `fbcon_putcs` use `par->rotate`? `fbcon_putcs` calls `ops->putcs`, and `ops` is `par->bitops`. If `bitops` is `fbcon_ops`, it uses `fb_blit_ops`. Thus, if `vc_resize` fails, and I keep the old `bitops`, `update_screen` calls `fbcon_putcs` -> old `putcs` with `vc` dimensions, and it *should* be fine.

The bug report says the `bitops` were updated to `cw_putcs`. Therefore deferring `set_blitting_type` is the correct approach.

I need `par->rotate` for calculating `cols`/`rows`. Here's my plan:

1.  In `fbcon_modechanged`: `p = &fb_display[vc->vc_num];`. Determine the *intended* rotation (`p->con_rotate`).
2.  Calculate target `cols`/`rows` based on `p->con_rotate` and `info->var`. Note: `FBCON_SWAP` uses `par->rotate`, so I might need to do the swap manually or temporarily set `par->rotate`.  I can verify if `set_blitting_type` only sets `par->rotate` and `bitops` and nothing else that `vc_resize` depends on. `vc_resize` only resizes `vc` buffers; it doesn't touch `fb`.
3.  Call `vc_resize`.
4.  If error: Handle it. If `bitops` is untouched, then it matches the old `vc` state. `vc


34/4 2026/01/29 20:23 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/video/fbdev/core/fbcon.c IncludeLines:true Name:fbcon_set_bitops]

Results:
map[SourceCode: 276:	static void fbcon_set_bitops(struct fbcon_par *par)
 277:	{
 278:		switch (par->rotate) {
 279:		default:
 280:			fallthrough;
 281:		case FB_ROTATE_UR:
 282:			fbcon_set_bitops_ur(par);
 283:			break;
 284:		case FB_ROTATE_CW:
 285:			fbcon_set_bitops_cw(par);
 286:			break;
 287:		case FB_ROTATE_UD:
 288:			fbcon_set_bitops_ud(par);
 289:			break;
 290:		case FB_ROTATE_CCW:
 291:			fbcon_set_bitops_ccw(par);
 292:			break;
 293:		}
 294:	}
 SourceFile:drivers/video/fbdev/core/fbcon.c]

35/4 2026/01/29 20:23 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 18601
							output: 49
							thoughts: 854
						

Thoughts:
**Analyzing the `fbcon_modechanged` Fix**

Okay, so I'm thinking through this bug fix for `fbcon_modechanged`. It looks like the core issue revolves around restoring the correct bit operations (`bitops`) after a potential failure in `vc_resize`. The critical part seems to be how rotation is handled, especially when tileblitting is involved.

Here's my train of thought:

1.  We need to preserve the old rotation value (`par->rotate`) before calling `set_blitting_type`. That's step one.

2.  Then, we check the return value of `vc_resize`. If it fails, that's when the restoration is needed.

3.  The initial logic looks sound: restore `par->rotate` to the saved value and call `fbcon_set_bitops(par)`. This should, in theory, restore everything to the pre-`vc_resize` state.

    But, there's a catch: `set_blitting_type` might call `fbcon_set_tileops`. And if we *were* tileblitting before, `par->rotate` is zero (set by `fbcon_set_rotation`). We have to determine the mode.

4.  I remember that `set_blitting_type` checks `info->flags & FBINFO_MISC_TILEBLITTING`. This flag is in `info`, which describes driver capabilities and shouldn't change mid-execution, right? If the flag is set, we're in tileblitting mode, and tileops are used. That should make things simpler. If `vc_resize` fails in tileblitting mode, we might not need to do anything with the bitops because they don't depend on rotation (at least, directly).

    Wait, `fbcon_set_rotation` in tileblitting mode sets `par->rotate` to 0. So, `par->rotate` is always 0 when tileblitting is active.

5.  So, if `vc_resize` fails in tileblitting mode, we have `par->rotate` = 0. We'd restore `par->rotate` to 0, call `fbcon_set_bitops`, which *doesn't* set tileops! So if tileblitting, we need to call the appropriate tileops function.

6.  So the revert logic needs to mirror `set_blitting_type`:

    *   If `vc_resize` fails:
        *   If `info->flags & FBINFO_MISC_TILEBLITTING` is set (tileblitting): we *don't* change bitops; we just return (because we assume tileops are unchanged).
        *   If not tileblitting, `par->rotate = old_rotate`, call `fbcon_set_bitops(par)`.

    And here is the code:

    ```c
        if (vc_resize(...) != 0) {
            if (info->flags & FBINFO_MISC_TILEBLITTING) {
                 // Ops are tileops. Rotation is 0.
                 // vc dimensions are old.
                 // Does tileops depend on vc dimensions matching?
                 // Probably. But we haven't changed ops since we entered this function
                 // (assuming info->flags didn't change).
                 // So if tileblitting, we effectively did nothing to ops.
                 // So we can just return.
            } else {
                 par->rotate = old_rotate;
                 fbcon_set_bitops(par);
            }
            return;
        }
    ```

7.  Let's look more closely at how `set_blitting_type` is implemented. It's:

    ```c
    		if ((info->flags & FBINFO_MISC_TILEBLITTING))
    			fbcon_set_tileops(vc, info);
    		else {
    			fbcon_set_rotation(info);
    			fbcon_set_bitops(par);
    		}
    ```

    Now it's crystal clear. If `info->flags` indicates tileblitting, we call `fbcon_set_tileops`. Otherwise, we set rotation and then bitops. So, in tileblitting mode, we *don't* touch `par->rotate` directly, unless the tileops function does something internally. We should double-check that, but likely not.

    Let's check `fbcon_set_tileops`.



36/4 2026/01/29 20:23 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/video/fbdev/core/fbcon.c IncludeLines:true Name:fbcon_set_tileops]

Results:
map[SourceCode: 173:	void fbcon_set_tileops(struct vc_data *vc, struct fb_info *info)
 174:	{
 175:		struct fb_tilemap map;
 176:		struct fbcon_par *par = info->fbcon_par;
 177:	
 178:		par->bitops = &tile_fbcon_bitops;
 179:	
 180:		if (par->p) {
 181:			map.width = vc->vc_font.width;
 182:			map.height = vc->vc_font.height;
 183:			map.depth = 1;
 184:			map.length = vc->vc_font.charcount;
 185:			map.data = par->p->fontdata;
 186:			info->tileops->fb_settile(info, &map);
 187:		}
 188:	}
 SourceFile:drivers/video/fbdev/core/tileblit.c]

37/4 2026/01/29 20:23 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 20786
							output: 41
							thoughts: 700
						

Thoughts:
**Analyzing the Virtual Console Resize Failure**

Okay, let's break this down. My initial thought process is this: The key issue seems to be related to the virtual console resize failing, particularly in the context of tileblitting and rotation. I see that the `set_blitting_type` function is called, which sets `par->bitops` to `&tile_fbcon_bitops`. Critically, it *doesn't* touch `par->rotate` in the tileblitting case. This means `par->rotate` retains its previous value, which is likely initialized to 0.

If `vc_resize` fails *and* we're in tileblitting mode, it seems like we can simply return, because neither `bitops` nor `par->rotate` have been changed. However, the reported crash involves rotation. Rotation implies non-tileblitting, or rather `fbcon_set_rotation` only sets `par->rotate` when NOT in tileblitting mode. Therefore, the crash must be happening in the `else` branch of `set_blitting_type`, which handles the non-tileblitting case.

This led me to propose a code modification:

```c
    int err;
    int old_rotate = par->rotate;

    // ... inside loop or function ...
    
    set_blitting_type(vc, info);
    
    // ...
    
    err = vc_resize(vc, cols, rows);
    if (err) {
        if (!(info->flags & FBINFO_MISC_TILEBLITTING)) {
             par->rotate = old_rotate;
             fbcon_set_bitops(par);
        }
        return;
    }
```

However, I need to consider `updatescrollmode`. It is called after `vc_resize`. If `vc_resize` fails and we return early, we will skip `updatescrollmode`. This is probably the correct behavior since scroll mode updates might depend on successful resize.

Another point: `fbcon_modechanged` has a check for a null or invalid `par` ( `if (!par || par->currcon < 0) return;`). Therefore, `par` *is* dereferenced there. Also, since `fbcon_modechanged` is void, I need to define the `err` and `old_rotate` variables inside it.

Let's go back and examine the context of `fbcon_modechanged`:

```c
2692:	static void fbcon_modechanged(struct fb_info *info)
2693:	{
2694:		struct fbcon_par *par = info->fbcon_par;
2695:		struct vc_data *vc;
2696:		struct fbcon_display *p;
2697:		int rows, cols;
            // Need to add: int err;
            // Need to add: int old_rotate;
```

Line 2707: `set_blitting_type(vc, info);`

Line 2715: `vc_resize(vc, cols, rows);`

So, the plan outlined above is a solid path forward.

I just need to verify the return type of `vc_resize` for absolute certainty. I will check `include/linux/console.h`, or just use codesearch, to make sure. I'm reasonably confident in this analysis.



38/4 2026/01/29 20:23 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/tty/vt/vt.c IncludeLines:true Name:vc_resize]

Results:
map[SourceCode:  46:	static inline int vc_resize(struct vc_data *vc, unsigned int cols,
  47:				    unsigned int lines)
  48:	{
  49:		return __vc_resize(vc, cols, lines, false);
  50:	}
 SourceFile:include/linux/vt_kern.h]

39/4 2026/01/29 20:23 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 22390
							output: 28
							thoughts: 1953
						

Thoughts:
**Analysis of the Framebuffer Rotation and Resize Logic**

Okay, so I'm looking at this code and trying to figure out the right place to fix a potential crash. The goal is to safely handle framebuffer resizing after a rotation change, specifically when `FBCON_SWAP` is used.

Here's my thought process: The function `vc_resize` returns an integer, which is likely an error code. `__vc_resize` returns an int, too, and is also an error code. So, the error return from the function `vc_resize(...)` needs to be checked.

The core issue involves handling an edge case: `FBCON_SWAP`, which uses `par->rotate`.  My proposed fix involves capturing the *old* `par->rotate` value *before* calling `set_blitting_type`.  That's important because `set_blitting_type` then updates `par->rotate`, and we want to preserve the original value in case the resize fails. The code then calls `vc_resize` using the new columns and rows, as determined by the updated rotation. If `vc_resize` fails, I need to restore the `par->rotate` to the `old_rotate` value and reset framebuffer bitops using `fbcon_set_bitops`.

Let's verify where `par` comes from. It's `info->fbcon_par`.  `fbcon_modechanged` checks that `par` is not NULL, so that's good.

Next, I need to make sure that `fbcon_set_bitops` is visible from `fbcon_modechanged`. Both are in `fbcon.c`, and it appears I *can* call it, so that's fine. `set_blitting_type` is also static, so accessible.

Now, I'm analyzing the flow. `fbcon_modechanged` calls `set_blitting_type` unconditionally after the validation check. Then, it checks `if (con_is_visible(vc))`. If the console is NOT visible, it skips the code block.

If `con_is_visible(vc)` is false, we set the blitting type but don't resize `vc`. It seems fine that we are not resizing if the console is not visible, we likely don't care about a potential crash when `cw_putcs` is called when `putcs` is not called due to the visibility check. However, `vc_resize` is called only if the console is visible.

Aha! What if we rotate a hidden console?  `vc_resize` is NOT called. But `set_blitting_type` IS called. So `par->rotate` and the framebuffer bitops (`bitops`) are updated, but `vc` dimensions remain OLD. Then, when the console becomes visible, something like `fbcon_switch` will be called. Does `fbcon_switch` resize?

`fbcon_switch` doesn't call `vc_resize` directly. It calls `fbcon_modechanged` indirectly, but not in a way that *guarantees* a resize is performed. This is problematic if we've updated the bitops, but the dimensions haven't been updated.

If `fbcon_modechanged` skipped `vc_resize` because the VC wasn't visible previously, it leads to an inconsistent state if we switch to that console.

Actually, `fbcon_modechanged` is called by `fbcon_update_vcs` which loops through *all* VCs. If a VC is not visible, `con_is_visible` is false (e.g., if `vc == fg_console` is false).

So, if I rotate a background console, `fbcon_rotate` sets `con_rotate`, `fbcon_modechanged` is then called.  `set_blitting_type` updates the bitops (which are in `par`...), and the `par` comes from `info->fbcon_par` which is per framebuffer, with one console displayed at a time. The active console's state lives in `par`.

`par->currcon` is the *current* console on the framebuffer.  In `fbcon_modechanged`, we have `vc = vc_cons[par->currcon].d;`. So `vc` is the current one on the screen. `con_is_visible(vc)` should be true, unless the console system is blanked or something. The implementation of `con_is_visible` might be a bit more complicated, but in general, if `par->currcon` corresponds to the console slot, and the framebuffer is mapped, then it should be visible, or at least the current one,


40/4 2026/01/29 20:24 tool read-file
0m Args:
map[File:drivers/video/fbdev/core/fbcon.c]

Results:
map[Contents:/*
 *  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
]

41/4 2026/01/29 20:24 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 59908
							output: 508
							thoughts: 1496
						

Thoughts:
**Analyzing and Implementing the `fbcon_modechanged` Fix**

Okay, so I've been looking at this `fbcon_modechanged` function, and my initial assessment is that the code looks sound, matching my expectations. I've added a few lines, specifically to preserve and restore the rotation state. It looks like it should handle the mode change properly, now.

I've introduced `old_rotate` to capture the `par->rotate` value *before* `set_blitting_type` is called. The `set_blitting_type` function, internally calls `fbcon_set_rotation`, which can potentially modify `par->rotate` based on tileblitting. I am accounting for this logic.

Now, if `vc_resize` fails (which is the critical point), the code checks if tileblitting is *not* enabled (`!(info->flags & FBINFO_MISC_TILEBLITTING)`). If it's *not* enabled, I restore the original rotation (`par->rotate = old_rotate`) and reset the bit operations using `fbcon_set_bitops()`. This ensures the console returns to its original rotation state if resizing fails.

Now, I'm thinking a bit more deeply about `fbcon_set_rotation`. It's crucial because it might reset `par->rotate` to zero if tileblitting *is* on. And after more analysis, it looks like `old_rotate` is updated *before* this can happen, so it captures the correct value, even in tileblitting scenarios. If tileblitting *is* on, the `restore` logic should be skipped, which is what I'm doing. It uses the `tile_fbcon_bitops` structure. So, if we are in tileblitting mode, `par->rotate` *should* be zero, and we *should* skip the restore. This is good. It seems correct!

The restore logic makes sure that we properly revert to our pre-change rotation *only* if tileblitting *is not* enabled. I don't see any issues. It will ensure we are in the correct state and preserve the rotation settings when `vc_resize` fails, unless tileblitting is on and we are always zeroed, anyway.

Finally, I confirmed that `fbcon_set_bitops` is indeed accessible from within `fbcon_modechanged`, as it's defined earlier in the `fbcon.c` file. So I should be good to go.

So, I'm comfortable with this approach. I'm going to implement the fix.



42/4 2026/01/29 20:24 tool codeeditor
0m Args:
map[CurrentCode:	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)) { NewCode:	p = &fb_display[vc->vc_num];
	old_rotate = par->rotate;
	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;
		err = vc_resize(vc, cols, rows);
		if (err) {
			if (!(info->flags & FBINFO_MISC_TILEBLITTING)) {
				par->rotate = old_rotate;
				fbcon_set_bitops(par);
			}
			return;
		}
		updatescrollmode(p, info, vc);
		scrollback_max = 0;
		scrollback_current = 0;

		if (fbcon_is_active(vc, info)) { SourceFile:drivers/video/fbdev/core/fbcon.c]

Results:
map[]

43/4 2026/01/29 20:24 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 62478
							output: 161
							thoughts: 31
						

Thoughts:
Okay, here's my take on those thoughts, framed as my own:

**Quick Catch: Variable Declarations Needed**

Alright, that's a classic slip-up. Just reviewing that `fbcon_modechanged` function, and I've completely spaced on declaring those two important variables: `err` and `old_rotate`. No, no, no, that's sloppy and will lead to unpredictable behavior, especially with error handling and state management. Right, need to rectify that immediately. Gotta make sure those declarations are at the top of the function. That's where they belong to ensure proper scope and initialization. Time to add those now and make sure to double check that I didn't miss any others that are implicitly used in the logic. Best to nip these little bugs in the bud before they blossom into real problems down the line.



44/4 2026/01/29 20:24 tool codeeditor
0m Args:
map[CurrentCode:	struct fbcon_par *par = info->fbcon_par;
	struct vc_data *vc;
	struct fbcon_display *p;
	int rows, cols;

	if (!par || par->currcon < 0) NewCode:	struct fbcon_par *par = info->fbcon_par;
	struct vc_data *vc;
	struct fbcon_display *p;
	int rows, cols;
	int old_rotate, err;

	if (!par || par->currcon < 0) SourceFile:drivers/video/fbdev/core/fbcon.c]

Results:
map[]

45/4 2026/01/29 20:24 llm patch-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 62855
							output: 692
							thoughts: 1473
						

Thoughts:
**Fixing the Rotation Inconsistency**

Okay, I believe I've got this figured out. The core issue was an inconsistency between the console's logical dimensions and the framebuffer operations, leading to crashes. My fix focuses on handling the case where `vc_resize` fails during a mode change. I've added variable declarations and error handling to `fbcon_modechanged`. The critical part is restoring the previous rotation state if `vc_resize` fails, *and* tileblitting isn't being used. This prevents the inconsistent state that causes the crash.

Let me break down the logic:

1.  `old_rotate` now captures the current rotation (`par->rotate`) before *any* changes. This acts as our safety net.
2.  `set_blitting_type` sets up the new rotation and associated operations. This is the intended state we are *trying* to achieve.
3.  `vc_resize` is then called with the dimensions calculated based on the *new* rotation.
4.  Here's the critical error handling: If `vc_resize` fails:
    *   We first check if tileblitting is off. This is important to ensure the proper restoration of the rotation.
    *   If tileblitting is off, we restore the *original* rotation (`par->rotate = old_rotate`) and call `fbcon_set_bitops` to revert the framebuffer operations to their *previous* state. This avoids the bad state.
    *   We then immediately return, skipping `update_screen`. Skipping `update_screen` is crucial, because that's where the crash would occur.
5.  If `vc_resize` succeeds, we proceed as normal, because everything is consistent.

This fix directly addresses the root cause: ensuring that we maintain consistency between the console's logical dimensions and the active framebuffer operations. By reverting to the *old* operations if the dimensions can't be updated, we avoid out-of-bounds access and the crash.

The crash reports point to issues within `sysimgblt.c` and `fb_imageblit`, which are invoked through the rotated operations used by `cw_putcs`. By reverting to `fbcon_set_bitops(par)` with the *old* rotation, we force the code to use the original, correct operations (like unrotated `fbcon_putcs`), which are consistent with the old dimensions. This avoids the out-of-bounds reads.

I'm feeling confident about this fix.

Now, there's a minor detail I want to clarify: The `con_is_visible(vc)` check.

If `con_is_visible(vc)` is false, `vc_resize` isn't called, so the dimensions don't change. However, `set_blitting_type` *is* called. This means that `par->rotate` and the blitting operations are updated to the *new* rotation, even though the console isn't resized. This initially seemed like a potential issue for inconsistency, but after digging deeper, it seems it's okay.

Here's why:

*   When a console switches (`fbcon_switch`), it calls `set_blitting_type` *and* `updatescrollmode`, but *not* `vc_resize`. It seems `fbcon_switch` is designed to work with *already* correctly sized consoles.
*   If a console *isn't* visible, `fbcon_modechanged` updates display settings and operations through `set_blitting_type`, but it *doesn't* call `var_to_display` (inside the `if (con_is_visible(vc))` block). So while the global `par` is updated with the new rotation and operations, the console's `vc->vc_cols` remains unchanged.
*   The function also updates `p->con_rotate` with the new rotation from `par->rotate`.
*   Crucially, when that console *does* become visible, `fbcon_switch` is called. Because the console's `vc` structure hasn't been updated, and `var_to_display` also did not run, the switch *reverts* to the old mode (it uses the old `p->rotate`). The pending rotation update is effectively lost.

In effect, rotating a background console does not take effect until it becomes foreground. I believe that this is the intended behavior. The update is transient while the console is not visible.

The crash occurs when the console *is* visible, and my fix is designed to handle this scenario.

I'm satisfied that my fix is robust. I will implement it.



46/3 2026/01/29 20:25 action test-patch
7m Results:
map[PatchDiff:diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 7be9e8653..4b7fbef76 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -2695,6 +2695,7 @@ static void fbcon_modechanged(struct fb_info *info)
 	struct vc_data *vc;
 	struct fbcon_display *p;
 	int rows, cols;
+	int old_rotate, err;
 
 	if (!par || par->currcon < 0)
 		return;
@@ -2704,6 +2705,7 @@ static void fbcon_modechanged(struct fb_info *info)
 		return;
 
 	p = &fb_display[vc->vc_num];
+	old_rotate = par->rotate;
 	set_blitting_type(vc, info);
 
 	if (con_is_visible(vc)) {
@@ -2712,7 +2714,14 @@ static void fbcon_modechanged(struct fb_info *info)
 		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);
+		err = vc_resize(vc, cols, rows);
+		if (err) {
+			if (!(info->flags & FBINFO_MISC_TILEBLITTING)) {
+				par->rotate = old_rotate;
+				fbcon_set_bitops(par);
+			}
+			return;
+		}
 		updatescrollmode(p, info, vc);
 		scrollback_max = 0;
 		scrollback_current = 0;
 TestError:]

47/1 2026/01/29 20:33 action get-maintainers
0m Results:
map[Recipients:[map[Email:deller@gmx.de Name:Helge Deller To:true] map[Email:dri-devel@lists.freedesktop.org Name: To:true] map[Email:linux-fbdev@vger.kernel.org Name: To:true] map[Email:linux-kernel@vger.kernel.org Name: To:false] map[Email:simona@ffwll.ch Name:Simona Vetter To:true]]]

48/1 2026/01/29 20:33 agent description-generator
2m Model:
gemini-3-pro-preview

Instruction:
You are an experienced Linux kernel developer tasked with writing a commit description for
a kernel bug fixing commit. The description should start with a one-line summary,
and then include description of the bug being fixed, and how it's fixed by the provided patch.
Your final reply should contain only the text of the commit description.
Phrase the one-line summary so that it is not longer than 72 characters.
The rest of the description must be word-wrapped at 72 characters.

Prefer calling several tools at the same time to save round-trips.

Prompt:
The crash that corresponds to the bug is:

R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001
R13: 00007fffd2735b50 R14: 00007f26d9187b40 R15: 0000000000000003
 </TASK>
==================================================================
BUG: KASAN: vmalloc-out-of-bounds in fb_write_offset drivers/video/fbdev/core/sysmem.h:30 [inline]
BUG: KASAN: vmalloc-out-of-bounds in fb_bitmap_2ppw drivers/video/fbdev/core/fb_imageblit.h:364 [inline]
BUG: KASAN: vmalloc-out-of-bounds in fb_bitmap_imageblit drivers/video/fbdev/core/fb_imageblit.h:462 [inline]
BUG: KASAN: vmalloc-out-of-bounds in fb_imageblit drivers/video/fbdev/core/fb_imageblit.h:492 [inline]
BUG: KASAN: vmalloc-out-of-bounds in sys_imageblit+0xfed/0x1ea0 drivers/video/fbdev/core/sysimgblt.c:24
Write of size 8 at addr ffffc90002c223c0 by task syz-executor173/6014

CPU: 0 UID: 0 PID: 6014 Comm: syz-executor173 Not tainted syzkaller #1 PREEMPT(full) 
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
Call Trace:
 <TASK>
 __dump_stack lib/dump_stack.c:94 [inline]
 dump_stack_lvl+0x10e/0x190 lib/dump_stack.c:120
 print_address_description mm/kasan/report.c:378 [inline]
 print_report+0x17e/0x810 mm/kasan/report.c:482
 kasan_report+0x147/0x180 mm/kasan/report.c:595
 fb_write_offset drivers/video/fbdev/core/sysmem.h:30 [inline]
 fb_bitmap_2ppw drivers/video/fbdev/core/fb_imageblit.h:364 [inline]
 fb_bitmap_imageblit drivers/video/fbdev/core/fb_imageblit.h:462 [inline]
 fb_imageblit drivers/video/fbdev/core/fb_imageblit.h:492 [inline]
 sys_imageblit+0xfed/0x1ea0 drivers/video/fbdev/core/sysimgblt.c:24
 drm_fbdev_shmem_defio_imageblit+0x2c/0x110 drivers/gpu/drm/drm_fbdev_shmem.c:39
 cw_putcs_aligned drivers/video/fbdev/core/fbcon_cw.c:110 [inline]
 cw_putcs+0x15af/0x1880 drivers/video/fbdev/core/fbcon_cw.c:158
 fbcon_putcs+0x3bd/0x590 drivers/video/fbdev/core/fbcon.c:1343
 do_update_region+0x3a1/0x460 drivers/tty/vt/vt.c:628
 redraw_screen+0x91a/0xe90 drivers/tty/vt/vt.c:980
 fbcon_rotate drivers/video/fbdev/core/fbcon.c:231 [inline]
 rotate_store+0x33f/0x510 drivers/video/fbdev/core/fbcon.c:3237
 kernfs_fop_write_iter+0x3af/0x540 fs/kernfs/file.c:352
 new_sync_write fs/read_write.c:593 [inline]
 vfs_write+0x5d2/0xb40 fs/read_write.c:686
 ksys_write+0x149/0x250 fs/read_write.c:738
 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
 do_syscall_64+0xe8/0xf80 arch/x86/entry/syscall_64.c:94
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f26d90f4749
Code: c0 79 93 eb d5 48 8d 7c 1d 00 eb 99 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 d8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fffd2735b38 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00007fffd2735b80 RCX: 00007f26d90f4749
RDX: 0000000000000007 RSI: 0000000000000000 RDI: 0000000000000003
RBP: 0000000000000001 R08: 0000000000000000 R09: 00007fffd2735b70
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000001
R13: 00007fffd2735b50 R14: 00007f26d9187b40 R15: 0000000000000003
 </TASK>

The buggy address belongs to a vmalloc virtual mapping
Memory state around the buggy address:
 ffffc90002c22280: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
 ffffc90002c22300: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
>ffffc90002c22380: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
                                           ^
 ffffc90002c22400: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
 ffffc90002c22480: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
==================================================================


The explanation of the root cause of the bug is:

The root cause of the crash is a failure to handle the error return from `vc_resize` in `fbcon_modechanged`, leading to an inconsistency between the console's logical dimensions and the installed framebuffer operations (specifically rotation support).

Here is the step-by-step analysis:

1.  **Rotation Trigger**: The user (or syzkaller) initiates a console rotation via a sysfs write to `rotate` (or `rotate_all`), which calls `fbcon_rotate`.
2.  **State Update**: `fbcon_rotate` updates the requested rotation in `fb_display[vc->vc_num].con_rotate` (e.g., to `FB_ROTATE_CW` for 90-degree clockwise rotation) and calls `fbcon_modechanged`.
3.  **Ops Update**: `fbcon_modechanged` calls `set_blitting_type`, which updates the `fbcon_par->bitops` to the rotated versions (e.g., `cw_fbcon_bitops` containing `cw_putcs`) based on the new rotation.
4.  **Resize Attempt**: `fbcon_modechanged` calculates the new logical dimensions (`cols` and `rows`) for the rotated screen. It calls `vc_resize(vc, cols, rows)` to update the console's text buffer and dimensions (`vc->vc_cols`, `vc->vc_rows`).
5.  **Ignored Failure**: `vc_resize` can fail (e.g., due to memory allocation failure `ENOMEM`, which is common with syzkaller fault injection). **Critically, `fbcon_modechanged` ignores the return value of `vc_resize`.**
6.  **Inconsistent State**: If `vc_resize` fails, the console's `vc_cols` and `vc_rows` remain at their *old* (unrotated) values. However, the bit-blitting operations (`cw_putcs`) are already set to the *new* (rotated) mode.
    *   For example, if the screen was Landscape (wide), `vc_cols` is large.
    *   After rotating to Portrait (tall), `vc_cols` should be smaller.
    *   Due to the failure, `vc_cols` remains large (Landscape width).
7.  **Out-of-Bounds Calculation**: `fbcon_modechanged` proceeds to call `update_screen`, which eventually calls `cw_putcs`.
    *   `cw_putcs` iterates over the characters to be drawn. The iteration limits are based on the (incorrectly large) `vc_cols`.
    *   `cw_putcs` calculates the physical Y coordinate (`image.dy`) in the framebuffer corresponding to the logical column `xx`:
        `image.dy = xx * vc->vc_font.width;`
    *   Since `xx` can go up to the old `vc_cols`, `image.dy` can exceed the physical height (`yres`) of the framebuffer (because the old width is larger than the new height).
8.  **The Crash**: `cw_putcs` calls `fb_imageblit` -> `fb_bitmap_2ppw` -> `fb_write_offset`.
    *   `fb_imageblit` calculates the memory offset: `dst = base + image.dy * stride + image.dx * bpp`.
    *   Because `image.dy` is too large (out of physical bounds), the calculated address points past the end of the allocated vmalloc framebuffer.
    *   `fb_write_offset` attempts to write to this address, triggering the `KASAN: vmalloc-out-of-bounds`.

**Fix**: `fbcon_modechanged` must check the return value of `vc_resize`. If it fails, it should abort the update and potentially revert the rotation setting/ops to ensure the console state remains consistent.

The diff of the bug fix is:

diff --git a/drivers/video/fbdev/core/fbcon.c b/drivers/video/fbdev/core/fbcon.c
index 7be9e8653..4b7fbef76 100644
--- a/drivers/video/fbdev/core/fbcon.c
+++ b/drivers/video/fbdev/core/fbcon.c
@@ -2695,6 +2695,7 @@ static void fbcon_modechanged(struct fb_info *info)
 	struct vc_data *vc;
 	struct fbcon_display *p;
 	int rows, cols;
+	int old_rotate, err;
 
 	if (!par || par->currcon < 0)
 		return;
@@ -2704,6 +2705,7 @@ static void fbcon_modechanged(struct fb_info *info)
 		return;
 
 	p = &fb_display[vc->vc_num];
+	old_rotate = par->rotate;
 	set_blitting_type(vc, info);
 
 	if (con_is_visible(vc)) {
@@ -2712,7 +2714,14 @@ static void fbcon_modechanged(struct fb_info *info)
 		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);
+		err = vc_resize(vc, cols, rows);
+		if (err) {
+			if (!(info->flags & FBINFO_MISC_TILEBLITTING)) {
+				par->rotate = old_rotate;
+				fbcon_set_bitops(par);
+			}
+			return;
+		}
 		updatescrollmode(p, info, vc);
 		scrollback_max = 0;
 		scrollback_current = 0;


Additional description of the patch:

The bug is caused by `fbcon_modechanged` ignoring the return value of `vc_resize`. When `vc_resize` fails (e.g., due to memory allocation failure), the console's logical dimensions (`vc_cols`, `vc_rows`) remain unchanged (e.g., large landscape width), but the framebuffer operations (`par->bitops`) are updated to the new rotation (e.g., portrait mode, which expects smaller width). This mismatch leads to out-of-bounds access in `cw_putcs` (or other rotated ops) when `update_screen` calls them with coordinates that are valid for the old dimensions but invalid for the new rotation.

The fix is to check the return value of `vc_resize` in `fbcon_modechanged`. If it fails, we must revert the changes to `par->rotate` and `par->bitops` to ensure the console state remains consistent with the (unchanged) dimensions.

Here is the patch:

```c
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;
	int old_rotate, err;

	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];
	old_rotate = par->rotate;
	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;
		err = vc_resize(vc, cols, rows);
		if (err) {
			if (!(info->flags & FBINFO_MISC_TILEBLITTING)) {
				par->rotate = old_rotate;
				fbcon_set_bitops(par);
			}
			return;
		}
		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);
	}
}
```



Reply:
fbcon: Handle vc_resize() failure in fbcon_modechanged()

The syzbot reported a vmalloc-out-of-bounds issue in fb_write_offset().
The call trace shows the crash happens in cw_putcs() during a console
rotation.

Root cause analysis shows that fbcon_modechanged() calls
set_blitting_type() which updates par->rotate and par->bitops to the
new rotation settings. Then it calls vc_resize() to update the console
dimensions. However, it ignores the return value of vc_resize().

If vc_resize() fails (e.g. due to OOM), the console dimensions
(vc->vc_cols/rows) remain unchanged, but par->rotate and par->bitops
are already updated to the new rotation. This inconsistency leads to
cw_putcs() using the old (larger) dimensions with the new framebuffer
layout. For example, when rotating from landscape to portrait, the old
width is larger than the new width, causing cw_putcs() to calculate
offsets that exceed the framebuffer bounds.

Fix this by checking the return value of vc_resize(). If it fails,
revert the rotation settings (par->rotate and par->bitops) to their
previous state to ensure consistency.

49/2 2026/01/29 20:33 llm description-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 6545
							output: 65
							thoughts: 862
						

Thoughts:
**Kernel Bug Fix: Framebuffer Rotation OOB Write**

Okay, here's what I'm thinking. I've got a `KASAN: vmalloc-out-of-bounds` error popping up in `fb_write_offset`, which traces back through `fb_bitmap_2ppw` to `cw_putcs`. I've got the crash log, root cause analysis, and a patch. I'll need to write up a commit description for this kernel bug fix.

The core issue appears to be related to framebuffer rotation. The sequence is: `fbcon_rotate` triggers `fbcon_modechanged`. `fbcon_modechanged` then updates the `bitops` via `set_blitting_type` to use rotated framebuffer operations like `cw_putcs`. Next, it recalculates dimensions and calls `vc_resize`. The problem arises when `vc_resize` fails (e.g., due to `ENOMEM`), but `fbcon_modechanged` blithely ignores the error! This leaves the virtual console dimensions (`vc`) in an old, unrotated state, while the `bitops` are new and rotated. The subsequent `update_screen` call, which then uses `cw_putcs`, uses the old `vc_cols` to calculate offsets in the rotated framebuffer. These offsets are then too large for the old, unrotated virtual console, resulting in an out-of-bounds write.

The provided fix is to check the return value of `vc_resize` within `fbcon_modechanged`. If it fails, we need to revert the rotation. This means restoring the original `par->rotate` and the original `bitops`. The patch appears to do this, manually restoring `par->rotate` and then calling `fbcon_set_bitops(par)`.

Now, I need to verify that `fbcon_set_bitops` exists and does what I expect it to do, and I need to confirm how `set_blitting_type` functions.

I'm thinking I'll use `codesearch-definition-source` to jump to `fbcon_modechanged` in `drivers/video/fbdev/core/fbcon.c`. I need to see what's going on in context and confirm the availability of `fbcon_set_bitops` there.

Hmm, wait. It seems `set_blitting_type` updates `par->bitops` *based* on a rotation request. The rotation itself - `par->rotate` - seems to be set *before* calling `fbcon_modechanged`, likely in `fbcon_rotate`. Let's confirm with the code.

Looking at it now, `fbcon_rotate` sets `fb_display[vc->vc_num].con_rotate` . So that is separate from `par->rotate` which `set_blitting_type` uses.

I need to confirm the flow and the use of the `rotate` value. My understanding is that `fbcon_rotate` requests the rotation (sets `fb_display[vc->vc_num].con_rotate`) and *then* calls `fbcon_modechanged`. `fbcon_modechanged` calls `set_blitting_type` which probably then uses `fb_display[vc->vc_num].con_rotate` (or another parameter) and updates `par->bitops`. The patch shows restoring `par->rotate`, so it must be getting changed somewhere. The patch does that inside of `fbcon_modechanged` though... so it seems that `fbcon_modechanged` is responsible for changing `par->rotate`, or the patch is rolling back *something* done earlier.

Let's check the source code to be precise. I'll start with `drivers/video/fbdev/core/fbcon.c`. I'm starting to get a clear picture here, and I just need to verify a few details to make sure the commit description is accurate and complete.



50/2 2026/01/29 20:33 tool codeexpert
0m Args:
map[Question:Show me the code for fbcon_modechanged, set_blitting_type, and fbcon_rotate in drivers/video/fbdev/core/fbcon.c. Also show fbcon_set_bitops definition if available.]

Results:
map[Answer:Here is the source code for the requested functions in `drivers/video/fbdev/core/fbcon.c`:

### `fbcon_modechanged`
This function is called when the video mode of the framebuffer has changed. It updates the console dimensions and re-initializes the blitting type.

```c
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);
	}
}
```

### `set_blitting_type`
This function determines whether to use tile-based blitting or bit-based blitting (with rotation support).

```c
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);
	}
}
```

### `fbcon_rotate`
This function sets the rotation for the console and triggers a mode change to apply it.

```c
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);
	}
}
```

### `fbcon_set_bitops`
This function selects the appropriate bitwise operations based on the current rotation of the console.

```c
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;
	}
}
```]

51/3 2026/01/29 20:33 agent codeexpert
0m Model:
gemini-3-flash-preview

Instruction:
You are a capable Linux kernel developer tasked with researching complex questions
about kernel source code. You will be given a concrete question, and need to provide
a concrete answer.
Use tools extensively while researching the question. Don't make assumptions,
or rely on your previous knowledge about the kernel source code, use available tools
to access the actual source code.
Use all available sources of information:
 - kernel source code
 - documentation in the Documentation dir in the source tree
 - git commits descriptions, git blame

Prefer calling several tools at the same time to save round-trips.

Prompt:
Show me the code for fbcon_modechanged, set_blitting_type, and fbcon_rotate in drivers/video/fbdev/core/fbcon.c. Also show fbcon_set_bitops definition if available.

Reply:
Here is the source code for the requested functions in `drivers/video/fbdev/core/fbcon.c`:

### `fbcon_modechanged`
This function is called when the video mode of the framebuffer has changed. It updates the console dimensions and re-initializes the blitting type.

```c
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);
	}
}
```

### `set_blitting_type`
This function determines whether to use tile-based blitting or bit-based blitting (with rotation support).

```c
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);
	}
}
```

### `fbcon_rotate`
This function sets the rotation for the console and triggers a mode change to apply it.

```c
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);
	}
}
```

### `fbcon_set_bitops`
This function selects the appropriate bitwise operations based on the current rotation of the console.

```c
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;
	}
}
```

52/4 2026/01/29 20:33 llm codeexpert
0m Model:
gemini-3-flash-preview

Tokens:
							input: 2267
							output: 32
							thoughts: 119
						

Thoughts:
**Accessing Key framebuffer console functions**

Okay, so I need to get the source code for a few critical functions within `drivers/video/fbdev/core/fbcon.c`. Specifically, I'm after: `fbcon_modechanged`, `set_blitting_type`, `fbcon_rotate`, and if it's there, `fbcon_set_bitops`. These are fundamental to how the framebuffer console interacts with the display, handling mode changes, blitting operations, rotation, and potentially bitwise operations.

I'll begin by quickly confirming that these entities are indeed declared and implemented within `fbcon.c`. It's always a good first step to avoid any wild goose chases! Once I'm sure they're present, I'll leverage the `codesearch-definition-source` tool – a lifesaver for quickly navigating this kind of kernel code. That should give me the exact source code definitions I need to understand how these core functions are working. Knowing the source is, of course, absolutely crucial to developing a thorough and nuanced understanding of the system as a whole.



53/4 2026/01/29 20:33 tool codesearch-file-index
0m Args:
map[SourceFile:drivers/video/fbdev/core/fbcon.c]

Results:
map[Entities:[map[Kind:function Name:__check_lockless_register_fb] map[Kind:function Name:__fbcon_clear] map[Kind:function Name:con2fb_acquire_newinfo] map[Kind:function Name:con2fb_init_display] map[Kind:function Name:con2fb_release_oldinfo] map[Kind:function Name:cursor_blink_show] map[Kind:function Name:cursor_blink_store] map[Kind:function Name:display_to_var] map[Kind:function Name:do_fb_registered] map[Kind:function Name:do_fbcon_takeover] map[Kind:function Name:fb_console_init] map[Kind:function Name:fb_console_setup] map[Kind:function Name:fb_flashcursor] map[Kind:function Name:fbcon_add_cursor_work] map[Kind:function Name:fbcon_blank] map[Kind:function Name:fbcon_bmove] map[Kind:function Name:fbcon_bmove_rec] map[Kind:function Name:fbcon_clear] map[Kind:function Name:fbcon_clear_margins] map[Kind:function Name:fbcon_cursor] map[Kind:function Name:fbcon_debug_enter] map[Kind:function Name:fbcon_debug_leave] map[Kind:function Name:fbcon_deinit] map[Kind:function Name:fbcon_del_cursor_work] map[Kind:function Name:fbcon_delete_mode] map[Kind:function Name:fbcon_delete_modelist] map[Kind:function Name:fbcon_do_set_font] map[Kind:function Name:fbcon_fb_blanked] map[Kind:function Name:fbcon_fb_registered] map[Kind:function Name:fbcon_fb_unbind] map[Kind:function Name:fbcon_fb_unregistered] map[Kind:function Name:fbcon_free_font] map[Kind:function Name:fbcon_generic_blank] map[Kind:function Name:fbcon_get_con2fb_map_ioctl] map[Kind:function Name:fbcon_get_font] map[Kind:function Name:fbcon_get_requirement] map[Kind:function Name:fbcon_get_rotate] map[Kind:function Name:fbcon_info_from_console] map[Kind:function Name:fbcon_init] map[Kind:function Name:fbcon_invalid_charcount] map[Kind:function Name:fbcon_invert_region] map[Kind:function Name:fbcon_is_active] map[Kind:function Name:fbcon_map_override] map[Kind:function Name:fbcon_mode_deleted] map[Kind:function Name:fbcon_modechange_possible] map[Kind:function Name:fbcon_modechanged] map[Kind:function Name:fbcon_new_modelist] map[Kind:function Name:fbcon_open] map[Kind:function Name:fbcon_prepare_logo] map[Kind:function Name:fbcon_putcs] map[Kind:function Name:fbcon_redraw] map[Kind:function Name:fbcon_redraw_blit] map[Kind:function Name:fbcon_redraw_move] map[Kind:function Name:fbcon_release] map[Kind:function Name:fbcon_release_all] map[Kind:function Name:fbcon_remap_all] map[Kind:function Name:fbcon_resize] map[Kind:function Name:fbcon_resumed] map[Kind:function Name:fbcon_rotate] map[Kind:function Name:fbcon_rotate_all] map[Kind:function Name:fbcon_scroll] map[Kind:function Name:fbcon_select_primary] map[Kind:function Name:fbcon_set_all_vcs] map[Kind:function Name:fbcon_set_bitops] map[Kind:function Name:fbcon_set_con2fb_map_ioctl] map[Kind:function Name:fbcon_set_def_font] map[Kind:function Name:fbcon_set_disp] map[Kind:function Name:fbcon_set_font] map[Kind:function Name:fbcon_set_palette] map[Kind:function Name:fbcon_set_rotation] map[Kind:function Name:fbcon_skip_panic] map[Kind:function Name:fbcon_start] map[Kind:function Name:fbcon_startup] map[Kind:function Name:fbcon_suspended] map[Kind:function Name:fbcon_switch] map[Kind:function Name:fbcon_unbind] map[Kind:function Name:fbcon_update_vcs] map[Kind:function Name:get_bg_color] map[Kind:function Name:get_color] map[Kind:function Name:get_fg_color] map[Kind:function Name:rotate_all_store] map[Kind:function Name:rotate_show] map[Kind:function Name:rotate_store] map[Kind:function Name:search_fb_in_map] map[Kind:function Name:search_for_mapped_con] map[Kind:function Name:set_blitting_type] map[Kind:function Name:set_con2fb_map] map[Kind:function Name:set_vc_hi_font] map[Kind:function Name:updatescrollmode] map[Kind:function Name:updatescrollmode_accel] map[Kind:function Name:var_to_display] map[Kind:function Name:ypan_down] map[Kind:function Name:ypan_down_redraw] map[Kind:function Name:ypan_up] map[Kind:function Name:ypan_up_redraw] map[Kind:function Name:ywrap_down] map[Kind:function Name:ywrap_up]]]

54/4 2026/01/29 20:33 llm codeexpert
0m Model:
gemini-3-flash-preview

Tokens:
							input: 4295
							output: 190
							thoughts: 69
						

Thoughts:
**Analyzing the Framebuffer Console Core Functions**

Okay, so I've been asked to review the key framebuffer console functions. Right off the bat, I know I can find everything I need in `drivers/video/fbdev/core/fbcon.c`. Let me just confirm that the functions in question are present: `fbcon_modechanged`, `set_blitting_type`, `fbcon_rotate`, and `fbcon_set_bitops`... Yep, they're all there. Good, good. Now that I have their location verified, it's time to dig into the source code for each of those. Let's get to it.



55/4 2026/01/29 20:33 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/video/fbdev/core/fbcon.c IncludeLines:true Name:fbcon_modechanged]

Results:
map[SourceCode:2692:	static void fbcon_modechanged(struct fb_info *info)
2693:	{
2694:		struct fbcon_par *par = info->fbcon_par;
2695:		struct vc_data *vc;
2696:		struct fbcon_display *p;
2697:		int rows, cols;
2698:	
2699:		if (!par || par->currcon < 0)
2700:			return;
2701:		vc = vc_cons[par->currcon].d;
2702:		if (vc->vc_mode != KD_TEXT ||
2703:		    fbcon_info_from_console(par->currcon) != info)
2704:			return;
2705:	
2706:		p = &fb_display[vc->vc_num];
2707:		set_blitting_type(vc, info);
2708:	
2709:		if (con_is_visible(vc)) {
2710:			var_to_display(p, &info->var, info);
2711:			cols = FBCON_SWAP(par->rotate, info->var.xres, info->var.yres);
2712:			rows = FBCON_SWAP(par->rotate, info->var.yres, info->var.xres);
2713:			cols /= vc->vc_font.width;
2714:			rows /= vc->vc_font.height;
2715:			vc_resize(vc, cols, rows);
2716:			updatescrollmode(p, info, vc);
2717:			scrollback_max = 0;
2718:			scrollback_current = 0;
2719:	
2720:			if (fbcon_is_active(vc, info)) {
2721:				par->var.xoffset = par->var.yoffset = p->yscroll = 0;
2722:				par->bitops->update_start(info);
2723:			}
2724:	
2725:			fbcon_set_palette(vc, color_table);
2726:			update_screen(vc);
2727:		}
2728:	}
 SourceFile:drivers/video/fbdev/core/fbcon.c]

56/4 2026/01/29 20:33 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/video/fbdev/core/fbcon.c IncludeLines:true Name:set_blitting_type]

Results:
map[SourceCode: 692:	static void set_blitting_type(struct vc_data *vc, struct fb_info *info)
 693:	{
 694:		struct fbcon_par *par = info->fbcon_par;
 695:	
 696:		par->p = &fb_display[vc->vc_num];
 697:	
 698:		if ((info->flags & FBINFO_MISC_TILEBLITTING))
 699:			fbcon_set_tileops(vc, info);
 700:		else {
 701:			fbcon_set_rotation(info);
 702:			fbcon_set_bitops(par);
 703:		}
 704:	}
 SourceFile:drivers/video/fbdev/core/fbcon.c]

57/4 2026/01/29 20:33 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/video/fbdev/core/fbcon.c IncludeLines:true Name:fbcon_rotate]

Results:
map[SourceCode: 213:	static void fbcon_rotate(struct fb_info *info, u32 rotate)
 214:	{
 215:		struct fbcon_par *par = info->fbcon_par;
 216:		struct fb_info *fb_info;
 217:	
 218:		if (!par || par->currcon == -1)
 219:			return;
 220:	
 221:		fb_info = fbcon_info_from_console(par->currcon);
 222:	
 223:		if (info == fb_info) {
 224:			struct fbcon_display *p = &fb_display[par->currcon];
 225:	
 226:			if (rotate < 4)
 227:				p->con_rotate = rotate;
 228:			else
 229:				p->con_rotate = 0;
 230:	
 231:			fbcon_modechanged(info);
 232:		}
 233:	}
 SourceFile:drivers/video/fbdev/core/fbcon.c]

58/4 2026/01/29 20:33 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/video/fbdev/core/fbcon.c IncludeLines:true Name:fbcon_set_bitops]

Results:
map[SourceCode: 276:	static void fbcon_set_bitops(struct fbcon_par *par)
 277:	{
 278:		switch (par->rotate) {
 279:		default:
 280:			fallthrough;
 281:		case FB_ROTATE_UR:
 282:			fbcon_set_bitops_ur(par);
 283:			break;
 284:		case FB_ROTATE_CW:
 285:			fbcon_set_bitops_cw(par);
 286:			break;
 287:		case FB_ROTATE_UD:
 288:			fbcon_set_bitops_ud(par);
 289:			break;
 290:		case FB_ROTATE_CCW:
 291:			fbcon_set_bitops_ccw(par);
 292:			break;
 293:		}
 294:	}
 SourceFile:drivers/video/fbdev/core/fbcon.c]

59/4 2026/01/29 20:33 llm codeexpert
0m Model:
gemini-3-flash-preview

Tokens:
							input: 6165
							output: 1017
							thoughts: 164
						

Thoughts:
**Locating the Requested Functions in `fbcon.c`**

Okay, I've got this. Diving into `drivers/video/fbdev/core/fbcon.c`, I've successfully pinpointed the exact locations for the functions requested. Specifically:

*   `fbcon_modechanged` is at lines 2692-2728.
*   `set_blitting_type` resides on lines 692-704.
*   `fbcon_rotate` can be found at lines 213-233.
*   And finally, `fbcon_set_bitops` is located at lines 276-294.

I should probably do a quick check to see if there are other, related functions in there, like `fbcon_set_tileops`. But the instructions are clear; the focus is on those specific functions. And yes, all four requested functions are right there, exactly where they should be.



60/2 2026/01/29 20:33 llm description-generator
0m Model:
gemini-3-pro-preview

Tokens:
							input: 9359
							output: 36
							thoughts: 477
						

Thoughts:
**Analyzing the Rotation Logic in the Framebuffer Console**

Alright, I've got the code here, and I'm tracing the flow to understand this rotation issue. It looks like `fbcon_rotate` initiates everything by setting `p->con_rotate` and then calling `fbcon_modechanged`. `fbcon_modechanged` is the workhorse here, and it's calling a series of functions that are crucial.

First, it calls `set_blitting_type` which, in turn, calls `fbcon_set_rotation`. I can guess that `fbcon_set_rotation` updates the `par->rotate` value to match the desired rotation from `p->con_rotate`. And it sets `fbcon_set_bitops` to set `par->bitops` based on `par->rotate`.

Then, `fbcon_modechanged` calculates the new `cols` and `rows` based on `par->rotate`, using `FBCON_SWAP`, which makes sense. Here's where it gets interesting: it calls `vc_resize(vc, cols, rows)`. If this resize fails, the critical point is that `vc->vc_cols` and `vc->vc_rows` retain their *old* values! But, `par->rotate` and `par->bitops` are *new*.

The problem surfaces in the subsequent call to `update_screen(vc)`. This function eventually uses the `par->bitops->putcs` function, like `cw_putcs`, to render characters. `cw_putcs` loops up to `vc->vc_cols`... which is the *old* value, and this is large. The offset calculation within `cw_putcs` uses this old, large `vc->vc_cols`, along with the `vc->vc_font.width` and the loop index.

Now, this is where the mismatch occurs. `cw_putcs` *assumes* the framebuffer layout is based on the *new* `par->rotate` (the rotated one). But, because the resize failed, `vc_cols` and `vc_rows` are still based on the *old* rotation. Considering landscape screens usually have `yres < xres`, treating a large X coordinate (from the old unrotated space) as a large Y coordinate (in the new rotated space) causes this out-of-bounds access. Essentially, the code tries to write outside of the allocated framebuffer region.

The fix, therefore, is focused on handling the failure case of `vc_resize`. I'll need to confirm what `fbcon_set_rotation` actually does, to be certain but this is my working hypothesis.



61/2 2026/01/29 20:33 tool codeexpert
0m Args:
map[Question:Show me fbcon_set_rotation in drivers/video/fbdev/core/fbcon.c]

Results:
map[Answer:The function `fbcon_set_rotation` is defined as an inline function in `drivers/video/fbdev/core/fbcon.c`. It sets the rotation for the framebuffer console based on the current display configuration, provided that tile blitting is not being used.

```c
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;
}
```

### Context:
- **`info`**: A pointer to `struct fb_info`, which contains the current state of the framebuffer.
- **`par`**: A pointer to `struct fbcon_par`, which holds private data for the framebuffer console.
- **`par->p`**: Points to a `struct fbcon_display` (typically representing the display for a specific virtual console).
- **`FBINFO_MISC_TILEBLITTING`**: If this flag is set in `info->flags`, it indicates the driver uses tile-based blitting, which generally doesn't support the same rotation mechanism as bit-aligned framebuffers in `fbcon`.
- **`con_rotate`**: The requested rotation value (usually 0, 1, 2, or 3, representing 0, 90, 180, and 270 degrees).]

62/3 2026/01/29 20:33 agent codeexpert
0m Model:
gemini-3-flash-preview

Instruction:
You are a capable Linux kernel developer tasked with researching complex questions
about kernel source code. You will be given a concrete question, and need to provide
a concrete answer.
Use tools extensively while researching the question. Don't make assumptions,
or rely on your previous knowledge about the kernel source code, use available tools
to access the actual source code.
Use all available sources of information:
 - kernel source code
 - documentation in the Documentation dir in the source tree
 - git commits descriptions, git blame

Prefer calling several tools at the same time to save round-trips.

Prompt:
Show me fbcon_set_rotation in drivers/video/fbdev/core/fbcon.c

Reply:
The function `fbcon_set_rotation` is defined as an inline function in `drivers/video/fbdev/core/fbcon.c`. It sets the rotation for the framebuffer console based on the current display configuration, provided that tile blitting is not being used.

```c
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;
}
```

### Context:
- **`info`**: A pointer to `struct fb_info`, which contains the current state of the framebuffer.
- **`par`**: A pointer to `struct fbcon_par`, which holds private data for the framebuffer console.
- **`par->p`**: Points to a `struct fbcon_display` (typically representing the display for a specific virtual console).
- **`FBINFO_MISC_TILEBLITTING`**: If this flag is set in `info->flags`, it indicates the driver uses tile-based blitting, which generally doesn't support the same rotation mechanism as bit-aligned framebuffers in `fbcon`.
- **`con_rotate`**: The requested rotation value (usually 0, 1, 2, or 3, representing 0, 90, 180, and 270 degrees).

63/4 2026/01/29 20:33 llm codeexpert
0m Model:
gemini-3-flash-preview

Tokens:
							input: 2238
							output: 48
							thoughts: 66
						

Thoughts:
Here's my thought process, as it stands:

**Locating and Examining `fbcon_set_rotation`**

Alright, I need to get the definitive source for the `fbcon_set_rotation` function. Specifically, I'm after the implementation within `drivers/video/fbdev/core/fbcon.c`. To get this, I will use `codesearch-definition-source`, because that will be the most direct way to get the exact source code definition I need. It's the cleanest approach, ensuring I get the correct implementation without any ambiguity. That should give me everything I need to analyze its behavior.



64/4 2026/01/29 20:33 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/video/fbdev/core/fbcon.c IncludeLines:true Name:fbcon_set_rotation]

Results:
map[SourceCode: 202:	static inline void fbcon_set_rotation(struct fb_info *info)
 203:	{
 204:		struct fbcon_par *par = info->fbcon_par;
 205:	
 206:		if (!(info->flags & FBINFO_MISC_TILEBLITTING) &&
 207:		    par->p->con_rotate < 4)
 208:			par->rotate = par->p->con_rotate;
 209:		else
 210:			par->rotate = 0;
 211:	}
 SourceFile:drivers/video/fbdev/core/fbcon.c]

65/4 2026/01/29 20:33 llm codeexpert
0m Model:
gemini-3-flash-preview

Tokens:
							input: 2676
							output: 46
							thoughts: 311
						

Thoughts:
**Analyzing `fbcon_set_rotation`**

Okay, so the code for `fbcon_set_rotation` from `drivers/video/fbdev/core/fbcon.c` is in front of me now. It's a remarkably concise function. Just a few lines, which is typical for these framebuffer console driver routines. The prompt simply asked for it, and here it is. I'm already thinking about what else might be useful to include, even if it wasn't explicitly requested. It's second nature to anticipate potential follow-up questions or requests for context.

First things first, I'm noting the core logic: it appears to set the rotation value, `par->rotate`, based on a couple of conditions. Specifically, it seems to depend on the `FBINFO_MISC_TILEBLITTING` flag within the framebuffer info structure and, if that's not set, it checks a rotation value, `par->p->con_rotate`, presumably representing a desired rotation angle or setting. If `con_rotate` is less than 4, it uses that value; otherwise it defaults to no rotation.

Now, I'm already mentally reaching for the definitions of `fbcon_par` and what `par->p` actually refers to. While this code snippet is self-contained, understanding the broader context of these structures and variables, especially `par->p`, would offer a much fuller understanding. Is `par->p` itself a pointer to a console-specific data structure? I'll probably want to peek at the definition of `struct fbcon_par` and see where this `p` field is defined. Giving that extra context would be a good service here.



66/4 2026/01/29 20:34 tool codesearch-definition-source
0m Args:
map[ContextFile:drivers/video/fbdev/core/fbcon.c IncludeLines:false Name:fbcon_par]

Results:
map[SourceCode:struct fbcon_par {
	struct fb_var_screeninfo var;  /* copy of the current fb_var_screeninfo */
	struct delayed_work cursor_work; /* Cursor timer */
	struct fb_cursor cursor_state;
	struct fbcon_display *p;
	struct fb_info *info;
        int    currcon;	                /* Current VC. */
	int    cur_blink_jiffies;
	int    cursor_flash;
	int    cursor_reset;
	int    blank_state;
	int    graphics;
	int    save_graphics; /* for debug enter/leave */
	bool   initialized;
	int    rotate;
	int    cur_rotate;
	char  *cursor_data;
	u8    *fontbuffer;
	u8    *fontdata;
	u8    *cursor_src;
	u32    cursor_size;
	u32    fd_size;

	const struct fbcon_bitops *bitops;
};
 SourceFile:drivers/video/fbdev/core/fbcon.h]

67/4 2026/01/29 20:34 llm codeexpert
0m Model:
gemini-3-flash-preview

Tokens:
							input: 3665
							output: 351
							thoughts: 105
						

Thoughts:
**Analyzing the Framebuffer Console Rotation Logic**

Okay, so I'm looking at the `fbcon_set_rotation` function. It's nestled away in `drivers/video/fbdev/core/fbcon.h`, which makes sense for framebuffer console device drivers. I see the `fbcon_par` struct, and within it, the `par->p` member points to a `struct fbcon_display`. That's where the core display information is held, good.

Now, I'm tracing the rotation logic. It seems `fbcon_set_rotation` primarily works by setting `par->rotate` based on `par->p->con_rotate`. Sounds straightforward enough. However, there's a crucial condition: tile blitting *cannot* be enabled, and `con_rotate` needs to be less than 4. If either of those conditions isn't met, the rotation is essentially disabled, and `par->rotate` gets reset to 0. That's a fail-safe. Good to know. It's a design choice I'd make myself, protecting against incompatible states.



68/2 2026/01/29 20:34 llm description-generator
1m Model:
gemini-3-pro-preview

Tokens:
							input: 10814
							output: 277
							thoughts: 6674
						

Thoughts:
**fbcon: Handle vc_resize() failure in fbcon_modechanged()**

Okay, so here's the deal: syzbot, likely through syzkaller, found a vmalloc-out-of-bounds issue in `fb_write_offset`. The call trace, and my own exploration, points to a crash in `cw_putcs()` – or at least, a blitting routine that's rotated, during a console rotation. The root cause is this: `fbcon_modechanged()` is the culprit. It calls `set_blitting_type()`, which dutifully updates the `par->rotate` and `par->bitops` variables to the *new* rotation settings. Then, and this is the rub, it calls `vc_resize()` to change the console dimensions to match that new orientation (potentially swapping width/height). But here's where the problem lies: `fbcon_modechanged()` *completely* ignores the return value of `vc_resize()`.

So, if `vc_resize()` fails (say, due to an out-of-memory error), the console dimensions (`vc->vc_cols/rows`) remain stuck at their *old* values. But `par->rotate` and `par->bitops` are *already* updated to the new, intended rotation. This creates an *inconsistency*.

The end result? `cw_putcs()` (or other blitting functions using rotated operations) then try to use these old dimensions with the new framebuffer layout. For example, if we're going from landscape to portrait, the old width is much larger than the new width in terms of logical columns. This leads to the rotated blit functions calculating offsets that exceed the framebuffer's bounds, which results in an out-of-bounds access. The fix is pretty straightforward: Check the return value of `vc_resize()`. If `vc_resize()` fails (returns an error), revert the rotation settings (`par->rotate` and `par->bitops`) to their original values before the attempted resize. This puts everything back into a consistent state, ensuring the dimensions match the blitting operations, and prevents the crash.



LLM Calls Summary:
Total Calls Total Tokens Avg Tokens Total Duration (Seconds) Avg Duration (Seconds)
Tool Calls Summary:
Total Calls Total Duration (Seconds) Avg Duration (Seconds)