[RFC] Allow changing font on inactive consoles

Colin Watson cjwatson at ubuntu.com
Fri Mar 5 00:26:17 UTC 2010


I'm planning to send something along these lines upstream after a little
more work.  Before I submit myself to their tender mercies, could
somebody do me a favour and give this a once-over to check that I'm
working along the right lines?

The background to this change is that I am truly, madly, deeply fed up
of fixing the userspace side of console font setup every two releases or
so.  I think this is the third or fourth time I've had to do it, and the
reason it's so delicate is because of a kernel limitation: even though
there's an ioctl to let you set the font on a non-foreground virtual
console, you can't really rely on it because vgacon has a single font
for all virtual consoles and stores it only in video memory and nowhere
else.  The font ioctls only work on KD_TEXT consoles, which I believe is
to stop vgacon trashing your graphics by scribbling over video memory.

As a result, if you happen to be in graphics mode to show a splash
screen, you can't use the font ioctls at all; or, for that matter, if
you find yourself racing to start X and X wins, you're hosed because any
attempt to set the font on a vgacon-based system will write to video
memory and confuse the living daylights out of X.

As far as I can see, the other console implementations don't have this
problem: they have per-console fonts and don't write anything to video
memory when changing the font, only when actually visible and displaying
text.  vgacon was quite a long way from this, but fixing it there seemed
the best way to get rid of this problem permanently.

I don't often program in kernelspace, and am somewhat reliant on
cargo-culting.  For example, I'm not sure I need to take the BKL while
setting the font in vgacon_switch, but: that's what the comparable code
in complete_change_console did; I think the console semaphore is
sometimes held when calling vgacon_switch but I'm not sure if it always
is; I don't think I can recursively take the console semaphore; and my
system locked up when I had no locking there at all.  Advice on the
Right Thing To Do would be greatly appreciated.

I've tested that basic font setting and VC switching works.  I made an
initial stab at fixing VC resizing when setting a font with a different
height, but this isn't working properly yet.  I haven't yet tested
getting from KD_GRAPHICS to KD_TEXT either by ioctl(KDSETMODE) or by VC
switching, although I thought fairly hard about it and tried to make
sure it would work.

I don't like the horrible FONT_EXTRA_WORDS thing, but it's in line with
the other console implementations; what's wrong with structs, eh?

Is it safe to write to the VGA sequence and graphics registers from
vgacon_startup?  It seems to work here ...

Is there anything else I should be considering?


commit eb6ebc903f4b048b4ecab0ec6c0dc1292fae40cb
Author: Colin Watson <cjwatson at canonical.com>
Date:   Thu Feb 25 21:23:32 2010 +0000

    Allow changing font on inactive consoles
    
    Console font changes were only permitted when the console was in KD_TEXT
    mode.  This was apparently because vgacon stored the font only in video
    memory, so it could only be changed on an active text console.  Both of
    these are inconvenient for userspace at boot time; they mean that, if
    you want to set the console font, you must do so when no splash screen
    is running and before X starts, and trying to make console set-up
    asynchronous is fraught with difficulty.
    
    Other than a little extra memory use for the font data (which can be
    shared if multiple VTs use the same font), there's no reason why vgacon
    can't store the font in kernel memory and set it on VT switches, as
    other console implementations already do.  This change does so and lifts
    the restrictions on the font ioctls.  In order to support
    BROKEN_GRAPHICS_PROGRAMS (the default), we save the default font on
    vgacon startup.
    
    As bonuses, we get per-console fonts in vgacon, and we can get rid of
    the nasty vgacon-specific code in complete_change_console.
    
    The downsides of this are 32KB extra memory for the default font (when
    BROKEN_GRAPHICS_PROGRAMS is defined), and a font change on every VC
    switch since by the time vgacon_switch is called we no longer know the
    previous active VC (but it doesn't seem to be perceptible here).
    
    Signed-off-by: Colin Watson <cjwatson at canonical.com>

diff --git a/drivers/char/vt.c b/drivers/char/vt.c
index 0c80c68..a32ad9d 100644
--- a/drivers/char/vt.c
+++ b/drivers/char/vt.c
@@ -3840,9 +3840,6 @@ static int con_font_get(struct vc_data *vc, struct console_font_op *op)
 	int rc = -EINVAL;
 	int c;
 
-	if (vc->vc_mode != KD_TEXT)
-		return -EINVAL;
-
 	if (op->data) {
 		font.data = kmalloc(max_font_size, GFP_KERNEL);
 		if (!font.data)
@@ -3895,8 +3892,6 @@ static int con_font_set(struct vc_data *vc, struct console_font_op *op)
 	int rc = -EINVAL;
 	int size;
 
-	if (vc->vc_mode != KD_TEXT)
-		return -EINVAL;
 	if (!op->data)
 		return -EINVAL;
 	if (op->charcount > 512)
@@ -3953,9 +3948,6 @@ static int con_font_default(struct vc_data *vc, struct console_font_op *op)
 	char *s = name;
 	int rc;
 
-	if (vc->vc_mode != KD_TEXT)
-		return -EINVAL;
-
 	if (!op->data)
 		s = NULL;
 	else if (strncpy_from_user(name, op->data, MAX_FONT_NAME - 1) < 0)
@@ -3981,9 +3973,6 @@ static int con_font_copy(struct vc_data *vc, struct console_font_op *op)
 	int con = op->height;
 	int rc;
 
-	if (vc->vc_mode != KD_TEXT)
-		return -EINVAL;
-
 	acquire_console_sem();
 	if (!vc->vc_sw->con_font_copy)
 		rc = -ENOSYS;
diff --git a/drivers/char/vt_ioctl.c b/drivers/char/vt_ioctl.c
index 73aff4c..10e31e0 100644
--- a/drivers/char/vt_ioctl.c
+++ b/drivers/char/vt_ioctl.c
@@ -1602,29 +1602,8 @@ static void complete_change_console(struct vc_data *vc)
 	 */
 	old_vc_mode = oldvc->vc_mode;
 
-#if defined(CONFIG_VGA_CONSOLE)
-	if (old_vc_mode == KD_TEXT && oldvc->vc_sw == &vga_con &&
-	    oldvc->vc_sw->con_font_get) {
-		if (!oldvc->vc_font.data)
-			oldvc->vc_font.data = kmalloc(max_font_size, 
-						      GFP_KERNEL);
-		lock_kernel();
-		oldvc->vc_sw->con_font_get(oldvc, &oldvc->vc_font);
-		unlock_kernel();
-	}
-#endif
 	switch_screen(vc);
 
-#if defined(CONFIG_VGA_CONSOLE)
-	if (vc->vc_mode == KD_TEXT && vc->vc_sw == &vga_con &&
-	    vc->vc_sw->con_font_set) {
-		if (vc->vc_font.data) {
-			lock_kernel();
-			vc->vc_sw->con_font_set(vc, &vc->vc_font, 0);
-			unlock_kernel();
-		}
-	}
-#endif
 	/*
 	 * This can't appear below a successful kill_pid().  If it did,
 	 * then the *blank_screen operation could occur while X, having
diff --git a/drivers/video/console/vgacon.c b/drivers/video/console/vgacon.c
index da55cca..613c2cd 100644
--- a/drivers/video/console/vgacon.c
+++ b/drivers/video/console/vgacon.c
@@ -70,6 +70,11 @@ static struct vgastate state;
  */
 #undef TRIDENT_GLITCH
 #define VGA_FONTWIDTH       8   /* VGA does not support fontwidths != 8 */
+
+/* Font; borrowed from fbcon.c */
+#define REFCOUNT(fd)	(((int *)(fd))[-1])
+#define FONT_EXTRA_WORDS 1
+
 /*
  *  Interface used by the world
  */
@@ -80,6 +85,8 @@ static void vgacon_deinit(struct vc_data *c);
 static void vgacon_cursor(struct vc_data *c, int mode);
 static int vgacon_switch(struct vc_data *c);
 static int vgacon_blank(struct vc_data *c, int blank, int mode_switch);
+static int vgacon_do_font_op(struct vgastate *state, char *arg, int set,
+			     int ch512);
 static int vgacon_set_palette(struct vc_data *vc, unsigned char *table);
 static int vgacon_scrolldelta(struct vc_data *c, int lines);
 static int vgacon_set_origin(struct vc_data *c);
@@ -103,6 +110,9 @@ static unsigned int	vga_default_font_height __read_mostly;	/* Height of default
 static unsigned char	vga_video_type		__read_mostly;	/* Card type */
 static unsigned char	vga_hardscroll_enabled	__read_mostly;
 static unsigned char	vga_hardscroll_user_enable __read_mostly = 1;
+#if defined(CAN_LOAD_EGA_FONTS) && defined(BROKEN_GRAPHICS_PROGRAMS)
+static unsigned char *	vga_default_font;
+#endif
 static unsigned char	vga_font_is_default = 1;
 static int		vga_vesa_blanked;
 static int 		vga_palette_blanked;
@@ -546,6 +556,21 @@ static const char *vgacon_startup(void)
 	vgacon_xres = screen_info.orig_video_cols * VGA_FONTWIDTH;
 	vgacon_yres = vga_scan_lines;
 
+#if defined(CAN_LOAD_EGA_FONTS) && defined(BROKEN_GRAPHICS_PROGRAMS)
+	/* Remember the default font so that we can restore it.  Otherwise,
+	 * switching VC from one with a custom font to one with the default
+	 * font will fail.
+	 */
+	/* Default font is always 256 characters */
+	vga_default_font = kmalloc(FONT_EXTRA_WORDS * sizeof(int) + 256 * 32,
+				   GFP_USER);
+	if (vga_default_font) {
+		vga_default_font += FONT_EXTRA_WORDS * sizeof(int);
+		REFCOUNT(vga_default_font) = 1; /* should never go to 0 */
+		vgacon_do_font_op(&state, vga_default_font, 0, 0);
+	}
+#endif
+
 	if (!vga_init_done) {
 		vgacon_scrollback_startup();
 		vga_init_done = 1;
@@ -574,6 +599,13 @@ static void vgacon_init(struct vc_data *c, int init)
 
 	c->vc_scan_lines = vga_scan_lines;
 	c->vc_font.height = vga_video_font_height;
+	c->vc_font.charcount = 256;
+#if defined(CAN_LOAD_EGA_FONTS) && defined(BROKEN_GRAPHICS_PROGRAMS)
+	c->vc_font.data = vga_default_font;
+	REFCOUNT(vga_default_font)++;
+#else
+	c->vc_font.data = NULL;
+#endif
 	c->vc_complement_mask = 0x7700;
 	if (vga_512_chars)
 		c->vc_hi_font_mask = 0x0800;
@@ -587,6 +619,13 @@ static void vgacon_init(struct vc_data *c, int init)
 		con_set_default_unimap(c);
 }
 
+static void vgacon_free_font(struct vc_data *c)
+{
+	if (c->vc_font.data && --REFCOUNT(c->vc_font.data) == 0)
+		kfree(c->vc_font.data - FONT_EXTRA_WORDS * sizeof(int));
+	c->vc_font.data = NULL;
+}
+
 static void vgacon_deinit(struct vc_data *c)
 {
 	/* When closing the active console, reset video origin */
@@ -599,6 +638,8 @@ static void vgacon_deinit(struct vc_data *c)
 		con_free_unimap(c);
 	c->vc_uni_pagedir_loc = &c->vc_uni_pagedir;
 	con_set_default_unimap(c);
+
+	vgacon_free_font(c);
 }
 
 static u8 vgacon_build_attr(struct vc_data *c, u8 color, u8 intensity,
@@ -838,6 +879,13 @@ static int vgacon_switch(struct vc_data *c)
 		     vga_video_num_columns <= screen_info.orig_video_cols &&
 		     vga_video_num_lines <= rows))
 			vgacon_doresize(c, c->vc_cols, c->vc_rows);
+
+#ifdef CAN_LOAD_EGA_FONTS
+		lock_kernel();
+		vgacon_do_font_op(&state, c->vc_font.data, 1,
+				  c->vc_font.charcount == 512);
+		unlock_kernel();
+#endif
 	}
 
 	vgacon_scrollback_init(c->vc_size_row);
@@ -1076,15 +1124,20 @@ static int vgacon_do_font_op(struct vgastate *state,char *arg,int set,int ch512)
 		beg = 0x0a;
 	}
 
+	if (set) {
+		vga_font_is_default = !arg;
+		if (!arg)
+			ch512 = 0;	/* Default font is always 256 */
+	}
+
 #ifdef BROKEN_GRAPHICS_PROGRAMS
 	/*
 	 * All fonts are loaded in slot 0 (0:1 for 512 ch)
 	 */
 
-	if (!arg)
-		return -EINVAL;	/* Return to default font not supported */
+	if (!arg && set)
+		arg = vga_default_font; /* Return to default font */
 
-	vga_font_is_default = 0;
 	font_select = ch512 ? 0x04 : 0x00;
 #else
 	/*
@@ -1092,12 +1145,8 @@ static int vgacon_do_font_op(struct vgastate *state,char *arg,int set,int ch512)
 	 * A custom font is loaded in slot 2 (256 ch) or 2:3 (512 ch)
 	 */
 
-	if (set) {
-		vga_font_is_default = !arg;
-		if (!arg)
-			ch512 = 0;	/* Default font is always 256 */
+	if (set)
 		font_select = arg ? (ch512 ? 0x0e : 0x0a) : 0x00;
-	}
 
 	if (!vga_font_is_default)
 		charmap += 4 * cmapsz;
@@ -1169,12 +1218,6 @@ static int vgacon_do_font_op(struct vgastate *state,char *arg,int set,int ch512)
 
 	/* if 512 char mode is already enabled don't re-enable it. */
 	if ((set) && (ch512 != vga_512_chars)) {
-		/* attribute controller */
-		for (i = 0; i < MAX_NR_CONSOLES; i++) {
-			struct vc_data *c = vc_cons[i].d;
-			if (c && c->vc_sw == &vga_con)
-				c->vc_hi_font_mask = ch512 ? 0x0800 : 0;
-		}
 		vga_512_chars = ch512;
 		/* 256-char: enable intensity bit
 		   512-char: disable intensity bit */
@@ -1197,7 +1240,7 @@ static int vgacon_do_font_op(struct vgastate *state,char *arg,int set,int ch512)
 static int vgacon_adjust_height(struct vc_data *vc, unsigned fontheight)
 {
 	unsigned char ovr, vde, fsr;
-	int rows, maxscan, i;
+	int rows, maxscan;
 
 	rows = vc->vc_scan_lines / fontheight;	/* Number of video rows we end up with */
 	maxscan = rows * fontheight - 1;	/* Scan lines to actually display-1 */
@@ -1234,27 +1277,16 @@ static int vgacon_adjust_height(struct vc_data *vc, unsigned fontheight)
 	spin_unlock_irq(&vga_lock);
 	vga_video_font_height = fontheight;
 
-	for (i = 0; i < MAX_NR_CONSOLES; i++) {
-		struct vc_data *c = vc_cons[i].d;
-
-		if (c && c->vc_sw == &vga_con) {
-			if (CON_IS_VISIBLE(c)) {
-			        /* void size to cause regs to be rewritten */
-				cursor_size_lastfrom = 0;
-				cursor_size_lastto = 0;
-				c->vc_sw->con_cursor(c, CM_DRAW);
-			}
-			c->vc_font.height = fontheight;
-			vc_resize(c, 0, rows);	/* Adjust console size */
-		}
-	}
 	return 0;
 }
 
 static int vgacon_font_set(struct vc_data *c, struct console_font *font, unsigned flags)
 {
 	unsigned charcount = font->charcount;
-	int rc;
+	int h = font->height;
+	int i;
+	u8 *new_data, *data = font->data;
+	int rc = 0;
 
 	if (vga_video_type < VIDEO_TYPE_EGAM)
 		return -EINVAL;
@@ -1263,26 +1295,78 @@ static int vgacon_font_set(struct vc_data *c, struct console_font *font, unsigne
 	    (charcount != 256 && charcount != 512))
 		return -EINVAL;
 
-	rc = vgacon_do_font_op(&state, font->data, 1, charcount == 512);
-	if (rc)
-		return rc;
+	new_data = kmalloc(FONT_EXTRA_WORDS * sizeof(int) + charcount * 32,
+			   GFP_USER);
+	if (!new_data)
+		return -ENOMEM;
+
+	new_data += FONT_EXTRA_WORDS * sizeof(int);
+	REFCOUNT(new_data) = 0;	/* usage counter */
+	memcpy(new_data, data, charcount * 32);
+
+	/* Check if the same font is on some other console already */
+	for (i = 0; i < MAX_NR_CONSOLES; i++) {
+		struct vc_data *other = vc_cons[i].d;
+		if (i != c->vc_num && other &&
+		    other->vc_font.height == h &&
+		    other->vc_font.charcount == charcount &&
+		    !memcmp(other->vc_font.data, new_data, charcount * 32)) {
+			kfree(new_data - FONT_EXTRA_WORDS * sizeof(int));
+			new_data = (u8 *)other->vc_font.data;
+			break;
+		}
+	}
+
+	if (CON_IS_VISIBLE(c)) {
+		rc = vgacon_do_font_op(&state, new_data, 1, charcount == 512);
+		if (rc)
+			return rc;
+	}
+
+	c->vc_font.height = h;
+	c->vc_font.charcount = charcount;
+	c->vc_font.data = new_data;
+	REFCOUNT(new_data)++;
+	/* attribute controller */
+	c->vc_hi_font_mask = (charcount == 512) ? 0x0800 : 0;
 
 	if (!(flags & KD_FONT_FLAG_DONT_RECALC))
 		rc = vgacon_adjust_height(c, font->height);
+	if (CON_IS_VISIBLE(c)) {
+		/* void size to cause regs to be rewritten */
+		cursor_size_lastfrom = 0;
+		cursor_size_lastto = 0;
+		c->vc_sw->con_cursor(c, CM_DRAW);
+	}
+	/* Adjust console size */
+	vc_resize(c, 0, c->vc_scan_lines / font->height);
 	return rc;
 }
 
 static int vgacon_font_get(struct vc_data *c, struct console_font *font)
 {
+	u8 *fontdata = c->vc_font.data;
+	u8 *data = font->data;
+	int i, j;
+
 	if (vga_video_type < VIDEO_TYPE_EGAM)
 		return -EINVAL;
 
 	font->width = VGA_FONTWIDTH;
 	font->height = c->vc_font.height;
-	font->charcount = vga_512_chars ? 512 : 256;
-	if (!font->data)
+	font->charcount = c->vc_font.charcount;
+	if (fontdata) {
+		j = font->height;
+		for (i = 0; i < font->charcount; i++) {
+			memcpy(data, fontdata, j);
+			memset(data + j, 0, 32 - j);
+			data += 32;
+			fontdata += j;
+		}
 		return 0;
-	return vgacon_do_font_op(&state, font->data, 0, vga_512_chars);
+	} else
+		return vgacon_do_font_op(&state, font->data, 0,
+					 font->charcount);
 }
 
 #else

Thanks,

-- 
Colin Watson                                       [cjwatson at ubuntu.com]




More information about the kernel-team mailing list