diff options
Diffstat (limited to 'drivers/char')
32 files changed, 2031 insertions, 473 deletions
diff --git a/drivers/char/Kconfig b/drivers/char/Kconfig index e023682be2c..3141dd3b6e5 100644 --- a/drivers/char/Kconfig +++ b/drivers/char/Kconfig @@ -666,6 +666,14 @@ config VIRTIO_CONSOLE help Virtio console for use with lguest and other hypervisors. + Also serves as a general-purpose serial device for data + transfer between the guest and host. Character devices at + /dev/vportNpn will be created when corresponding ports are + found, where N is the device number and n is the port number + within that device. If specified by the host, a sysfs + attribute called 'name' will be populated with a name for + the port which can be used by udev scripts to create a + symlink to the device. config HVCS tristate "IBM Hypervisor Virtual Console Server support" diff --git a/drivers/char/agp/intel-agp.c b/drivers/char/agp/intel-agp.c index 3999a5f25f3..919a28558d3 100644 --- a/drivers/char/agp/intel-agp.c +++ b/drivers/char/agp/intel-agp.c @@ -8,8 +8,12 @@ #include <linux/kernel.h> #include <linux/pagemap.h> #include <linux/agp_backend.h> +#include <asm/smp.h> #include "agp.h" +int intel_agp_enabled; +EXPORT_SYMBOL(intel_agp_enabled); + /* * If we have Intel graphics, we're not going to have anything other than * an Intel IOMMU. So make the correct use of the PCI DMA API contingent @@ -64,6 +68,10 @@ #define PCI_DEVICE_ID_INTEL_IRONLAKE_MA_HB 0x0062 #define PCI_DEVICE_ID_INTEL_IRONLAKE_MC2_HB 0x006a #define PCI_DEVICE_ID_INTEL_IRONLAKE_M_IG 0x0046 +#define PCI_DEVICE_ID_INTEL_SANDYBRIDGE_HB 0x0100 +#define PCI_DEVICE_ID_INTEL_SANDYBRIDGE_IG 0x0102 +#define PCI_DEVICE_ID_INTEL_SANDYBRIDGE_M_HB 0x0104 +#define PCI_DEVICE_ID_INTEL_SANDYBRIDGE_M_IG 0x0106 /* cover 915 and 945 variants */ #define IS_I915 (agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_E7221_HB || \ @@ -98,7 +106,9 @@ agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_IRONLAKE_D_HB || \ agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_IRONLAKE_M_HB || \ agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_IRONLAKE_MA_HB || \ - agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_IRONLAKE_MC2_HB) + agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_IRONLAKE_MC2_HB || \ + agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_SANDYBRIDGE_HB || \ + agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_SANDYBRIDGE_M_HB) extern int agp_memory_reserved; @@ -147,6 +157,25 @@ extern int agp_memory_reserved; #define INTEL_I7505_AGPCTRL 0x70 #define INTEL_I7505_MCHCFG 0x50 +#define SNB_GMCH_CTRL 0x50 +#define SNB_GMCH_GMS_STOLEN_MASK 0xF8 +#define SNB_GMCH_GMS_STOLEN_32M (1 << 3) +#define SNB_GMCH_GMS_STOLEN_64M (2 << 3) +#define SNB_GMCH_GMS_STOLEN_96M (3 << 3) +#define SNB_GMCH_GMS_STOLEN_128M (4 << 3) +#define SNB_GMCH_GMS_STOLEN_160M (5 << 3) +#define SNB_GMCH_GMS_STOLEN_192M (6 << 3) +#define SNB_GMCH_GMS_STOLEN_224M (7 << 3) +#define SNB_GMCH_GMS_STOLEN_256M (8 << 3) +#define SNB_GMCH_GMS_STOLEN_288M (9 << 3) +#define SNB_GMCH_GMS_STOLEN_320M (0xa << 3) +#define SNB_GMCH_GMS_STOLEN_352M (0xb << 3) +#define SNB_GMCH_GMS_STOLEN_384M (0xc << 3) +#define SNB_GMCH_GMS_STOLEN_416M (0xd << 3) +#define SNB_GMCH_GMS_STOLEN_448M (0xe << 3) +#define SNB_GMCH_GMS_STOLEN_480M (0xf << 3) +#define SNB_GMCH_GMS_STOLEN_512M (0x10 << 3) + static const struct aper_size_info_fixed intel_i810_sizes[] = { {64, 16384, 4}, @@ -293,6 +322,13 @@ static void intel_agp_insert_sg_entries(struct agp_memory *mem, off_t pg_start, int mask_type) { int i, j; + u32 cache_bits = 0; + + if (agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_SANDYBRIDGE_HB || + agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_SANDYBRIDGE_M_HB) + { + cache_bits = I830_PTE_SYSTEM_CACHED; + } for (i = 0, j = pg_start; i < mem->page_count; i++, j++) { writel(agp_bridge->driver->mask_memory(agp_bridge, @@ -613,7 +649,7 @@ static struct aper_size_info_fixed intel_i830_sizes[] = static void intel_i830_init_gtt_entries(void) { u16 gmch_ctrl; - int gtt_entries; + int gtt_entries = 0; u8 rdct; int local = 0; static const int ddt[4] = { 0, 16, 32, 64 }; @@ -705,6 +741,63 @@ static void intel_i830_init_gtt_entries(void) gtt_entries = 0; break; } + } else if (agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_SANDYBRIDGE_HB || + agp_bridge->dev->device == PCI_DEVICE_ID_INTEL_SANDYBRIDGE_M_HB) { + /* + * SandyBridge has new memory control reg at 0x50.w + */ + u16 snb_gmch_ctl; + pci_read_config_word(intel_private.pcidev, SNB_GMCH_CTRL, &snb_gmch_ctl); + switch (snb_gmch_ctl & SNB_GMCH_GMS_STOLEN_MASK) { + case SNB_GMCH_GMS_STOLEN_32M: + gtt_entries = MB(32) - KB(size); + break; + case SNB_GMCH_GMS_STOLEN_64M: + gtt_entries = MB(64) - KB(size); + break; + case SNB_GMCH_GMS_STOLEN_96M: + gtt_entries = MB(96) - KB(size); + break; + case SNB_GMCH_GMS_STOLEN_128M: + gtt_entries = MB(128) - KB(size); + break; + case SNB_GMCH_GMS_STOLEN_160M: + gtt_entries = MB(160) - KB(size); + break; + case SNB_GMCH_GMS_STOLEN_192M: + gtt_entries = MB(192) - KB(size); + break; + case SNB_GMCH_GMS_STOLEN_224M: + gtt_entries = MB(224) - KB(size); + break; + case SNB_GMCH_GMS_STOLEN_256M: + gtt_entries = MB(256) - KB(size); + break; + case SNB_GMCH_GMS_STOLEN_288M: + gtt_entries = MB(288) - KB(size); + break; + case SNB_GMCH_GMS_STOLEN_320M: + gtt_entries = MB(320) - KB(size); + break; + case SNB_GMCH_GMS_STOLEN_352M: + gtt_entries = MB(352) - KB(size); + break; + case SNB_GMCH_GMS_STOLEN_384M: + gtt_entries = MB(384) - KB(size); + break; + case SNB_GMCH_GMS_STOLEN_416M: + gtt_entries = MB(416) - KB(size); + break; + case SNB_GMCH_GMS_STOLEN_448M: + gtt_entries = MB(448) - KB(size); + break; + case SNB_GMCH_GMS_STOLEN_480M: + gtt_entries = MB(480) - KB(size); + break; + case SNB_GMCH_GMS_STOLEN_512M: + gtt_entries = MB(512) - KB(size); + break; + } } else { switch (gmch_ctrl & I855_GMCH_GMS_MASK) { case I855_GMCH_GMS_STOLEN_1M: @@ -815,12 +908,6 @@ static void intel_i830_setup_flush(void) intel_i830_fini_flush(); } -static void -do_wbinvd(void *null) -{ - wbinvd(); -} - /* The chipset_flush interface needs to get data that has already been * flushed out of the CPU all the way out to main memory, because the GPU * doesn't snoop those buffers. @@ -837,12 +924,10 @@ static void intel_i830_chipset_flush(struct agp_bridge_data *bridge) memset(pg, 0, 1024); - if (cpu_has_clflush) { + if (cpu_has_clflush) clflush_cache_range(pg, 1024); - } else { - if (on_each_cpu(do_wbinvd, NULL, 1) != 0) - printk(KERN_ERR "Timed out waiting for cache flush.\n"); - } + else if (wbinvd_on_all_cpus() != 0) + printk(KERN_ERR "Timed out waiting for cache flush.\n"); } /* The intel i830 automatically initializes the agp aperture during POST. @@ -1364,6 +1449,8 @@ static void intel_i965_get_gtt_range(int *gtt_offset, int *gtt_size) case PCI_DEVICE_ID_INTEL_IRONLAKE_M_HB: case PCI_DEVICE_ID_INTEL_IRONLAKE_MA_HB: case PCI_DEVICE_ID_INTEL_IRONLAKE_MC2_HB: + case PCI_DEVICE_ID_INTEL_SANDYBRIDGE_HB: + case PCI_DEVICE_ID_INTEL_SANDYBRIDGE_M_HB: *gtt_offset = *gtt_size = MB(2); break; default: @@ -2345,9 +2432,9 @@ static const struct intel_driver_description { NULL, &intel_g33_driver }, { PCI_DEVICE_ID_INTEL_Q33_HB, PCI_DEVICE_ID_INTEL_Q33_IG, 0, "Q33", NULL, &intel_g33_driver }, - { PCI_DEVICE_ID_INTEL_PINEVIEW_M_HB, PCI_DEVICE_ID_INTEL_PINEVIEW_M_IG, 0, "Pineview", + { PCI_DEVICE_ID_INTEL_PINEVIEW_M_HB, PCI_DEVICE_ID_INTEL_PINEVIEW_M_IG, 0, "GMA3150", NULL, &intel_g33_driver }, - { PCI_DEVICE_ID_INTEL_PINEVIEW_HB, PCI_DEVICE_ID_INTEL_PINEVIEW_IG, 0, "Pineview", + { PCI_DEVICE_ID_INTEL_PINEVIEW_HB, PCI_DEVICE_ID_INTEL_PINEVIEW_IG, 0, "GMA3150", NULL, &intel_g33_driver }, { PCI_DEVICE_ID_INTEL_GM45_HB, PCI_DEVICE_ID_INTEL_GM45_IG, 0, "GM45", NULL, &intel_i965_driver }, @@ -2362,13 +2449,17 @@ static const struct intel_driver_description { { PCI_DEVICE_ID_INTEL_G41_HB, PCI_DEVICE_ID_INTEL_G41_IG, 0, "G41", NULL, &intel_i965_driver }, { PCI_DEVICE_ID_INTEL_IRONLAKE_D_HB, PCI_DEVICE_ID_INTEL_IRONLAKE_D_IG, 0, - "Ironlake/D", NULL, &intel_i965_driver }, + "HD Graphics", NULL, &intel_i965_driver }, { PCI_DEVICE_ID_INTEL_IRONLAKE_M_HB, PCI_DEVICE_ID_INTEL_IRONLAKE_M_IG, 0, - "Ironlake/M", NULL, &intel_i965_driver }, + "HD Graphics", NULL, &intel_i965_driver }, { PCI_DEVICE_ID_INTEL_IRONLAKE_MA_HB, PCI_DEVICE_ID_INTEL_IRONLAKE_M_IG, 0, - "Ironlake/MA", NULL, &intel_i965_driver }, + "HD Graphics", NULL, &intel_i965_driver }, { PCI_DEVICE_ID_INTEL_IRONLAKE_MC2_HB, PCI_DEVICE_ID_INTEL_IRONLAKE_M_IG, 0, - "Ironlake/MC2", NULL, &intel_i965_driver }, + "HD Graphics", NULL, &intel_i965_driver }, + { PCI_DEVICE_ID_INTEL_SANDYBRIDGE_HB, PCI_DEVICE_ID_INTEL_SANDYBRIDGE_IG, 0, + "Sandybridge", NULL, &intel_i965_driver }, + { PCI_DEVICE_ID_INTEL_SANDYBRIDGE_M_HB, PCI_DEVICE_ID_INTEL_SANDYBRIDGE_M_IG, 0, + "Sandybridge", NULL, &intel_i965_driver }, { 0, 0, 0, NULL, NULL, NULL } }; @@ -2378,7 +2469,7 @@ static int __devinit agp_intel_probe(struct pci_dev *pdev, struct agp_bridge_data *bridge; u8 cap_ptr = 0; struct resource *r; - int i; + int i, err; cap_ptr = pci_find_capability(pdev, PCI_CAP_ID_AGP); @@ -2470,7 +2561,10 @@ static int __devinit agp_intel_probe(struct pci_dev *pdev, } pci_set_drvdata(pdev, bridge); - return agp_add_bridge(bridge); + err = agp_add_bridge(bridge); + if (!err) + intel_agp_enabled = 1; + return err; } static void __devexit agp_intel_remove(struct pci_dev *pdev) @@ -2575,6 +2669,8 @@ static struct pci_device_id agp_intel_pci_table[] = { ID(PCI_DEVICE_ID_INTEL_IRONLAKE_M_HB), ID(PCI_DEVICE_ID_INTEL_IRONLAKE_MA_HB), ID(PCI_DEVICE_ID_INTEL_IRONLAKE_MC2_HB), + ID(PCI_DEVICE_ID_INTEL_SANDYBRIDGE_HB), + ID(PCI_DEVICE_ID_INTEL_SANDYBRIDGE_M_HB), { } }; diff --git a/drivers/char/cyclades.c b/drivers/char/cyclades.c index 4254457d391..b861c08263a 100644 --- a/drivers/char/cyclades.c +++ b/drivers/char/cyclades.c @@ -158,13 +158,11 @@ static unsigned int cy_isa_addresses[] = { #define NR_ISA_ADDRS ARRAY_SIZE(cy_isa_addresses) -#ifdef MODULE static long maddr[NR_CARDS]; static int irq[NR_CARDS]; module_param_array(maddr, long, NULL, 0); module_param_array(irq, int, NULL, 0); -#endif #endif /* CONFIG_ISA */ @@ -598,12 +596,6 @@ static void cyy_chip_tx(struct cyclades_card *cinfo, unsigned int chip, save_car = readb(base_addr + (CyCAR << index)); cy_writeb(base_addr + (CyCAR << index), save_xir); - /* validate the port# (as configured and open) */ - if (channel + chip * 4 >= cinfo->nports) { - cy_writeb(base_addr + (CySRER << index), - readb(base_addr + (CySRER << index)) & ~CyTxRdy); - goto end; - } info = &cinfo->ports[channel + chip * 4]; tty = tty_port_tty_get(&info->port); if (tty == NULL) { @@ -3316,13 +3308,10 @@ static int __init cy_detect_isa(void) unsigned short cy_isa_irq, nboard; void __iomem *cy_isa_address; unsigned short i, j, cy_isa_nchan; -#ifdef MODULE int isparam = 0; -#endif nboard = 0; -#ifdef MODULE /* Check for module parameters */ for (i = 0; i < NR_CARDS; i++) { if (maddr[i] || i) { @@ -3332,7 +3321,6 @@ static int __init cy_detect_isa(void) if (!maddr[i]) break; } -#endif /* scan the address table probing for Cyclom-Y/ISA boards */ for (i = 0; i < NR_ISA_ADDRS; i++) { @@ -3353,11 +3341,10 @@ static int __init cy_detect_isa(void) iounmap(cy_isa_address); continue; } -#ifdef MODULE + if (isparam && i < NR_CARDS && irq[i]) cy_isa_irq = irq[i]; else -#endif /* find out the board's irq by probing */ cy_isa_irq = detect_isa_irq(cy_isa_address); if (cy_isa_irq == 0) { @@ -4208,3 +4195,4 @@ module_exit(cy_cleanup_module); MODULE_LICENSE("GPL"); MODULE_VERSION(CY_VERSION); MODULE_ALIAS_CHARDEV_MAJOR(CYCLADES_MAJOR); +MODULE_FIRMWARE("cyzfirm.bin"); diff --git a/drivers/char/hvc_beat.c b/drivers/char/hvc_beat.c index 0afc8b82212..5fe4631e2a6 100644 --- a/drivers/char/hvc_beat.c +++ b/drivers/char/hvc_beat.c @@ -84,7 +84,7 @@ static int hvc_beat_put_chars(uint32_t vtermno, const char *buf, int cnt) return cnt; } -static struct hv_ops hvc_beat_get_put_ops = { +static const struct hv_ops hvc_beat_get_put_ops = { .get_chars = hvc_beat_get_chars, .put_chars = hvc_beat_put_chars, }; @@ -99,7 +99,7 @@ static int hvc_beat_config(char *p) static int __init hvc_beat_console_init(void) { - if (hvc_beat_useit && machine_is_compatible("Beat")) { + if (hvc_beat_useit && of_machine_is_compatible("Beat")) { hvc_instantiate(0, 0, &hvc_beat_get_put_ops); } return 0; diff --git a/drivers/char/hvc_console.c b/drivers/char/hvc_console.c index 416d3423150..465185fc0f5 100644 --- a/drivers/char/hvc_console.c +++ b/drivers/char/hvc_console.c @@ -125,7 +125,7 @@ static struct hvc_struct *hvc_get_by_index(int index) * 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 struct hv_ops *cons_ops[MAX_NR_HVC_CONSOLES]; +static const struct hv_ops *cons_ops[MAX_NR_HVC_CONSOLES]; static uint32_t vtermnos[MAX_NR_HVC_CONSOLES] = {[0 ... MAX_NR_HVC_CONSOLES - 1] = -1}; @@ -146,7 +146,7 @@ static void hvc_console_print(struct console *co, const char *b, return; /* This console adapter was removed so it is not usable. */ - if (vtermnos[index] < 0) + if (vtermnos[index] == -1) return; while (count > 0 || i > 0) { @@ -247,7 +247,7 @@ static void destroy_hvc_struct(struct kref *kref) * vty adapters do NOT get an hvc_instantiate() callback since they * appear after early console init. */ -int hvc_instantiate(uint32_t vtermno, int index, struct hv_ops *ops) +int hvc_instantiate(uint32_t vtermno, int index, const struct hv_ops *ops) { struct hvc_struct *hp; @@ -748,8 +748,9 @@ static const struct tty_operations hvc_ops = { .chars_in_buffer = hvc_chars_in_buffer, }; -struct hvc_struct __devinit *hvc_alloc(uint32_t vtermno, int data, - struct hv_ops *ops, int outbuf_size) +struct hvc_struct *hvc_alloc(uint32_t vtermno, int data, + const struct hv_ops *ops, + int outbuf_size) { struct hvc_struct *hp; int i; diff --git a/drivers/char/hvc_console.h b/drivers/char/hvc_console.h index 10950ca706d..54381eba4e4 100644 --- a/drivers/char/hvc_console.h +++ b/drivers/char/hvc_console.h @@ -55,7 +55,7 @@ struct hvc_struct { int outbuf_size; int n_outbuf; uint32_t vtermno; - struct hv_ops *ops; + const struct hv_ops *ops; int irq_requested; int data; struct winsize ws; @@ -76,11 +76,12 @@ struct hv_ops { }; /* Register a vterm and a slot index for use as a console (console_init) */ -extern int hvc_instantiate(uint32_t vtermno, int index, struct hv_ops *ops); +extern int hvc_instantiate(uint32_t vtermno, int index, + const struct hv_ops *ops); /* register a vterm for hvc tty operation (module_init or hotplug add) */ -extern struct hvc_struct * __devinit hvc_alloc(uint32_t vtermno, int data, - struct hv_ops *ops, int outbuf_size); +extern struct hvc_struct * hvc_alloc(uint32_t vtermno, int data, + const struct hv_ops *ops, int outbuf_size); /* remove a vterm from hvc tty operation (module_exit or hotplug remove) */ extern int hvc_remove(struct hvc_struct *hp); diff --git a/drivers/char/hvc_iseries.c b/drivers/char/hvc_iseries.c index 936d05bf37f..fd0242676a2 100644 --- a/drivers/char/hvc_iseries.c +++ b/drivers/char/hvc_iseries.c @@ -197,7 +197,7 @@ done: return sent; } -static struct hv_ops hvc_get_put_ops = { +static const struct hv_ops hvc_get_put_ops = { .get_chars = get_chars, .put_chars = put_chars, .notifier_add = notifier_add_irq, diff --git a/drivers/char/hvc_iucv.c b/drivers/char/hvc_iucv.c index fe62bd0e17b..21681a81cc3 100644 --- a/drivers/char/hvc_iucv.c +++ b/drivers/char/hvc_iucv.c @@ -922,7 +922,7 @@ static int hvc_iucv_pm_restore_thaw(struct device *dev) /* HVC operations */ -static struct hv_ops hvc_iucv_ops = { +static const struct hv_ops hvc_iucv_ops = { .get_chars = hvc_iucv_get_chars, .put_chars = hvc_iucv_put_chars, .notifier_add = hvc_iucv_notifier_add, diff --git a/drivers/char/hvc_rtas.c b/drivers/char/hvc_rtas.c index 88590d04004..61c4a61558d 100644 --- a/drivers/char/hvc_rtas.c +++ b/drivers/char/hvc_rtas.c @@ -71,7 +71,7 @@ static int hvc_rtas_read_console(uint32_t vtermno, char *buf, int count) return i; } -static struct hv_ops hvc_rtas_get_put_ops = { +static const struct hv_ops hvc_rtas_get_put_ops = { .get_chars = hvc_rtas_read_console, .put_chars = hvc_rtas_write_console, }; diff --git a/drivers/char/hvc_udbg.c b/drivers/char/hvc_udbg.c index bd63ba878a5..b0957e61a7b 100644 --- a/drivers/char/hvc_udbg.c +++ b/drivers/char/hvc_udbg.c @@ -58,7 +58,7 @@ static int hvc_udbg_get(uint32_t vtermno, char *buf, int count) return i; } -static struct hv_ops hvc_udbg_ops = { +static const struct hv_ops hvc_udbg_ops = { .get_chars = hvc_udbg_get, .put_chars = hvc_udbg_put, }; diff --git a/drivers/char/hvc_vio.c b/drivers/char/hvc_vio.c index 10be343d6ae..27370e99c66 100644 --- a/drivers/char/hvc_vio.c +++ b/drivers/char/hvc_vio.c @@ -77,7 +77,7 @@ static int filtered_get_chars(uint32_t vtermno, char *buf, int count) return got; } -static struct hv_ops hvc_get_put_ops = { +static const struct hv_ops hvc_get_put_ops = { .get_chars = filtered_get_chars, .put_chars = hvc_put_chars, .notifier_add = notifier_add_irq, diff --git a/drivers/char/hvc_xen.c b/drivers/char/hvc_xen.c index b1a71638c77..60446f82a3f 100644 --- a/drivers/char/hvc_xen.c +++ b/drivers/char/hvc_xen.c @@ -122,7 +122,7 @@ static int read_console(uint32_t vtermno, char *buf, int len) return recv; } -static struct hv_ops hvc_ops = { +static const struct hv_ops hvc_ops = { .get_chars = read_console, .put_chars = write_console, .notifier_add = notifier_add_irq, diff --git a/drivers/char/hw_random/Kconfig b/drivers/char/hw_random/Kconfig index 87060266ef9..d31483c5488 100644 --- a/drivers/char/hw_random/Kconfig +++ b/drivers/char/hw_random/Kconfig @@ -114,7 +114,7 @@ config HW_RANDOM_IXP4XX config HW_RANDOM_OMAP tristate "OMAP Random Number Generator support" - depends on HW_RANDOM && (ARCH_OMAP16XX || ARCH_OMAP24XX) + depends on HW_RANDOM && (ARCH_OMAP16XX || ARCH_OMAP2) default HW_RANDOM ---help--- This driver provides kernel-side support for the Random Number @@ -186,3 +186,15 @@ config HW_RANDOM_MXC_RNGA module will be called mxc-rnga. If unsure, say Y. + +config HW_RANDOM_NOMADIK + tristate "ST-Ericsson Nomadik Random Number Generator support" + depends on HW_RANDOM && PLAT_NOMADIK + ---help--- + This driver provides kernel-side support for the Random Number + Generator hardware found on ST-Ericsson SoCs (8815 and 8500). + + To compile this driver as a module, choose M here: the + module will be called nomadik-rng. + + If unsure, say Y. diff --git a/drivers/char/hw_random/Makefile b/drivers/char/hw_random/Makefile index 5eeb1303f0d..4273308aa1e 100644 --- a/drivers/char/hw_random/Makefile +++ b/drivers/char/hw_random/Makefile @@ -18,3 +18,4 @@ obj-$(CONFIG_HW_RANDOM_VIRTIO) += virtio-rng.o obj-$(CONFIG_HW_RANDOM_TX4939) += tx4939-rng.o obj-$(CONFIG_HW_RANDOM_MXC_RNGA) += mxc-rnga.o obj-$(CONFIG_HW_RANDOM_OCTEON) += octeon-rng.o +obj-$(CONFIG_HW_RANDOM_NOMADIK) += nomadik-rng.o diff --git a/drivers/char/hw_random/nomadik-rng.c b/drivers/char/hw_random/nomadik-rng.c new file mode 100644 index 00000000000..a8b4c401014 --- /dev/null +++ b/drivers/char/hw_random/nomadik-rng.c @@ -0,0 +1,103 @@ +/* + * Nomadik RNG support + * Copyright 2009 Alessandro Rubini + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/amba/bus.h> +#include <linux/hw_random.h> +#include <linux/io.h> + +static int nmk_rng_read(struct hwrng *rng, void *data, size_t max, bool wait) +{ + void __iomem *base = (void __iomem *)rng->priv; + + /* + * The register is 32 bits and gives 16 random bits (low half). + * A subsequent read will delay the core for 400ns, so we just read + * once and accept the very unlikely very small delay, even if wait==0. + */ + *(u16 *)data = __raw_readl(base + 8) & 0xffff; + return 2; +} + +/* we have at most one RNG per machine, granted */ +static struct hwrng nmk_rng = { + .name = "nomadik", + .read = nmk_rng_read, +}; + +static int nmk_rng_probe(struct amba_device *dev, struct amba_id *id) +{ + void __iomem *base; + int ret; + + ret = amba_request_regions(dev, dev->dev.init_name); + if (ret) + return ret; + ret = -ENOMEM; + base = ioremap(dev->res.start, resource_size(&dev->res)); + if (!base) + goto out_release; + nmk_rng.priv = (unsigned long)base; + ret = hwrng_register(&nmk_rng); + if (ret) + goto out_unmap; + return 0; + +out_unmap: + iounmap(base); +out_release: + amba_release_regions(dev); + return ret; +} + +static int nmk_rng_remove(struct amba_device *dev) +{ + void __iomem *base = (void __iomem *)nmk_rng.priv; + hwrng_unregister(&nmk_rng); + iounmap(base); + amba_release_regions(dev); + return 0; +} + +static struct amba_id nmk_rng_ids[] = { + { + .id = 0x000805e1, + .mask = 0x000fffff, /* top bits are rev and cfg: accept all */ + }, + {0, 0}, +}; + +static struct amba_driver nmk_rng_driver = { + .drv = { + .owner = THIS_MODULE, + .name = "rng", + }, + .probe = nmk_rng_probe, + .remove = nmk_rng_remove, + .id_table = nmk_rng_ids, +}; + +static int __init nmk_rng_init(void) +{ + return amba_driver_register(&nmk_rng_driver); +} + +static void __devexit nmk_rng_exit(void) +{ + amba_driver_unregister(&nmk_rng_driver); +} + +module_init(nmk_rng_init); +module_exit(nmk_rng_exit); + +MODULE_LICENSE("GPL"); diff --git a/drivers/char/ip2/ip2main.c b/drivers/char/ip2/ip2main.c index 517271c762e..911e1da6def 100644 --- a/drivers/char/ip2/ip2main.c +++ b/drivers/char/ip2/ip2main.c @@ -208,6 +208,7 @@ static int DumpFifoBuffer( char __user *, int); static void ip2_init_board(int, const struct firmware *); static unsigned short find_eisa_board(int); +static int ip2_setup(char *str); /***************/ /* Static Data */ @@ -263,7 +264,7 @@ static int tracewrap; /* Macros */ /**********/ -#if defined(MODULE) && defined(IP2DEBUG_OPEN) +#ifdef IP2DEBUG_OPEN #define DBG_CNT(s) printk(KERN_DEBUG "(%s): [%x] ttyc=%d, modc=%x -> %s\n", \ tty->name,(pCh->flags), \ tty->count,/*GET_USE_COUNT(module)*/0,s) @@ -285,7 +286,10 @@ MODULE_AUTHOR("Doug McNash"); MODULE_DESCRIPTION("Computone IntelliPort Plus Driver"); MODULE_LICENSE("GPL"); +#define MAX_CMD_STR 50 + static int poll_only; +static char cmd[MAX_CMD_STR]; static int Eisa_irq; static int Eisa_slot; @@ -309,6 +313,8 @@ module_param_array(io, int, NULL, 0); MODULE_PARM_DESC(io, "I/O ports for IntelliPort Cards"); module_param(poll_only, bool, 0); MODULE_PARM_DESC(poll_only, "Do not use card interrupts"); +module_param_string(ip2, cmd, MAX_CMD_STR, 0); +MODULE_PARM_DESC(ip2, "Contains module parameter passed with 'ip2='"); /* for sysfs class support */ static struct class *ip2_class; @@ -487,7 +493,6 @@ static const struct firmware *ip2_request_firmware(void) return fw; } -#ifndef MODULE /****************************************************************************** * ip2_setup: * str: kernel command line string @@ -531,7 +536,6 @@ static int __init ip2_setup(char *str) return 1; } __setup("ip2=", ip2_setup); -#endif /* !MODULE */ static int __init ip2_loadmain(void) { @@ -539,14 +543,20 @@ static int __init ip2_loadmain(void) int err = 0; i2eBordStrPtr pB = NULL; int rc = -1; - struct pci_dev *pdev = NULL; const struct firmware *fw = NULL; + char *str; + + str = cmd; if (poll_only) { /* Hard lock the interrupts to zero */ irq[0] = irq[1] = irq[2] = irq[3] = poll_only = 0; } + /* Check module parameter with 'ip2=' has been passed or not */ + if (!poll_only && (!strncmp(str, "ip2=", 4))) + ip2_setup(str); + ip2trace(ITRC_NO_PORT, ITRC_INIT, ITRC_ENTER, 0); /* process command line arguments to modprobe or @@ -612,6 +622,7 @@ static int __init ip2_loadmain(void) case PCI: #ifdef CONFIG_PCI { + struct pci_dev *pdev = NULL; u32 addr; int status; @@ -626,7 +637,7 @@ static int __init ip2_loadmain(void) if (pci_enable_device(pdev)) { dev_err(&pdev->dev, "can't enable device\n"); - break; + goto out; } ip2config.type[i] = PCI; ip2config.pci_dev[i] = pci_dev_get(pdev); @@ -638,6 +649,8 @@ static int __init ip2_loadmain(void) dev_err(&pdev->dev, "I/O address error\n"); ip2config.irq[i] = pdev->irq; +out: + pci_dev_put(pdev); } #else printk(KERN_ERR "IP2: PCI card specified but PCI " @@ -656,7 +669,6 @@ static int __init ip2_loadmain(void) break; } /* switch */ } /* for */ - pci_dev_put(pdev); for (i = 0; i < IP2_MAX_BOARDS; ++i) { if (ip2config.addr[i]) { @@ -3197,3 +3209,5 @@ static struct pci_device_id ip2main_pci_tbl[] __devinitdata = { }; MODULE_DEVICE_TABLE(pci, ip2main_pci_tbl); + +MODULE_FIRMWARE("intelliport2.bin"); diff --git a/drivers/char/isicom.c b/drivers/char/isicom.c index 300d5bd6cd0..be2e8f9a27c 100644 --- a/drivers/char/isicom.c +++ b/drivers/char/isicom.c @@ -113,6 +113,8 @@ * 64-bit verification */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/module.h> #include <linux/firmware.h> #include <linux/kernel.h> @@ -140,7 +142,6 @@ #define InterruptTheCard(base) outw(0, (base) + 0xc) #define ClearInterrupt(base) inw((base) + 0x0a) -#define pr_dbg(str...) pr_debug("ISICOM: " str) #ifdef DEBUG #define isicom_paranoia_check(a, b, c) __isicom_paranoia_check((a), (b), (c)) #else @@ -249,8 +250,7 @@ static int lock_card(struct isi_board *card) spin_unlock_irqrestore(&card->card_lock, card->flags); msleep(10); } - printk(KERN_WARNING "ISICOM: Failed to lock Card (0x%lx)\n", - card->base); + pr_warning("Failed to lock Card (0x%lx)\n", card->base); return 0; /* Failed to acquire the card! */ } @@ -379,13 +379,13 @@ static inline int __isicom_paranoia_check(struct isi_port const *port, char *name, const char *routine) { if (!port) { - printk(KERN_WARNING "ISICOM: Warning: bad isicom magic for " - "dev %s in %s.\n", name, routine); + pr_warning("Warning: bad isicom magic for dev %s in %s.\n", + name, routine); return 1; } if (port->magic != ISICOM_MAGIC) { - printk(KERN_WARNING "ISICOM: Warning: NULL isicom port for " - "dev %s in %s.\n", name, routine); + pr_warning("Warning: NULL isicom port for dev %s in %s.\n", + name, routine); return 1; } @@ -450,8 +450,8 @@ static void isicom_tx(unsigned long _data) if (!(inw(base + 0x02) & (1 << port->channel))) continue; - pr_dbg("txing %d bytes, port%d.\n", txcount, - port->channel + 1); + pr_debug("txing %d bytes, port%d.\n", + txcount, port->channel + 1); outw((port->channel << isi_card[card].shift_count) | txcount, base); residue = NO; @@ -547,8 +547,8 @@ static irqreturn_t isicom_interrupt(int irq, void *dev_id) byte_count = header & 0xff; if (channel + 1 > card->port_count) { - printk(KERN_WARNING "ISICOM: isicom_interrupt(0x%lx): " - "%d(channel) > port_count.\n", base, channel+1); + pr_warning("%s(0x%lx): %d(channel) > port_count.\n", + __func__, base, channel+1); outw(0x0000, base+0x04); /* enable interrupts */ spin_unlock(&card->card_lock); return IRQ_HANDLED; @@ -582,14 +582,15 @@ static irqreturn_t isicom_interrupt(int irq, void *dev_id) if (port->status & ISI_DCD) { if (!(header & ISI_DCD)) { /* Carrier has been lost */ - pr_dbg("interrupt: DCD->low.\n" - ); + pr_debug("%s: DCD->low.\n", + __func__); port->status &= ~ISI_DCD; tty_hangup(tty); } } else if (header & ISI_DCD) { /* Carrier has been detected */ - pr_dbg("interrupt: DCD->high.\n"); + pr_debug("%s: DCD->high.\n", + __func__); port->status |= ISI_DCD; wake_up_interruptible(&port->port.open_wait); } @@ -641,17 +642,19 @@ static irqreturn_t isicom_interrupt(int irq, void *dev_id) break; case 2: /* Statistics */ - pr_dbg("isicom_interrupt: stats!!!.\n"); + pr_debug("%s: stats!!!\n", __func__); break; default: - pr_dbg("Intr: Unknown code in status packet.\n"); + pr_debug("%s: Unknown code in status packet.\n", + __func__); break; } } else { /* Data Packet */ count = tty_prepare_flip_string(tty, &rp, byte_count & ~1); - pr_dbg("Intr: Can rx %d of %d bytes.\n", count, byte_count); + pr_debug("%s: Can rx %d of %d bytes.\n", + __func__, count, byte_count); word_count = count >> 1; insw(base, rp, word_count); byte_count -= (word_count << 1); @@ -661,8 +664,8 @@ static irqreturn_t isicom_interrupt(int irq, void *dev_id) byte_count -= 2; } if (byte_count > 0) { - pr_dbg("Intr(0x%lx:%d): Flip buffer overflow! dropping " - "bytes...\n", base, channel + 1); + pr_debug("%s(0x%lx:%d): Flip buffer overflow! dropping bytes...\n", + __func__, base, channel + 1); /* drain out unread xtra data */ while (byte_count > 0) { inw(base); @@ -888,8 +891,8 @@ static void isicom_shutdown_port(struct isi_port *port) struct isi_board *card = port->card; if (--card->count < 0) { - pr_dbg("isicom_shutdown_port: bad board(0x%lx) count %d.\n", - card->base, card->count); + pr_debug("%s: bad board(0x%lx) count %d.\n", + __func__, card->base, card->count); card->count = 0; } /* last port was closed, shutdown that board too */ @@ -1681,13 +1684,13 @@ static int __init isicom_init(void) retval = tty_register_driver(isicom_normal); if (retval) { - pr_dbg("Couldn't register the dialin driver\n"); + pr_debug("Couldn't register the dialin driver\n"); goto err_puttty; } retval = pci_register_driver(&isicom_driver); if (retval < 0) { - printk(KERN_ERR "ISICOM: Unable to register pci driver.\n"); + pr_err("Unable to register pci driver.\n"); goto err_unrtty; } @@ -1717,3 +1720,8 @@ module_exit(isicom_exit); MODULE_AUTHOR("MultiTech"); MODULE_DESCRIPTION("Driver for the ISI series of cards by MultiTech"); MODULE_LICENSE("GPL"); +MODULE_FIRMWARE("isi608.bin"); +MODULE_FIRMWARE("isi608em.bin"); +MODULE_FIRMWARE("isi616em.bin"); +MODULE_FIRMWARE("isi4608.bin"); +MODULE_FIRMWARE("isi4616.bin"); diff --git a/drivers/char/keyboard.c b/drivers/char/keyboard.c index f706b1dffdb..ada25bb8941 100644 --- a/drivers/char/keyboard.c +++ b/drivers/char/keyboard.c @@ -1185,11 +1185,6 @@ static void kbd_keycode(unsigned int keycode, int down, int hw_raw) rep = (down == 2); -#ifdef CONFIG_MAC_EMUMOUSEBTN - if (mac_hid_mouse_emulate_buttons(1, keycode, down)) - return; -#endif /* CONFIG_MAC_EMUMOUSEBTN */ - if ((raw_mode = (kbd->kbdmode == VC_RAW)) && !hw_raw) if (emulate_raw(vc, keycode, !down << 7)) if (keycode < BTN_MISC && printk_ratelimit()) @@ -1328,6 +1323,21 @@ static void kbd_event(struct input_handle *handle, unsigned int event_type, schedule_console_callback(); } +static bool kbd_match(struct input_handler *handler, struct input_dev *dev) +{ + int i; + + if (test_bit(EV_SND, dev->evbit)) + return true; + + if (test_bit(EV_KEY, dev->evbit)) + for (i = KEY_RESERVED; i < BTN_MISC; i++) + if (test_bit(i, dev->keybit)) + return true; + + return false; +} + /* * When a keyboard (or other input device) is found, the kbd_connect * function is called. The function then looks at the device, and if it @@ -1339,14 +1349,6 @@ static int kbd_connect(struct input_handler *handler, struct input_dev *dev, { struct input_handle *handle; int error; - int i; - - for (i = KEY_RESERVED; i < BTN_MISC; i++) - if (test_bit(i, dev->keybit)) - break; - - if (i == BTN_MISC && !test_bit(EV_SND, dev->evbit)) - return -ENODEV; handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL); if (!handle) @@ -1412,6 +1414,7 @@ MODULE_DEVICE_TABLE(input, kbd_ids); static struct input_handler kbd_handler = { .event = kbd_event, + .match = kbd_match, .connect = kbd_connect, .disconnect = kbd_disconnect, .start = kbd_start, diff --git a/drivers/char/moxa.c b/drivers/char/moxa.c index 63ee3bbc1ce..166495d6a1d 100644 --- a/drivers/char/moxa.c +++ b/drivers/char/moxa.c @@ -164,24 +164,25 @@ static unsigned int moxaFuncTout = HZ / 2; static unsigned int moxaLowWaterChk; static DEFINE_MUTEX(moxa_openlock); static DEFINE_SPINLOCK(moxa_lock); -/* Variables for insmod */ -#ifdef MODULE + static unsigned long baseaddr[MAX_BOARDS]; static unsigned int type[MAX_BOARDS]; static unsigned int numports[MAX_BOARDS]; -#endif MODULE_AUTHOR("William Chen"); MODULE_DESCRIPTION("MOXA Intellio Family Multiport Board Device Driver"); MODULE_LICENSE("GPL"); -#ifdef MODULE +MODULE_FIRMWARE("c218tunx.cod"); +MODULE_FIRMWARE("cp204unx.cod"); +MODULE_FIRMWARE("c320tunx.cod"); + module_param_array(type, uint, NULL, 0); MODULE_PARM_DESC(type, "card type: C218=2, C320=4"); module_param_array(baseaddr, ulong, NULL, 0); MODULE_PARM_DESC(baseaddr, "base address"); module_param_array(numports, uint, NULL, 0); MODULE_PARM_DESC(numports, "numports (ignored for C218)"); -#endif + module_param(ttymajor, int, 0); /* @@ -1024,6 +1025,8 @@ static int __init moxa_init(void) { unsigned int isabrds = 0; int retval = 0; + struct moxa_board_conf *brd = moxa_boards; + unsigned int i; printk(KERN_INFO "MOXA Intellio family driver version %s\n", MOXA_VERSION); @@ -1051,10 +1054,7 @@ static int __init moxa_init(void) } /* Find the boards defined from module args. */ -#ifdef MODULE - { - struct moxa_board_conf *brd = moxa_boards; - unsigned int i; + for (i = 0; i < MAX_BOARDS; i++) { if (!baseaddr[i]) break; @@ -1087,8 +1087,6 @@ static int __init moxa_init(void) isabrds++; } } - } -#endif #ifdef CONFIG_PCI retval = pci_register_driver(&moxa_pci_driver); diff --git a/drivers/char/mxser.c b/drivers/char/mxser.c index 3d923065d9a..e0c5d2a6904 100644 --- a/drivers/char/mxser.c +++ b/drivers/char/mxser.c @@ -895,8 +895,7 @@ static int mxser_activate(struct tty_port *port, struct tty_struct *tty) if (inb(info->ioaddr + UART_LSR) == 0xff) { spin_unlock_irqrestore(&info->slock, flags); if (capable(CAP_SYS_ADMIN)) { - if (tty) - set_bit(TTY_IO_ERROR, &tty->flags); + set_bit(TTY_IO_ERROR, &tty->flags); return 0; } else return -ENODEV; diff --git a/drivers/char/nozomi.c b/drivers/char/nozomi.c index 2ad7d37afbd..a3f32a15fde 100644 --- a/drivers/char/nozomi.c +++ b/drivers/char/nozomi.c @@ -136,10 +136,6 @@ static int debug; #define RECEIVE_BUF_MAX 4 -/* Define all types of vendors and devices to support */ -#define VENDOR1 0x1931 /* Vendor Option */ -#define DEVICE1 0x000c /* HSDPA card */ - #define R_IIR 0x0000 /* Interrupt Identity Register */ #define R_FCR 0x0000 /* Flow Control Register */ #define R_IER 0x0004 /* Interrupt Enable Register */ @@ -371,6 +367,8 @@ struct port { struct mutex tty_sem; wait_queue_head_t tty_wait; struct async_icount tty_icount; + + struct nozomi *dc; }; /* Private data one for each card in the system */ @@ -405,7 +403,7 @@ struct buffer { /* Global variables */ static const struct pci_device_id nozomi_pci_tbl[] __devinitconst = { - {PCI_DEVICE(VENDOR1, DEVICE1)}, + {PCI_DEVICE(0x1931, 0x000c)}, /* Nozomi HSDPA */ {}, }; @@ -414,6 +412,8 @@ MODULE_DEVICE_TABLE(pci, nozomi_pci_tbl); static struct nozomi *ndevs[NOZOMI_MAX_CARDS]; static struct tty_driver *ntty_driver; +static const struct tty_port_operations noz_tty_port_ops; + /* * find card by tty_index */ @@ -853,8 +853,6 @@ static int receive_data(enum port_type index, struct nozomi *dc) goto put; } - tty_buffer_request_room(tty, size); - while (size > 0) { read_mem32((u32 *) buf, addr + offset, RECEIVE_BUF_MAX); @@ -1473,9 +1471,11 @@ static int __devinit nozomi_card_init(struct pci_dev *pdev, for (i = 0; i < MAX_PORT; i++) { struct device *tty_dev; - - mutex_init(&dc->port[i].tty_sem); - tty_port_init(&dc->port[i].port); + struct port *port = &dc->port[i]; + port->dc = dc; + mutex_init(&port->tty_sem); + tty_port_init(&port->port); + port->port.ops = &noz_tty_port_ops; tty_dev = tty_register_device(ntty_driver, dc->index_start + i, &pdev->dev); @@ -1600,67 +1600,74 @@ static void set_dtr(const struct tty_struct *tty, int dtr) * ---------------------------------------------------------------------------- */ -/* Called when the userspace process opens the tty, /dev/noz*. */ -static int ntty_open(struct tty_struct *tty, struct file *file) +static int ntty_install(struct tty_driver *driver, struct tty_struct *tty) { struct port *port = get_port_by_tty(tty); struct nozomi *dc = get_dc_by_tty(tty); - unsigned long flags; - + int ret; if (!port || !dc || dc->state != NOZOMI_STATE_READY) return -ENODEV; - - if (mutex_lock_interruptible(&port->tty_sem)) - return -ERESTARTSYS; - - port->port.count++; - dc->open_ttys++; - - /* Enable interrupt downlink for channel */ - if (port->port.count == 1) { - tty->driver_data = port; - tty_port_tty_set(&port->port, tty); - DBG1("open: %d", port->token_dl); - spin_lock_irqsave(&dc->spin_mutex, flags); - dc->last_ier = dc->last_ier | port->token_dl; - writew(dc->last_ier, dc->reg_ier); - spin_unlock_irqrestore(&dc->spin_mutex, flags); + ret = tty_init_termios(tty); + if (ret == 0) { + tty_driver_kref_get(driver); + driver->ttys[tty->index] = tty; } - mutex_unlock(&port->tty_sem); - return 0; + return ret; } -/* Called when the userspace process close the tty, /dev/noz*. Also - called immediately if ntty_open fails in which case tty->driver_data - will be NULL an we exit by the first return */ +static void ntty_cleanup(struct tty_struct *tty) +{ + tty->driver_data = NULL; +} -static void ntty_close(struct tty_struct *tty, struct file *file) +static int ntty_activate(struct tty_port *tport, struct tty_struct *tty) { - struct nozomi *dc = get_dc_by_tty(tty); - struct port *nport = tty->driver_data; - struct tty_port *port = &nport->port; + struct port *port = container_of(tport, struct port, port); + struct nozomi *dc = port->dc; unsigned long flags; - if (!dc || !nport) - return; + DBG1("open: %d", port->token_dl); + spin_lock_irqsave(&dc->spin_mutex, flags); + dc->last_ier = dc->last_ier | port->token_dl; + writew(dc->last_ier, dc->reg_ier); + dc->open_ttys++; + spin_unlock_irqrestore(&dc->spin_mutex, flags); + printk("noz: activated %d: %p\n", tty->index, tport); + return 0; +} - /* Users cannot interrupt a close */ - mutex_lock(&nport->tty_sem); +static int ntty_open(struct tty_struct *tty, struct file *filp) +{ + struct port *port = get_port_by_tty(tty); + return tty_port_open(&port->port, tty, filp); +} - WARN_ON(!port->count); +static void ntty_shutdown(struct tty_port *tport) +{ + struct port *port = container_of(tport, struct port, port); + struct nozomi *dc = port->dc; + unsigned long flags; + DBG1("close: %d", port->token_dl); + spin_lock_irqsave(&dc->spin_mutex, flags); + dc->last_ier &= ~(port->token_dl); + writew(dc->last_ier, dc->reg_ier); dc->open_ttys--; - port->count--; + spin_unlock_irqrestore(&dc->spin_mutex, flags); + printk("noz: shutdown %p\n", tport); +} - if (port->count == 0) { - DBG1("close: %d", nport->token_dl); - tty_port_tty_set(port, NULL); - spin_lock_irqsave(&dc->spin_mutex, flags); - dc->last_ier &= ~(nport->token_dl); - writew(dc->last_ier, dc->reg_ier); - spin_unlock_irqrestore(&dc->spin_mutex, flags); - } - mutex_unlock(&nport->tty_sem); +static void ntty_close(struct tty_struct *tty, struct file *filp) +{ + struct port *port = tty->driver_data; + if (port) + tty_port_close(&port->port, tty, filp); +} + +static void ntty_hangup(struct tty_struct *tty) +{ + struct port *port = tty->driver_data; + tty_port_hangup(&port->port); } /* @@ -1680,15 +1687,7 @@ static int ntty_write(struct tty_struct *tty, const unsigned char *buffer, if (!dc || !port) return -ENODEV; - if (unlikely(!mutex_trylock(&port->tty_sem))) { - /* - * must test lock as tty layer wraps calls - * to this function with BKL - */ - dev_err(&dc->pdev->dev, "Would have deadlocked - " - "return EAGAIN\n"); - return -EAGAIN; - } + mutex_lock(&port->tty_sem); if (unlikely(!port->port.count)) { DBG1(" "); @@ -1728,25 +1727,23 @@ exit: * This method is called by the upper tty layer. * #according to sources N_TTY.c it expects a value >= 0 and * does not check for negative values. + * + * If the port is unplugged report lots of room and let the bits + * dribble away so we don't block anything. */ static int ntty_write_room(struct tty_struct *tty) { struct port *port = tty->driver_data; - int room = 0; + int room = 4096; const struct nozomi *dc = get_dc_by_tty(tty); - if (!dc || !port) - return 0; - if (!mutex_trylock(&port->tty_sem)) - return 0; - - if (!port->port.count) - goto exit; - - room = port->fifo_ul.size - kfifo_len(&port->fifo_ul); - -exit: - mutex_unlock(&port->tty_sem); + if (dc) { + mutex_lock(&port->tty_sem); + if (port->port.count) + room = port->fifo_ul.size - + kfifo_len(&port->fifo_ul); + mutex_unlock(&port->tty_sem); + } return room; } @@ -1906,10 +1903,16 @@ exit_in_buffer: return rval; } +static const struct tty_port_operations noz_tty_port_ops = { + .activate = ntty_activate, + .shutdown = ntty_shutdown, +}; + static const struct tty_operations tty_ops = { .ioctl = ntty_ioctl, .open = ntty_open, .close = ntty_close, + .hangup = ntty_hangup, .write = ntty_write, .write_room = ntty_write_room, .unthrottle = ntty_unthrottle, @@ -1917,6 +1920,8 @@ static const struct tty_operations tty_ops = { .chars_in_buffer = ntty_chars_in_buffer, .tiocmget = ntty_tiocmget, .tiocmset = ntty_tiocmset, + .install = ntty_install, + .cleanup = ntty_cleanup, }; /* Module initialization */ diff --git a/drivers/char/nvram.c b/drivers/char/nvram.c index fdbcc9fd6d3..5eb83c3ca20 100644 --- a/drivers/char/nvram.c +++ b/drivers/char/nvram.c @@ -336,14 +336,12 @@ static int nvram_ioctl(struct inode *inode, struct file *file, static int nvram_open(struct inode *inode, struct file *file) { - lock_kernel(); spin_lock(&nvram_state_lock); if ((nvram_open_cnt && (file->f_flags & O_EXCL)) || (nvram_open_mode & NVRAM_EXCL) || ((file->f_mode & FMODE_WRITE) && (nvram_open_mode & NVRAM_WRITE))) { spin_unlock(&nvram_state_lock); - unlock_kernel(); return -EBUSY; } @@ -354,7 +352,6 @@ static int nvram_open(struct inode *inode, struct file *file) nvram_open_cnt++; spin_unlock(&nvram_state_lock); - unlock_kernel(); return 0; } diff --git a/drivers/char/pcmcia/cm4000_cs.c b/drivers/char/pcmcia/cm4000_cs.c index 2db4c0a29b0..c9bc896d68a 100644 --- a/drivers/char/pcmcia/cm4000_cs.c +++ b/drivers/char/pcmcia/cm4000_cs.c @@ -1047,7 +1047,7 @@ release_io: static ssize_t cmm_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { - struct cm4000_dev *dev = (struct cm4000_dev *) filp->private_data; + struct cm4000_dev *dev = filp->private_data; unsigned int iobase = dev->p_dev->io.BasePort1; unsigned short s; unsigned char tmp; diff --git a/drivers/char/serial167.c b/drivers/char/serial167.c index 452370af95d..986aa606a6b 100644 --- a/drivers/char/serial167.c +++ b/drivers/char/serial167.c @@ -658,8 +658,7 @@ static irqreturn_t cd2401_rx_interrupt(int irq, void *dev_id) info->mon.char_max = char_count; info->mon.char_last = char_count; #endif - len = tty_buffer_request_room(tty, char_count); - while (len--) { + while (char_count--) { data = base_addr[CyRDR]; tty_insert_flip_char(tty, data, TTY_NORMAL); #ifdef CYCLOM_16Y_HACK diff --git a/drivers/char/specialix.c b/drivers/char/specialix.c index 268e17f9ec3..07ac14d949c 100644 --- a/drivers/char/specialix.c +++ b/drivers/char/specialix.c @@ -646,8 +646,6 @@ static void sx_receive(struct specialix_board *bp) dprintk(SX_DEBUG_RX, "port: %p: count: %d\n", port, count); port->hits[count > 8 ? 9 : count]++; - tty_buffer_request_room(tty, count); - while (count--) tty_insert_flip_char(tty, sx_in(bp, CD186x_RDR), TTY_NORMAL); tty_flip_buffer_push(tty); diff --git a/drivers/char/synclink.c b/drivers/char/synclink.c index 4846b73ef28..0658fc54822 100644 --- a/drivers/char/synclink.c +++ b/drivers/char/synclink.c @@ -2031,7 +2031,7 @@ static int mgsl_put_char(struct tty_struct *tty, unsigned char ch) if (mgsl_paranoia_check(info, tty->name, "mgsl_put_char")) return 0; - if (!tty || !info->xmit_buf) + if (!info->xmit_buf) return 0; spin_lock_irqsave(&info->irq_spinlock, flags); @@ -2121,7 +2121,7 @@ static int mgsl_write(struct tty_struct * tty, if (mgsl_paranoia_check(info, tty->name, "mgsl_write")) goto cleanup; - if (!tty || !info->xmit_buf) + if (!info->xmit_buf) goto cleanup; if ( info->params.mode == MGSL_MODE_HDLC || diff --git a/drivers/char/synclink_gt.c b/drivers/char/synclink_gt.c index 8678f0c8699..4561ce2fba6 100644 --- a/drivers/char/synclink_gt.c +++ b/drivers/char/synclink_gt.c @@ -468,7 +468,7 @@ static unsigned int free_tbuf_count(struct slgt_info *info); static unsigned int tbuf_bytes(struct slgt_info *info); static void reset_tbufs(struct slgt_info *info); static void tdma_reset(struct slgt_info *info); -static void tx_load(struct slgt_info *info, const char *buf, unsigned int count); +static bool tx_load(struct slgt_info *info, const char *buf, unsigned int count); static void get_signals(struct slgt_info *info); static void set_signals(struct slgt_info *info); @@ -813,59 +813,32 @@ static int write(struct tty_struct *tty, int ret = 0; struct slgt_info *info = tty->driver_data; unsigned long flags; - unsigned int bufs_needed; if (sanity_check(info, tty->name, "write")) - goto cleanup; + return -EIO; + DBGINFO(("%s write count=%d\n", info->device_name, count)); - if (!info->tx_buf) - goto cleanup; + if (!info->tx_buf || (count > info->max_frame_size)) + return -EIO; - if (count > info->max_frame_size) { - ret = -EIO; - goto cleanup; - } + if (!count || tty->stopped || tty->hw_stopped) + return 0; - if (!count) - goto cleanup; + spin_lock_irqsave(&info->lock, flags); - if (!info->tx_active && info->tx_count) { + if (info->tx_count) { /* send accumulated data from send_char() */ - tx_load(info, info->tx_buf, info->tx_count); - goto start; + if (!tx_load(info, info->tx_buf, info->tx_count)) + goto cleanup; + info->tx_count = 0; } - bufs_needed = (count/DMABUFSIZE); - if (count % DMABUFSIZE) - ++bufs_needed; - if (bufs_needed > free_tbuf_count(info)) - goto cleanup; - ret = info->tx_count = count; - tx_load(info, buf, count); - goto start; - -start: - if (info->tx_count && !tty->stopped && !tty->hw_stopped) { - spin_lock_irqsave(&info->lock,flags); - if (!info->tx_active) - tx_start(info); - else if (!(rd_reg32(info, TDCSR) & BIT0)) { - /* transmit still active but transmit DMA stopped */ - unsigned int i = info->tbuf_current; - if (!i) - i = info->tbuf_count; - i--; - /* if DMA buf unsent must try later after tx idle */ - if (desc_count(info->tbufs[i])) - ret = 0; - } - if (ret > 0) - update_tx_timer(info); - spin_unlock_irqrestore(&info->lock,flags); - } + if (tx_load(info, buf, count)) + ret = count; cleanup: + spin_unlock_irqrestore(&info->lock, flags); DBGINFO(("%s write rc=%d\n", info->device_name, ret)); return ret; } @@ -882,7 +855,7 @@ static int put_char(struct tty_struct *tty, unsigned char ch) if (!info->tx_buf) return 0; spin_lock_irqsave(&info->lock,flags); - if (!info->tx_active && (info->tx_count < info->max_frame_size)) { + if (info->tx_count < info->max_frame_size) { info->tx_buf[info->tx_count++] = ch; ret = 1; } @@ -981,10 +954,8 @@ static void flush_chars(struct tty_struct *tty) DBGINFO(("%s flush_chars start transmit\n", info->device_name)); spin_lock_irqsave(&info->lock,flags); - if (!info->tx_active && info->tx_count) { - tx_load(info, info->tx_buf,info->tx_count); - tx_start(info); - } + if (info->tx_count && tx_load(info, info->tx_buf, info->tx_count)) + info->tx_count = 0; spin_unlock_irqrestore(&info->lock,flags); } @@ -997,10 +968,9 @@ static void flush_buffer(struct tty_struct *tty) return; DBGINFO(("%s flush_buffer\n", info->device_name)); - spin_lock_irqsave(&info->lock,flags); - if (!info->tx_active) - info->tx_count = 0; - spin_unlock_irqrestore(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); + info->tx_count = 0; + spin_unlock_irqrestore(&info->lock, flags); tty_wakeup(tty); } @@ -1033,12 +1003,10 @@ static void tx_release(struct tty_struct *tty) if (sanity_check(info, tty->name, "tx_release")) return; DBGINFO(("%s tx_release\n", info->device_name)); - spin_lock_irqsave(&info->lock,flags); - if (!info->tx_active && info->tx_count) { - tx_load(info, info->tx_buf, info->tx_count); - tx_start(info); - } - spin_unlock_irqrestore(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); + if (info->tx_count && tx_load(info, info->tx_buf, info->tx_count)) + info->tx_count = 0; + spin_unlock_irqrestore(&info->lock, flags); } /* @@ -1506,27 +1474,25 @@ static netdev_tx_t hdlcdev_xmit(struct sk_buff *skb, DBGINFO(("%s hdlc_xmit\n", dev->name)); + if (!skb->len) + return NETDEV_TX_OK; + /* stop sending until this frame completes */ netif_stop_queue(dev); - /* copy data to device buffers */ - info->tx_count = skb->len; - tx_load(info, skb->data, skb->len); - /* update network statistics */ dev->stats.tx_packets++; dev->stats.tx_bytes += skb->len; - /* done with socket buffer, so free it */ - dev_kfree_skb(skb); - /* save start time for transmit timeout detection */ dev->trans_start = jiffies; - spin_lock_irqsave(&info->lock,flags); - tx_start(info); - update_tx_timer(info); - spin_unlock_irqrestore(&info->lock,flags); + spin_lock_irqsave(&info->lock, flags); + tx_load(info, skb->data, skb->len); + spin_unlock_irqrestore(&info->lock, flags); + + /* done with socket buffer, so free it */ + dev_kfree_skb(skb); return NETDEV_TX_OK; } @@ -2180,7 +2146,7 @@ static void isr_serial(struct slgt_info *info) if (info->params.mode == MGSL_MODE_ASYNC) { if (status & IRQ_TXIDLE) { - if (info->tx_count) + if (info->tx_active) isr_txeom(info, status); } if (info->rx_pio && (status & IRQ_RXDATA)) @@ -2276,13 +2242,42 @@ static void isr_tdma(struct slgt_info *info) } } +/* + * return true if there are unsent tx DMA buffers, otherwise false + * + * if there are unsent buffers then info->tbuf_start + * is set to index of first unsent buffer + */ +static bool unsent_tbufs(struct slgt_info *info) +{ + unsigned int i = info->tbuf_current; + bool rc = false; + + /* + * search backwards from last loaded buffer (precedes tbuf_current) + * for first unsent buffer (desc_count > 0) + */ + + do { + if (i) + i--; + else + i = info->tbuf_count - 1; + if (!desc_count(info->tbufs[i])) + break; + info->tbuf_start = i; + rc = true; + } while (i != info->tbuf_current); + + return rc; +} + static void isr_txeom(struct slgt_info *info, unsigned short status) { DBGISR(("%s txeom status=%04x\n", info->device_name, status)); slgt_irq_off(info, IRQ_TXDATA + IRQ_TXIDLE + IRQ_TXUNDER); tdma_reset(info); - reset_tbufs(info); if (status & IRQ_TXUNDER) { unsigned short val = rd_reg16(info, TCR); wr_reg16(info, TCR, (unsigned short)(val | BIT2)); /* set reset bit */ @@ -2297,8 +2292,12 @@ static void isr_txeom(struct slgt_info *info, unsigned short status) info->icount.txok++; } + if (unsent_tbufs(info)) { + tx_start(info); + update_tx_timer(info); + return; + } info->tx_active = false; - info->tx_count = 0; del_timer(&info->tx_timer); @@ -3949,7 +3948,7 @@ static void tx_start(struct slgt_info *info) info->tx_enabled = true; } - if (info->tx_count) { + if (desc_count(info->tbufs[info->tbuf_start])) { info->drop_rts_on_tx_done = false; if (info->params.mode != MGSL_MODE_ASYNC) { @@ -4772,25 +4771,36 @@ static unsigned int tbuf_bytes(struct slgt_info *info) } /* - * load transmit DMA buffer(s) with data + * load data into transmit DMA buffer ring and start transmitter if needed + * return true if data accepted, otherwise false (buffers full) */ -static void tx_load(struct slgt_info *info, const char *buf, unsigned int size) +static bool tx_load(struct slgt_info *info, const char *buf, unsigned int size) { unsigned short count; unsigned int i; struct slgt_desc *d; - if (size == 0) - return; + /* check required buffer space */ + if (DIV_ROUND_UP(size, DMABUFSIZE) > free_tbuf_count(info)) + return false; DBGDATA(info, buf, size, "tx"); + /* + * copy data to one or more DMA buffers in circular ring + * tbuf_start = first buffer for this data + * tbuf_current = next free buffer + * + * Copy all data before making data visible to DMA controller by + * setting descriptor count of the first buffer. + * This prevents an active DMA controller from reading the first DMA + * buffers of a frame and stopping before the final buffers are filled. + */ + info->tbuf_start = i = info->tbuf_current; while (size) { d = &info->tbufs[i]; - if (++i == info->tbuf_count) - i = 0; count = (unsigned short)((size > DMABUFSIZE) ? DMABUFSIZE : size); memcpy(d->buf, buf, count); @@ -4808,11 +4818,27 @@ static void tx_load(struct slgt_info *info, const char *buf, unsigned int size) else set_desc_eof(*d, 0); - set_desc_count(*d, count); + /* set descriptor count for all but first buffer */ + if (i != info->tbuf_start) + set_desc_count(*d, count); d->buf_count = count; + + if (++i == info->tbuf_count) + i = 0; } info->tbuf_current = i; + + /* set first buffer count to make new data visible to DMA controller */ + d = &info->tbufs[info->tbuf_start]; + set_desc_count(*d, d->buf_count); + + /* start transmitter if needed and update transmit timeout */ + if (!info->tx_active) + tx_start(info); + update_tx_timer(info); + + return true; } static int register_test(struct slgt_info *info) @@ -4934,9 +4960,7 @@ static int loopback_test(struct slgt_info *info) spin_lock_irqsave(&info->lock,flags); async_mode(info); rx_start(info); - info->tx_count = count; tx_load(info, buf, count); - tx_start(info); spin_unlock_irqrestore(&info->lock, flags); /* wait for receive complete */ diff --git a/drivers/char/tty_buffer.c b/drivers/char/tty_buffer.c index 66fa4e10d76..af8d9771572 100644 --- a/drivers/char/tty_buffer.c +++ b/drivers/char/tty_buffer.c @@ -231,9 +231,10 @@ int tty_buffer_request_room(struct tty_struct *tty, size_t size) EXPORT_SYMBOL_GPL(tty_buffer_request_room); /** - * tty_insert_flip_string - Add characters to the tty buffer + * tty_insert_flip_string_fixed_flag - Add characters to the tty buffer * @tty: tty structure * @chars: characters + * @flag: flag value for each character * @size: size * * Queue a series of bytes to the tty buffering. All the characters @@ -242,18 +243,19 @@ EXPORT_SYMBOL_GPL(tty_buffer_request_room); * Locking: Called functions may take tty->buf.lock */ -int tty_insert_flip_string(struct tty_struct *tty, const unsigned char *chars, - size_t size) +int tty_insert_flip_string_fixed_flag(struct tty_struct *tty, + const unsigned char *chars, char flag, size_t size) { int copied = 0; do { - int space = tty_buffer_request_room(tty, size - copied); + int goal = min(size - copied, TTY_BUFFER_PAGE); + int space = tty_buffer_request_room(tty, goal); struct tty_buffer *tb = tty->buf.tail; /* If there is no space then tb may be NULL */ if (unlikely(space == 0)) break; memcpy(tb->char_buf_ptr + tb->used, chars, space); - memset(tb->flag_buf_ptr + tb->used, TTY_NORMAL, space); + memset(tb->flag_buf_ptr + tb->used, flag, space); tb->used += space; copied += space; chars += space; @@ -262,7 +264,7 @@ int tty_insert_flip_string(struct tty_struct *tty, const unsigned char *chars, } while (unlikely(size > copied)); return copied; } -EXPORT_SYMBOL(tty_insert_flip_string); +EXPORT_SYMBOL(tty_insert_flip_string_fixed_flag); /** * tty_insert_flip_string_flags - Add characters to the tty buffer @@ -283,7 +285,8 @@ int tty_insert_flip_string_flags(struct tty_struct *tty, { int copied = 0; do { - int space = tty_buffer_request_room(tty, size - copied); + int goal = min(size - copied, TTY_BUFFER_PAGE); + int space = tty_buffer_request_room(tty, goal); struct tty_buffer *tb = tty->buf.tail; /* If there is no space then tb may be NULL */ if (unlikely(space == 0)) diff --git a/drivers/char/tty_ldisc.c b/drivers/char/tty_ldisc.c index 3f653f7d849..500e740ec5e 100644 --- a/drivers/char/tty_ldisc.c +++ b/drivers/char/tty_ldisc.c @@ -706,12 +706,13 @@ static void tty_reset_termios(struct tty_struct *tty) /** * tty_ldisc_reinit - reinitialise the tty ldisc * @tty: tty to reinit + * @ldisc: line discipline to reinitialize * - * Switch the tty back to N_TTY line discipline and leave the - * ldisc state closed + * Switch the tty to a line discipline and leave the ldisc + * state closed */ -static void tty_ldisc_reinit(struct tty_struct *tty) +static void tty_ldisc_reinit(struct tty_struct *tty, int ldisc) { struct tty_ldisc *ld; @@ -721,10 +722,10 @@ static void tty_ldisc_reinit(struct tty_struct *tty) /* * Switch the line discipline back */ - ld = tty_ldisc_get(N_TTY); + ld = tty_ldisc_get(ldisc); BUG_ON(IS_ERR(ld)); tty_ldisc_assign(tty, ld); - tty_set_termios_ldisc(tty, N_TTY); + tty_set_termios_ldisc(tty, ldisc); } /** @@ -745,6 +746,8 @@ static void tty_ldisc_reinit(struct tty_struct *tty) void tty_ldisc_hangup(struct tty_struct *tty) { struct tty_ldisc *ld; + int reset = tty->driver->flags & TTY_DRIVER_RESET_TERMIOS; + int err = 0; /* * FIXME! What are the locking issues here? This may me overdoing @@ -772,25 +775,32 @@ void tty_ldisc_hangup(struct tty_struct *tty) wake_up_interruptible_poll(&tty->read_wait, POLLIN); /* * Shutdown the current line discipline, and reset it to - * N_TTY. + * N_TTY if need be. + * + * Avoid racing set_ldisc or tty_ldisc_release */ - if (tty->driver->flags & TTY_DRIVER_RESET_TERMIOS) { - /* Avoid racing set_ldisc or tty_ldisc_release */ - mutex_lock(&tty->ldisc_mutex); - tty_ldisc_halt(tty); - if (tty->ldisc) { /* Not yet closed */ - /* Switch back to N_TTY */ - tty_ldisc_reinit(tty); - /* At this point we have a closed ldisc and we want to - reopen it. We could defer this to the next open but - it means auditing a lot of other paths so this is - a FIXME */ + mutex_lock(&tty->ldisc_mutex); + tty_ldisc_halt(tty); + /* At this point we have a closed ldisc and we want to + reopen it. We could defer this to the next open but + it means auditing a lot of other paths so this is + a FIXME */ + if (tty->ldisc) { /* Not yet closed */ + if (reset == 0) { + tty_ldisc_reinit(tty, tty->termios->c_line); + err = tty_ldisc_open(tty, tty->ldisc); + } + /* If the re-open fails or we reset then go to N_TTY. The + N_TTY open cannot fail */ + if (reset || err) { + tty_ldisc_reinit(tty, N_TTY); WARN_ON(tty_ldisc_open(tty, tty->ldisc)); - tty_ldisc_enable(tty); } - mutex_unlock(&tty->ldisc_mutex); - tty_reset_termios(tty); + tty_ldisc_enable(tty); } + mutex_unlock(&tty->ldisc_mutex); + if (reset) + tty_reset_termios(tty); } /** diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index a035ae39a35..f404ccfc9c2 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -1,18 +1,6 @@ -/*D:300 - * The Guest console driver - * - * Writing console drivers is one of the few remaining Dark Arts in Linux. - * Fortunately for us, the path of virtual consoles has been well-trodden by - * the PowerPC folks, who wrote "hvc_console.c" to generically support any - * virtual console. We use that infrastructure which only requires us to write - * the basic put_chars and get_chars functions and call the right register - * functions. - :*/ - -/*M:002 The console can be flooded: while the Guest is processing input the - * Host can send more. Buffering in the Host could alleviate this, but it is a - * difficult problem in general. :*/ -/* Copyright (C) 2006, 2007 Rusty Russell, IBM Corporation +/* + * Copyright (C) 2006, 2007, 2009 Rusty Russell, IBM Corporation + * Copyright (C) 2009, 2010 Red Hat, Inc. * * 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 @@ -28,142 +16,694 @@ * 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/cdev.h> +#include <linux/debugfs.h> +#include <linux/device.h> #include <linux/err.h> +#include <linux/fs.h> #include <linux/init.h> +#include <linux/list.h> +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/spinlock.h> #include <linux/virtio.h> #include <linux/virtio_console.h> +#include <linux/wait.h> +#include <linux/workqueue.h> #include "hvc_console.h" -/*D:340 These represent our input and output console queues, and the virtio - * operations for them. */ -static struct virtqueue *in_vq, *out_vq; -static struct virtio_device *vdev; +/* + * This is a global struct for storing common data for all the devices + * this driver handles. + * + * Mainly, it has a linked list for all the consoles in one place so + * that callbacks from hvc for get_chars(), put_chars() work properly + * across multiple devices and multiple ports per device. + */ +struct ports_driver_data { + /* Used for registering chardevs */ + struct class *class; + + /* Used for exporting per-port information to debugfs */ + struct dentry *debugfs_dir; + + /* Number of devices this driver is handling */ + unsigned int index; + + /* + * This is used to keep track of the number of hvc consoles + * spawned by this driver. This number is given as the first + * argument to hvc_alloc(). To correctly map an initial + * console spawned via hvc_instantiate to the console being + * hooked up via hvc_alloc, we need to pass the same vtermno. + * + * We also just assume the first console being initialised was + * the first one that got used as the initial console. + */ + unsigned int next_vtermno; + + /* All the console devices handled by this driver */ + struct list_head consoles; +}; +static struct ports_driver_data pdrvdata; + +DEFINE_SPINLOCK(pdrvdata_lock); + +/* This struct holds information that's relevant only for console ports */ +struct console { + /* We'll place all consoles in a list in the pdrvdata struct */ + struct list_head list; + + /* The hvc device associated with this console port */ + struct hvc_struct *hvc; + + /* + * This number identifies the number that we used to register + * with hvc in hvc_instantiate() and hvc_alloc(); this is the + * number passed on by the hvc callbacks to us to + * differentiate between the other console ports handled by + * this driver + */ + u32 vtermno; +}; + +struct port_buffer { + char *buf; + + /* size of the buffer in *buf above */ + size_t size; + + /* used length of the buffer */ + size_t len; + /* offset in the buf from which to consume data */ + size_t offset; +}; + +/* + * This is a per-device struct that stores data common to all the + * ports for that device (vdev->priv). + */ +struct ports_device { + /* + * Workqueue handlers where we process deferred work after + * notification + */ + struct work_struct control_work; + struct work_struct config_work; + + struct list_head ports; + + /* To protect the list of ports */ + spinlock_t ports_lock; + + /* To protect the vq operations for the control channel */ + spinlock_t cvq_lock; + + /* The current config space is stored here */ + struct virtio_console_config config; + + /* The virtio device we're associated with */ + struct virtio_device *vdev; + + /* + * A couple of virtqueues for the control channel: one for + * guest->host transfers, one for host->guest transfers + */ + struct virtqueue *c_ivq, *c_ovq; + + /* Array of per-port IO virtqueues */ + struct virtqueue **in_vqs, **out_vqs; + + /* Used for numbering devices for sysfs and debugfs */ + unsigned int drv_index; + + /* Major number for this device. Ports will be created as minors. */ + int chr_major; +}; + +/* This struct holds the per-port data */ +struct port { + /* Next port in the list, head is in the ports_device */ + struct list_head list; + + /* Pointer to the parent virtio_console device */ + struct ports_device *portdev; + + /* The current buffer from which data has to be fed to readers */ + struct port_buffer *inbuf; + + /* + * To protect the operations on the in_vq associated with this + * port. Has to be a spinlock because it can be called from + * interrupt context (get_char()). + */ + spinlock_t inbuf_lock; + + /* The IO vqs for this port */ + struct virtqueue *in_vq, *out_vq; + + /* File in the debugfs directory that exposes this port's information */ + struct dentry *debugfs_file; + + /* + * The entries in this struct will be valid if this port is + * hooked up to an hvc console + */ + struct console cons; + + /* Each port associates with a separate char device */ + struct cdev cdev; + struct device *dev; + + /* A waitqueue for poll() or blocking read operations */ + wait_queue_head_t waitqueue; + + /* The 'name' of the port that we expose via sysfs properties */ + char *name; + + /* The 'id' to identify the port with the Host */ + u32 id; + + /* Is the host device open */ + bool host_connected; + + /* We should allow only one process to open a port */ + bool guest_connected; +}; + +/* This is the very early arch-specified put chars function. */ +static int (*early_put_chars)(u32, const char *, int); + +static struct port *find_port_by_vtermno(u32 vtermno) +{ + struct port *port; + struct console *cons; + unsigned long flags; + + spin_lock_irqsave(&pdrvdata_lock, flags); + list_for_each_entry(cons, &pdrvdata.consoles, list) { + if (cons->vtermno == vtermno) { + port = container_of(cons, struct port, cons); + goto out; + } + } + port = NULL; +out: + spin_unlock_irqrestore(&pdrvdata_lock, flags); + return port; +} + +static struct port *find_port_by_id(struct ports_device *portdev, u32 id) +{ + struct port *port; + unsigned long flags; + + spin_lock_irqsave(&portdev->ports_lock, flags); + list_for_each_entry(port, &portdev->ports, list) + if (port->id == id) + goto out; + port = NULL; +out: + spin_unlock_irqrestore(&portdev->ports_lock, flags); + + return port; +} + +static struct port *find_port_by_vq(struct ports_device *portdev, + struct virtqueue *vq) +{ + struct port *port; + unsigned long flags; + + spin_lock_irqsave(&portdev->ports_lock, flags); + list_for_each_entry(port, &portdev->ports, list) + if (port->in_vq == vq || port->out_vq == vq) + goto out; + port = NULL; +out: + spin_unlock_irqrestore(&portdev->ports_lock, flags); + return port; +} + +static bool is_console_port(struct port *port) +{ + if (port->cons.hvc) + return true; + return false; +} + +static inline bool use_multiport(struct ports_device *portdev) +{ + /* + * This condition can be true when put_chars is called from + * early_init + */ + if (!portdev->vdev) + return 0; + return portdev->vdev->features[0] & (1 << VIRTIO_CONSOLE_F_MULTIPORT); +} -/* This is our input buffer, and how much data is left in it. */ -static unsigned int in_len; -static char *in, *inbuf; +static void free_buf(struct port_buffer *buf) +{ + kfree(buf->buf); + kfree(buf); +} + +static struct port_buffer *alloc_buf(size_t buf_size) +{ + struct port_buffer *buf; -/* The operations for our console. */ -static struct hv_ops virtio_cons; + buf = kmalloc(sizeof(*buf), GFP_KERNEL); + if (!buf) + goto fail; + buf->buf = kzalloc(buf_size, GFP_KERNEL); + if (!buf->buf) + goto free_buf; + buf->len = 0; + buf->offset = 0; + buf->size = buf_size; + return buf; + +free_buf: + kfree(buf); +fail: + return NULL; +} + +/* Callers should take appropriate locks */ +static void *get_inbuf(struct port *port) +{ + struct port_buffer *buf; + struct virtqueue *vq; + unsigned int len; -/* The hvc device */ -static struct hvc_struct *hvc; + vq = port->in_vq; + buf = vq->vq_ops->get_buf(vq, &len); + if (buf) { + buf->len = len; + buf->offset = 0; + } + return buf; +} -/*D:310 The put_chars() callback is pretty straightforward. +/* + * Create a scatter-gather list representing our input buffer and put + * it in the queue. * - * We turn the characters into a scatter-gather list, add it to the output - * queue and then kick the Host. Then we sit here waiting for it to finish: - * inefficient in theory, but in practice implementations will do it - * immediately (lguest's Launcher does). */ -static int put_chars(u32 vtermno, const char *buf, int count) + * Callers should take appropriate locks. + */ +static int add_inbuf(struct virtqueue *vq, struct port_buffer *buf) { struct scatterlist sg[1]; + int ret; + + sg_init_one(sg, buf->buf, buf->size); + + ret = vq->vq_ops->add_buf(vq, sg, 0, 1, buf); + vq->vq_ops->kick(vq); + return ret; +} + +/* Discard any unread data this port has. Callers lockers. */ +static void discard_port_data(struct port *port) +{ + struct port_buffer *buf; + struct virtqueue *vq; unsigned int len; + int ret; - /* This is a convenient routine to initialize a single-elem sg list */ - sg_init_one(sg, buf, count); + vq = port->in_vq; + if (port->inbuf) + buf = port->inbuf; + else + buf = vq->vq_ops->get_buf(vq, &len); - /* add_buf wants a token to identify this buffer: we hand it any - * non-NULL pointer, since there's only ever one buffer. */ - if (out_vq->vq_ops->add_buf(out_vq, sg, 1, 0, (void *)1) >= 0) { - /* Tell Host to go! */ - out_vq->vq_ops->kick(out_vq); - /* Chill out until it's done with the buffer. */ - while (!out_vq->vq_ops->get_buf(out_vq, &len)) - cpu_relax(); + ret = 0; + while (buf) { + if (add_inbuf(vq, buf) < 0) { + ret++; + free_buf(buf); + } + buf = vq->vq_ops->get_buf(vq, &len); } + port->inbuf = NULL; + if (ret) + dev_warn(port->dev, "Errors adding %d buffers back to vq\n", + ret); +} - /* We're expected to return the amount of data we wrote: all of it. */ - return count; +static bool port_has_data(struct port *port) +{ + unsigned long flags; + bool ret; + + spin_lock_irqsave(&port->inbuf_lock, flags); + if (port->inbuf) { + ret = true; + goto out; + } + port->inbuf = get_inbuf(port); + if (port->inbuf) { + ret = true; + goto out; + } + ret = false; +out: + spin_unlock_irqrestore(&port->inbuf_lock, flags); + return ret; } -/* Create a scatter-gather list representing our input buffer and put it in the - * queue. */ -static void add_inbuf(void) +static ssize_t send_control_msg(struct port *port, unsigned int event, + unsigned int value) { struct scatterlist sg[1]; - sg_init_one(sg, inbuf, PAGE_SIZE); + struct virtio_console_control cpkt; + struct virtqueue *vq; + unsigned int len; + + if (!use_multiport(port->portdev)) + return 0; + + cpkt.id = port->id; + cpkt.event = event; + cpkt.value = value; - /* We should always be able to add one buffer to an empty queue. */ - if (in_vq->vq_ops->add_buf(in_vq, sg, 0, 1, inbuf) < 0) - BUG(); - in_vq->vq_ops->kick(in_vq); + vq = port->portdev->c_ovq; + + sg_init_one(sg, &cpkt, sizeof(cpkt)); + if (vq->vq_ops->add_buf(vq, sg, 1, 0, &cpkt) >= 0) { + vq->vq_ops->kick(vq); + while (!vq->vq_ops->get_buf(vq, &len)) + cpu_relax(); + } + return 0; } -/*D:350 get_chars() is the callback from the hvc_console infrastructure when - * an interrupt is received. - * - * Most of the code deals with the fact that the hvc_console() infrastructure - * only asks us for 16 bytes at a time. We keep in_offset and in_used fields - * for partially-filled buffers. */ -static int get_chars(u32 vtermno, char *buf, int count) +static ssize_t send_buf(struct port *port, void *in_buf, size_t in_count) { - /* If we don't have an input queue yet, we can't get input. */ - BUG_ON(!in_vq); + struct scatterlist sg[1]; + struct virtqueue *out_vq; + ssize_t ret; + unsigned int len; + + out_vq = port->out_vq; + + sg_init_one(sg, in_buf, in_count); + ret = out_vq->vq_ops->add_buf(out_vq, sg, 1, 0, in_buf); + + /* Tell Host to go! */ + out_vq->vq_ops->kick(out_vq); + + if (ret < 0) { + len = 0; + goto fail; + } + + /* + * Wait till the host acknowledges it pushed out the data we + * sent. Also ensure we return to userspace the number of + * bytes that were successfully consumed by the host. + */ + while (!out_vq->vq_ops->get_buf(out_vq, &len)) + cpu_relax(); +fail: + /* We're expected to return the amount of data we wrote */ + return len; +} + +/* + * Give out the data that's requested from the buffer that we have + * queued up. + */ +static ssize_t fill_readbuf(struct port *port, char *out_buf, size_t out_count, + bool to_user) +{ + struct port_buffer *buf; + unsigned long flags; + + if (!out_count || !port_has_data(port)) + return 0; + + buf = port->inbuf; + out_count = min(out_count, buf->len - buf->offset); + + if (to_user) { + ssize_t ret; + + ret = copy_to_user(out_buf, buf->buf + buf->offset, out_count); + if (ret) + return -EFAULT; + } else { + memcpy(out_buf, buf->buf + buf->offset, out_count); + } + + buf->offset += out_count; + + if (buf->offset == buf->len) { + /* + * We're done using all the data in this buffer. + * Re-queue so that the Host can send us more data. + */ + spin_lock_irqsave(&port->inbuf_lock, flags); + port->inbuf = NULL; + + if (add_inbuf(port->in_vq, buf) < 0) + dev_warn(port->dev, "failed add_buf\n"); + + spin_unlock_irqrestore(&port->inbuf_lock, flags); + } + /* Return the number of bytes actually copied */ + return out_count; +} + +/* The condition that must be true for polling to end */ +static bool wait_is_over(struct port *port) +{ + return port_has_data(port) || !port->host_connected; +} + +static ssize_t port_fops_read(struct file *filp, char __user *ubuf, + size_t count, loff_t *offp) +{ + struct port *port; + ssize_t ret; + + port = filp->private_data; - /* No buffer? Try to get one. */ - if (!in_len) { - in = in_vq->vq_ops->get_buf(in_vq, &in_len); - if (!in) + if (!port_has_data(port)) { + /* + * If nothing's connected on the host just return 0 in + * case of list_empty; this tells the userspace app + * that there's no connection + */ + if (!port->host_connected) return 0; + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + + ret = wait_event_interruptible(port->waitqueue, + wait_is_over(port)); + if (ret < 0) + return ret; + } + /* + * We could've received a disconnection message while we were + * waiting for more data. + * + * This check is not clubbed in the if() statement above as we + * might receive some data as well as the host could get + * disconnected after we got woken up from our wait. So we + * really want to give off whatever data we have and only then + * check for host_connected. + */ + if (!port_has_data(port) && !port->host_connected) + return 0; + + return fill_readbuf(port, ubuf, count, true); +} + +static ssize_t port_fops_write(struct file *filp, const char __user *ubuf, + size_t count, loff_t *offp) +{ + struct port *port; + char *buf; + ssize_t ret; + + port = filp->private_data; + + count = min((size_t)(32 * 1024), count); + + buf = kmalloc(count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = copy_from_user(buf, ubuf, count); + if (ret) { + ret = -EFAULT; + goto free_buf; } - /* You want more than we have to give? Well, try wanting less! */ - if (in_len < count) - count = in_len; + ret = send_buf(port, buf, count); +free_buf: + kfree(buf); + return ret; +} + +static unsigned int port_fops_poll(struct file *filp, poll_table *wait) +{ + struct port *port; + unsigned int ret; + + port = filp->private_data; + poll_wait(filp, &port->waitqueue, wait); + + ret = 0; + if (port->inbuf) + ret |= POLLIN | POLLRDNORM; + if (port->host_connected) + ret |= POLLOUT; + if (!port->host_connected) + ret |= POLLHUP; + + return ret; +} + +static int port_fops_release(struct inode *inode, struct file *filp) +{ + struct port *port; + + port = filp->private_data; + + /* Notify host of port being closed */ + send_control_msg(port, VIRTIO_CONSOLE_PORT_OPEN, 0); + + spin_lock_irq(&port->inbuf_lock); + port->guest_connected = false; + + discard_port_data(port); + + spin_unlock_irq(&port->inbuf_lock); + + return 0; +} + +static int port_fops_open(struct inode *inode, struct file *filp) +{ + struct cdev *cdev = inode->i_cdev; + struct port *port; + + port = container_of(cdev, struct port, cdev); + filp->private_data = port; + + /* + * Don't allow opening of console port devices -- that's done + * via /dev/hvc + */ + if (is_console_port(port)) + return -ENXIO; - /* Copy across to their buffer and increment offset. */ - memcpy(buf, in, count); - in += count; - in_len -= count; + /* Allow only one process to open a particular port at a time */ + spin_lock_irq(&port->inbuf_lock); + if (port->guest_connected) { + spin_unlock_irq(&port->inbuf_lock); + return -EMFILE; + } + + port->guest_connected = true; + spin_unlock_irq(&port->inbuf_lock); - /* Finished? Re-register buffer so Host will use it again. */ - if (in_len == 0) - add_inbuf(); + /* Notify host of port being opened */ + send_control_msg(filp->private_data, VIRTIO_CONSOLE_PORT_OPEN, 1); - return count; + return 0; } -/*:*/ -/*D:320 Console drivers are initialized very early so boot messages can go out, - * so we do things slightly differently from the generic virtio initialization - * of the net and block drivers. +/* + * The file operations that we support: programs in the guest can open + * a console device, read from it, write to it, poll for data and + * close it. The devices are at + * /dev/vport<device number>p<port number> + */ +static const struct file_operations port_fops = { + .owner = THIS_MODULE, + .open = port_fops_open, + .read = port_fops_read, + .write = port_fops_write, + .poll = port_fops_poll, + .release = port_fops_release, +}; + +/* + * The put_chars() callback is pretty straightforward. * - * At this stage, the console is output-only. It's too early to set up a - * virtqueue, so we let the drivers do some boutique early-output thing. */ -int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int)) + * We turn the characters into a scatter-gather list, add it to the + * output queue and then kick the Host. Then we sit here waiting for + * it to finish: inefficient in theory, but in practice + * implementations will do it immediately (lguest's Launcher does). + */ +static int put_chars(u32 vtermno, const char *buf, int count) { - virtio_cons.put_chars = put_chars; - return hvc_instantiate(0, 0, &virtio_cons); + struct port *port; + + port = find_port_by_vtermno(vtermno); + if (!port) + return 0; + + if (unlikely(early_put_chars)) + return early_put_chars(vtermno, buf, count); + + return send_buf(port, (void *)buf, count); } /* - * virtio console configuration. This supports: - * - console resize + * get_chars() is the callback from the hvc_console infrastructure + * when an interrupt is received. + * + * We call out to fill_readbuf that gets us the required data from the + * buffers that are queued up. */ -static void virtcons_apply_config(struct virtio_device *dev) +static int get_chars(u32 vtermno, char *buf, int count) { + struct port *port; + + port = find_port_by_vtermno(vtermno); + if (!port) + return 0; + + /* If we don't have an input queue yet, we can't get input. */ + BUG_ON(!port->in_vq); + + return fill_readbuf(port, buf, count, false); +} + +static void resize_console(struct port *port) +{ + struct virtio_device *vdev; struct winsize ws; - if (virtio_has_feature(dev, VIRTIO_CONSOLE_F_SIZE)) { - dev->config->get(dev, - offsetof(struct virtio_console_config, cols), - &ws.ws_col, sizeof(u16)); - dev->config->get(dev, - offsetof(struct virtio_console_config, rows), - &ws.ws_row, sizeof(u16)); - hvc_resize(hvc, ws); + vdev = port->portdev->vdev; + if (virtio_has_feature(vdev, VIRTIO_CONSOLE_F_SIZE)) { + vdev->config->get(vdev, + offsetof(struct virtio_console_config, cols), + &ws.ws_col, sizeof(u16)); + vdev->config->get(vdev, + offsetof(struct virtio_console_config, rows), + &ws.ws_row, sizeof(u16)); + hvc_resize(port->cons.hvc, ws); } } -/* - * we support only one console, the hvc struct is a global var - * We set the configuration at this point, since we now have a tty - */ +/* We set the configuration at this point, since we now have a tty */ static int notifier_add_vio(struct hvc_struct *hp, int data) { + struct port *port; + + port = find_port_by_vtermno(hp->vtermno); + if (!port) + return -EINVAL; + hp->irq_requested = 1; - virtcons_apply_config(vdev); + resize_console(port); return 0; } @@ -173,79 +713,800 @@ static void notifier_del_vio(struct hvc_struct *hp, int data) hp->irq_requested = 0; } -static void hvc_handle_input(struct virtqueue *vq) +/* The operations for console ports. */ +static const struct hv_ops hv_ops = { + .get_chars = get_chars, + .put_chars = put_chars, + .notifier_add = notifier_add_vio, + .notifier_del = notifier_del_vio, + .notifier_hangup = notifier_del_vio, +}; + +/* + * Console drivers are initialized very early so boot messages can go + * out, so we do things slightly differently from the generic virtio + * initialization of the net and block drivers. + * + * At this stage, the console is output-only. It's too early to set + * up a virtqueue, so we let the drivers do some boutique early-output + * thing. + */ +int __init virtio_cons_early_init(int (*put_chars)(u32, const char *, int)) +{ + early_put_chars = put_chars; + return hvc_instantiate(0, 0, &hv_ops); +} + +int init_port_console(struct port *port) +{ + int ret; + + /* + * The Host's telling us this port is a console port. Hook it + * up with an hvc console. + * + * To set up and manage our virtual console, we call + * hvc_alloc(). + * + * The first argument of hvc_alloc() is the virtual console + * number. The second argument is the parameter for the + * notification mechanism (like irq number). We currently + * leave this as zero, virtqueues have implicit notifications. + * + * The third argument is a "struct hv_ops" containing the + * put_chars() get_chars(), notifier_add() and notifier_del() + * pointers. The final argument is the output buffer size: we + * can do any size, so we put PAGE_SIZE here. + */ + port->cons.vtermno = pdrvdata.next_vtermno; + + port->cons.hvc = hvc_alloc(port->cons.vtermno, 0, &hv_ops, PAGE_SIZE); + if (IS_ERR(port->cons.hvc)) { + ret = PTR_ERR(port->cons.hvc); + dev_err(port->dev, + "error %d allocating hvc for port\n", ret); + port->cons.hvc = NULL; + return ret; + } + spin_lock_irq(&pdrvdata_lock); + pdrvdata.next_vtermno++; + list_add_tail(&port->cons.list, &pdrvdata.consoles); + spin_unlock_irq(&pdrvdata_lock); + port->guest_connected = true; + + /* Notify host of port being opened */ + send_control_msg(port, VIRTIO_CONSOLE_PORT_OPEN, 1); + + return 0; +} + +static ssize_t show_port_name(struct device *dev, + struct device_attribute *attr, char *buffer) +{ + struct port *port; + + port = dev_get_drvdata(dev); + + return sprintf(buffer, "%s\n", port->name); +} + +static DEVICE_ATTR(name, S_IRUGO, show_port_name, NULL); + +static struct attribute *port_sysfs_entries[] = { + &dev_attr_name.attr, + NULL +}; + +static struct attribute_group port_attribute_group = { + .name = NULL, /* put in device directory */ + .attrs = port_sysfs_entries, +}; + +static int debugfs_open(struct inode *inode, struct file *filp) +{ + filp->private_data = inode->i_private; + return 0; +} + +static ssize_t debugfs_read(struct file *filp, char __user *ubuf, + size_t count, loff_t *offp) +{ + struct port *port; + char *buf; + ssize_t ret, out_offset, out_count; + + out_count = 1024; + buf = kmalloc(out_count, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + port = filp->private_data; + out_offset = 0; + out_offset += snprintf(buf + out_offset, out_count, + "name: %s\n", port->name ? port->name : ""); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "guest_connected: %d\n", port->guest_connected); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "host_connected: %d\n", port->host_connected); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "is_console: %s\n", + is_console_port(port) ? "yes" : "no"); + out_offset += snprintf(buf + out_offset, out_count - out_offset, + "console_vtermno: %u\n", port->cons.vtermno); + + ret = simple_read_from_buffer(ubuf, count, offp, buf, out_offset); + kfree(buf); + return ret; +} + +static const struct file_operations port_debugfs_ops = { + .owner = THIS_MODULE, + .open = debugfs_open, + .read = debugfs_read, +}; + +/* Remove all port-specific data. */ +static int remove_port(struct port *port) +{ + struct port_buffer *buf; + + spin_lock_irq(&port->portdev->ports_lock); + list_del(&port->list); + spin_unlock_irq(&port->portdev->ports_lock); + + if (is_console_port(port)) { + spin_lock_irq(&pdrvdata_lock); + list_del(&port->cons.list); + spin_unlock_irq(&pdrvdata_lock); + hvc_remove(port->cons.hvc); + } + if (port->guest_connected) + send_control_msg(port, VIRTIO_CONSOLE_PORT_OPEN, 0); + + sysfs_remove_group(&port->dev->kobj, &port_attribute_group); + device_destroy(pdrvdata.class, port->dev->devt); + cdev_del(&port->cdev); + + /* Remove unused data this port might have received. */ + discard_port_data(port); + + /* Remove buffers we queued up for the Host to send us data in. */ + while ((buf = port->in_vq->vq_ops->detach_unused_buf(port->in_vq))) + free_buf(buf); + + kfree(port->name); + + debugfs_remove(port->debugfs_file); + + kfree(port); + return 0; +} + +/* Any private messages that the Host and Guest want to share */ +static void handle_control_message(struct ports_device *portdev, + struct port_buffer *buf) +{ + struct virtio_console_control *cpkt; + struct port *port; + size_t name_size; + int err; + + cpkt = (struct virtio_console_control *)(buf->buf + buf->offset); + + port = find_port_by_id(portdev, cpkt->id); + if (!port) { + /* No valid header at start of buffer. Drop it. */ + dev_dbg(&portdev->vdev->dev, + "Invalid index %u in control packet\n", cpkt->id); + return; + } + + switch (cpkt->event) { + case VIRTIO_CONSOLE_CONSOLE_PORT: + if (!cpkt->value) + break; + if (is_console_port(port)) + break; + + init_port_console(port); + /* + * Could remove the port here in case init fails - but + * have to notify the host first. + */ + break; + case VIRTIO_CONSOLE_RESIZE: + if (!is_console_port(port)) + break; + port->cons.hvc->irq_requested = 1; + resize_console(port); + break; + case VIRTIO_CONSOLE_PORT_OPEN: + port->host_connected = cpkt->value; + wake_up_interruptible(&port->waitqueue); + break; + case VIRTIO_CONSOLE_PORT_NAME: + /* + * Skip the size of the header and the cpkt to get the size + * of the name that was sent + */ + name_size = buf->len - buf->offset - sizeof(*cpkt) + 1; + + port->name = kmalloc(name_size, GFP_KERNEL); + if (!port->name) { + dev_err(port->dev, + "Not enough space to store port name\n"); + break; + } + strncpy(port->name, buf->buf + buf->offset + sizeof(*cpkt), + name_size - 1); + port->name[name_size - 1] = 0; + + /* + * Since we only have one sysfs attribute, 'name', + * create it only if we have a name for the port. + */ + err = sysfs_create_group(&port->dev->kobj, + &port_attribute_group); + if (err) + dev_err(port->dev, + "Error %d creating sysfs device attributes\n", + err); + + break; + case VIRTIO_CONSOLE_PORT_REMOVE: + /* + * Hot unplug the port. We don't decrement nr_ports + * since we don't want to deal with extra complexities + * of using the lowest-available port id: We can just + * pick up the nr_ports number as the id and not have + * userspace send it to us. This helps us in two + * ways: + * + * - We don't need to have a 'port_id' field in the + * config space when a port is hot-added. This is a + * good thing as we might queue up multiple hotplug + * requests issued in our workqueue. + * + * - Another way to deal with this would have been to + * use a bitmap of the active ports and select the + * lowest non-active port from that map. That + * bloats the already tight config space and we + * would end up artificially limiting the + * max. number of ports to sizeof(bitmap). Right + * now we can support 2^32 ports (as the port id is + * stored in a u32 type). + * + */ + remove_port(port); + break; + } +} + +static void control_work_handler(struct work_struct *work) +{ + struct ports_device *portdev; + struct virtqueue *vq; + struct port_buffer *buf; + unsigned int len; + + portdev = container_of(work, struct ports_device, control_work); + vq = portdev->c_ivq; + + spin_lock(&portdev->cvq_lock); + while ((buf = vq->vq_ops->get_buf(vq, &len))) { + spin_unlock(&portdev->cvq_lock); + + buf->len = len; + buf->offset = 0; + + handle_control_message(portdev, buf); + + spin_lock(&portdev->cvq_lock); + if (add_inbuf(portdev->c_ivq, buf) < 0) { + dev_warn(&portdev->vdev->dev, + "Error adding buffer to queue\n"); + free_buf(buf); + } + } + spin_unlock(&portdev->cvq_lock); +} + +static void in_intr(struct virtqueue *vq) { - if (hvc_poll(hvc)) + struct port *port; + unsigned long flags; + + port = find_port_by_vq(vq->vdev->priv, vq); + if (!port) + return; + + spin_lock_irqsave(&port->inbuf_lock, flags); + if (!port->inbuf) + port->inbuf = get_inbuf(port); + + /* + * Don't queue up data when port is closed. This condition + * can be reached when a console port is not yet connected (no + * tty is spawned) and the host sends out data to console + * ports. For generic serial ports, the host won't + * (shouldn't) send data till the guest is connected. + */ + if (!port->guest_connected) + discard_port_data(port); + + spin_unlock_irqrestore(&port->inbuf_lock, flags); + + wake_up_interruptible(&port->waitqueue); + + if (is_console_port(port) && hvc_poll(port->cons.hvc)) hvc_kick(); } -/*D:370 Once we're further in boot, we get probed like any other virtio device. - * At this stage we set up the output virtqueue. - * - * To set up and manage our virtual console, we call hvc_alloc(). Since we - * never remove the console device we never need this pointer again. +static void control_intr(struct virtqueue *vq) +{ + struct ports_device *portdev; + + portdev = vq->vdev->priv; + schedule_work(&portdev->control_work); +} + +static void config_intr(struct virtio_device *vdev) +{ + struct ports_device *portdev; + + portdev = vdev->priv; + if (use_multiport(portdev)) { + /* Handle port hot-add */ + schedule_work(&portdev->config_work); + } + /* + * We'll use this way of resizing only for legacy support. + * For newer userspace (VIRTIO_CONSOLE_F_MULTPORT+), use + * control messages to indicate console size changes so that + * it can be done per-port + */ + resize_console(find_port_by_id(portdev, 0)); +} + +static unsigned int fill_queue(struct virtqueue *vq, spinlock_t *lock) +{ + struct port_buffer *buf; + unsigned int nr_added_bufs; + int ret; + + nr_added_bufs = 0; + do { + buf = alloc_buf(PAGE_SIZE); + if (!buf) + break; + + spin_lock_irq(lock); + ret = add_inbuf(vq, buf); + if (ret < 0) { + spin_unlock_irq(lock); + free_buf(buf); + break; + } + nr_added_bufs++; + spin_unlock_irq(lock); + } while (ret > 0); + + return nr_added_bufs; +} + +static int add_port(struct ports_device *portdev, u32 id) +{ + char debugfs_name[16]; + struct port *port; + struct port_buffer *buf; + dev_t devt; + unsigned int nr_added_bufs; + int err; + + port = kmalloc(sizeof(*port), GFP_KERNEL); + if (!port) { + err = -ENOMEM; + goto fail; + } + + port->portdev = portdev; + port->id = id; + + port->name = NULL; + port->inbuf = NULL; + port->cons.hvc = NULL; + + port->host_connected = port->guest_connected = false; + + port->in_vq = portdev->in_vqs[port->id]; + port->out_vq = portdev->out_vqs[port->id]; + + cdev_init(&port->cdev, &port_fops); + + devt = MKDEV(portdev->chr_major, id); + err = cdev_add(&port->cdev, devt, 1); + if (err < 0) { + dev_err(&port->portdev->vdev->dev, + "Error %d adding cdev for port %u\n", err, id); + goto free_port; + } + port->dev = device_create(pdrvdata.class, &port->portdev->vdev->dev, + devt, port, "vport%up%u", + port->portdev->drv_index, id); + if (IS_ERR(port->dev)) { + err = PTR_ERR(port->dev); + dev_err(&port->portdev->vdev->dev, + "Error %d creating device for port %u\n", + err, id); + goto free_cdev; + } + + spin_lock_init(&port->inbuf_lock); + init_waitqueue_head(&port->waitqueue); + + /* Fill the in_vq with buffers so the host can send us data. */ + nr_added_bufs = fill_queue(port->in_vq, &port->inbuf_lock); + if (!nr_added_bufs) { + dev_err(port->dev, "Error allocating inbufs\n"); + err = -ENOMEM; + goto free_device; + } + + /* + * If we're not using multiport support, this has to be a console port + */ + if (!use_multiport(port->portdev)) { + err = init_port_console(port); + if (err) + goto free_inbufs; + } + + spin_lock_irq(&portdev->ports_lock); + list_add_tail(&port->list, &port->portdev->ports); + spin_unlock_irq(&portdev->ports_lock); + + /* + * Tell the Host we're set so that it can send us various + * configuration parameters for this port (eg, port name, + * caching, whether this is a console port, etc.) + */ + send_control_msg(port, VIRTIO_CONSOLE_PORT_READY, 1); + + if (pdrvdata.debugfs_dir) { + /* + * Finally, create the debugfs file that we can use to + * inspect a port's state at any time + */ + sprintf(debugfs_name, "vport%up%u", + port->portdev->drv_index, id); + port->debugfs_file = debugfs_create_file(debugfs_name, 0444, + pdrvdata.debugfs_dir, + port, + &port_debugfs_ops); + } + return 0; + +free_inbufs: + while ((buf = port->in_vq->vq_ops->detach_unused_buf(port->in_vq))) + free_buf(buf); +free_device: + device_destroy(pdrvdata.class, port->dev->devt); +free_cdev: + cdev_del(&port->cdev); +free_port: + kfree(port); +fail: + return err; +} + +/* + * The workhandler for config-space updates. * - * Finally we put our input buffer in the input queue, ready to receive. */ -static int __devinit virtcons_probe(struct virtio_device *dev) + * This is called when ports are hot-added. + */ +static void config_work_handler(struct work_struct *work) +{ + struct virtio_console_config virtconconf; + struct ports_device *portdev; + struct virtio_device *vdev; + int err; + + portdev = container_of(work, struct ports_device, config_work); + + vdev = portdev->vdev; + vdev->config->get(vdev, + offsetof(struct virtio_console_config, nr_ports), + &virtconconf.nr_ports, + sizeof(virtconconf.nr_ports)); + + if (portdev->config.nr_ports == virtconconf.nr_ports) { + /* + * Port 0 got hot-added. Since we already did all the + * other initialisation for it, just tell the Host + * that the port is ready if we find the port. In + * case the port was hot-removed earlier, we call + * add_port to add the port. + */ + struct port *port; + + port = find_port_by_id(portdev, 0); + if (!port) + add_port(portdev, 0); + else + send_control_msg(port, VIRTIO_CONSOLE_PORT_READY, 1); + return; + } + if (virtconconf.nr_ports > portdev->config.max_nr_ports) { + dev_warn(&vdev->dev, + "More ports specified (%u) than allowed (%u)", + portdev->config.nr_ports + 1, + portdev->config.max_nr_ports); + return; + } + if (virtconconf.nr_ports < portdev->config.nr_ports) + return; + + /* Hot-add ports */ + while (virtconconf.nr_ports - portdev->config.nr_ports) { + err = add_port(portdev, portdev->config.nr_ports); + if (err) + break; + portdev->config.nr_ports++; + } +} + +static int init_vqs(struct ports_device *portdev) { - vq_callback_t *callbacks[] = { hvc_handle_input, NULL}; - const char *names[] = { "input", "output" }; - struct virtqueue *vqs[2]; + vq_callback_t **io_callbacks; + char **io_names; + struct virtqueue **vqs; + u32 i, j, nr_ports, nr_queues; int err; - vdev = dev; + nr_ports = portdev->config.max_nr_ports; + nr_queues = use_multiport(portdev) ? (nr_ports + 1) * 2 : 2; - /* This is the scratch page we use to receive console input */ - inbuf = kmalloc(PAGE_SIZE, GFP_KERNEL); - if (!inbuf) { + vqs = kmalloc(nr_queues * sizeof(struct virtqueue *), GFP_KERNEL); + if (!vqs) { err = -ENOMEM; goto fail; } + io_callbacks = kmalloc(nr_queues * sizeof(vq_callback_t *), GFP_KERNEL); + if (!io_callbacks) { + err = -ENOMEM; + goto free_vqs; + } + io_names = kmalloc(nr_queues * sizeof(char *), GFP_KERNEL); + if (!io_names) { + err = -ENOMEM; + goto free_callbacks; + } + portdev->in_vqs = kmalloc(nr_ports * sizeof(struct virtqueue *), + GFP_KERNEL); + if (!portdev->in_vqs) { + err = -ENOMEM; + goto free_names; + } + portdev->out_vqs = kmalloc(nr_ports * sizeof(struct virtqueue *), + GFP_KERNEL); + if (!portdev->out_vqs) { + err = -ENOMEM; + goto free_invqs; + } + + /* + * For backward compat (newer host but older guest), the host + * spawns a console port first and also inits the vqs for port + * 0 before others. + */ + j = 0; + io_callbacks[j] = in_intr; + io_callbacks[j + 1] = NULL; + io_names[j] = "input"; + io_names[j + 1] = "output"; + j += 2; + if (use_multiport(portdev)) { + io_callbacks[j] = control_intr; + io_callbacks[j + 1] = NULL; + io_names[j] = "control-i"; + io_names[j + 1] = "control-o"; + + for (i = 1; i < nr_ports; i++) { + j += 2; + io_callbacks[j] = in_intr; + io_callbacks[j + 1] = NULL; + io_names[j] = "input"; + io_names[j + 1] = "output"; + } + } /* Find the queues. */ - /* FIXME: This is why we want to wean off hvc: we do nothing - * when input comes in. */ - err = vdev->config->find_vqs(vdev, 2, vqs, callbacks, names); + err = portdev->vdev->config->find_vqs(portdev->vdev, nr_queues, vqs, + io_callbacks, + (const char **)io_names); if (err) + goto free_outvqs; + + j = 0; + portdev->in_vqs[0] = vqs[0]; + portdev->out_vqs[0] = vqs[1]; + j += 2; + if (use_multiport(portdev)) { + portdev->c_ivq = vqs[j]; + portdev->c_ovq = vqs[j + 1]; + + for (i = 1; i < nr_ports; i++) { + j += 2; + portdev->in_vqs[i] = vqs[j]; + portdev->out_vqs[i] = vqs[j + 1]; + } + } + kfree(io_callbacks); + kfree(io_names); + kfree(vqs); + + return 0; + +free_names: + kfree(io_names); +free_callbacks: + kfree(io_callbacks); +free_outvqs: + kfree(portdev->out_vqs); +free_invqs: + kfree(portdev->in_vqs); +free_vqs: + kfree(vqs); +fail: + return err; +} + +static const struct file_operations portdev_fops = { + .owner = THIS_MODULE, +}; + +/* + * Once we're further in boot, we get probed like any other virtio + * device. + * + * If the host also supports multiple console ports, we check the + * config space to see how many ports the host has spawned. We + * initialize each port found. + */ +static int __devinit virtcons_probe(struct virtio_device *vdev) +{ + struct ports_device *portdev; + u32 i; + int err; + bool multiport; + + portdev = kmalloc(sizeof(*portdev), GFP_KERNEL); + if (!portdev) { + err = -ENOMEM; + goto fail; + } + + /* Attach this portdev to this virtio_device, and vice-versa. */ + portdev->vdev = vdev; + vdev->priv = portdev; + + spin_lock_irq(&pdrvdata_lock); + portdev->drv_index = pdrvdata.index++; + spin_unlock_irq(&pdrvdata_lock); + + portdev->chr_major = register_chrdev(0, "virtio-portsdev", + &portdev_fops); + if (portdev->chr_major < 0) { + dev_err(&vdev->dev, + "Error %d registering chrdev for device %u\n", + portdev->chr_major, portdev->drv_index); + err = portdev->chr_major; goto free; + } - in_vq = vqs[0]; - out_vq = vqs[1]; + multiport = false; + portdev->config.nr_ports = 1; + portdev->config.max_nr_ports = 1; + if (virtio_has_feature(vdev, VIRTIO_CONSOLE_F_MULTIPORT)) { + multiport = true; + vdev->features[0] |= 1 << VIRTIO_CONSOLE_F_MULTIPORT; - /* Start using the new console output. */ - virtio_cons.get_chars = get_chars; - virtio_cons.put_chars = put_chars; - virtio_cons.notifier_add = notifier_add_vio; - virtio_cons.notifier_del = notifier_del_vio; - virtio_cons.notifier_hangup = notifier_del_vio; - - /* The first argument of hvc_alloc() is the virtual console number, so - * we use zero. The second argument is the parameter for the - * notification mechanism (like irq number). We currently leave this - * as zero, virtqueues have implicit notifications. - * - * The third argument is a "struct hv_ops" containing the put_chars() - * get_chars(), notifier_add() and notifier_del() pointers. - * The final argument is the output buffer size: we can do any size, - * so we put PAGE_SIZE here. */ - hvc = hvc_alloc(0, 0, &virtio_cons, PAGE_SIZE); - if (IS_ERR(hvc)) { - err = PTR_ERR(hvc); - goto free_vqs; + vdev->config->get(vdev, offsetof(struct virtio_console_config, + nr_ports), + &portdev->config.nr_ports, + sizeof(portdev->config.nr_ports)); + vdev->config->get(vdev, offsetof(struct virtio_console_config, + max_nr_ports), + &portdev->config.max_nr_ports, + sizeof(portdev->config.max_nr_ports)); + if (portdev->config.nr_ports > portdev->config.max_nr_ports) { + dev_warn(&vdev->dev, + "More ports (%u) specified than allowed (%u). Will init %u ports.", + portdev->config.nr_ports, + portdev->config.max_nr_ports, + portdev->config.max_nr_ports); + + portdev->config.nr_ports = portdev->config.max_nr_ports; + } + } + + /* Let the Host know we support multiple ports.*/ + vdev->config->finalize_features(vdev); + + err = init_vqs(portdev); + if (err < 0) { + dev_err(&vdev->dev, "Error %d initializing vqs\n", err); + goto free_chrdev; + } + + spin_lock_init(&portdev->ports_lock); + INIT_LIST_HEAD(&portdev->ports); + + if (multiport) { + unsigned int nr_added_bufs; + + spin_lock_init(&portdev->cvq_lock); + INIT_WORK(&portdev->control_work, &control_work_handler); + INIT_WORK(&portdev->config_work, &config_work_handler); + + nr_added_bufs = fill_queue(portdev->c_ivq, &portdev->cvq_lock); + if (!nr_added_bufs) { + dev_err(&vdev->dev, + "Error allocating buffers for control queue\n"); + err = -ENOMEM; + goto free_vqs; + } } - /* Register the input buffer the first time. */ - add_inbuf(); + for (i = 0; i < portdev->config.nr_ports; i++) + add_port(portdev, i); + + /* Start using the new console output. */ + early_put_chars = NULL; return 0; free_vqs: vdev->config->del_vqs(vdev); + kfree(portdev->in_vqs); + kfree(portdev->out_vqs); +free_chrdev: + unregister_chrdev(portdev->chr_major, "virtio-portsdev"); free: - kfree(inbuf); + kfree(portdev); fail: return err; } +static void virtcons_remove(struct virtio_device *vdev) +{ + struct ports_device *portdev; + struct port *port, *port2; + struct port_buffer *buf; + unsigned int len; + + portdev = vdev->priv; + + cancel_work_sync(&portdev->control_work); + cancel_work_sync(&portdev->config_work); + + list_for_each_entry_safe(port, port2, &portdev->ports, list) + remove_port(port); + + unregister_chrdev(portdev->chr_major, "virtio-portsdev"); + + while ((buf = portdev->c_ivq->vq_ops->get_buf(portdev->c_ivq, &len))) + free_buf(buf); + + while ((buf = portdev->c_ivq->vq_ops->detach_unused_buf(portdev->c_ivq))) + free_buf(buf); + + vdev->config->del_vqs(vdev); + kfree(portdev->in_vqs); + kfree(portdev->out_vqs); + + kfree(portdev); +} + static struct virtio_device_id id_table[] = { { VIRTIO_ID_CONSOLE, VIRTIO_DEV_ANY_ID }, { 0 }, @@ -253,6 +1514,7 @@ static struct virtio_device_id id_table[] = { static unsigned int features[] = { VIRTIO_CONSOLE_F_SIZE, + VIRTIO_CONSOLE_F_MULTIPORT, }; static struct virtio_driver virtio_console = { @@ -262,14 +1524,41 @@ static struct virtio_driver virtio_console = { .driver.owner = THIS_MODULE, .id_table = id_table, .probe = virtcons_probe, - .config_changed = virtcons_apply_config, + .remove = virtcons_remove, + .config_changed = config_intr, }; static int __init init(void) { + int err; + + pdrvdata.class = class_create(THIS_MODULE, "virtio-ports"); + if (IS_ERR(pdrvdata.class)) { + err = PTR_ERR(pdrvdata.class); + pr_err("Error %d creating virtio-ports class\n", err); + return err; + } + + pdrvdata.debugfs_dir = debugfs_create_dir("virtio-ports", NULL); + if (!pdrvdata.debugfs_dir) { + pr_warning("Error %ld creating debugfs dir for virtio-ports\n", + PTR_ERR(pdrvdata.debugfs_dir)); + } + INIT_LIST_HEAD(&pdrvdata.consoles); + return register_virtio_driver(&virtio_console); } + +static void __exit fini(void) +{ + unregister_virtio_driver(&virtio_console); + + class_destroy(pdrvdata.class); + if (pdrvdata.debugfs_dir) + debugfs_remove_recursive(pdrvdata.debugfs_dir); +} module_init(init); +module_exit(fini); MODULE_DEVICE_TABLE(virtio, id_table); MODULE_DESCRIPTION("Virtio console driver"); diff --git a/drivers/char/vme_scc.c b/drivers/char/vme_scc.c index 994e1a58b98..8b24729fec8 100644 --- a/drivers/char/vme_scc.c +++ b/drivers/char/vme_scc.c @@ -136,7 +136,7 @@ static const struct tty_port_operations scc_port_ops = { * vme_scc_init() and support functions *---------------------------------------------------------------------------*/ -static int scc_init_drivers(void) +static int __init scc_init_drivers(void) { int error; @@ -172,7 +172,7 @@ static int scc_init_drivers(void) /* ports[] array is indexed by line no (i.e. [0] for ttyS0, [1] for ttyS1). */ -static void scc_init_portstructs(void) +static void __init scc_init_portstructs(void) { struct scc_port *port; int i; @@ -195,7 +195,7 @@ static void scc_init_portstructs(void) #ifdef CONFIG_MVME147_SCC -static int mvme147_scc_init(void) +static int __init mvme147_scc_init(void) { struct scc_port *port; int error; @@ -298,7 +298,7 @@ fail: #ifdef CONFIG_MVME162_SCC -static int mvme162_scc_init(void) +static int __init mvme162_scc_init(void) { struct scc_port *port; int error; @@ -404,7 +404,7 @@ fail: #ifdef CONFIG_BVME6000_SCC -static int bvme6000_scc_init(void) +static int __init bvme6000_scc_init(void) { struct scc_port *port; int error; @@ -503,7 +503,7 @@ fail_free_b_rx: #endif -static int vme_scc_init(void) +static int __init vme_scc_init(void) { int res = -ENODEV; diff --git a/drivers/char/vt_ioctl.c b/drivers/char/vt_ioctl.c index 6aa10284104..87778dcf872 100644 --- a/drivers/char/vt_ioctl.c +++ b/drivers/char/vt_ioctl.c @@ -888,7 +888,7 @@ int vt_ioctl(struct tty_struct *tty, struct file * file, ret = -EFAULT; goto out; } - if (tmp.mode != VT_AUTO && tmp.mode != VT_PROCESS) { + if (tmp.mode != VT_AUTO && tmp.mode != VT_PROCESS && tmp.mode != VT_PROCESS_AUTO) { ret = -EINVAL; goto out; } @@ -1622,7 +1622,7 @@ static void complete_change_console(struct vc_data *vc) * telling it that it has acquired. Also check if it has died and * clean up (similar to logic employed in change_console()) */ - if (vc->vt_mode.mode == VT_PROCESS) { + if (vc->vt_mode.mode == VT_PROCESS || vc->vt_mode.mode == VT_PROCESS_AUTO) { /* * Send the signal as privileged - kill_pid() will * tell us if the process has gone or something else @@ -1682,7 +1682,7 @@ void change_console(struct vc_data *new_vc) * vt to auto control. */ vc = vc_cons[fg_console].d; - if (vc->vt_mode.mode == VT_PROCESS) { + if (vc->vt_mode.mode == VT_PROCESS || vc->vt_mode.mode == VT_PROCESS_AUTO) { /* * Send the signal as privileged - kill_pid() will * tell us if the process has gone or something else @@ -1693,27 +1693,28 @@ void change_console(struct vc_data *new_vc) */ vc->vt_newvt = new_vc->vc_num; if (kill_pid(vc->vt_pid, vc->vt_mode.relsig, 1) == 0) { + if(vc->vt_mode.mode == VT_PROCESS) + /* + * It worked. Mark the vt to switch to and + * return. The process needs to send us a + * VT_RELDISP ioctl to complete the switch. + */ + return; + } else { /* - * It worked. Mark the vt to switch to and - * return. The process needs to send us a - * VT_RELDISP ioctl to complete the switch. + * The controlling process has died, so we revert back to + * normal operation. In this case, we'll also change back + * to KD_TEXT mode. I'm not sure if this is strictly correct + * but it saves the agony when the X server dies and the screen + * remains blanked due to KD_GRAPHICS! It would be nice to do + * this outside of VT_PROCESS but there is no single process + * to account for and tracking tty count may be undesirable. */ - return; + reset_vc(vc); } /* - * The controlling process has died, so we revert back to - * normal operation. In this case, we'll also change back - * to KD_TEXT mode. I'm not sure if this is strictly correct - * but it saves the agony when the X server dies and the screen - * remains blanked due to KD_GRAPHICS! It would be nice to do - * this outside of VT_PROCESS but there is no single process - * to account for and tracking tty count may be undesirable. - */ - reset_vc(vc); - - /* - * Fall through to normal (VT_AUTO) handling of the switch... + * Fall through to normal (VT_AUTO and VT_PROCESS_AUTO) handling of the switch... */ } |