diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-03-22 19:59:19 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-03-22 20:16:14 -0700 |
commit | d4c6fa73fe984e504d52f3d6bba291fd76fe49f7 (patch) | |
tree | 47842ddebb2a48cc1513b36fba18835678e2b94e /drivers/tty/hvc | |
parent | aab008db8063364dc3c8ccf4981c21124866b395 (diff) | |
parent | 4bc25af79ec54b79266148f8c1b84bb1e7ff2621 (diff) |
Merge tag 'stable/for-linus-3.4-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/konrad/xen
Pull xen updates from Konrad Rzeszutek Wilk:
"which has three neat features:
- PV multiconsole support, so that there can be hvc1, hvc2, etc; This
can be used in HVM and in PV mode.
- P-state and C-state power management driver that uploads said power
management data to the hypervisor. It also inhibits cpufreq
scaling drivers to load so that only the hypervisor can make power
management decisions - fixing a weird perf bug.
There is one thing in the Kconfig that you won't like: "default y
if (X86_ACPI_CPUFREQ = y || X86_POWERNOW_K8 = y)" (note, that it
all depends on CONFIG_XEN which depends on CONFIG_PARAVIRT which by
default is off). I've a fix to convert that boolean expression
into "default m" which I am going to post after the cpufreq git
pull - as the two patches to make this work depend on a fix in Dave
Jones's tree.
- Function Level Reset (FLR) support in the Xen PCI backend.
Fixes:
- Kconfig dependencies for Xen PV keyboard and video
- Compile warnings and constify fixes
- Change over to use percpu_xxx instead of this_cpu_xxx"
Fix up trivial conflicts in drivers/tty/hvc/hvc_xen.c due to changes to
a removed commit.
* tag 'stable/for-linus-3.4-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/konrad/xen:
xen kconfig: relax INPUT_XEN_KBDDEV_FRONTEND deps
xen/acpi-processor: C and P-state driver that uploads said data to hypervisor.
xen: constify all instances of "struct attribute_group"
xen/xenbus: ignore console/0
hvc_xen: introduce HVC_XEN_FRONTEND
hvc_xen: implement multiconsole support
hvc_xen: support PV on HVM consoles
xenbus: don't free other end details too early
xen/enlighten: Expose MWAIT and MWAIT_LEAF if hypervisor OKs it.
xen/setup/pm/acpi: Remove the call to boot_option_idle_override.
xenbus: address compiler warnings
xen: use this_cpu_xxx replace percpu_xxx funcs
xen/pciback: Support pci_reset_function, aka FLR or D3 support.
pci: Introduce __pci_reset_function_locked to be used when holding device_lock.
xen: Utilize the restore_msi_irqs hook.
Diffstat (limited to 'drivers/tty/hvc')
-rw-r--r-- | drivers/tty/hvc/Kconfig | 8 | ||||
-rw-r--r-- | drivers/tty/hvc/hvc_xen.c | 465 |
2 files changed, 430 insertions, 43 deletions
diff --git a/drivers/tty/hvc/Kconfig b/drivers/tty/hvc/Kconfig index 48cb8d3d175..0282a83f51f 100644 --- a/drivers/tty/hvc/Kconfig +++ b/drivers/tty/hvc/Kconfig @@ -66,6 +66,14 @@ config HVC_XEN help Xen virtual console device driver +config HVC_XEN_FRONTEND + bool "Xen Hypervisor Multiple Consoles support" + depends on HVC_XEN + select XEN_XENBUS_FRONTEND + default y + help + Xen driver for secondary virtual consoles + config HVC_UDBG bool "udbg based fake hypervisor console" depends on PPC && EXPERIMENTAL diff --git a/drivers/tty/hvc/hvc_xen.c b/drivers/tty/hvc/hvc_xen.c index a1b0a75c3ea..83d5c88e716 100644 --- a/drivers/tty/hvc/hvc_xen.c +++ b/drivers/tty/hvc/hvc_xen.c @@ -23,44 +23,74 @@ #include <linux/err.h> #include <linux/init.h> #include <linux/types.h> +#include <linux/list.h> +#include <asm/io.h> #include <asm/xen/hypervisor.h> #include <xen/xen.h> +#include <xen/interface/xen.h> +#include <xen/hvm.h> +#include <xen/grant_table.h> #include <xen/page.h> #include <xen/events.h> #include <xen/interface/io/console.h> #include <xen/hvc-console.h> +#include <xen/xenbus.h> #include "hvc_console.h" #define HVC_COOKIE 0x58656e /* "Xen" in hex */ -static struct hvc_struct *hvc; -static int xencons_irq; +struct xencons_info { + struct list_head list; + struct xenbus_device *xbdev; + struct xencons_interface *intf; + unsigned int evtchn; + struct hvc_struct *hvc; + int irq; + int vtermno; + grant_ref_t gntref; +}; + +static LIST_HEAD(xenconsoles); +static DEFINE_SPINLOCK(xencons_lock); /* ------------------------------------------------------------------ */ -static unsigned long console_pfn = ~0ul; +static struct xencons_info *vtermno_to_xencons(int vtermno) +{ + struct xencons_info *entry, *n, *ret = NULL; + + if (list_empty(&xenconsoles)) + return NULL; + + list_for_each_entry_safe(entry, n, &xenconsoles, list) { + if (entry->vtermno == vtermno) { + ret = entry; + break; + } + } + + return ret; +} -static inline struct xencons_interface *xencons_interface(void) +static inline int xenbus_devid_to_vtermno(int devid) { - if (console_pfn == ~0ul) - return mfn_to_virt(xen_start_info->console.domU.mfn); - else - return __va(console_pfn << PAGE_SHIFT); + return devid + HVC_COOKIE; } -static inline void notify_daemon(void) +static inline void notify_daemon(struct xencons_info *cons) { /* Use evtchn: this is called early, before irq is set up. */ - notify_remote_via_evtchn(xen_start_info->console.domU.evtchn); + notify_remote_via_evtchn(cons->evtchn); } -static int __write_console(const char *data, int len) +static int __write_console(struct xencons_info *xencons, + const char *data, int len) { - struct xencons_interface *intf = xencons_interface(); XENCONS_RING_IDX cons, prod; + struct xencons_interface *intf = xencons->intf; int sent = 0; cons = intf->out_cons; @@ -75,13 +105,16 @@ static int __write_console(const char *data, int len) intf->out_prod = prod; if (sent) - notify_daemon(); + notify_daemon(xencons); return sent; } static int domU_write_console(uint32_t vtermno, const char *data, int len) { int ret = len; + struct xencons_info *cons = vtermno_to_xencons(vtermno); + if (cons == NULL) + return -EINVAL; /* * Make sure the whole buffer is emitted, polling if @@ -90,7 +123,7 @@ static int domU_write_console(uint32_t vtermno, const char *data, int len) * kernel is crippled. */ while (len) { - int sent = __write_console(data, len); + int sent = __write_console(cons, data, len); data += sent; len -= sent; @@ -104,9 +137,13 @@ static int domU_write_console(uint32_t vtermno, const char *data, int len) static int domU_read_console(uint32_t vtermno, char *buf, int len) { - struct xencons_interface *intf = xencons_interface(); + struct xencons_interface *intf; XENCONS_RING_IDX cons, prod; int recv = 0; + struct xencons_info *xencons = vtermno_to_xencons(vtermno); + if (xencons == NULL) + return -EINVAL; + intf = xencons->intf; cons = intf->in_cons; prod = intf->in_prod; @@ -119,7 +156,7 @@ static int domU_read_console(uint32_t vtermno, char *buf, int len) mb(); /* read ring before consuming */ intf->in_cons = cons; - notify_daemon(); + notify_daemon(xencons); return recv; } @@ -157,68 +194,407 @@ static struct hv_ops dom0_hvc_ops = { .notifier_hangup = notifier_hangup_irq, }; -static int __init xen_hvc_init(void) +static int xen_hvm_console_init(void) +{ + int r; + uint64_t v = 0; + unsigned long mfn; + struct xencons_info *info; + + if (!xen_hvm_domain()) + return -ENODEV; + + info = vtermno_to_xencons(HVC_COOKIE); + if (!info) { + info = kzalloc(sizeof(struct xencons_info), GFP_KERNEL | __GFP_ZERO); + if (!info) + return -ENOMEM; + } + + /* already configured */ + if (info->intf != NULL) + return 0; + + r = hvm_get_parameter(HVM_PARAM_CONSOLE_EVTCHN, &v); + if (r < 0) { + kfree(info); + return -ENODEV; + } + info->evtchn = v; + hvm_get_parameter(HVM_PARAM_CONSOLE_PFN, &v); + if (r < 0) { + kfree(info); + return -ENODEV; + } + mfn = v; + info->intf = ioremap(mfn << PAGE_SHIFT, PAGE_SIZE); + if (info->intf == NULL) { + kfree(info); + return -ENODEV; + } + info->vtermno = HVC_COOKIE; + + spin_lock(&xencons_lock); + list_add_tail(&info->list, &xenconsoles); + spin_unlock(&xencons_lock); + + return 0; +} + +static int xen_pv_console_init(void) { - struct hvc_struct *hp; - struct hv_ops *ops; + struct xencons_info *info; if (!xen_pv_domain()) return -ENODEV; - if (xen_initial_domain()) { - ops = &dom0_hvc_ops; - xencons_irq = bind_virq_to_irq(VIRQ_CONSOLE, 0); - } else { - if (!xen_start_info->console.domU.evtchn) - return -ENODEV; + if (!xen_start_info->console.domU.evtchn) + return -ENODEV; - ops = &domU_hvc_ops; - xencons_irq = bind_evtchn_to_irq(xen_start_info->console.domU.evtchn); + info = vtermno_to_xencons(HVC_COOKIE); + if (!info) { + info = kzalloc(sizeof(struct xencons_info), GFP_KERNEL | __GFP_ZERO); + if (!info) + return -ENOMEM; } - if (xencons_irq < 0) - xencons_irq = 0; - else - irq_set_noprobe(xencons_irq); - hp = hvc_alloc(HVC_COOKIE, xencons_irq, ops, 256); - if (IS_ERR(hp)) - return PTR_ERR(hp); + /* already configured */ + if (info->intf != NULL) + return 0; + + info->evtchn = xen_start_info->console.domU.evtchn; + info->intf = mfn_to_virt(xen_start_info->console.domU.mfn); + info->vtermno = HVC_COOKIE; + + spin_lock(&xencons_lock); + list_add_tail(&info->list, &xenconsoles); + spin_unlock(&xencons_lock); + + return 0; +} + +static int xen_initial_domain_console_init(void) +{ + struct xencons_info *info; + + if (!xen_initial_domain()) + return -ENODEV; + + info = vtermno_to_xencons(HVC_COOKIE); + if (!info) { + info = kzalloc(sizeof(struct xencons_info), GFP_KERNEL | __GFP_ZERO); + if (!info) + return -ENOMEM; + } - hvc = hp; + info->irq = bind_virq_to_irq(VIRQ_CONSOLE, 0); + info->vtermno = HVC_COOKIE; - console_pfn = mfn_to_pfn(xen_start_info->console.domU.mfn); + spin_lock(&xencons_lock); + list_add_tail(&info->list, &xenconsoles); + spin_unlock(&xencons_lock); return 0; } void xen_console_resume(void) { - if (xencons_irq) - rebind_evtchn_irq(xen_start_info->console.domU.evtchn, xencons_irq); + struct xencons_info *info = vtermno_to_xencons(HVC_COOKIE); + if (info != NULL && info->irq) + rebind_evtchn_irq(info->evtchn, info->irq); +} + +static void xencons_disconnect_backend(struct xencons_info *info) +{ + if (info->irq > 0) + unbind_from_irqhandler(info->irq, NULL); + info->irq = 0; + if (info->evtchn > 0) + xenbus_free_evtchn(info->xbdev, info->evtchn); + info->evtchn = 0; + if (info->gntref > 0) + gnttab_free_grant_references(info->gntref); + info->gntref = 0; + if (info->hvc != NULL) + hvc_remove(info->hvc); + info->hvc = NULL; +} + +static void xencons_free(struct xencons_info *info) +{ + free_page((unsigned long)info->intf); + info->intf = NULL; + info->vtermno = 0; + kfree(info); +} + +static int xen_console_remove(struct xencons_info *info) +{ + xencons_disconnect_backend(info); + spin_lock(&xencons_lock); + list_del(&info->list); + spin_unlock(&xencons_lock); + if (info->xbdev != NULL) + xencons_free(info); + else { + if (xen_hvm_domain()) + iounmap(info->intf); + kfree(info); + } + return 0; +} + +#ifdef CONFIG_HVC_XEN_FRONTEND +static struct xenbus_driver xencons_driver; + +static int xencons_remove(struct xenbus_device *dev) +{ + return xen_console_remove(dev_get_drvdata(&dev->dev)); +} + +static int xencons_connect_backend(struct xenbus_device *dev, + struct xencons_info *info) +{ + int ret, evtchn, devid, ref, irq; + struct xenbus_transaction xbt; + grant_ref_t gref_head; + unsigned long mfn; + + ret = xenbus_alloc_evtchn(dev, &evtchn); + if (ret) + return ret; + info->evtchn = evtchn; + irq = bind_evtchn_to_irq(evtchn); + if (irq < 0) + return irq; + info->irq = irq; + devid = dev->nodename[strlen(dev->nodename) - 1] - '0'; + info->hvc = hvc_alloc(xenbus_devid_to_vtermno(devid), + irq, &domU_hvc_ops, 256); + if (IS_ERR(info->hvc)) + return PTR_ERR(info->hvc); + if (xen_pv_domain()) + mfn = virt_to_mfn(info->intf); + else + mfn = __pa(info->intf) >> PAGE_SHIFT; + ret = gnttab_alloc_grant_references(1, &gref_head); + if (ret < 0) + return ret; + info->gntref = gref_head; + ref = gnttab_claim_grant_reference(&gref_head); + if (ref < 0) + return ref; + gnttab_grant_foreign_access_ref(ref, info->xbdev->otherend_id, + mfn, 0); + + again: + ret = xenbus_transaction_start(&xbt); + if (ret) { + xenbus_dev_fatal(dev, ret, "starting transaction"); + return ret; + } + ret = xenbus_printf(xbt, dev->nodename, "ring-ref", "%d", ref); + if (ret) + goto error_xenbus; + ret = xenbus_printf(xbt, dev->nodename, "port", "%u", + evtchn); + if (ret) + goto error_xenbus; + ret = xenbus_printf(xbt, dev->nodename, "type", "ioemu"); + if (ret) + goto error_xenbus; + ret = xenbus_transaction_end(xbt, 0); + if (ret) { + if (ret == -EAGAIN) + goto again; + xenbus_dev_fatal(dev, ret, "completing transaction"); + return ret; + } + + xenbus_switch_state(dev, XenbusStateInitialised); + return 0; + + error_xenbus: + xenbus_transaction_end(xbt, 1); + xenbus_dev_fatal(dev, ret, "writing xenstore"); + return ret; +} + +static int __devinit xencons_probe(struct xenbus_device *dev, + const struct xenbus_device_id *id) +{ + int ret, devid; + struct xencons_info *info; + + devid = dev->nodename[strlen(dev->nodename) - 1] - '0'; + if (devid == 0) + return -ENODEV; + + info = kzalloc(sizeof(struct xencons_info), GFP_KERNEL | __GFP_ZERO); + if (!info) + goto error_nomem; + dev_set_drvdata(&dev->dev, info); + info->xbdev = dev; + info->vtermno = xenbus_devid_to_vtermno(devid); + info->intf = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO); + if (!info->intf) + goto error_nomem; + + ret = xencons_connect_backend(dev, info); + if (ret < 0) + goto error; + spin_lock(&xencons_lock); + list_add_tail(&info->list, &xenconsoles); + spin_unlock(&xencons_lock); + + return 0; + + error_nomem: + ret = -ENOMEM; + xenbus_dev_fatal(dev, ret, "allocating device memory"); + error: + xencons_disconnect_backend(info); + xencons_free(info); + return ret; +} + +static int xencons_resume(struct xenbus_device *dev) +{ + struct xencons_info *info = dev_get_drvdata(&dev->dev); + + xencons_disconnect_backend(info); + memset(info->intf, 0, PAGE_SIZE); + return xencons_connect_backend(dev, info); +} + +static void xencons_backend_changed(struct xenbus_device *dev, + enum xenbus_state backend_state) +{ + switch (backend_state) { + case XenbusStateReconfiguring: + case XenbusStateReconfigured: + case XenbusStateInitialising: + case XenbusStateInitialised: + case XenbusStateUnknown: + case XenbusStateClosed: + break; + + case XenbusStateInitWait: + break; + + case XenbusStateConnected: + xenbus_switch_state(dev, XenbusStateConnected); + break; + + case XenbusStateClosing: + xenbus_frontend_closed(dev); + break; + } +} + +static const struct xenbus_device_id xencons_ids[] = { + { "console" }, + { "" } +}; + + +static DEFINE_XENBUS_DRIVER(xencons, "xenconsole", + .probe = xencons_probe, + .remove = xencons_remove, + .resume = xencons_resume, + .otherend_changed = xencons_backend_changed, +); +#endif /* CONFIG_HVC_XEN_FRONTEND */ + +static int __init xen_hvc_init(void) +{ + int r; + struct xencons_info *info; + const struct hv_ops *ops; + + if (!xen_domain()) + return -ENODEV; + + if (xen_initial_domain()) { + ops = &dom0_hvc_ops; + r = xen_initial_domain_console_init(); + if (r < 0) + return r; + info = vtermno_to_xencons(HVC_COOKIE); + } else { + ops = &domU_hvc_ops; + if (xen_hvm_domain()) + r = xen_hvm_console_init(); + else + r = xen_pv_console_init(); + if (r < 0) + return r; + + info = vtermno_to_xencons(HVC_COOKIE); + info->irq = bind_evtchn_to_irq(info->evtchn); + } + if (info->irq < 0) + info->irq = 0; /* NO_IRQ */ + else + irq_set_noprobe(info->irq); + + info->hvc = hvc_alloc(HVC_COOKIE, info->irq, ops, 256); + if (IS_ERR(info->hvc)) { + r = PTR_ERR(info->hvc); + spin_lock(&xencons_lock); + list_del(&info->list); + spin_unlock(&xencons_lock); + if (info->irq) + unbind_from_irqhandler(info->irq, NULL); + kfree(info); + return r; + } + + r = 0; +#ifdef CONFIG_HVC_XEN_FRONTEND + r = xenbus_register_frontend(&xencons_driver); +#endif + return r; } static void __exit xen_hvc_fini(void) { - if (hvc) - hvc_remove(hvc); + struct xencons_info *entry, *next; + + if (list_empty(&xenconsoles)) + return; + + list_for_each_entry_safe(entry, next, &xenconsoles, list) { + xen_console_remove(entry); + } } static int xen_cons_init(void) { - struct hv_ops *ops; + const struct hv_ops *ops; - if (!xen_pv_domain()) + if (!xen_domain()) return 0; if (xen_initial_domain()) ops = &dom0_hvc_ops; - else + else { + int r; ops = &domU_hvc_ops; + if (xen_hvm_domain()) + r = xen_hvm_console_init(); + else + r = xen_pv_console_init(); + if (r < 0) + return r; + } + hvc_instantiate(HVC_COOKIE, 0, ops); return 0; } + module_init(xen_hvc_init); module_exit(xen_hvc_fini); console_initcall(xen_cons_init); @@ -230,6 +606,9 @@ static void xenboot_write_console(struct console *console, const char *string, unsigned int linelen, off = 0; const char *pos; + if (!xen_pv_domain()) + return; + dom0_write_console(0, string, len); if (xen_initial_domain()) |