5 Commits

Author SHA1 Message Date
fe3f58c15f bbswitch_dev: disable runtime PM on removal
Call pm_runtime_forbid() to balance it with pm_runtime_allow(), this
ensures that the runtime usage counter before loading and after
unloading match.
2016-05-27 02:56:24 +02:00
fb3d5a614a bbswitch_dev: use autosuspend to avoid sleeping too fast 2016-05-23 19:50:37 +02:00
fd774b1f5e bbswitch_dev: remove artificial delays
These delays were added as attempt to rule out the possibility that the
hardware was accessed too fast, but it does not seem to help. Remove it.
2016-05-23 19:49:21 +02:00
daa6411911 [DO NOT MERGE] [WIP] Add PCI driver
This is work in progress, I intend to merge the bbswitch_dev code into
the main module. Some comments in README and bbswitch.c might be stale.

Create a PCI driver such that runtime PM works. Without a bound driver,
the kernel assumes that the device is D0 state during suspend (which
therefore needs the notifier hack in bbswitch) but more importantly, it
will prevent the PCIe port from going to sleep in Linux v4.7.

Currently I use this to debug an infinite loop on my Clevo P651RA
laptop, it can be loaded as follows:

    make modname=bbswitch_dev
    insmod bbswitch_dev.ko
    echo > /sys/bus/pci/drivers/bbswitch/new_id 10de 13d9

Needs Mika's pci/pm series ("PCI: Put PCIe ports into D3 during
suspend"), qeued for v4.7 via
https://git.kernel.org/cgit/linux/kernel/git/helgaas/pci.git/commit/?h=pci/pm
2016-05-19 15:54:23 +02:00
915413ab92 Disable DSM if power resources are in use
The Optimus _DSM function would prepare a device to be put in D3cold
state when _PS3 is called. Newer laptops should not use this since
Windows 8 introduced a new method to put devices in D3cold state[1].

Hopefully this fixes an infinite loop on a Clevo P651RA. Actually
putting the parent device (PCIe port) is not done in this patch.

 [1]: https://msdn.microsoft.com/windows/hardware/drivers/bringup/firmware-requirements-for-d3cold
2016-05-13 21:35:54 +02:00
5 changed files with 139 additions and 181 deletions

View File

@ -59,45 +59,6 @@ To uninstall it, run:
# make -f Makefile.dkms uninstall # make -f Makefile.dkms uninstall
Lenovo IdeaPad Y470/Y570 and Toshiba SATELLITE P870
---------------------------------------------------
[This kernel bug](https://bugzilla.kernel.org/show_bug.cgi?id=42696) is fixed in
Linux 3.9-rc1 and Linux 3.8.5 which obsoletes this hack. Linux 3.7 is
incompatible with this hack (and already EOL'd, so a backport fix won't be made
available).
In kernel version 3.6 and older, you need to apply an ugly hack on these laptops
to make bbswitch and the
driver (both nouveau and nvidia) work. For now I have decided not to put the
hack in the bbswitch module since it is a very ugly hack that is comparable to
writing a maximum allowable speed of 130 km/h on a traffic sign for a road
where 120 km/h is allowed just because the radar gun does not work properly.
The module has been tested on a Lenovo IdeaPad Y570 running an up-to-date
version of Ubuntu 11.10 Oneiric (64-bit) with Bumblebee 3.0 (3.0-1~oneiricppa2)
installed using the nvidia driver.
To make use of it, use the `hack-lenovo` branch. An example using DKMS:
$ git clone git://github.com/Bumblebee-Project/bbswitch.git -b hack-lenovo
$ cd bbswitch
$ mkdir /usr/src/acpi-handle-hack-0.0.2
# cp Makefile acpi-handle-hack.c /usr/src/acpi-handle-hack-0.0.2
# cp dkms/acpi-handle-hack.conf /usr/src/acpi-handle-hack-0.0.2/dkms.conf
# dkms install -m acpi-handle-hack -v 0.0.2
If everything goes well, you now need to get the hack loaded on boot. On
Ubuntu and Debian, this can be done with:
echo acpi-handle-hack | sudo tee -a /etc/modules
sudo update-initramfs -u
For other systems, adopt the instructions from the *Disable card on boot*
section below. Please do not copy these instructions to blogs/forums/whatever
without warning that the method is a hack (you can refer to the metaphore above)
and that it may crash the machine if incorrectly applied. To apply these
changes, you have to reboot (technically, unloading nvidia/nouveau, bbswitch and
stopping bumblebeed, `modprobe acpi-handle-hack` and starting bumblebeed should
work as well, but saying reboot is shorter)
Usage Usage
----- -----
@ -204,3 +165,9 @@ issues on this module in the issue tracker and provide the following details:
Upload the generated tarball on the above Launchpad URL and provide a link to Upload the generated tarball on the above Launchpad URL and provide a link to
the comment containing your report. the comment containing your report.
TODO
----
With the new PCI device approach, if load_state=0, then starting bumblebeed will
unload bbswitch. Fix Bumblebee not to unload the module when the PM method is
bbswitch and the loaded module is bbswitch.

View File

@ -1,134 +0,0 @@
/**
* Very ugly hack to work around a wrongly detected ACPI handle, see
* https://bugzilla.kernel.org/show_bug.cgi?id=42696
* https://bugzilla.kernel.org/show_bug.cgi?id=60829
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/acpi.h>
#include <linux/dmi.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Dirty ACPI handle hack for Lenovo IdeaPad Y[45]70");
MODULE_AUTHOR("Peter Lekensteyn <lekensteyn@gmail.com>");
MODULE_VERSION("0.0.2");
static struct pci_dev *dis_dev;
static acpi_handle orig_handle;
/**
* Returns true if the system needs an ACPI handle hack
*/
static bool __init need_acpi_handle_hack(void) {
return dmi_match(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y470 ")
|| dmi_match(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y480")
|| dmi_match(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y570 ")
|| dmi_match(DMI_PRODUCT_VERSION, "LENOVO IDEAPAD Y570 ") /* sys-product-name: PIQY0 */
|| dmi_match(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y580")
|| dmi_match(DMI_PRODUCT_VERSION, "Lenovo IdeaPad U510")
|| dmi_match(DMI_PRODUCT_VERSION, "PSPLBE-01V00HFR") /* TOSHIBA SATELLITE P870 */
|| dmi_match(DMI_PRODUCT_VERSION, "PSPLBA-02300S") /* TOSHIBA Satellite P870 */
|| dmi_match(DMI_PRODUCT_VERSION, "PSPLFE-00E009FR") /* TOSHIBA Satellite P870 */
|| dmi_match(DMI_PRODUCT_VERSION, "Lenovo G580")
|| dmi_match(DMI_PRODUCT_VERSION, "Lenovo G780")
|| dmi_match(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Z500")
|| (dmi_match(DMI_SYS_VENDOR, "LENOVO") && dmi_match(DMI_PRODUCT_NAME, "PIQY0")) /* Lenovo IdeaPad Y570 */
|| dmi_match(DMI_PRODUCT_NAME, "Aspire V5-573G")
|| dmi_match(DMI_PRODUCT_NAME, "Aspire V5-573PG")
;
}
static struct pci_dev __init *get_discrete_device(void) {
struct pci_dev *pdev = NULL;
while ((pdev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pdev)) != NULL) {
int pci_class = pdev->class >> 8;
if (pci_class != PCI_CLASS_DISPLAY_VGA &&
pci_class != PCI_CLASS_DISPLAY_3D)
continue;
if (pdev->vendor != PCI_VENDOR_ID_INTEL) {
return pdev;
}
}
return NULL;
}
/**
* Very ugly hack to set the ACPI handle, do not use this as exemplary code!
*/
static void dev_set_acpi_handle(struct pci_dev *pdev, acpi_handle handle) {
#ifdef ACPI_HANDLE_SET
ACPI_HANDLE_SET(&pdev->dev, handle);
#else
/* for Linux 3.7 and earlier */
pdev->dev.archdata.acpi_handle = handle;
#endif
}
static int __init hack_apply(void) {
acpi_handle tmp_handle, new_handle;
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
if (!need_acpi_handle_hack()) {
pr_err("Machine does not need ACPI handle hack\n");
return -ENODEV;
}
dis_dev = get_discrete_device();
if (!dis_dev) {
pr_err("No discrete video card found\n");
return -ENODEV;
}
#ifdef ACPI_HANDLE
/* since Linux 3.8 */
orig_handle = ACPI_HANDLE(&dis_dev->dev);
#else
/* removed since Linux 3.13 */
orig_handle = DEVICE_ACPI_HANDLE(&dis_dev->dev);
#endif
if (!orig_handle) {
pr_err("No ACPI handle found for discrete video card\n");
goto free_dev;
}
if (ACPI_FAILURE(acpi_get_name(orig_handle, ACPI_SINGLE_NAME, &buf))) {
pr_err("Could not acquire name for discrete video card\n");
goto free_dev;
}
if (strcmp((char *)buf.pointer, "PEGP") == 0) {
pr_err("Handle has already been changed to PEGP\n");
goto free_name;
}
/* \_SB.PCI0.PEG0.VGA_ -> \_SB.PCI0.PEG0.PEGP */
if (ACPI_FAILURE(acpi_get_parent(orig_handle, &tmp_handle))) {
pr_err("No parent device found for %s\n", (char *)buf.pointer);
goto free_name;
}
if (ACPI_FAILURE(acpi_get_handle(tmp_handle, "PEGP", &new_handle))) {
pr_err("No PEGP handle found on %s\n", (char *)buf.pointer);
goto free_name;
}
pr_info("Setting new ACPI handle for discrete video card\n");
dev_set_acpi_handle(dis_dev, new_handle);
kfree(buf.pointer);
pci_dev_put(dis_dev);
return 0;
free_name:
kfree(buf.pointer);
free_dev:
pci_dev_put(dis_dev);
return -ENODEV;
}
static void __exit hack_undo(void) {
if (orig_handle) {
pr_info("Restoring original ACPI handle for discrete"
" video card\n");
dev_set_acpi_handle(dis_dev, orig_handle);
}
}
module_init(hack_apply);
module_exit(hack_undo);

View File

@ -8,6 +8,16 @@
* # echo ON > /proc/acpi/bbswitch * # echo ON > /proc/acpi/bbswitch
* Get status * Get status
* # cat /proc/acpi/bbswitch * # cat /proc/acpi/bbswitch
*
* Note: only one PCI driver (bbswitch, nouveau, etc.) can bind to a PCI device.
* When turning a device OFF, bbswitch tries to bind itself to the PCI device.
* When turning a device ON, bbswitch unbinds a device (if it was bound) before
* returning from the write.
*
* TODO is this true?
* The new dual module approach is used to allow for backwards compatibility,
* Bumblebee unloads any driver that is loaded for a device, that would however
* result in unloading the main bbswitch module.
*/ */
/* /*
* Copyright (C) 2011-2013 Bumblebee Project * Copyright (C) 2011-2013 Bumblebee Project
@ -197,6 +207,26 @@ static int bbswitch_optimus_dsm(void) {
return 0; return 0;
} }
// Windows 8/8.1/10 do not use DSM to put the device in D3cold state,
// instead it disables power resources on the parent PCIe port device.
static bool has_pr3_support(void) {
acpi_handle parent_handle;
struct acpi_device *ad = NULL;
if (ACPI_FAILURE(acpi_get_parent(dis_handle, &parent_handle))) {
pr_warn("Failed to obtain the parent device\n");
return false;
}
acpi_bus_get_device(parent_handle, &ad);
if (!ad) {
pr_warn("Failed to obtain an ACPI device for handle\n");
return false;
}
return ad->power.flags.power_resources;
}
static int bbswitch_acpi_off(void) { static int bbswitch_acpi_off(void) {
if (dsm_type == DSM_TYPE_NVIDIA) { if (dsm_type == DSM_TYPE_NVIDIA) {
char args[] = {2, 0, 0, 0}; char args[] = {2, 0, 0, 0};
@ -436,7 +466,9 @@ static int __init bbswitch_init(void) {
return -ENODEV; return -ENODEV;
} }
if (!skip_optimus_dsm && if (has_pr3_support()) {
pr_info("skipping _DSM as _PR3 support is detected\n");
} else if (!skip_optimus_dsm &&
has_dsm_func(acpi_optimus_dsm_muid, 0x100, 0x1A)) { has_dsm_func(acpi_optimus_dsm_muid, 0x100, 0x1A)) {
dsm_type = DSM_TYPE_OPTIMUS; dsm_type = DSM_TYPE_OPTIMUS;
pr_info("detected an Optimus _DSM function\n"); pr_info("detected an Optimus _DSM function\n");

100
bbswitch_dev.c Normal file
View File

@ -0,0 +1,100 @@
/*
* TODO merge into main bbswitch module.
* TODO on ON call device_release_driver
* TODO how to bind to a specific device from kernel space? Can't use
* driver_probe_device (https://lkml.org/lkml/2014/2/14/628). Maybe use
* driver_override or new_id/bind/remove_id from userspace?
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/pci.h>
#include <linux/module.h>
#include <linux/pm_runtime.h>
#include <linux/delay.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Toggle the discrete graphics card (PCI driver)");
MODULE_AUTHOR("Peter Wu <peter@lekensteyn.nl>");
static int bbswitch_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
/* TODO how to discover devices? */
/* Prevent kernel from detaching the PCI device for some devices that
* generate hotplug events. The graphics card is typically not physically
* removable. */
pci_ignore_hotplug(dev);
pm_runtime_set_active(&dev->dev); /* clear any errors */
/* Use autosuspend to avoid lspci waking up the device multiple times. */
pm_runtime_set_autosuspend_delay(&dev->dev, 2000);
pm_runtime_use_autosuspend(&dev->dev);
pm_runtime_allow(&dev->dev);
pm_runtime_put_autosuspend(&dev->dev);
return 0;
}
static void bbswitch_pci_remove(struct pci_dev *dev)
{
pm_runtime_get_noresume(&dev->dev);
pm_runtime_dont_use_autosuspend(&dev->dev);
pm_runtime_forbid(&dev->dev);
}
static int bbswitch_runtime_suspend(struct device *dev) {
struct pci_dev *pdev = to_pci_dev(dev);
pr_info("disabling discrete graphics\n");
/* TODO if _PR3 is not supported, call Optimus DSM here. */
/* TODO for v1 Optimus, call DSM here. */
/* Save state now that the device is still awake, makes PCI layer happy */
pci_save_state(pdev);
/* TODO if _PR3 is supported, should this be PCI_D3hot? */
pci_set_power_state(pdev, PCI_D3cold);
return 0;
}
static int bbswitch_runtime_resume(struct device *dev) {
pr_info("enabling discrete graphics\n");
/* TODO for v1 Optimus, call DSM here. */
/* Nothing to do for Optimus, the PCI layer already moved into D0 state. */
return 0;
}
static struct dev_pm_ops bbswitch_pm_ops = {
.runtime_suspend = bbswitch_runtime_suspend,
.runtime_resume = bbswitch_runtime_resume,
/* No runtime_idle callback, the default zero delay is sufficient. */
};
static struct pci_driver bbswitch_pci_driver = {
.name = KBUILD_MODNAME,
.id_table = NULL, /* will be added dynamically */
.probe = bbswitch_pci_probe,
.remove = bbswitch_pci_remove,
.driver.pm = &bbswitch_pm_ops,
};
static int __init bbswitch_dev_init(void) {
int ret;
ret = pci_register_driver(&bbswitch_pci_driver);
#if 0
ret = pci_add_dynid(&bbswitch_pci_driver, PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID,
PCI_ANY_ID, PCI_ANY_ID, PCI_BASE_CLASS_DISPLAY << 16, 0xff0000);
#endif
return ret;
}
static void __exit bbswitch_dev_exit(void) {
pci_unregister_driver(&bbswitch_pci_driver);
}
module_init(bbswitch_dev_init);
module_exit(bbswitch_dev_exit);
/* vim: set sw=4 ts=4 et: */

View File

@ -1,7 +0,0 @@
PACKAGE_NAME="acpi-handle-hack"
PACKAGE_VERSION="0.0.2"
MAKE[0]="make KVERSION=$kernelver modname=acpi-handle-hack"
CLEAN="make clean"
BUILT_MODULE_NAME[0]="acpi-handle-hack"
DEST_MODULE_LOCATION[0]="/kernel/drivers/acpi"
AUTOINSTALL="yes"