hvc_console.c

来自「Linux Kernel 2.6.9 for OMAP1710」· C语言 代码 · 共 880 行 · 第 1/2 页

C
880
字号
/* * Copyright (C) 2001 Anton Blanchard <anton@au.ibm.com>, IBM * Copyright (C) 2001 Paul Mackerras <paulus@au.ibm.com>, IBM * Copyright (C) 2004 Benjamin Herrenschmidt <benh@kernel.crashing.org>, IBM Corp. * Copyright (C) 2004 IBM Corporation * * Additional Author(s): *  Ryan S. Arnold <rsa@us.ibm.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. *  * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the * GNU General Public License for more details. *  * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA */#include <linux/console.h>#include <linux/cpumask.h>#include <linux/init.h>#include <linux/kbd_kern.h>#include <linux/kernel.h>#include <linux/kobject.h>#include <linux/kthread.h>#include <linux/list.h>#include <linux/module.h>#include <linux/major.h>#include <linux/sysrq.h>#include <linux/tty.h>#include <linux/tty_flip.h>#include <linux/sched.h>#include <linux/spinlock.h>#include <asm/uaccess.h>#include <asm/hvconsole.h>#include <asm/vio.h>#define HVC_MAJOR	229#define HVC_MINOR	0#define TIMEOUT		((HZ + 99) / 100)/* * Wait this long per iteration while trying to push buffered data to the * hypervisor before allowing the tty to complete a close operation. */#define HVC_CLOSE_WAIT (HZ/100) /* 1/10 of a second *//* * The Linux TTY code does not support dynamic addition of tty derived devices * so we need to know how many tty devices we might need when space is allocated * for the tty device.  Since this driver supports hotplug of vty adapters we * need to make sure we have enough allocated. */#define HVC_ALLOC_TTY_ADAPTERS	8static struct tty_driver *hvc_driver;#ifdef CONFIG_MAGIC_SYSRQstatic int sysrq_pressed;#endif#define N_OUTBUF	16#define N_INBUF		16#define __ALIGNED__	__attribute__((__aligned__(8)))struct hvc_struct {	spinlock_t lock;	int index;	struct tty_struct *tty;	unsigned int count;	int do_wakeup;	char outbuf[N_OUTBUF] __ALIGNED__;	int n_outbuf;	uint32_t vtermno;	int irq_requested;	int irq;	struct list_head next;	struct kobject kobj; /* ref count & hvc_struct lifetime */	struct vio_dev *vdev;};/* dynamic list of hvc_struct instances */static struct list_head hvc_structs = LIST_HEAD_INIT(hvc_structs);/* * Protect the list of hvc_struct instances from inserts and removals during * list traversal. */static spinlock_t hvc_structs_lock = SPIN_LOCK_UNLOCKED;/* * Initial console vtermnos for console API usage prior to full console * initialization.  Any vty adapter outside this range will not have usable * console interfaces but can still be used as a tty device.  This has to be * static because kmalloc will not work during early console init. */static uint32_t vtermnos[MAX_NR_HVC_CONSOLES];/* Used for accounting purposes */static int num_vterms = 0;static struct task_struct *hvc_task;/* * This value is used to associate a tty->index value to a hvc_struct based * upon order of exposure via hvc_probe(). */static int hvc_count = -1;/* Picks up late kicks after list walk but before schedule() */static int hvc_kicked;/* Wake the sleeping khvcd */static void hvc_kick(void){	hvc_kicked = 1;	wake_up_process(hvc_task);}/* * NOTE: This API isn't used if the console adapter doesn't support interrupts. * In this case the console is poll driven. */static irqreturn_t hvc_handle_interrupt(int irq, void *dev_instance, struct pt_regs *regs){	hvc_kick();	return IRQ_HANDLED;}static void hvc_unthrottle(struct tty_struct *tty){	hvc_kick();}/* * Do not call this function with either the hvc_strucst_lock or the hvc_struct * lock held.  If successful, this function increments the kobject reference * count against the target hvc_struct so it should be released when finished. */struct hvc_struct *hvc_get_by_index(int index){	struct hvc_struct *hp;	unsigned long flags;	spin_lock(&hvc_structs_lock);	list_for_each_entry(hp, &hvc_structs, next) {		spin_lock_irqsave(&hp->lock, flags);		if (hp->index == index) {			kobject_get(&hp->kobj);			spin_unlock_irqrestore(&hp->lock, flags);			spin_unlock(&hvc_structs_lock);			return hp;		}		spin_unlock_irqrestore(&hp->lock, flags);	}	hp = NULL;	spin_unlock(&hvc_structs_lock);	return hp;}/* * The TTY interface won't be used until after the vio layer has exposed the vty * adapter to the kernel. */static int hvc_open(struct tty_struct *tty, struct file * filp){	struct hvc_struct *hp;	unsigned long flags;	int irq = NO_IRQ;	int rc = 0;	struct kobject *kobjp;	/* Auto increments kobject reference if found. */	if (!(hp = hvc_get_by_index(tty->index))) {		printk(KERN_WARNING "hvc_console: tty open failed, no vty associated with tty.\n");		return -ENODEV;	}	spin_lock_irqsave(&hp->lock, flags);	/* Check and then increment for fast path open. */	if (hp->count++ > 0) {		spin_unlock_irqrestore(&hp->lock, flags);		hvc_kick();		return 0;	} /* else count == 0 */	tty->driver_data = hp;	hp->tty = tty;	/* Save for request_irq outside of spin_lock. */	irq = hp->irq;	if (irq != NO_IRQ)		hp->irq_requested = 1;	kobjp = &hp->kobj;	spin_unlock_irqrestore(&hp->lock, flags);	/* check error, fallback to non-irq */	if (irq != NO_IRQ)		rc = request_irq(irq, hvc_handle_interrupt, SA_INTERRUPT, "hvc_console", hp);	/*	 * If the request_irq() fails and we return an error.  The tty layer	 * will call hvc_close() after a failed open but we don't want to clean	 * up there so we'll clean up here and clear out the previously set	 * tty fields and return the kobject reference.	 */	if (rc) {		spin_lock_irqsave(&hp->lock, flags);		hp->tty = NULL;		hp->irq_requested = 0;		spin_unlock_irqrestore(&hp->lock, flags);		tty->driver_data = NULL;		kobject_put(kobjp);	}	/* Force wakeup of the polling thread */	hvc_kick();	return rc;}static void hvc_close(struct tty_struct *tty, struct file * filp){	struct hvc_struct *hp;	struct kobject *kobjp;	int irq = NO_IRQ;	unsigned long flags;	if (tty_hung_up_p(filp))		return;	/*	 * No driver_data means that this close was issued after a failed	 * hvcs_open by the tty layer's release_dev() function and we can just	 * exit cleanly because the kobject reference wasn't made.	 */	if (!tty->driver_data)		return;	hp = tty->driver_data;	spin_lock_irqsave(&hp->lock, flags);	kobjp = &hp->kobj;	if (--hp->count == 0) {		if (hp->irq_requested)			irq = hp->irq;		hp->irq_requested = 0;		/* We are done with the tty pointer now. */		hp->tty = NULL;		spin_unlock_irqrestore(&hp->lock, flags);		/*		 * Chain calls chars_in_buffer() and returns immediately if		 * there is no buffered data otherwise sleeps on a wait queue		 * waking periodically to check chars_in_buffer().		 */		tty_wait_until_sent(tty, HVC_CLOSE_WAIT);		/*		 * Since the line disc doesn't block writes during tty close		 * operations we'll set driver_data to NULL and then make sure		 * to check tty->driver_data for NULL in hvc_write().		 */		tty->driver_data = NULL;		if (irq != NO_IRQ)			free_irq(irq, hp);	} else {		if (hp->count < 0)			printk(KERN_ERR "hvc_close %X: oops, count is %d\n",				hp->vtermno, hp->count);		spin_unlock_irqrestore(&hp->lock, flags);	}	kobject_put(kobjp);}static void hvc_hangup(struct tty_struct *tty){	struct hvc_struct *hp = tty->driver_data;	unsigned long flags;	int irq = NO_IRQ;	int temp_open_count;	struct kobject *kobjp;	spin_lock_irqsave(&hp->lock, flags);	kobjp = &hp->kobj;	temp_open_count = hp->count;	hp->count = 0;	hp->n_outbuf = 0;	hp->tty = NULL;	if (hp->irq_requested)		/* Saved for use outside of spin_lock. */		irq = hp->irq;	hp->irq_requested = 0;	spin_unlock_irqrestore(&hp->lock, flags);	if (irq != NO_IRQ)		free_irq(irq, hp);	while(temp_open_count) {		--temp_open_count;		kobject_put(kobjp);	}}/* * Push buffered characters whether they were just recently buffered or waiting * on a blocked hypervisor.  Call this function with hp->lock held. */static void hvc_push(struct hvc_struct *hp){	int n;	n = hvc_put_chars(hp->vtermno, hp->outbuf, hp->n_outbuf);	if (n <= 0) {		if (n == 0)			return;		/* throw away output on error; this happens when		   there is no session connected to the vterm. */		hp->n_outbuf = 0;	} else		hp->n_outbuf -= n;	if (hp->n_outbuf > 0)		memmove(hp->outbuf, hp->outbuf + n, hp->n_outbuf);	else		hp->do_wakeup = 1;}static inline int __hvc_write_user(struct hvc_struct *hp,				   const unsigned char *buf, int count){	char *tbuf, *p;	int tbsize, rsize, written = 0;	unsigned long flags;	tbsize = min(count, (int)PAGE_SIZE);	if (!(tbuf = kmalloc(tbsize, GFP_KERNEL)))		return -ENOMEM;	while ((rsize = count - written) > 0) {		int wsize;		if (rsize > tbsize)			rsize = tbsize;		p = tbuf;		rsize -= copy_from_user(p, buf, rsize);		if (!rsize) {			if (written == 0)				written = -EFAULT;			break;		}		buf += rsize;		spin_lock_irqsave(&hp->lock, flags);		/* Push pending writes: make some room in buffer */		if (hp->n_outbuf > 0)			hvc_push(hp);		for (wsize = N_OUTBUF - hp->n_outbuf; rsize && wsize;		     wsize = N_OUTBUF - hp->n_outbuf) {			if (wsize > rsize)				wsize = rsize;			memcpy(hp->outbuf + hp->n_outbuf, p, wsize);			hp->n_outbuf += wsize;			hvc_push(hp);			rsize -= wsize;			p += wsize;			written += wsize;		}		spin_unlock_irqrestore(&hp->lock, flags);		if (rsize)			break;		if (count < tbsize)			tbsize = count;	}	kfree(tbuf);	return written;}static inline int __hvc_write_kernel(struct hvc_struct *hp,				   const unsigned char *buf, int count){	unsigned long flags;	int rsize, written = 0;	spin_lock_irqsave(&hp->lock, flags);	/* Push pending writes */	if (hp->n_outbuf > 0)		hvc_push(hp);	while (count > 0 && (rsize = N_OUTBUF - hp->n_outbuf) > 0) {		if (rsize > count)			rsize = count;		memcpy(hp->outbuf + hp->n_outbuf, buf, rsize);		count -= rsize;		buf += rsize;		hp->n_outbuf += rsize;		written += rsize;		hvc_push(hp);	}	spin_unlock_irqrestore(&hp->lock, flags);	return written;}static int hvc_write(struct tty_struct *tty, int from_user,		     const unsigned char *buf, int count){	struct hvc_struct *hp = tty->driver_data;	int written;	/* This write was probably executed during a tty close. */	if (!hp)		return -EPIPE;	if (from_user)		written = __hvc_write_user(hp, buf, count);	else		written = __hvc_write_kernel(hp, buf, count);	/*	 * Racy, but harmless, kick thread if there is still pending data.	 * There really is nothing wrong with kicking the thread, even if there	 * is no buffered data.	 */	if (hp->n_outbuf)

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?