summaryrefslogtreecommitdiffstats
path: root/drivers/tty/vt
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/tty/vt')
-rw-r--r--drivers/tty/vt/consolemap.c115
-rw-r--r--drivers/tty/vt/keyboard.c4
-rw-r--r--drivers/tty/vt/vt.c92
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
*/