diff options
Diffstat (limited to 'drivers/gpu/vga')
| -rw-r--r-- | drivers/gpu/vga/Kconfig | 3 | ||||
| -rw-r--r-- | drivers/gpu/vga/vga_switcheroo.c | 497 | ||||
| -rw-r--r-- | drivers/gpu/vga/vgaarb.c | 219 |
3 files changed, 505 insertions, 214 deletions
diff --git a/drivers/gpu/vga/Kconfig b/drivers/gpu/vga/Kconfig index 96c83a9a76b..29437eabe09 100644 --- a/drivers/gpu/vga/Kconfig +++ b/drivers/gpu/vga/Kconfig @@ -1,7 +1,7 @@ config VGA_ARB bool "VGA Arbitration" if EXPERT default y - depends on PCI + depends on (PCI && !S390) help Some "legacy" VGA devices implemented on PCI typically have the same hard-decoded addresses as they did on ISA. When multiple PCI devices @@ -21,6 +21,7 @@ config VGA_SWITCHEROO bool "Laptop Hybrid Graphics - GPU switching support" depends on X86 depends on ACPI + select VGA_ARB help Many laptops released in 2008/9/10 have two GPUs with a multiplexer to switch between them. This adds support for dynamic switching when diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index e01cacba685..6866448083b 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -18,7 +18,6 @@ */ #include <linux/module.h> -#include <linux/dmi.h> #include <linux/seq_file.h> #include <linux/uaccess.h> #include <linux/fs.h> @@ -26,17 +25,21 @@ #include <linux/fb.h> #include <linux/pci.h> +#include <linux/console.h> #include <linux/vga_switcheroo.h> +#include <linux/pm_runtime.h> + +#include <linux/vgaarb.h> struct vga_switcheroo_client { struct pci_dev *pdev; struct fb_info *fb_info; int pwr_state; - void (*set_gpu_state)(struct pci_dev *pdev, enum vga_switcheroo_state); - void (*reprobe)(struct pci_dev *pdev); - bool (*can_switch)(struct pci_dev *pdev); + const struct vga_switcheroo_client_ops *ops; int id; bool active; + bool driver_power_control; + struct list_head list; }; static DEFINE_MUTEX(vgasr_mutex); @@ -51,16 +54,52 @@ struct vgasr_priv { struct dentry *switch_file; int registered_clients; - struct vga_switcheroo_client clients[VGA_SWITCHEROO_MAX_CLIENTS]; + struct list_head clients; struct vga_switcheroo_handler *handler; }; +#define ID_BIT_AUDIO 0x100 +#define client_is_audio(c) ((c)->id & ID_BIT_AUDIO) +#define client_is_vga(c) ((c)->id == -1 || !client_is_audio(c)) +#define client_id(c) ((c)->id & ~ID_BIT_AUDIO) + static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv); static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv); /* only one switcheroo per system */ -static struct vgasr_priv vgasr_priv; +static struct vgasr_priv vgasr_priv = { + .clients = LIST_HEAD_INIT(vgasr_priv.clients), +}; + +static bool vga_switcheroo_ready(void) +{ + /* we're ready if we get two clients + handler */ + return !vgasr_priv.active && + vgasr_priv.registered_clients == 2 && vgasr_priv.handler; +} + +static void vga_switcheroo_enable(void) +{ + int ret; + struct vga_switcheroo_client *client; + + /* call the handler to init */ + if (vgasr_priv.handler->init) + vgasr_priv.handler->init(); + + list_for_each_entry(client, &vgasr_priv.clients, list) { + if (client->id != -1) + continue; + ret = vgasr_priv.handler->get_client_id(client->pdev); + if (ret < 0) + return; + + client->id = ret; + } + vga_switcheroo_debugfs_init(&vgasr_priv); + vgasr_priv.active = true; +} int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler) { @@ -71,6 +110,10 @@ int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler) } vgasr_priv.handler = handler; + if (vga_switcheroo_ready()) { + printk(KERN_INFO "vga_switcheroo: enabled\n"); + vga_switcheroo_enable(); + } mutex_unlock(&vgasr_mutex); return 0; } @@ -80,78 +123,122 @@ void vga_switcheroo_unregister_handler(void) { mutex_lock(&vgasr_mutex); vgasr_priv.handler = NULL; + if (vgasr_priv.active) { + pr_info("vga_switcheroo: disabled\n"); + vga_switcheroo_debugfs_fini(&vgasr_priv); + vgasr_priv.active = false; + } mutex_unlock(&vgasr_mutex); } EXPORT_SYMBOL(vga_switcheroo_unregister_handler); -static void vga_switcheroo_enable(void) +static int register_client(struct pci_dev *pdev, + const struct vga_switcheroo_client_ops *ops, + int id, bool active, bool driver_power_control) { - int i; - int ret; - /* call the handler to init */ - vgasr_priv.handler->init(); - - for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { - ret = vgasr_priv.handler->get_client_id(vgasr_priv.clients[i].pdev); - if (ret < 0) - return; + struct vga_switcheroo_client *client; - vgasr_priv.clients[i].id = ret; - } - vga_switcheroo_debugfs_init(&vgasr_priv); - vgasr_priv.active = true; -} + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return -ENOMEM; -int vga_switcheroo_register_client(struct pci_dev *pdev, - void (*set_gpu_state)(struct pci_dev *pdev, enum vga_switcheroo_state), - void (*reprobe)(struct pci_dev *pdev), - bool (*can_switch)(struct pci_dev *pdev)) -{ - int index; + client->pwr_state = VGA_SWITCHEROO_ON; + client->pdev = pdev; + client->ops = ops; + client->id = id; + client->active = active; + client->driver_power_control = driver_power_control; mutex_lock(&vgasr_mutex); - /* don't do IGD vs DIS here */ - if (vgasr_priv.registered_clients & 1) - index = 1; - else - index = 0; - - vgasr_priv.clients[index].pwr_state = VGA_SWITCHEROO_ON; - vgasr_priv.clients[index].pdev = pdev; - vgasr_priv.clients[index].set_gpu_state = set_gpu_state; - vgasr_priv.clients[index].reprobe = reprobe; - vgasr_priv.clients[index].can_switch = can_switch; - vgasr_priv.clients[index].id = -1; - if (pdev->resource[PCI_ROM_RESOURCE].flags & IORESOURCE_ROM_SHADOW) - vgasr_priv.clients[index].active = true; - - vgasr_priv.registered_clients |= (1 << index); - - /* if we get two clients + handler */ - if (vgasr_priv.registered_clients == 0x3 && vgasr_priv.handler) { + list_add_tail(&client->list, &vgasr_priv.clients); + if (client_is_vga(client)) + vgasr_priv.registered_clients++; + + if (vga_switcheroo_ready()) { printk(KERN_INFO "vga_switcheroo: enabled\n"); vga_switcheroo_enable(); } mutex_unlock(&vgasr_mutex); return 0; } + +int vga_switcheroo_register_client(struct pci_dev *pdev, + const struct vga_switcheroo_client_ops *ops, + bool driver_power_control) +{ + return register_client(pdev, ops, -1, + pdev == vga_default_device(), driver_power_control); +} EXPORT_SYMBOL(vga_switcheroo_register_client); +int vga_switcheroo_register_audio_client(struct pci_dev *pdev, + const struct vga_switcheroo_client_ops *ops, + int id, bool active) +{ + return register_client(pdev, ops, id | ID_BIT_AUDIO, active, false); +} +EXPORT_SYMBOL(vga_switcheroo_register_audio_client); + +static struct vga_switcheroo_client * +find_client_from_pci(struct list_head *head, struct pci_dev *pdev) +{ + struct vga_switcheroo_client *client; + list_for_each_entry(client, head, list) + if (client->pdev == pdev) + return client; + return NULL; +} + +static struct vga_switcheroo_client * +find_client_from_id(struct list_head *head, int client_id) +{ + struct vga_switcheroo_client *client; + list_for_each_entry(client, head, list) + if (client->id == client_id) + return client; + return NULL; +} + +static struct vga_switcheroo_client * +find_active_client(struct list_head *head) +{ + struct vga_switcheroo_client *client; + list_for_each_entry(client, head, list) + if (client->active && client_is_vga(client)) + return client; + return NULL; +} + +int vga_switcheroo_get_client_state(struct pci_dev *pdev) +{ + struct vga_switcheroo_client *client; + + client = find_client_from_pci(&vgasr_priv.clients, pdev); + if (!client) + return VGA_SWITCHEROO_NOT_FOUND; + if (!vgasr_priv.active) + return VGA_SWITCHEROO_INIT; + return client->pwr_state; +} +EXPORT_SYMBOL(vga_switcheroo_get_client_state); + void vga_switcheroo_unregister_client(struct pci_dev *pdev) { - int i; + struct vga_switcheroo_client *client; mutex_lock(&vgasr_mutex); - for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { - if (vgasr_priv.clients[i].pdev == pdev) { - vgasr_priv.registered_clients &= ~(1 << i); - break; - } + client = find_client_from_pci(&vgasr_priv.clients, pdev); + if (client) { + if (client_is_vga(client)) + vgasr_priv.registered_clients--; + list_del(&client->list); + kfree(client); + } + if (vgasr_priv.active && vgasr_priv.registered_clients < 2) { + printk(KERN_INFO "vga_switcheroo: disabled\n"); + vga_switcheroo_debugfs_fini(&vgasr_priv); + vgasr_priv.active = false; } - - printk(KERN_INFO "vga_switcheroo: disabled\n"); - vga_switcheroo_debugfs_fini(&vgasr_priv); - vgasr_priv.active = false; mutex_unlock(&vgasr_mutex); } EXPORT_SYMBOL(vga_switcheroo_unregister_client); @@ -159,29 +246,30 @@ EXPORT_SYMBOL(vga_switcheroo_unregister_client); void vga_switcheroo_client_fb_set(struct pci_dev *pdev, struct fb_info *info) { - int i; + struct vga_switcheroo_client *client; mutex_lock(&vgasr_mutex); - for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { - if (vgasr_priv.clients[i].pdev == pdev) { - vgasr_priv.clients[i].fb_info = info; - break; - } - } + client = find_client_from_pci(&vgasr_priv.clients, pdev); + if (client) + client->fb_info = info; mutex_unlock(&vgasr_mutex); } EXPORT_SYMBOL(vga_switcheroo_client_fb_set); static int vga_switcheroo_show(struct seq_file *m, void *v) { - int i; + struct vga_switcheroo_client *client; + int i = 0; mutex_lock(&vgasr_mutex); - for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { - seq_printf(m, "%d:%s:%c:%s:%s\n", i, - vgasr_priv.clients[i].id == VGA_SWITCHEROO_DIS ? "DIS" : "IGD", - vgasr_priv.clients[i].active ? '+' : ' ', - vgasr_priv.clients[i].pwr_state ? "Pwr" : "Off", - pci_name(vgasr_priv.clients[i].pdev)); + list_for_each_entry(client, &vgasr_priv.clients, list) { + seq_printf(m, "%d:%s%s:%c:%s%s:%s\n", i, + client_id(client) == VGA_SWITCHEROO_DIS ? "DIS" : "IGD", + client_is_vga(client) ? "" : "-Audio", + client->active ? '+' : ' ', + client->driver_power_control ? "Dyn" : "", + client->pwr_state ? "Pwr" : "Off", + pci_name(client->pdev)); + i++; } mutex_unlock(&vgasr_mutex); return 0; @@ -194,54 +282,52 @@ static int vga_switcheroo_debugfs_open(struct inode *inode, struct file *file) static int vga_switchon(struct vga_switcheroo_client *client) { + if (client->driver_power_control) + return 0; if (vgasr_priv.handler->power_state) vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_ON); /* call the driver callback to turn on device */ - client->set_gpu_state(client->pdev, VGA_SWITCHEROO_ON); + client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_ON); client->pwr_state = VGA_SWITCHEROO_ON; return 0; } static int vga_switchoff(struct vga_switcheroo_client *client) { + if (client->driver_power_control) + return 0; /* call the driver callback to turn off device */ - client->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF); + client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF); if (vgasr_priv.handler->power_state) vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_OFF); client->pwr_state = VGA_SWITCHEROO_OFF; return 0; } +static void set_audio_state(int id, int state) +{ + struct vga_switcheroo_client *client; + + client = find_client_from_id(&vgasr_priv.clients, id | ID_BIT_AUDIO); + if (client && client->pwr_state != state) { + client->ops->set_gpu_state(client->pdev, state); + client->pwr_state = state; + } +} + /* stage one happens before delay */ static int vga_switchto_stage1(struct vga_switcheroo_client *new_client) { - int ret; - int i; - struct vga_switcheroo_client *active = NULL; + struct vga_switcheroo_client *active; - if (new_client->active == true) - return 0; - - for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { - if (vgasr_priv.clients[i].active == true) { - active = &vgasr_priv.clients[i]; - break; - } - } + active = find_active_client(&vgasr_priv.clients); if (!active) return 0; - /* power up the first device */ - ret = pci_enable_device(new_client->pdev); - if (ret) - return ret; - if (new_client->pwr_state == VGA_SWITCHEROO_OFF) vga_switchon(new_client); - /* swap shadow resource to denote boot VGA device has changed so X starts on new device */ - active->pdev->resource[PCI_ROM_RESOURCE].flags &= ~IORESOURCE_ROM_SHADOW; - new_client->pdev->resource[PCI_ROM_RESOURCE].flags |= IORESOURCE_ROM_SHADOW; + vga_set_default_device(new_client->pdev); return 0; } @@ -249,47 +335,59 @@ static int vga_switchto_stage1(struct vga_switcheroo_client *new_client) static int vga_switchto_stage2(struct vga_switcheroo_client *new_client) { int ret; - int i; - struct vga_switcheroo_client *active = NULL; + struct vga_switcheroo_client *active; - for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { - if (vgasr_priv.clients[i].active == true) { - active = &vgasr_priv.clients[i]; - break; - } - } + active = find_active_client(&vgasr_priv.clients); if (!active) return 0; active->active = false; + set_audio_state(active->id, VGA_SWITCHEROO_OFF); + if (new_client->fb_info) { struct fb_event event; + console_lock(); event.info = new_client->fb_info; fb_notifier_call_chain(FB_EVENT_REMAP_ALL_CONSOLE, &event); + console_unlock(); } ret = vgasr_priv.handler->switchto(new_client->id); if (ret) return ret; - if (new_client->reprobe) - new_client->reprobe(new_client->pdev); + if (new_client->ops->reprobe) + new_client->ops->reprobe(new_client->pdev); if (active->pwr_state == VGA_SWITCHEROO_ON) vga_switchoff(active); + set_audio_state(new_client->id, VGA_SWITCHEROO_ON); + new_client->active = true; return 0; } +static bool check_can_switch(void) +{ + struct vga_switcheroo_client *client; + + list_for_each_entry(client, &vgasr_priv.clients, list) { + if (!client->ops->can_switch(client->pdev)) { + printk(KERN_ERR "vga_switcheroo: client %x refused switch\n", client->id); + return false; + } + } + return true; +} + static ssize_t vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos) { char usercmd[64]; - const char *pdev_name; - int i, ret; + int ret; bool delay = false, can_switch; bool just_mux = false; int client_id = -1; @@ -310,21 +408,27 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf, /* pwr off the device not in use */ if (strncmp(usercmd, "OFF", 3) == 0) { - for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { - if (vgasr_priv.clients[i].active) + list_for_each_entry(client, &vgasr_priv.clients, list) { + if (client->active || client_is_audio(client)) + continue; + if (client->driver_power_control) continue; - if (vgasr_priv.clients[i].pwr_state == VGA_SWITCHEROO_ON) - vga_switchoff(&vgasr_priv.clients[i]); + set_audio_state(client->id, VGA_SWITCHEROO_OFF); + if (client->pwr_state == VGA_SWITCHEROO_ON) + vga_switchoff(client); } goto out; } /* pwr on the device not in use */ if (strncmp(usercmd, "ON", 2) == 0) { - for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { - if (vgasr_priv.clients[i].active) + list_for_each_entry(client, &vgasr_priv.clients, list) { + if (client->active || client_is_audio(client)) continue; - if (vgasr_priv.clients[i].pwr_state == VGA_SWITCHEROO_OFF) - vga_switchon(&vgasr_priv.clients[i]); + if (client->driver_power_control) + continue; + if (client->pwr_state == VGA_SWITCHEROO_OFF) + vga_switchon(client); + set_audio_state(client->id, VGA_SWITCHEROO_ON); } goto out; } @@ -357,13 +461,9 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf, if (client_id == -1) goto out; - - for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { - if (vgasr_priv.clients[i].id == client_id) { - client = &vgasr_priv.clients[i]; - break; - } - } + client = find_client_from_id(&vgasr_priv.clients, client_id); + if (!client) + goto out; vgasr_priv.delayed_switch_active = false; @@ -372,21 +472,16 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf, goto out; } + if (client->active) + goto out; + /* okay we want a switch - test if devices are willing to switch */ - can_switch = true; - for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { - can_switch = vgasr_priv.clients[i].can_switch(vgasr_priv.clients[i].pdev); - if (can_switch == false) { - printk(KERN_ERR "vga_switcheroo: client %d refused switch\n", i); - break; - } - } + can_switch = check_can_switch(); if (can_switch == false && delay == false) goto out; - if (can_switch == true) { - pdev_name = pci_name(client->pdev); + if (can_switch) { ret = vga_switchto_stage1(client); if (ret) printk(KERN_ERR "vga_switcheroo: switching failed stage 1 %d\n", ret); @@ -457,10 +552,7 @@ fail: int vga_switcheroo_process_delayed_switch(void) { - struct vga_switcheroo_client *client = NULL; - const char *pdev_name; - bool can_switch = true; - int i; + struct vga_switcheroo_client *client; int ret; int err = -EINVAL; @@ -470,20 +562,11 @@ int vga_switcheroo_process_delayed_switch(void) printk(KERN_INFO "vga_switcheroo: processing delayed switch to %d\n", vgasr_priv.delayed_client_id); - for (i = 0; i < VGA_SWITCHEROO_MAX_CLIENTS; i++) { - if (vgasr_priv.clients[i].id == vgasr_priv.delayed_client_id) - client = &vgasr_priv.clients[i]; - can_switch = vgasr_priv.clients[i].can_switch(vgasr_priv.clients[i].pdev); - if (can_switch == false) { - printk(KERN_ERR "vga_switcheroo: client %d refused switch\n", i); - break; - } - } - - if (can_switch == false || client == NULL) + client = find_client_from_id(&vgasr_priv.clients, + vgasr_priv.delayed_client_id); + if (!client || !check_can_switch()) goto err; - pdev_name = pci_name(client->pdev); ret = vga_switchto_stage2(client); if (ret) printk(KERN_ERR "vga_switcheroo: delayed switching failed stage 2 %d\n", ret); @@ -496,3 +579,127 @@ err: } EXPORT_SYMBOL(vga_switcheroo_process_delayed_switch); +static void vga_switcheroo_power_switch(struct pci_dev *pdev, enum vga_switcheroo_state state) +{ + struct vga_switcheroo_client *client; + + if (!vgasr_priv.handler->power_state) + return; + + client = find_client_from_pci(&vgasr_priv.clients, pdev); + if (!client) + return; + + if (!client->driver_power_control) + return; + + vgasr_priv.handler->power_state(client->id, state); +} + +/* force a PCI device to a certain state - mainly to turn off audio clients */ + +void vga_switcheroo_set_dynamic_switch(struct pci_dev *pdev, enum vga_switcheroo_state dynamic) +{ + struct vga_switcheroo_client *client; + + client = find_client_from_pci(&vgasr_priv.clients, pdev); + if (!client) + return; + + if (!client->driver_power_control) + return; + + client->pwr_state = dynamic; + set_audio_state(client->id, dynamic); +} +EXPORT_SYMBOL(vga_switcheroo_set_dynamic_switch); + +/* switcheroo power domain */ +static int vga_switcheroo_runtime_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + int ret; + + ret = dev->bus->pm->runtime_suspend(dev); + if (ret) + return ret; + if (vgasr_priv.handler->switchto) + vgasr_priv.handler->switchto(VGA_SWITCHEROO_IGD); + vga_switcheroo_power_switch(pdev, VGA_SWITCHEROO_OFF); + return 0; +} + +static int vga_switcheroo_runtime_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + int ret; + + vga_switcheroo_power_switch(pdev, VGA_SWITCHEROO_ON); + ret = dev->bus->pm->runtime_resume(dev); + if (ret) + return ret; + + return 0; +} + +/* this version is for the case where the power switch is separate + to the device being powered down. */ +int vga_switcheroo_init_domain_pm_ops(struct device *dev, struct dev_pm_domain *domain) +{ + /* copy over all the bus versions */ + if (dev->bus && dev->bus->pm) { + domain->ops = *dev->bus->pm; + domain->ops.runtime_suspend = vga_switcheroo_runtime_suspend; + domain->ops.runtime_resume = vga_switcheroo_runtime_resume; + + dev->pm_domain = domain; + return 0; + } + dev->pm_domain = NULL; + return -EINVAL; +} +EXPORT_SYMBOL(vga_switcheroo_init_domain_pm_ops); + +static int vga_switcheroo_runtime_resume_hdmi_audio(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + int ret; + struct vga_switcheroo_client *client, *found = NULL; + + /* we need to check if we have to switch back on the video + device so the audio device can come back */ + list_for_each_entry(client, &vgasr_priv.clients, list) { + if (PCI_SLOT(client->pdev->devfn) == PCI_SLOT(pdev->devfn) && client_is_vga(client)) { + found = client; + ret = pm_runtime_get_sync(&client->pdev->dev); + if (ret) { + if (ret != 1) + return ret; + } + break; + } + } + ret = dev->bus->pm->runtime_resume(dev); + + /* put the reference for the gpu */ + if (found) { + pm_runtime_mark_last_busy(&found->pdev->dev); + pm_runtime_put_autosuspend(&found->pdev->dev); + } + return ret; +} + +int vga_switcheroo_init_domain_pm_optimus_hdmi_audio(struct device *dev, struct dev_pm_domain *domain) +{ + /* copy over all the bus versions */ + if (dev->bus && dev->bus->pm) { + domain->ops = *dev->bus->pm; + domain->ops.runtime_resume = vga_switcheroo_runtime_resume_hdmi_audio; + + dev->pm_domain = domain; + return 0; + } + dev->pm_domain = NULL; + return -EINVAL; +} +EXPORT_SYMBOL(vga_switcheroo_init_domain_pm_optimus_hdmi_audio); diff --git a/drivers/gpu/vga/vgaarb.c b/drivers/gpu/vga/vgaarb.c index ace2b1623b2..af025970835 100644 --- a/drivers/gpu/vga/vgaarb.c +++ b/drivers/gpu/vga/vgaarb.c @@ -61,7 +61,7 @@ struct vga_device { unsigned int mem_lock_cnt; /* legacy MEM lock count */ unsigned int io_norm_cnt; /* normal IO count */ unsigned int mem_norm_cnt; /* normal MEM count */ - + bool bridge_has_one_vga; /* allow IRQ enable/disable hook */ void *cookie; void (*irq_set_state)(void *cookie, bool enable); @@ -136,6 +136,17 @@ struct pci_dev *vga_default_device(void) { return vga_default; } + +EXPORT_SYMBOL_GPL(vga_default_device); + +void vga_set_default_device(struct pci_dev *pdev) +{ + if (vga_default == pdev) + return; + + pci_dev_put(vga_default); + vga_default = pci_dev_get(pdev); +} #endif static inline void vga_irq_set_state(struct vga_device *vgadev, bool state) @@ -151,7 +162,7 @@ static inline void vga_irq_set_state(struct vga_device *vgadev, bool state) static void vga_check_first_use(void) { /* we should inform all GPUs in the system that - * VGA arb has occured and to try and disable resources + * VGA arb has occurred and to try and disable resources * if they can */ if (!vga_arbiter_used) { vga_arbiter_used = true; @@ -165,6 +176,8 @@ static struct vga_device *__vga_tryget(struct vga_device *vgadev, unsigned int wants, legacy_wants, match; struct vga_device *conflict; unsigned int pci_bits; + u32 flags = 0; + /* Account for "normal" resources to lock. If we decode the legacy, * counterpart, we need to request it as well */ @@ -237,21 +250,28 @@ static struct vga_device *__vga_tryget(struct vga_device *vgadev, /* looks like he doesn't have a lock, we can steal * them from him */ - vga_irq_set_state(conflict, false); + flags = 0; pci_bits = 0; - if (lwants & (VGA_RSRC_LEGACY_MEM|VGA_RSRC_NORMAL_MEM)) - pci_bits |= PCI_COMMAND_MEMORY; - if (lwants & (VGA_RSRC_LEGACY_IO|VGA_RSRC_NORMAL_IO)) - pci_bits |= PCI_COMMAND_IO; - pci_set_vga_state(conflict->pdev, false, pci_bits, - change_bridge); - conflict->owns &= ~lwants; + if (!conflict->bridge_has_one_vga) { + vga_irq_set_state(conflict, false); + flags |= PCI_VGA_STATE_CHANGE_DECODES; + if (match & (VGA_RSRC_LEGACY_MEM|VGA_RSRC_NORMAL_MEM)) + pci_bits |= PCI_COMMAND_MEMORY; + if (match & (VGA_RSRC_LEGACY_IO|VGA_RSRC_NORMAL_IO)) + pci_bits |= PCI_COMMAND_IO; + } + + if (change_bridge) + flags |= PCI_VGA_STATE_CHANGE_BRIDGE; + + pci_set_vga_state(conflict->pdev, false, pci_bits, flags); + conflict->owns &= ~match; /* If he also owned non-legacy, that is no longer the case */ - if (lwants & VGA_RSRC_LEGACY_MEM) + if (match & VGA_RSRC_LEGACY_MEM) conflict->owns &= ~VGA_RSRC_NORMAL_MEM; - if (lwants & VGA_RSRC_LEGACY_IO) + if (match & VGA_RSRC_LEGACY_IO) conflict->owns &= ~VGA_RSRC_NORMAL_IO; } @@ -261,14 +281,24 @@ enable_them: * also have in "decodes". We can lock resources we don't decode but * not own them. */ + flags = 0; pci_bits = 0; - if (wants & (VGA_RSRC_LEGACY_MEM|VGA_RSRC_NORMAL_MEM)) - pci_bits |= PCI_COMMAND_MEMORY; - if (wants & (VGA_RSRC_LEGACY_IO|VGA_RSRC_NORMAL_IO)) - pci_bits |= PCI_COMMAND_IO; - pci_set_vga_state(vgadev->pdev, true, pci_bits, !!(wants & VGA_RSRC_LEGACY_MASK)); - vga_irq_set_state(vgadev, true); + if (!vgadev->bridge_has_one_vga) { + flags |= PCI_VGA_STATE_CHANGE_DECODES; + if (wants & (VGA_RSRC_LEGACY_MEM|VGA_RSRC_NORMAL_MEM)) + pci_bits |= PCI_COMMAND_MEMORY; + if (wants & (VGA_RSRC_LEGACY_IO|VGA_RSRC_NORMAL_IO)) + pci_bits |= PCI_COMMAND_IO; + } + if (!!(wants & VGA_RSRC_LEGACY_MASK)) + flags |= PCI_VGA_STATE_CHANGE_BRIDGE; + + pci_set_vga_state(vgadev->pdev, true, pci_bits, flags); + + if (!vgadev->bridge_has_one_vga) { + vga_irq_set_state(vgadev, true); + } vgadev->owns |= (wants & vgadev->decodes); lock_them: vgadev->locks |= (rsrc & VGA_RSRC_LEGACY_MASK); @@ -421,6 +451,60 @@ bail: } EXPORT_SYMBOL(vga_put); +/* Rules for using a bridge to control a VGA descendant decoding: + if a bridge has only one VGA descendant then it can be used + to control the VGA routing for that device. + It should always use the bridge closest to the device to control it. + If a bridge has a direct VGA descendant, but also have a sub-bridge + VGA descendant then we cannot use that bridge to control the direct VGA descendant. + So for every device we register, we need to iterate all its parent bridges + so we can invalidate any devices using them properly. +*/ +static void vga_arbiter_check_bridge_sharing(struct vga_device *vgadev) +{ + struct vga_device *same_bridge_vgadev; + struct pci_bus *new_bus, *bus; + struct pci_dev *new_bridge, *bridge; + + vgadev->bridge_has_one_vga = true; + + if (list_empty(&vga_list)) + return; + + /* okay iterate the new devices bridge hierarachy */ + new_bus = vgadev->pdev->bus; + while (new_bus) { + new_bridge = new_bus->self; + + /* go through list of devices already registered */ + list_for_each_entry(same_bridge_vgadev, &vga_list, list) { + bus = same_bridge_vgadev->pdev->bus; + bridge = bus->self; + + /* see if the share a bridge with this device */ + if (new_bridge == bridge) { + /* if their direct parent bridge is the same + as any bridge of this device then it can't be used + for that device */ + same_bridge_vgadev->bridge_has_one_vga = false; + } + + /* now iterate the previous devices bridge hierarchy */ + /* if the new devices parent bridge is in the other devices + hierarchy then we can't use it to control this device */ + while (bus) { + bridge = bus->self; + if (bridge) { + if (bridge == vgadev->pdev->bus->self) + vgadev->bridge_has_one_vga = false; + } + bus = bus->parent; + } + } + new_bus = new_bus->parent; + } +} + /* * Currently, we assume that the "initial" setup of the system is * not sane, that is we come up with conflicting devices and let @@ -497,9 +581,11 @@ static bool vga_arbiter_add_pci_device(struct pci_dev *pdev) #ifndef __ARCH_HAS_VGA_DEFAULT_DEVICE if (vga_default == NULL && ((vgadev->owns & VGA_RSRC_LEGACY_MASK) == VGA_RSRC_LEGACY_MASK)) - vga_default = pci_dev_get(pdev); + vga_set_default_device(pdev); #endif + vga_arbiter_check_bridge_sharing(vgadev); + /* Add to the list */ list_add(&vgadev->list, &vga_list); vga_count++; @@ -530,10 +616,10 @@ static bool vga_arbiter_del_pci_device(struct pci_dev *pdev) goto bail; } - if (vga_default == pdev) { - pci_dev_put(vga_default); - vga_default = NULL; - } +#ifndef __ARCH_HAS_VGA_DEFAULT_DEVICE + if (vga_default == pdev) + vga_set_default_device(NULL); +#endif if (vgadev->decodes & (VGA_RSRC_LEGACY_IO | VGA_RSRC_LEGACY_MEM)) vga_decode_count--; @@ -558,10 +644,12 @@ bail: static inline void vga_update_device_decodes(struct vga_device *vgadev, int new_decodes) { - int old_decodes; - struct vga_device *new_vgadev, *conflict; + int old_decodes, decodes_removed, decodes_unlocked; old_decodes = vgadev->decodes; + decodes_removed = ~new_decodes & old_decodes; + decodes_unlocked = vgadev->locks & decodes_removed; + vgadev->owns &= ~decodes_removed; vgadev->decodes = new_decodes; pr_info("vgaarb: device changed decodes: PCI:%s,olddecodes=%s,decodes=%s:owns=%s\n", @@ -570,31 +658,22 @@ static inline void vga_update_device_decodes(struct vga_device *vgadev, vga_iostate_to_str(vgadev->decodes), vga_iostate_to_str(vgadev->owns)); - - /* if we own the decodes we should move them along to - another card */ - if ((vgadev->owns & old_decodes) && (vga_count > 1)) { - /* set us to own nothing */ - vgadev->owns &= ~old_decodes; - list_for_each_entry(new_vgadev, &vga_list, list) { - if ((new_vgadev != vgadev) && - (new_vgadev->decodes & VGA_RSRC_LEGACY_MASK)) { - pr_info("vgaarb: transferring owner from PCI:%s to PCI:%s\n", pci_name(vgadev->pdev), pci_name(new_vgadev->pdev)); - conflict = __vga_tryget(new_vgadev, VGA_RSRC_LEGACY_MASK); - if (!conflict) - __vga_put(new_vgadev, VGA_RSRC_LEGACY_MASK); - break; - } - } + /* if we removed locked decodes, lock count goes to zero, and release */ + if (decodes_unlocked) { + if (decodes_unlocked & VGA_RSRC_LEGACY_IO) + vgadev->io_lock_cnt = 0; + if (decodes_unlocked & VGA_RSRC_LEGACY_MEM) + vgadev->mem_lock_cnt = 0; + __vga_put(vgadev, decodes_unlocked); } /* change decodes counter */ - if (old_decodes != new_decodes) { - if (new_decodes & (VGA_RSRC_LEGACY_IO | VGA_RSRC_LEGACY_MEM)) - vga_decode_count++; - else - vga_decode_count--; - } + if (old_decodes & VGA_RSRC_LEGACY_MASK && + !(new_decodes & VGA_RSRC_LEGACY_MASK)) + vga_decode_count--; + if (!(old_decodes & VGA_RSRC_LEGACY_MASK) && + new_decodes & VGA_RSRC_LEGACY_MASK) + vga_decode_count++; pr_debug("vgaarb: decoding count now is: %d\n", vga_decode_count); } @@ -774,7 +853,7 @@ static ssize_t vga_arb_read(struct file *file, char __user * buf, */ spin_lock_irqsave(&vga_lock, flags); - /* If we are targetting the default, use it */ + /* If we are targeting the default, use it */ pdev = priv->target; if (pdev == NULL || pdev == PCI_INVALID_CARD) { spin_unlock_irqrestore(&vga_lock, flags); @@ -916,14 +995,20 @@ static ssize_t vga_arb_write(struct file *file, const char __user * buf, uc = &priv->cards[i]; } - if (!uc) - return -EINVAL; + if (!uc) { + ret_val = -EINVAL; + goto done; + } - if (io_state & VGA_RSRC_LEGACY_IO && uc->io_cnt == 0) - return -EINVAL; + if (io_state & VGA_RSRC_LEGACY_IO && uc->io_cnt == 0) { + ret_val = -EINVAL; + goto done; + } - if (io_state & VGA_RSRC_LEGACY_MEM && uc->mem_cnt == 0) - return -EINVAL; + if (io_state & VGA_RSRC_LEGACY_MEM && uc->mem_cnt == 0) { + ret_val = -EINVAL; + goto done; + } vga_put(pdev, io_state); @@ -976,7 +1061,6 @@ static ssize_t vga_arb_write(struct file *file, const char __user * buf, } } else if (strncmp(curr_pos, "target ", 7) == 0) { - struct pci_bus *pbus; unsigned int domain, bus, devfn; struct vga_device *vgadev; @@ -995,19 +1079,11 @@ static ssize_t vga_arb_write(struct file *file, const char __user * buf, pr_debug("vgaarb: %s ==> %x:%x:%x.%x\n", curr_pos, domain, bus, PCI_SLOT(devfn), PCI_FUNC(devfn)); - pbus = pci_find_bus(domain, bus); - pr_debug("vgaarb: pbus %p\n", pbus); - if (pbus == NULL) { - pr_err("vgaarb: invalid PCI domain and/or bus address %x:%x\n", - domain, bus); - ret_val = -ENODEV; - goto done; - } - pdev = pci_get_slot(pbus, devfn); + pdev = pci_get_domain_bus_and_slot(domain, bus, devfn); pr_debug("vgaarb: pdev %p\n", pdev); if (!pdev) { - pr_err("vgaarb: invalid PCI address %x:%x\n", - bus, devfn); + pr_err("vgaarb: invalid PCI address %x:%x:%x\n", + domain, bus, devfn); ret_val = -ENODEV; goto done; } @@ -1094,10 +1170,9 @@ static int vga_arb_open(struct inode *inode, struct file *file) pr_debug("%s\n", __func__); - priv = kmalloc(sizeof(struct vga_arb_private), GFP_KERNEL); + priv = kzalloc(sizeof(*priv), GFP_KERNEL); if (priv == NULL) return -ENOMEM; - memset(priv, 0, sizeof(*priv)); spin_lock_init(&priv->lock); file->private_data = priv; @@ -1222,6 +1297,7 @@ static int __init vga_arb_device_init(void) { int rc; struct pci_dev *pdev; + struct vga_device *vgadev; rc = misc_register(&vga_arb_device); if (rc < 0) @@ -1238,6 +1314,13 @@ static int __init vga_arb_device_init(void) vga_arbiter_add_pci_device(pdev); pr_info("vgaarb: loaded\n"); + + list_for_each_entry(vgadev, &vga_list, list) { + if (vgadev->bridge_has_one_vga) + pr_info("vgaarb: bridge control possible %s\n", pci_name(vgadev->pdev)); + else + pr_info("vgaarb: no bridge control possible %s\n", pci_name(vgadev->pdev)); + } return rc; } subsys_initcall(vga_arb_device_init); |
