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 + -
显示快捷键?