diff options
Diffstat (limited to 'drivers/tty/vt')
-rw-r--r-- | drivers/tty/vt/consolemap.c | 115 | ||||
-rw-r--r-- | drivers/tty/vt/keyboard.c | 4 | ||||
-rw-r--r-- | drivers/tty/vt/vt.c | 92 |
3 files changed, 139 insertions, 72 deletions
diff --git a/drivers/tty/vt/consolemap.c b/drivers/tty/vt/consolemap.c index 9d7ab7b66a8a..71e81406ef71 100644 --- a/drivers/tty/vt/consolemap.c +++ b/drivers/tty/vt/consolemap.c @@ -9,6 +9,17 @@ * Support for multiple unimaps by Jakub Jelinek <jj@ultra.linux.cz>, July 1998 * * Fix bug in inverse translation. Stanislav Voronyi <stas@cnti.uanet.kharkov.ua>, Dec 1998 + * + * In order to prevent the following circular lock dependency: + * &mm->mmap_sem --> cpu_hotplug.lock --> console_lock --> &mm->mmap_sem + * + * We cannot allow page fault to happen while holding the console_lock. + * Therefore, all the userspace copy operations have to be done outside + * the console_lock critical sections. + * + * As all the affected functions are all called directly from vt_ioctl(), we + * can allocate some small buffers directly on stack without worrying about + * stack overflow. */ #include <linux/module.h> @@ -22,6 +33,7 @@ #include <linux/console.h> #include <linux/consolemap.h> #include <linux/vt_kern.h> +#include <linux/string.h> static unsigned short translations[][256] = { /* 8-bit Latin-1 mapped to Unicode -- trivial mapping */ @@ -309,18 +321,19 @@ static void update_user_maps(void) int con_set_trans_old(unsigned char __user * arg) { int i; - unsigned short *p = translations[USER_MAP]; + unsigned short inbuf[E_TABSZ]; if (!access_ok(VERIFY_READ, arg, E_TABSZ)) return -EFAULT; - console_lock(); - for (i=0; i<E_TABSZ ; i++) { + for (i = 0; i < E_TABSZ ; i++) { unsigned char uc; __get_user(uc, arg+i); - p[i] = UNI_DIRECT_BASE | uc; + inbuf[i] = UNI_DIRECT_BASE | uc; } + console_lock(); + memcpy(translations[USER_MAP], inbuf, sizeof(inbuf)); update_user_maps(); console_unlock(); return 0; @@ -330,35 +343,37 @@ int con_get_trans_old(unsigned char __user * arg) { int i, ch; unsigned short *p = translations[USER_MAP]; + unsigned char outbuf[E_TABSZ]; if (!access_ok(VERIFY_WRITE, arg, E_TABSZ)) return -EFAULT; console_lock(); - for (i=0; i<E_TABSZ ; i++) + for (i = 0; i < E_TABSZ ; i++) { ch = conv_uni_to_pc(vc_cons[fg_console].d, p[i]); - __put_user((ch & ~0xff) ? 0 : ch, arg+i); + outbuf[i] = (ch & ~0xff) ? 0 : ch; } console_unlock(); + + for (i = 0; i < E_TABSZ ; i++) + __put_user(outbuf[i], arg+i); return 0; } int con_set_trans_new(ushort __user * arg) { int i; - unsigned short *p = translations[USER_MAP]; + unsigned short inbuf[E_TABSZ]; if (!access_ok(VERIFY_READ, arg, E_TABSZ*sizeof(unsigned short))) return -EFAULT; - console_lock(); - for (i=0; i<E_TABSZ ; i++) { - unsigned short us; - __get_user(us, arg+i); - p[i] = us; - } + for (i = 0; i < E_TABSZ ; i++) + __get_user(inbuf[i], arg+i); + console_lock(); + memcpy(translations[USER_MAP], inbuf, sizeof(inbuf)); update_user_maps(); console_unlock(); return 0; @@ -367,16 +382,17 @@ int con_set_trans_new(ushort __user * arg) int con_get_trans_new(ushort __user * arg) { int i; - unsigned short *p = translations[USER_MAP]; + unsigned short outbuf[E_TABSZ]; if (!access_ok(VERIFY_WRITE, arg, E_TABSZ*sizeof(unsigned short))) return -EFAULT; console_lock(); - for (i=0; i<E_TABSZ ; i++) - __put_user(p[i], arg+i); + memcpy(outbuf, translations[USER_MAP], sizeof(outbuf)); console_unlock(); - + + for (i = 0; i < E_TABSZ ; i++) + __put_user(outbuf[i], arg+i); return 0; } @@ -536,10 +552,20 @@ int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list) { int err = 0, err1, i; struct uni_pagedir *p, *q; + struct unipair *unilist, *plist; if (!ct) return 0; + unilist = kmalloc_array(ct, sizeof(struct unipair), GFP_KERNEL); + if (!unilist) + return -ENOMEM; + + for (i = ct, plist = unilist; i; i--, plist++, list++) { + __get_user(plist->unicode, &list->unicode); + __get_user(plist->fontpos, &list->fontpos); + } + console_lock(); /* Save original vc_unipagdir_loc in case we allocate a new one */ @@ -557,8 +583,8 @@ int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list) err1 = con_do_clear_unimap(vc); if (err1) { - console_unlock(); - return err1; + err = err1; + goto out_unlock; } /* @@ -592,8 +618,8 @@ int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list) *vc->vc_uni_pagedir_loc = p; con_release_unimap(q); kfree(q); - console_unlock(); - return err1; + err = err1; + goto out_unlock; } } } else { @@ -617,22 +643,17 @@ int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list) /* * Insert user specified unicode pairs into new table. */ - while (ct--) { - unsigned short unicode, fontpos; - __get_user(unicode, &list->unicode); - __get_user(fontpos, &list->fontpos); - if ((err1 = con_insert_unipair(p, unicode,fontpos)) != 0) + for (plist = unilist; ct; ct--, plist++) { + err1 = con_insert_unipair(p, plist->unicode, plist->fontpos); + if (err1) err = err1; - list++; } /* * Merge with fontmaps of any other virtual consoles. */ - if (con_unify_unimap(vc, p)) { - console_unlock(); - return err; - } + if (con_unify_unimap(vc, p)) + goto out_unlock; for (i = 0; i <= 3; i++) set_inverse_transl(vc, p, i); /* Update inverse translations */ @@ -640,6 +661,7 @@ int con_set_unimap(struct vc_data *vc, ushort ct, struct unipair __user *list) out_unlock: console_unlock(); + kfree(unilist); return err; } @@ -735,9 +757,15 @@ EXPORT_SYMBOL(con_copy_unimap); */ int con_get_unimap(struct vc_data *vc, ushort ct, ushort __user *uct, struct unipair __user *list) { - int i, j, k, ect; + int i, j, k; + ushort ect; u16 **p1, *p2; struct uni_pagedir *p; + struct unipair *unilist, *plist; + + unilist = kmalloc_array(ct, sizeof(struct unipair), GFP_KERNEL); + if (!unilist) + return -ENOMEM; console_lock(); @@ -750,21 +778,26 @@ int con_get_unimap(struct vc_data *vc, ushort ct, ushort __user *uct, struct uni for (j = 0; j < 32; j++) { p2 = *(p1++); if (p2) - for (k = 0; k < 64; k++) { - if (*p2 < MAX_GLYPH && ect++ < ct) { - __put_user((u_short)((i<<11)+(j<<6)+k), - &list->unicode); - __put_user((u_short) *p2, - &list->fontpos); - list++; + for (k = 0; k < 64; k++, p2++) { + if (*p2 >= MAX_GLYPH) + continue; + if (ect < ct) { + unilist[ect].unicode = + (i<<11)+(j<<6)+k; + unilist[ect].fontpos = *p2; } - p2++; + ect++; } } } } - __put_user(ect, uct); console_unlock(); + for (i = min(ect, ct), plist = unilist; i; i--, list++, plist++) { + __put_user(plist->unicode, &list->unicode); + __put_user(plist->fontpos, &list->fontpos); + } + __put_user(ect, uct); + kfree(unilist); return ((ect <= ct) ? 0 : -ENOMEM); } diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c index 0f8caae4267d..3dd6a491cdba 100644 --- a/drivers/tty/vt/keyboard.c +++ b/drivers/tty/vt/keyboard.c @@ -982,7 +982,7 @@ static void kbd_led_trigger_activate(struct led_classdev *cdev) KBD_LED_TRIGGER((_led_bit) + 8, _name) static struct kbd_led_trigger kbd_led_triggers[] = { - KBD_LED_TRIGGER(VC_SCROLLOCK, "kbd-scrollock"), + KBD_LED_TRIGGER(VC_SCROLLOCK, "kbd-scrolllock"), KBD_LED_TRIGGER(VC_NUMLOCK, "kbd-numlock"), KBD_LED_TRIGGER(VC_CAPSLOCK, "kbd-capslock"), KBD_LED_TRIGGER(VC_KANALOCK, "kbd-kanalock"), @@ -1256,7 +1256,7 @@ static int emulate_raw(struct vc_data *vc, unsigned int keycode, case KEY_SYSRQ: /* * Real AT keyboards (that's what we're trying - * to emulate here emit 0xe0 0x2a 0xe0 0x37 when + * to emulate here) emit 0xe0 0x2a 0xe0 0x37 when * pressing PrtSc/SysRq alone, but simply 0x54 * when pressing Alt+PrtSc/SysRq. */ diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index 95528461a021..4c10a9df3b91 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -315,38 +315,27 @@ void schedule_console_callback(void) schedule_work(&console_work); } -static void scrup(struct vc_data *vc, unsigned int t, unsigned int b, int nr) +static void con_scroll(struct vc_data *vc, unsigned int t, unsigned int b, + enum con_scroll dir, unsigned int nr) { - unsigned short *d, *s; + u16 *clear, *d, *s; - if (t+nr >= b) + if (t + nr >= b) nr = b - t - 1; if (b > vc->vc_rows || t >= b || nr < 1) return; - if (con_is_visible(vc) && vc->vc_sw->con_scroll(vc, t, b, SM_UP, nr)) + if (con_is_visible(vc) && vc->vc_sw->con_scroll(vc, t, b, dir, nr)) return; - d = (unsigned short *)(vc->vc_origin + vc->vc_size_row * t); - s = (unsigned short *)(vc->vc_origin + vc->vc_size_row * (t + nr)); - scr_memmovew(d, s, (b - t - nr) * vc->vc_size_row); - scr_memsetw(d + (b - t - nr) * vc->vc_cols, vc->vc_video_erase_char, - vc->vc_size_row * nr); -} -static void scrdown(struct vc_data *vc, unsigned int t, unsigned int b, int nr) -{ - unsigned short *s; - unsigned int step; + s = clear = (u16 *)(vc->vc_origin + vc->vc_size_row * t); + d = (u16 *)(vc->vc_origin + vc->vc_size_row * (t + nr)); - if (t+nr >= b) - nr = b - t - 1; - if (b > vc->vc_rows || t >= b || nr < 1) - return; - if (con_is_visible(vc) && vc->vc_sw->con_scroll(vc, t, b, SM_DOWN, nr)) - return; - s = (unsigned short *)(vc->vc_origin + vc->vc_size_row * t); - step = vc->vc_cols * nr; - scr_memmovew(s + step, s, (b - t - nr) * vc->vc_size_row); - scr_memsetw(s, vc->vc_video_erase_char, 2 * step); + if (dir == SM_UP) { + clear = s + (b - t - nr) * vc->vc_cols; + swap(s, d); + } + scr_memmovew(d, s, (b - t - nr) * vc->vc_size_row); + scr_memsetw(clear, vc->vc_video_erase_char, vc->vc_size_row * nr); } static void do_update_region(struct vc_data *vc, unsigned long start, int count) @@ -870,10 +859,15 @@ static int vc_do_resize(struct tty_struct *tty, struct vc_data *vc, if (new_cols == vc->vc_cols && new_rows == vc->vc_rows) return 0; + if (new_screen_size > (4 << 20)) + return -EINVAL; newscreen = kmalloc(new_screen_size, GFP_USER); if (!newscreen) return -ENOMEM; + if (vc == sel_cons) + clear_selection(); + old_rows = vc->vc_rows; old_row_size = vc->vc_size_row; @@ -1115,7 +1109,7 @@ static void lf(struct vc_data *vc) * if below scrolling region */ if (vc->vc_y + 1 == vc->vc_bottom) - scrup(vc, vc->vc_top, vc->vc_bottom, 1); + con_scroll(vc, vc->vc_top, vc->vc_bottom, SM_UP, 1); else if (vc->vc_y < vc->vc_rows - 1) { vc->vc_y++; vc->vc_pos += vc->vc_size_row; @@ -1130,7 +1124,7 @@ static void ri(struct vc_data *vc) * if above scrolling region */ if (vc->vc_y == vc->vc_top) - scrdown(vc, vc->vc_top, vc->vc_bottom, 1); + con_scroll(vc, vc->vc_top, vc->vc_bottom, SM_DOWN, 1); else if (vc->vc_y > 0) { vc->vc_y--; vc->vc_pos -= vc->vc_size_row; @@ -1176,7 +1170,7 @@ static void csi_J(struct vc_data *vc, int vpar) break; case 3: /* erase scroll-back buffer (and whole display) */ scr_memsetw(vc->vc_screenbuf, vc->vc_video_erase_char, - vc->vc_screenbuf_size >> 1); + vc->vc_screenbuf_size); set_origin(vc); if (con_is_visible(vc)) update_screen(vc); @@ -1626,7 +1620,7 @@ static void csi_L(struct vc_data *vc, unsigned int nr) nr = vc->vc_rows - vc->vc_y; else if (!nr) nr = 1; - scrdown(vc, vc->vc_y, vc->vc_bottom, nr); + con_scroll(vc, vc->vc_y, vc->vc_bottom, SM_DOWN, nr); vc->vc_need_wrap = 0; } @@ -1647,7 +1641,7 @@ static void csi_M(struct vc_data *vc, unsigned int nr) nr = vc->vc_rows - vc->vc_y; else if (!nr) nr=1; - scrup(vc, vc->vc_y, vc->vc_bottom, nr); + con_scroll(vc, vc->vc_y, vc->vc_bottom, SM_UP, nr); vc->vc_need_wrap = 0; } @@ -4286,6 +4280,46 @@ void vcs_scr_updated(struct vc_data *vc) notify_update(vc); } +void vc_scrolldelta_helper(struct vc_data *c, int lines, + unsigned int rolled_over, void *base, unsigned int size) +{ + unsigned long ubase = (unsigned long)base; + ptrdiff_t scr_end = (void *)c->vc_scr_end - base; + ptrdiff_t vorigin = (void *)c->vc_visible_origin - base; + ptrdiff_t origin = (void *)c->vc_origin - base; + int margin = c->vc_size_row * 4; + int from, wrap, from_off, avail; + + /* Turn scrollback off */ + if (!lines) { + c->vc_visible_origin = c->vc_origin; + return; + } + + /* Do we have already enough to allow jumping from 0 to the end? */ + if (rolled_over > scr_end + margin) { + from = scr_end; + wrap = rolled_over + c->vc_size_row; + } else { + from = 0; + wrap = size; + } + + from_off = (vorigin - from + wrap) % wrap + lines * c->vc_size_row; + avail = (origin - from + wrap) % wrap; + + /* Only a little piece would be left? Show all incl. the piece! */ + if (avail < 2 * margin) + margin = 0; + if (from_off < margin) + from_off = 0; + if (from_off > avail - margin) + from_off = avail; + + c->vc_visible_origin = ubase + (from + from_off) % wrap; +} +EXPORT_SYMBOL_GPL(vc_scrolldelta_helper); + /* * Visible symbols for modules */ |