43 Commits
v0.2 ... v0.5

Author SHA1 Message Date
b12b017f3a Update to version 0.5, update NEWS 2012-10-22 17:03:33 +02:00
2adcb24207 Merge branch 'master' into develop 2012-10-22 17:03:19 +02:00
8beb9e3f88 Add missing 'static' keyword 2012-10-22 16:43:40 +02:00
da55a70331 Add modeline for vim 2012-10-19 17:32:16 +02:00
7f9fb7adf9 Power on the PCIe port before accessing PCI config space (#35)
Fixes regression in Linux 3.6 when run-time power management is enabled. See
https://bugzilla.kernel.org/show_bug.cgi?id=48981 for a discussion.
2012-10-19 17:28:59 +02:00
b8a6029692 Convert printk to pr_... macros
For pr_debug outputs on _DSM results, use "make DEBUG=1"
2012-10-19 16:58:43 +02:00
aeff596524 Makefile: fix clean and install target
clean: do not try to wipe /usr/src/linux-...

load: rmmod and insmod do not have to be in /sbin (for example, /usr/bin
on Arch Linux). Assume that the user has a sane PATH.
2012-10-14 11:34:52 +02:00
dbf8aaf8e2 Use D3cold instead of D3hot to reflect real device state 2012-10-14 11:31:47 +02:00
d54dd454b6 Merge pull request #31 from wzzrd/develop
Add install and uninstall targets to Makefile
2012-08-26 06:52:27 -07:00
5f2db6c26a Add install and uninstall targets to Makefile
For if you do not want to or are unable to use dkms
2012-08-26 15:40:05 +02:00
8ada8849f2 Compatibility with older DKMS versions (GH-25) 2012-07-07 20:01:33 +02:00
979fbcf552 Merge branch 'develop' 2012-04-26 12:59:41 +02:00
d12ce73a0d Update to version 0.4.2, update NEWS 2012-04-26 12:56:47 +02:00
0d1429e8bb Fix documentation error (Closes GH-16) 2012-04-26 12:49:14 +02:00
af91008deb Fix formatting of arguments on case of ACPI failure (GH-12) 2012-03-01 21:53:52 +01:00
e13e6ffc54 Fix null deref when ACPI method call failed (GH-12) 2012-03-01 13:18:24 +01:00
f034252a13 Don't share buffer for muid/args, it'll be overwritten 2012-02-29 00:49:34 +01:00
647e3dddea Buffer overflow by one byte 2012-02-29 00:30:14 +01:00
804773eb22 Fix misuse of sizeof which trims arguments/muid (GH-12) 2012-02-29 00:10:32 +01:00
7c30e38bd1 Revert "Add support for WMMX method"
This reverts commit 11ddd3469f.
2012-01-28 10:57:04 +01:00
e321debb5b Revert "Support Optimus machines through WMMX (GH-2)"
This reverts commit 0ec41fa2da.
2012-01-28 10:57:03 +01:00
f4c2fadaf5 Revert "Load module dependency on make load: wmi"
This reverts commit fd74ba5bd8.
2012-01-28 10:56:58 +01:00
fd74ba5bd8 Load module dependency on make load: wmi 2012-01-24 14:33:21 +01:00
0ec41fa2da Support Optimus machines through WMMX (GH-2)
This should finally support machines like the Lenovo Y570. WMMX methods should
be more reliable than direct DSM probing so let's try that before calling DSM
directly.
2012-01-24 14:30:53 +01:00
7fd02221b2 Print version on load 2012-01-24 00:04:41 +01:00
11ddd3469f Add support for WMMX method
This should support at least the Lenovo Ideapad Y470 and Y570. It probes the
WMMX method before calling the _DSM method directly since that method should
work for all machines that support the NVIDIA DSM.
2012-01-23 23:52:27 +01:00
4f73396ba7 Add easier way to use DKMS 2012-01-21 15:02:11 +01:00
8c0c5aa863 Allow everyone to get the status of the card (Closes GH-8) 2012-01-21 12:30:41 +01:00
d5ff9a1fb9 Merge branch 'develop' 2012-01-16 17:24:47 +01:00
b0712e5185 Update to version 0.4.1, update NEWS 2012-01-16 17:23:20 +01:00
2d1effc903 Fix a harmless error message 2012-01-16 12:19:36 +01:00
c1dd7140df Merge branch 'develop' 2012-01-15 17:18:45 +01:00
8bd07d14af Bump to version 0.4, update NEWS 2012-01-15 17:18:14 +01:00
ebf86f0617 Detect 3D controllers as gfx device too (GH-3) 2012-01-15 16:35:07 +01:00
a904b442af Add debug message in case no ACPI handle is found 2012-01-15 10:43:44 +01:00
fd4dabf37e Merge branch 'develop' 2012-01-14 23:18:54 +01:00
c8b303c36c Add NEWS file, bump version to 0.3 2012-01-14 23:16:45 +01:00
9f0ae8f723 Ignore files generated from build 2012-01-14 22:54:33 +01:00
031bcfe8a6 Update README with module options 2012-01-14 22:49:46 +01:00
5e297c27ad Add note that a driver must not be loaded if the card is off 2012-01-14 22:13:55 +01:00
358ce15bc1 Probe for nvidia DSM on Intel ACPI handle (GH-4) 2012-01-14 19:58:56 +01:00
04cdd2d5f9 Update instructions with request for ACPI handle mapping 2012-01-14 17:15:23 +01:00
6ed65463c8 Update README with request for machine information 2012-01-14 11:14:43 +01:00
6 changed files with 251 additions and 59 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*.cmd
.tmp_versions
Module.symvers
*.ko
*.mod.c
*.o
modules.order

View File

@ -5,12 +5,27 @@ KVERSION := $(shell uname -r)
KDIR := /lib/modules/$(KVERSION)/build
PWD := "$$(pwd)"
ifdef DEBUG
CFLAGS_$(obj-m) := -DDEBUG
endif
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
$(MAKE) O=$(PWD) -C $(KDIR) M=$(PWD) clean
load:
-/sbin/rmmod $(modname)
/sbin/insmod $(modname).ko
-rmmod $(modname)
insmod $(modname).ko
install:
mkdir -p /lib/modules/$(KVERSION)/misc/$(modname)
install -m 0755 -o root -g root $(modname).ko /lib/modules/$(KVERSION)/misc/$(modname)
depmod -a
uninstall:
rm /lib/modules/$(KVERSION)/misc/$(modname)/$(modname).ko
rmdir /lib/modules/$(KVERSION)/misc/$(modname)
rmdir /lib/modules/$(KVERSION)/misc
depmod -a

24
Makefile.dkms Normal file
View File

@ -0,0 +1,24 @@
modname := bbswitch
DKMS := dkms
modver := $(shell awk -F'"' '/define *BBSWITCH_VERSION/{print $$2}' < bbswitch.c)
# directory in which generated files are stored
DKMS_DEST := /usr/src/$(modname)-$(modver)
all: install
src_install:
mkdir -p '$(DKMS_DEST)'
cp Makefile bbswitch.c '$(DKMS_DEST)'
sed 's/#MODULE_VERSION#/$(modver)/' dkms/dkms.conf > '$(DKMS_DEST)/dkms.conf'
build: src_install
$(DKMS) build -m bbswitch -v $(modver)
install: build
$(DKMS) install -m bbswitch -v $(modver)
uninstall:
$(DKMS) remove -m bbswitch -v $(modver) --all
.PHONY: all src_install build install uninstall

35
NEWS Normal file
View File

@ -0,0 +1,35 @@
Version 0.5 - 22 October 2012
* Improved compatibility with older DKMS versions.
* Set device state to D3cold instead of D3hot.
* Only print DSM method call results when debugging is enabled.
* Fixed runtime power management regression in Linux 3.6.
Version 0.4.2 - 26 April 2012
* Fixed a documentation error on unload_state.
* Added Makefile.dkms and documentation for easier installation using DKMS.
* Make /proc/acpi/bbswitch world-writable
* Fix NULL pointer dereference when reporting a failure during ACPI method
evaluation.
Version 0.4.1 - 16 January 2012
* Corrected a small error that yielded an confusing error message "The discrete
card could not be enabled by a _DSM call"
Version 0.4 - 15 January 2012
* Support for models that have a "3D controller" instead of "VGA compatible
controller".
Version 0.3 - 14 January 2012
* Support for models that have the nvidia DSM method on the integrated Intel
video card instead of the nvidia one. This includes at the Acer Travelmate
8472TG and Acer Aspire 5745G.
Version 0.2 - 2 January 2012
* Initial release, adding a kernel module that can disable discrete nvidia cards
on Optimus systems.

View File

@ -38,10 +38,16 @@ information.
DKMS support
------------
Change `#MODULE_VERSION#` to the current version of bbswitch. Copy the
Makefile, C source and dkms.conf file to `/usr/src/bbswitch-VERSION/` (replace
VERSION with the current version of bbswitch which has been inserted for
`#MODULE_VERSION#`.
If you have DKMS installed, you can install bbswitch in such a way that it
survives kernel upgrades. It is recommended to remove older versions of bbswitch
by running `dkms remove -m bbswitch -v OLDVERSION --all` as root. To install
the new version, simply run:
# make -f Makefile.dkms
To uninstall it, run:
# make -f Makefile.dkms uninstall
Usage
-----
@ -64,6 +70,43 @@ unload the driver,
$ dmesg |tail -1
bbswitch: device 0000:01:00.0 is in use by driver 'nouveau', refusing OFF
Do **not** attempt to load a driver while the card is off or the card won't be
usable until the PCI configuration space has been recovered (for example, after
writing the contents manually or rebooting).
### Module options
The module has some options that control the behavior on loading and unloading:
`load_state` and `unload_state`. Valid values are `-1`, `0` and `1` meaning "do
not change the card state", "turn the card off" and "turn the card on"
respectively. For example, if you want to have `bbswitch` disable the card
immediately when loading the module while enabling the card on unload, load the
module with:
# modprobe bbswitch load_state=0 unload_state=1
The `unload_state` value can be changed on runtime, the above command yields the
same behavior as:
# modprobe bbswitch load_state=0
# echo 1 | tee /sys/module/bbswitch/parameters/unload_state
If not explictly set, the default behavior is not to change the power state of
the discrete video card which equals to `load_state=-1 unload_state=-1`.
### Disable card on boot
These options can be useful to disable the card on boot time. Depending on your
distribution, `/etc/modules`, `/etc/modules.conf` or some other file can be used
to load modules on boot time. Adding the below line to the file makes the card
get disabled on boot:
bbswitch load_state=0
You have to update your initial ramdisk (initrd) for the changes propagate to
the boot process. On Debian and Ubuntu, this can performed by running
`update-initramfs -u` as root.
Reporting bugs
--------------
@ -73,6 +116,21 @@ issues on this module in the issue tracker and provide the following details:
- The output of `dmesg | grep -C 10 bbswitch:`
- The kernel version `uname -a`
- Your distribution and version (if applicable)
- The output of `lspci -d10de: -vvv`
- The version of your Xorg and the driver
- The output of `acpidump` (run it as root, e.g. `sudo acpidump > acpidump.txt`)
- Submit your machine information on https://bugs.launchpad.net/bugs/752542;
the instructions are listed in the bug description. Summary: install the
packages containing `dmidecode`, `acpidump` and `iasl` and then run:
wget http://lekensteyn.nl/files/get-acpi-info.sh
sh get-acpi-info.sh
- Information about the ACPI handles associated with PCI devices. Since this is
a kernel module, you'll need kernel headers, gcc and automake. Commands:
git clone git://github.com/Lekensteyn/acpi-stuff.git --depth 1
cd acpi-stuff/acpi_dump_info
make
sudo make load
cat /proc/acpi/dump_info
Upload the generated tarball on the above Launchpad URL and provide a link to
the comment containing your report.

View File

@ -9,17 +9,22 @@
* Get status
* # cat /proc/acpi/bbswitch
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/pci.h>
#include <linux/acpi.h>
#include <linux/module.h>
#include <asm/uaccess.h>
#include <linux/suspend.h>
#include <linux/seq_file.h>
#include <linux/pm_runtime.h>
#define BBSWITCH_VERSION "0.5"
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Toggle the discrete graphics card");
MODULE_AUTHOR("Peter Lekensteyn <lekensteyn@gmail.com>");
MODULE_VERSION("0.2");
MODULE_VERSION(BBSWITCH_VERSION);
enum {
CARD_UNCHANGED = -1,
@ -67,14 +72,13 @@ static acpi_handle dis_handle;
/* used for keeping the PM event handler */
static struct notifier_block nb;
/* whether the card was off before suspend or not; on: 0, off: 1 */
int dis_before_suspend_disabled;
static int dis_before_suspend_disabled;
static char *buffer_to_string(const char buffer[], char *target) {
static char *buffer_to_string(const char *buffer, size_t n, char *target) {
int i;
for (i=0; i<sizeof(buffer); i++) {
sprintf(target + i * 5, "%02X,", buffer[i]);
for (i=0; i<n; i++) {
snprintf(target + i * 5, 5 * (n - i), "0x%02X,", buffer ? buffer[i] & 0xFF : 0);
}
target[sizeof(buffer) * 5] = '\0';
return target;
}
@ -110,12 +114,12 @@ static int acpi_call_dsm(acpi_handle handle, const char muid[16], int revid,
err = acpi_evaluate_object(handle, "_DSM", &input, &output);
if (err) {
char tmp[5 * max(sizeof(muid), sizeof(args))];
char muid_str[5 * 16];
char args_str[5 * 4];
printk(KERN_WARNING "bbswitch: failed to evaluate _DSM {%s} %X %X"
" {%s}: %s\n",
buffer_to_string(muid, tmp), revid, func,
buffer_to_string(args, tmp), acpi_format_exception(err));
pr_warn("failed to evaluate _DSM {%s} 0x%X 0x%X {%s}: %s\n",
buffer_to_string(muid, 16, muid_str), revid, func,
buffer_to_string(args, 4, args_str), acpi_format_exception(err));
return err;
}
@ -132,8 +136,8 @@ static int acpi_call_dsm(acpi_handle handle, const char muid[16], int revid,
*result |= (obj->buffer.pointer[3] << 24);
}
} else {
printk(KERN_WARNING "bbswitch: _DSM call yields an unsupported result"
" type: %X\n", obj->type);
pr_warn("_DSM call yields an unsupported result type: %#x\n",
obj->type);
}
kfree(output.pointer);
@ -163,8 +167,7 @@ static int bbswitch_optimus_dsm(void) {
// failure
return 1;
}
printk(KERN_DEBUG "bbswitch: Result of Optimus _DSM call: %08X\n",
result);
pr_debug("Result of Optimus _DSM call: %08X\n", result);
}
return 0;
}
@ -179,8 +182,7 @@ static int bbswitch_acpi_off(void) {
// failure
return 1;
}
printk(KERN_DEBUG "bbswitch: Result of _DSM call for OFF: %08X\n",
result);
pr_debug("Result of _DSM call for OFF: %08X\n", result);
}
return 0;
}
@ -190,13 +192,12 @@ static int bbswitch_acpi_on(void) {
char args[] = {1, 0, 0, 0};
u32 result = 0;
if (!acpi_call_dsm(dis_handle, acpi_nvidia_dsm_muid, 0x102, 0x3, args,
if (acpi_call_dsm(dis_handle, acpi_nvidia_dsm_muid, 0x102, 0x3, args,
&result)) {
// failure
return 1;
}
printk(KERN_DEBUG "bbswitch: Result of _DSM call for ON: %08X\n",
result);
pr_debug("Result of _DSM call for ON: %08X\n", result);
}
return 0;
}
@ -219,47 +220,54 @@ static void bbswitch_off(void) {
// to prevent the system from possibly locking up, don't disable the device
// if it's still in use by a driver (i.e. nouveau or nvidia)
if (dis_dev->driver) {
printk(KERN_WARNING "bbswitch: device %s is in use by driver '%s', "
"refusing OFF\n", dev_name(&dis_dev->dev), dis_dev->driver->name);
pr_warn("device %s is in use by driver '%s', refusing OFF\n",
dev_name(&dis_dev->dev), dis_dev->driver->name);
return;
}
printk(KERN_INFO "bbswitch: disabling discrete graphics\n");
pr_info("disabling discrete graphics\n");
if (bbswitch_optimus_dsm()) {
printk(KERN_WARNING "bbswitch: Optimus ACPI call failed, the device is"
" not disabled\n");
pr_warn("Optimus ACPI call failed, the device is not disabled\n");
return;
}
pci_save_state(dis_dev);
pci_clear_master(dis_dev);
pci_disable_device(dis_dev);
pci_set_power_state(dis_dev, PCI_D3hot);
pci_set_power_state(dis_dev, PCI_D3cold);
if (bbswitch_acpi_off())
printk(KERN_WARNING "bbswitch: The discrete card could not be disabled"
" by a _DSM call\n");
pr_warn("The discrete card could not be disabled by a _DSM call\n");
}
static void bbswitch_on(void) {
if (!is_card_disabled())
return;
printk(KERN_INFO "bbswitch: enabling discrete graphics\n");
pr_info("enabling discrete graphics\n");
if (bbswitch_acpi_on())
printk(KERN_WARNING "bbswitch: The discrete card could not be enabled"
" by a _DSM call\n");
pr_warn("The discrete card could not be enabled by a _DSM call\n");
pci_set_power_state(dis_dev, PCI_D0);
pci_restore_state(dis_dev);
if (pci_enable_device(dis_dev))
printk(KERN_WARNING "bbswitch: failed to enable %s\n",
dev_name(&dis_dev->dev));
pr_warn("failed to enable %s\n", dev_name(&dis_dev->dev));
pci_set_master(dis_dev);
}
/* power bus so we can read PCI configuration space */
static void dis_dev_get(void) {
if (dis_dev->bus && dis_dev->bus->self)
pm_runtime_get_sync(&dis_dev->bus->self->dev);
}
static void dis_dev_put(void) {
if (dis_dev->bus && dis_dev->bus->self)
pm_runtime_put_sync(&dis_dev->bus->self->dev);
}
static ssize_t bbswitch_proc_write(struct file *fp, const char __user *buff,
size_t len, loff_t *off) {
char cmd[8];
@ -270,19 +278,25 @@ static ssize_t bbswitch_proc_write(struct file *fp, const char __user *buff,
if (copy_from_user(cmd, buff, len))
return -EFAULT;
dis_dev_get();
if (strncmp(cmd, "OFF", 3) == 0)
bbswitch_off();
if (strncmp(cmd, "ON", 2) == 0)
bbswitch_on();
dis_dev_put();
return len;
}
static int bbswitch_proc_show(struct seq_file *seqfp, void *p) {
// show the card state. Example output: 0000:01:00:00 ON
dis_dev_get();
seq_printf(seqfp, "%s %s\n", dev_name(&dis_dev->dev),
is_card_disabled() ? "OFF" : "ON");
dis_dev_put();
return 0;
}
static int bbswitch_proc_open(struct inode *inode, struct file *file) {
@ -294,19 +308,24 @@ static int bbswitch_pm_handler(struct notifier_block *nbp,
switch (event_type) {
case PM_HIBERNATION_PREPARE:
case PM_SUSPEND_PREPARE:
dis_dev_get();
dis_before_suspend_disabled = is_card_disabled();
// enable the device before suspend to avoid the PCI config space from
// being saved incorrectly
if (dis_before_suspend_disabled)
bbswitch_on();
dis_dev_put();
break;
case PM_POST_HIBERNATION:
case PM_POST_SUSPEND:
case PM_POST_RESTORE:
// after suspend, the card is on, but if it was off before suspend,
// disable it again
if (dis_before_suspend_disabled)
if (dis_before_suspend_disabled) {
dis_dev_get();
bbswitch_off();
dis_dev_put();
}
break;
case PM_RESTORE_PREPARE:
// deliberately don't do anything as it does not occur before suspend
@ -328,56 +347,84 @@ static struct file_operations bbswitch_fops = {
static int __init bbswitch_init(void) {
struct proc_dir_entry *acpi_entry;
struct pci_dev *pdev = NULL;
int class = PCI_CLASS_DISPLAY_VGA << 8;
acpi_handle igd_handle = NULL;
while ((pdev = pci_get_class(class, pdev)) != NULL) {
pr_info("version %s\n", BBSWITCH_VERSION);
while ((pdev = pci_get_device(PCI_ANY_ID, PCI_ANY_ID, pdev)) != NULL) {
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
acpi_handle handle;
int pci_class = pdev->class >> 8;
handle = DEVICE_ACPI_HANDLE(&pdev->dev);
if (!handle)
if (pci_class != PCI_CLASS_DISPLAY_VGA &&
pci_class != PCI_CLASS_DISPLAY_3D)
continue;
if (pdev->vendor != PCI_VENDOR_ID_INTEL) {
handle = DEVICE_ACPI_HANDLE(&pdev->dev);
if (!handle) {
pr_warn("cannot find ACPI handle for VGA device %s\n",
dev_name(&pdev->dev));
continue;
}
acpi_get_name(handle, ACPI_FULL_PATHNAME, &buf);
if (pdev->vendor == PCI_VENDOR_ID_INTEL) {
igd_handle = handle;
pr_info("Found integrated VGA device %s: %s\n",
dev_name(&pdev->dev), (char *)buf.pointer);
} else {
dis_dev = pdev;
dis_handle = handle;
acpi_get_name(handle, ACPI_FULL_PATHNAME, &buf);
printk(KERN_INFO "bbswitch: Found discrete VGA device %s: %s\n",
pr_info("Found discrete VGA device %s: %s\n",
dev_name(&pdev->dev), (char *)buf.pointer);
}
kfree(buf.pointer);
}
if (dis_dev == NULL) {
printk(KERN_ERR "bbswitch: No discrete VGA device found\n");
pr_err("No discrete VGA device found\n");
return -ENODEV;
}
if (has_dsm_func(acpi_optimus_dsm_muid, 0x100, 0x1A)) {
dsm_type = DSM_TYPE_OPTIMUS;
printk(KERN_INFO "bbswitch: detected an Optimus _DSM function\n");
pr_info("detected an Optimus _DSM function\n");
} else if (has_dsm_func(acpi_nvidia_dsm_muid, 0x102, 0x3)) {
dsm_type = DSM_TYPE_NVIDIA;
printk(KERN_INFO "bbswitch: detected a nVidia _DSM function\n");
pr_info("detected a nVidia _DSM function\n");
} else {
printk(KERN_ERR "bbswitch: No suitable _DSM call found.\n");
return -ENODEV;
/* At least two Acer machines are known to use the intel ACPI handle
* with the legacy nvidia DSM */
dis_handle = igd_handle;
if (has_dsm_func(acpi_nvidia_dsm_muid, 0x102, 0x3)) {
dsm_type = DSM_TYPE_NVIDIA;
pr_info("detected a nVidia _DSM function on the"
" integrated video card\n");
} else {
pr_err("No suitable _DSM call found.\n");
return -ENODEV;
}
}
acpi_entry = proc_create("bbswitch", 0660, acpi_root_dir, &bbswitch_fops);
acpi_entry = proc_create("bbswitch", 0664, acpi_root_dir, &bbswitch_fops);
if (acpi_entry == NULL) {
printk(KERN_ERR "bbswitch: Couldn't create proc entry\n");
pr_err("Couldn't create proc entry\n");
return -ENOMEM;
}
dis_dev_get();
if (load_state == CARD_ON)
bbswitch_on();
else if (load_state == CARD_OFF)
bbswitch_off();
printk(KERN_INFO "bbswitch: Succesfully loaded. Discrete card %s is %s\n",
pr_info("Succesfully loaded. Discrete card %s is %s\n",
dev_name(&dis_dev->dev), is_card_disabled() ? "off" : "on");
dis_dev_put();
nb.notifier_call = &bbswitch_pm_handler;
register_pm_notifier(&nb);
@ -387,17 +434,23 @@ static int __init bbswitch_init(void) {
static void __exit bbswitch_exit(void) {
remove_proc_entry("bbswitch", acpi_root_dir);
dis_dev_get();
if (unload_state == CARD_ON)
bbswitch_on();
else if (unload_state == CARD_OFF)
bbswitch_off();
printk(KERN_INFO "bbswitch: Unloaded. Discrete card %s is %s\n",
pr_info("Unloaded. Discrete card %s is %s\n",
dev_name(&dis_dev->dev), is_card_disabled() ? "off" : "on");
dis_dev_put();
if (nb.notifier_call)
unregister_pm_notifier(&nb);
}
module_init(bbswitch_init);
module_exit(bbswitch_exit);
/* vim: set sw=4 ts=4 et: */