|
@@ -16,6 +16,8 @@
|
|
|
#include <linux/tty.h>
|
|
|
#include <linux/timer.h>
|
|
|
#include <linux/kernel.h>
|
|
|
+#include <linux/compat.h>
|
|
|
+#include <linux/module.h>
|
|
|
#include <linux/kd.h>
|
|
|
#include <linux/vt.h>
|
|
|
#include <linux/string.h>
|
|
@@ -61,6 +63,133 @@ extern struct tty_driver *console_driver;
|
|
|
|
|
|
static void complete_change_console(struct vc_data *vc);
|
|
|
|
|
|
+/*
|
|
|
+ * User space VT_EVENT handlers
|
|
|
+ */
|
|
|
+
|
|
|
+struct vt_event_wait {
|
|
|
+ struct list_head list;
|
|
|
+ struct vt_event event;
|
|
|
+ int done;
|
|
|
+};
|
|
|
+
|
|
|
+static LIST_HEAD(vt_events);
|
|
|
+static DEFINE_SPINLOCK(vt_event_lock);
|
|
|
+static DECLARE_WAIT_QUEUE_HEAD(vt_event_waitqueue);
|
|
|
+
|
|
|
+/**
|
|
|
+ * vt_event_post
|
|
|
+ * @event: the event that occurred
|
|
|
+ * @old: old console
|
|
|
+ * @new: new console
|
|
|
+ *
|
|
|
+ * Post an VT event to interested VT handlers
|
|
|
+ */
|
|
|
+
|
|
|
+void vt_event_post(unsigned int event, unsigned int old, unsigned int new)
|
|
|
+{
|
|
|
+ struct list_head *pos, *head;
|
|
|
+ unsigned long flags;
|
|
|
+ int wake = 0;
|
|
|
+
|
|
|
+ spin_lock_irqsave(&vt_event_lock, flags);
|
|
|
+ head = &vt_events;
|
|
|
+
|
|
|
+ list_for_each(pos, head) {
|
|
|
+ struct vt_event_wait *ve = list_entry(pos,
|
|
|
+ struct vt_event_wait, list);
|
|
|
+ if (!(ve->event.event & event))
|
|
|
+ continue;
|
|
|
+ ve->event.event = event;
|
|
|
+ /* kernel view is consoles 0..n-1, user space view is
|
|
|
+ console 1..n with 0 meaning current, so we must bias */
|
|
|
+ ve->event.old = old + 1;
|
|
|
+ ve->event.new = new + 1;
|
|
|
+ wake = 1;
|
|
|
+ ve->done = 1;
|
|
|
+ }
|
|
|
+ spin_unlock_irqrestore(&vt_event_lock, flags);
|
|
|
+ if (wake)
|
|
|
+ wake_up_interruptible(&vt_event_waitqueue);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * vt_event_wait - wait for an event
|
|
|
+ * @vw: our event
|
|
|
+ *
|
|
|
+ * Waits for an event to occur which completes our vt_event_wait
|
|
|
+ * structure. On return the structure has wv->done set to 1 for success
|
|
|
+ * or 0 if some event such as a signal ended the wait.
|
|
|
+ */
|
|
|
+
|
|
|
+static void vt_event_wait(struct vt_event_wait *vw)
|
|
|
+{
|
|
|
+ unsigned long flags;
|
|
|
+ /* Prepare the event */
|
|
|
+ INIT_LIST_HEAD(&vw->list);
|
|
|
+ vw->done = 0;
|
|
|
+ /* Queue our event */
|
|
|
+ spin_lock_irqsave(&vt_event_lock, flags);
|
|
|
+ list_add(&vw->list, &vt_events);
|
|
|
+ spin_unlock_irqrestore(&vt_event_lock, flags);
|
|
|
+ /* Wait for it to pass */
|
|
|
+ wait_event_interruptible(vt_event_waitqueue, vw->done);
|
|
|
+ /* Dequeue it */
|
|
|
+ spin_lock_irqsave(&vt_event_lock, flags);
|
|
|
+ list_del(&vw->list);
|
|
|
+ spin_unlock_irqrestore(&vt_event_lock, flags);
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * vt_event_wait_ioctl - event ioctl handler
|
|
|
+ * @arg: argument to ioctl
|
|
|
+ *
|
|
|
+ * Implement the VT_WAITEVENT ioctl using the VT event interface
|
|
|
+ */
|
|
|
+
|
|
|
+static int vt_event_wait_ioctl(struct vt_event __user *event)
|
|
|
+{
|
|
|
+ struct vt_event_wait vw;
|
|
|
+
|
|
|
+ if (copy_from_user(&vw.event, event, sizeof(struct vt_event)))
|
|
|
+ return -EFAULT;
|
|
|
+ /* Highest supported event for now */
|
|
|
+ if (vw.event.event & ~VT_MAX_EVENT)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ vt_event_wait(&vw);
|
|
|
+ /* If it occurred report it */
|
|
|
+ if (vw.done) {
|
|
|
+ if (copy_to_user(event, &vw.event, sizeof(struct vt_event)))
|
|
|
+ return -EFAULT;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return -EINTR;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * vt_waitactive - active console wait
|
|
|
+ * @event: event code
|
|
|
+ * @n: new console
|
|
|
+ *
|
|
|
+ * Helper for event waits. Used to implement the legacy
|
|
|
+ * event waiting ioctls in terms of events
|
|
|
+ */
|
|
|
+
|
|
|
+int vt_waitactive(int n)
|
|
|
+{
|
|
|
+ struct vt_event_wait vw;
|
|
|
+ do {
|
|
|
+ if (n == fg_console + 1)
|
|
|
+ break;
|
|
|
+ vw.event.event = VT_EVENT_SWITCH;
|
|
|
+ vt_event_wait(&vw);
|
|
|
+ if (vw.done == 0)
|
|
|
+ return -EINTR;
|
|
|
+ } while (vw.event.new != n);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
/*
|
|
|
* these are the valid i/o ports we're allowed to change. they map all the
|
|
|
* video ports
|
|
@@ -360,6 +489,8 @@ do_unimap_ioctl(int cmd, struct unimapdesc __user *user_ud, int perm, struct vc_
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+
|
|
|
/*
|
|
|
* We handle the console-specific ioctl's here. We allow the
|
|
|
* capability to modify any console, not just the fg_console.
|
|
@@ -842,6 +973,41 @@ int vt_ioctl(struct tty_struct *tty, struct file * file,
|
|
|
}
|
|
|
break;
|
|
|
|
|
|
+ case VT_SETACTIVATE:
|
|
|
+ {
|
|
|
+ struct vt_setactivate vsa;
|
|
|
+
|
|
|
+ if (!perm)
|
|
|
+ goto eperm;
|
|
|
+
|
|
|
+ if (copy_from_user(&vsa, (struct vt_setactivate __user *)arg,
|
|
|
+ sizeof(struct vt_setactivate)))
|
|
|
+ return -EFAULT;
|
|
|
+ if (vsa.console == 0 || vsa.console > MAX_NR_CONSOLES)
|
|
|
+ ret = -ENXIO;
|
|
|
+ else {
|
|
|
+ vsa.console--;
|
|
|
+ acquire_console_sem();
|
|
|
+ ret = vc_allocate(vsa.console);
|
|
|
+ if (ret == 0) {
|
|
|
+ struct vc_data *nvc;
|
|
|
+ /* This is safe providing we don't drop the
|
|
|
+ console sem between vc_allocate and
|
|
|
+ finishing referencing nvc */
|
|
|
+ nvc = vc_cons[vsa.console].d;
|
|
|
+ nvc->vt_mode = vsa.mode;
|
|
|
+ nvc->vt_mode.frsig = 0;
|
|
|
+ put_pid(nvc->vt_pid);
|
|
|
+ nvc->vt_pid = get_pid(task_pid(current));
|
|
|
+ }
|
|
|
+ release_console_sem();
|
|
|
+ if (ret)
|
|
|
+ break;
|
|
|
+ /* Commence switch and lock */
|
|
|
+ set_console(arg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/*
|
|
|
* wait until the specified VT has been activated
|
|
|
*/
|
|
@@ -851,7 +1017,7 @@ int vt_ioctl(struct tty_struct *tty, struct file * file,
|
|
|
if (arg == 0 || arg > MAX_NR_CONSOLES)
|
|
|
ret = -ENXIO;
|
|
|
else
|
|
|
- ret = vt_waitactive(arg - 1);
|
|
|
+ ret = vt_waitactive(arg);
|
|
|
break;
|
|
|
|
|
|
/*
|
|
@@ -1159,6 +1325,9 @@ int vt_ioctl(struct tty_struct *tty, struct file * file,
|
|
|
ret = put_user(vc->vc_hi_font_mask,
|
|
|
(unsigned short __user *)arg);
|
|
|
break;
|
|
|
+ case VT_WAITEVENT:
|
|
|
+ ret = vt_event_wait_ioctl((struct vt_event __user *)arg);
|
|
|
+ break;
|
|
|
default:
|
|
|
ret = -ENOIOCTLCMD;
|
|
|
}
|
|
@@ -1170,54 +1339,6 @@ eperm:
|
|
|
goto out;
|
|
|
}
|
|
|
|
|
|
-/*
|
|
|
- * Sometimes we want to wait until a particular VT has been activated. We
|
|
|
- * do it in a very simple manner. Everybody waits on a single queue and
|
|
|
- * get woken up at once. Those that are satisfied go on with their business,
|
|
|
- * while those not ready go back to sleep. Seems overkill to add a wait
|
|
|
- * to each vt just for this - usually this does nothing!
|
|
|
- */
|
|
|
-static DECLARE_WAIT_QUEUE_HEAD(vt_activate_queue);
|
|
|
-
|
|
|
-/*
|
|
|
- * Sleeps until a vt is activated, or the task is interrupted. Returns
|
|
|
- * 0 if activation, -EINTR if interrupted by a signal handler.
|
|
|
- */
|
|
|
-int vt_waitactive(int vt)
|
|
|
-{
|
|
|
- int retval;
|
|
|
- DECLARE_WAITQUEUE(wait, current);
|
|
|
-
|
|
|
- add_wait_queue(&vt_activate_queue, &wait);
|
|
|
- for (;;) {
|
|
|
- retval = 0;
|
|
|
-
|
|
|
- /*
|
|
|
- * Synchronize with redraw_screen(). By acquiring the console
|
|
|
- * semaphore we make sure that the console switch is completed
|
|
|
- * before we return. If we didn't wait for the semaphore, we
|
|
|
- * could return at a point where fg_console has already been
|
|
|
- * updated, but the console switch hasn't been completed.
|
|
|
- */
|
|
|
- acquire_console_sem();
|
|
|
- set_current_state(TASK_INTERRUPTIBLE);
|
|
|
- if (vt == fg_console) {
|
|
|
- release_console_sem();
|
|
|
- break;
|
|
|
- }
|
|
|
- release_console_sem();
|
|
|
- retval = -ERESTARTNOHAND;
|
|
|
- if (signal_pending(current))
|
|
|
- break;
|
|
|
- schedule();
|
|
|
- }
|
|
|
- remove_wait_queue(&vt_activate_queue, &wait);
|
|
|
- __set_current_state(TASK_RUNNING);
|
|
|
- return retval;
|
|
|
-}
|
|
|
-
|
|
|
-#define vt_wake_waitactive() wake_up(&vt_activate_queue)
|
|
|
-
|
|
|
void reset_vc(struct vc_data *vc)
|
|
|
{
|
|
|
vc->vc_mode = KD_TEXT;
|
|
@@ -1256,12 +1377,216 @@ void vc_SAK(struct work_struct *work)
|
|
|
release_console_sem();
|
|
|
}
|
|
|
|
|
|
+#ifdef CONFIG_COMPAT
|
|
|
+
|
|
|
+struct compat_consolefontdesc {
|
|
|
+ unsigned short charcount; /* characters in font (256 or 512) */
|
|
|
+ unsigned short charheight; /* scan lines per character (1-32) */
|
|
|
+ compat_caddr_t chardata; /* font data in expanded form */
|
|
|
+};
|
|
|
+
|
|
|
+static inline int
|
|
|
+compat_fontx_ioctl(int cmd, struct compat_consolefontdesc __user *user_cfd,
|
|
|
+ int perm, struct console_font_op *op)
|
|
|
+{
|
|
|
+ struct compat_consolefontdesc cfdarg;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (copy_from_user(&cfdarg, user_cfd, sizeof(struct compat_consolefontdesc)))
|
|
|
+ return -EFAULT;
|
|
|
+
|
|
|
+ switch (cmd) {
|
|
|
+ case PIO_FONTX:
|
|
|
+ if (!perm)
|
|
|
+ return -EPERM;
|
|
|
+ op->op = KD_FONT_OP_SET;
|
|
|
+ op->flags = KD_FONT_FLAG_OLD;
|
|
|
+ op->width = 8;
|
|
|
+ op->height = cfdarg.charheight;
|
|
|
+ op->charcount = cfdarg.charcount;
|
|
|
+ op->data = compat_ptr(cfdarg.chardata);
|
|
|
+ return con_font_op(vc_cons[fg_console].d, op);
|
|
|
+ case GIO_FONTX:
|
|
|
+ op->op = KD_FONT_OP_GET;
|
|
|
+ op->flags = KD_FONT_FLAG_OLD;
|
|
|
+ op->width = 8;
|
|
|
+ op->height = cfdarg.charheight;
|
|
|
+ op->charcount = cfdarg.charcount;
|
|
|
+ op->data = compat_ptr(cfdarg.chardata);
|
|
|
+ i = con_font_op(vc_cons[fg_console].d, op);
|
|
|
+ if (i)
|
|
|
+ return i;
|
|
|
+ cfdarg.charheight = op->height;
|
|
|
+ cfdarg.charcount = op->charcount;
|
|
|
+ if (copy_to_user(user_cfd, &cfdarg, sizeof(struct compat_consolefontdesc)))
|
|
|
+ return -EFAULT;
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return -EINVAL;
|
|
|
+}
|
|
|
+
|
|
|
+struct compat_console_font_op {
|
|
|
+ compat_uint_t op; /* operation code KD_FONT_OP_* */
|
|
|
+ compat_uint_t flags; /* KD_FONT_FLAG_* */
|
|
|
+ compat_uint_t width, height; /* font size */
|
|
|
+ compat_uint_t charcount;
|
|
|
+ compat_caddr_t data; /* font data with height fixed to 32 */
|
|
|
+};
|
|
|
+
|
|
|
+static inline int
|
|
|
+compat_kdfontop_ioctl(struct compat_console_font_op __user *fontop,
|
|
|
+ int perm, struct console_font_op *op, struct vc_data *vc)
|
|
|
+{
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (copy_from_user(op, fontop, sizeof(struct compat_console_font_op)))
|
|
|
+ return -EFAULT;
|
|
|
+ if (!perm && op->op != KD_FONT_OP_GET)
|
|
|
+ return -EPERM;
|
|
|
+ op->data = compat_ptr(((struct compat_console_font_op *)op)->data);
|
|
|
+ op->flags |= KD_FONT_FLAG_OLD;
|
|
|
+ i = con_font_op(vc, op);
|
|
|
+ if (i)
|
|
|
+ return i;
|
|
|
+ ((struct compat_console_font_op *)op)->data = (unsigned long)op->data;
|
|
|
+ if (copy_to_user(fontop, op, sizeof(struct compat_console_font_op)))
|
|
|
+ return -EFAULT;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+struct compat_unimapdesc {
|
|
|
+ unsigned short entry_ct;
|
|
|
+ compat_caddr_t entries;
|
|
|
+};
|
|
|
+
|
|
|
+static inline int
|
|
|
+compat_unimap_ioctl(unsigned int cmd, struct compat_unimapdesc __user *user_ud,
|
|
|
+ int perm, struct vc_data *vc)
|
|
|
+{
|
|
|
+ struct compat_unimapdesc tmp;
|
|
|
+ struct unipair __user *tmp_entries;
|
|
|
+
|
|
|
+ if (copy_from_user(&tmp, user_ud, sizeof tmp))
|
|
|
+ return -EFAULT;
|
|
|
+ tmp_entries = compat_ptr(tmp.entries);
|
|
|
+ if (tmp_entries)
|
|
|
+ if (!access_ok(VERIFY_WRITE, tmp_entries,
|
|
|
+ tmp.entry_ct*sizeof(struct unipair)))
|
|
|
+ return -EFAULT;
|
|
|
+ switch (cmd) {
|
|
|
+ case PIO_UNIMAP:
|
|
|
+ if (!perm)
|
|
|
+ return -EPERM;
|
|
|
+ return con_set_unimap(vc, tmp.entry_ct, tmp_entries);
|
|
|
+ case GIO_UNIMAP:
|
|
|
+ if (!perm && fg_console != vc->vc_num)
|
|
|
+ return -EPERM;
|
|
|
+ return con_get_unimap(vc, tmp.entry_ct, &(user_ud->entry_ct), tmp_entries);
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+long vt_compat_ioctl(struct tty_struct *tty, struct file * file,
|
|
|
+ unsigned int cmd, unsigned long arg)
|
|
|
+{
|
|
|
+ struct vc_data *vc = tty->driver_data;
|
|
|
+ struct console_font_op op; /* used in multiple places here */
|
|
|
+ struct kbd_struct *kbd;
|
|
|
+ unsigned int console;
|
|
|
+ void __user *up = (void __user *)arg;
|
|
|
+ int perm;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ console = vc->vc_num;
|
|
|
+
|
|
|
+ lock_kernel();
|
|
|
+
|
|
|
+ if (!vc_cons_allocated(console)) { /* impossible? */
|
|
|
+ ret = -ENOIOCTLCMD;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * To have permissions to do most of the vt ioctls, we either have
|
|
|
+ * to be the owner of the tty, or have CAP_SYS_TTY_CONFIG.
|
|
|
+ */
|
|
|
+ perm = 0;
|
|
|
+ if (current->signal->tty == tty || capable(CAP_SYS_TTY_CONFIG))
|
|
|
+ perm = 1;
|
|
|
+
|
|
|
+ kbd = kbd_table + console;
|
|
|
+ switch (cmd) {
|
|
|
+ /*
|
|
|
+ * these need special handlers for incompatible data structures
|
|
|
+ */
|
|
|
+ case PIO_FONTX:
|
|
|
+ case GIO_FONTX:
|
|
|
+ ret = compat_fontx_ioctl(cmd, up, perm, &op);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case KDFONTOP:
|
|
|
+ ret = compat_kdfontop_ioctl(up, perm, &op, vc);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case PIO_UNIMAP:
|
|
|
+ case GIO_UNIMAP:
|
|
|
+ ret = do_unimap_ioctl(cmd, up, perm, vc);
|
|
|
+ break;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * all these treat 'arg' as an integer
|
|
|
+ */
|
|
|
+ case KIOCSOUND:
|
|
|
+ case KDMKTONE:
|
|
|
+#ifdef CONFIG_X86
|
|
|
+ case KDADDIO:
|
|
|
+ case KDDELIO:
|
|
|
+#endif
|
|
|
+ case KDSETMODE:
|
|
|
+ case KDMAPDISP:
|
|
|
+ case KDUNMAPDISP:
|
|
|
+ case KDSKBMODE:
|
|
|
+ case KDSKBMETA:
|
|
|
+ case KDSKBLED:
|
|
|
+ case KDSETLED:
|
|
|
+ case KDSIGACCEPT:
|
|
|
+ case VT_ACTIVATE:
|
|
|
+ case VT_WAITACTIVE:
|
|
|
+ case VT_RELDISP:
|
|
|
+ case VT_DISALLOCATE:
|
|
|
+ case VT_RESIZE:
|
|
|
+ case VT_RESIZEX:
|
|
|
+ goto fallback;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * the rest has a compatible data structure behind arg,
|
|
|
+ * but we have to convert it to a proper 64 bit pointer.
|
|
|
+ */
|
|
|
+ default:
|
|
|
+ arg = (unsigned long)compat_ptr(arg);
|
|
|
+ goto fallback;
|
|
|
+ }
|
|
|
+out:
|
|
|
+ unlock_kernel();
|
|
|
+ return ret;
|
|
|
+
|
|
|
+fallback:
|
|
|
+ unlock_kernel();
|
|
|
+ return vt_ioctl(tty, file, cmd, arg);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#endif /* CONFIG_COMPAT */
|
|
|
+
|
|
|
+
|
|
|
/*
|
|
|
- * Performs the back end of a vt switch
|
|
|
+ * Performs the back end of a vt switch. Called under the console
|
|
|
+ * semaphore.
|
|
|
*/
|
|
|
static void complete_change_console(struct vc_data *vc)
|
|
|
{
|
|
|
unsigned char old_vc_mode;
|
|
|
+ int old = fg_console;
|
|
|
|
|
|
last_console = fg_console;
|
|
|
|
|
@@ -1325,7 +1650,7 @@ static void complete_change_console(struct vc_data *vc)
|
|
|
/*
|
|
|
* Wake anyone waiting for their VT to activate
|
|
|
*/
|
|
|
- vt_wake_waitactive();
|
|
|
+ vt_event_post(VT_EVENT_SWITCH, old, vc->vc_num);
|
|
|
return;
|
|
|
}
|
|
|
|
|
@@ -1398,3 +1723,58 @@ void change_console(struct vc_data *new_vc)
|
|
|
|
|
|
complete_change_console(new_vc);
|
|
|
}
|
|
|
+
|
|
|
+/* Perform a kernel triggered VT switch for suspend/resume */
|
|
|
+
|
|
|
+static int disable_vt_switch;
|
|
|
+
|
|
|
+int vt_move_to_console(unsigned int vt, int alloc)
|
|
|
+{
|
|
|
+ int prev;
|
|
|
+
|
|
|
+ acquire_console_sem();
|
|
|
+ /* Graphics mode - up to X */
|
|
|
+ if (disable_vt_switch) {
|
|
|
+ release_console_sem();
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ prev = fg_console;
|
|
|
+
|
|
|
+ if (alloc && vc_allocate(vt)) {
|
|
|
+ /* we can't have a free VC for now. Too bad,
|
|
|
+ * we don't want to mess the screen for now. */
|
|
|
+ release_console_sem();
|
|
|
+ return -ENOSPC;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (set_console(vt)) {
|
|
|
+ /*
|
|
|
+ * We're unable to switch to the SUSPEND_CONSOLE.
|
|
|
+ * Let the calling function know so it can decide
|
|
|
+ * what to do.
|
|
|
+ */
|
|
|
+ release_console_sem();
|
|
|
+ return -EIO;
|
|
|
+ }
|
|
|
+ release_console_sem();
|
|
|
+ if (vt_waitactive(vt + 1)) {
|
|
|
+ pr_debug("Suspend: Can't switch VCs.");
|
|
|
+ return -EINTR;
|
|
|
+ }
|
|
|
+ return prev;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Normally during a suspend, we allocate a new console and switch to it.
|
|
|
+ * When we resume, we switch back to the original console. This switch
|
|
|
+ * can be slow, so on systems where the framebuffer can handle restoration
|
|
|
+ * of video registers anyways, there's little point in doing the console
|
|
|
+ * switch. This function allows you to disable it by passing it '0'.
|
|
|
+ */
|
|
|
+void pm_set_vt_switch(int do_switch)
|
|
|
+{
|
|
|
+ acquire_console_sem();
|
|
|
+ disable_vt_switch = !do_switch;
|
|
|
+ release_console_sem();
|
|
|
+}
|
|
|
+EXPORT_SYMBOL(pm_set_vt_switch);
|