From: siefca Date: Sat, 25 Nov 2006 12:09:52 +0000 (+0000) Subject: - added Wake-On-Lan work-around for nForce ethernet drivers X-Git-Tag: 0.4.1.3~35 X-Git-Url: http://git.pld-linux.org/?a=commitdiff_plain;h=3b04fe1f0d8eedb9166d568fd0b6d1e66fcb7965;p=projects%2Frc-scripts.git - added Wake-On-Lan work-around for nForce ethernet drivers - added pci-config utility which allows power state manipulation and devices listing svn-id: @7989 --- diff --git a/AUTHORS b/AUTHORS index 2d9d5a65..7c8853c2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -29,8 +29,10 @@ Jan R Micha³ Kochanowicz fixes, progress() -Pawe³ Wilk +Pawe³ Wilk executing scripts on iface up/down + timezone setup & resolvesymlink + nVidia ethernet Wake-On-Lan work-around Robert ¦laski ATM support diff --git a/rc-scripts.spec.in b/rc-scripts.spec.in index 34c74f90..c25fb9ad 100644 --- a/rc-scripts.spec.in +++ b/rc-scripts.spec.in @@ -218,6 +218,7 @@ mv -f /etc/sysconfig/network-scripts/ifcfg-* /etc/sysconfig/interfaces %attr(755,root,root) %{_sbindir}/ppp-watch %attr(755,root,root) %{_sbindir}/netreport %attr(755,root,root) %{_sbindir}/setsysfont +%attr(755,root,root) %{_sbindir}/pci-config %attr(4755,root,root) %{_sbindir}/usernetctl %attr(755,root,root) %{_sbindir}/if* diff --git a/rc.d/init.d/functions b/rc.d/init.d/functions index cbb5b051..c4b9ddf1 100644 --- a/rc.d/init.d/functions +++ b/rc.d/init.d/functions @@ -884,6 +884,58 @@ relabel_selinux() { echo $SELINUX > $selinuxfs/enforce } +# Wake-On-Lan workaround for nForce ethernet drivers. +# To realy help it also requires patched kernel module. +# Written by Pawel Wilk using idea from Arjen Verweij, +# see http://atlas.et.tudelft.nl/verwei90/nforce2/wol.html +# +# Call it only when system halts/suspends, there is +# no return to the D0 power state after execution! +# +forcedeth_workaround() +{ + [ -x /sbin/ethtool ] || return 2 + [ -x /sbin/pci-config || return 2 + grep -qi forcedeth /proc/modules || return 0 + + # FIXME: put here condition - kernel/module version checking + # when the problem will be fixed in the driver + + typeset iface bus_dev_fn bus lookfor dev_index cur_state + + for iface in $(ip link show | awk -F'[ :]+' '/eth[0-9]+/ {print $2}') + do + if LC_ALL=C ethtool -i "${iface}" 2>&1 | egrep -qi 'driver:[[:blank:]]forcedeth'; then + case $(LC_ALL=C ethtool "${iface}" 2>&1 | awk 'tolower($1) ~ "wake-on:" {print $2}') in + *d*) continue ;; # 'd' letter means that the WON was DISABLED for interface + "") continue ;; # empty string means that the WON is not supported here + esac + bus_dev_fn=$(LC_ALL=C ethtool -i ${iface} 2>&1 | awk -F'[ :.]+' '/^bus-info:/ {printf ("%d %d %d",$3,$4,$5) }') + if [ -n "${bus_dev_fn}" -a "${bus_dev_fn}" != "0 0 0" ]; then + bus=$(echo "${bus_dev_fn}" | awk '{print $1}') + lookfor=$(echo "${bus_dev_fn}" | awk '{print "at bus "$1" device/function "$2"/"$3}') + dev_index=$(LC_ALL=C pci-config -B${bus} 2>&1 | grep -i "${lookfor}" | awk '/Device \#[0-9]+/ {print $2}') + if [ -n "${dev_index}" ]; then + show "Forcing sleep state for the nForce interface %s" ${iface} ; busy + ip link set ${iface} up 2>&1 >/dev/null # need it to be up + sleep 1 + pci-config -S -$dev_index 2>&1 >/dev/null + RESULT=$? + cur_state=$(pci-config -a -$dev_index 2>&1 | awk -F'[ \t:.]+' ' tolower($2$3) ~ "powerstate" {print tolower($4)}') + if [ "${cur_state}" != "d3" ]; then + RESULT=1 + fi + if [ $RESULT -gt 0 ]; then + fail + else + ok + fi + fi + fi + fi + done +} + # Remove duplicate entries from mtab (for vserver guest use only) clean_vserver_mtab() { :>/etc/mtab.clean diff --git a/rc.d/rc.shutdown b/rc.d/rc.shutdown index 525d6589..e1891d02 100755 --- a/rc.d/rc.shutdown +++ b/rc.d/rc.shutdown @@ -48,6 +48,9 @@ fi halt -w if ! is_yes "$VSERVER"; then + # Work-around for the nVidia drivers Wake-On-Lan functionality. + forcedeth_workaround + # Turn off swap, then unmount file systems. run_cmd "Turning off swap" swapoff -a diff --git a/src/Makefile.am b/src/Makefile.am index 48ca4b45..99588c6e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -25,7 +25,8 @@ sbin_PROGRAMS = \ netreport \ ppp-watch \ start-stop-daemon \ - usernetctl + usernetctl \ + pci-config EXTRA_PROGRAMS = \ testd @@ -34,7 +35,7 @@ EXTRA_DIST = $(sysconf_DATA) doexec_SOURCES = doexec.c -resolvesymlink_SOURCE = resolvesymlink.c +resolvesymlink_SOURCES = resolvesymlink.c ipcalc_SOURCES = ipcalc.c ipcalc_LDADD = -lpopt @@ -66,3 +67,5 @@ consoletype_SOURCES = consoletype.c start_stop_daemon_SOURCES = start-stop-daemon.c +pci-config_SOURCES = pci-config.c + diff --git a/src/pci-config.c b/src/pci-config.c new file mode 100644 index 00000000..a8f09cc7 --- /dev/null +++ b/src/pci-config.c @@ -0,0 +1,446 @@ +/* pci-config-space.c: Read the PCI configuration space. + + Read the PCI configuration space using the Intel bus-bridge interface + registers. This bypasses the BIOS32 entry, and thus we are not assured + of working on all systems. + + Copyright 1998-2002 by Donald Becker. + This software may be used and distributed according to the terms of + the GNU General Public License (GPL), incorporated herein by reference. + Contact the author for use under other terms. + + The author may be reached as becker@scyld.com, or C/O + Scyld Computing Corporation + 410 Severn Ave., Suite 210 + Annapolis MD 21403 + + Support and updates available at + http://www.scyld.com/diag/index.html + + Common-sense licensing statement: Using any portion of this program in + your own program means that you must give credit to the original author + and release the resulting code under the GPL. + */ + +static char *version_msg = +"pci-config.c:v2.03 4/15/2002 Donald Becker (becker@scyld.com)\n" +" http://www.scyld.com/diag/index.html\n"; +static char *usage_msg = +"Usage: pci-config [-aDfSvVW] [-# ]\n"; + +static char *long_usage_msg =" + + This program shows the contents of PCI configuration space. + It reads the hardware registers, and thus must be run as 'root'. + + Running this program with no options shows the installed PCI devices. + Each line is prefixed by its index which may be used with -# + e.g. \"pci-config -#3\" to specify the device to operate on. + + Commonly use options are + -# Operate only on DEVICE-INDEX e.g -#3 + + The operations on the selected device are + -a --show-addresses Show PCI address registers. + -S --sleep Put device to sleep (ACPI D3 state) + -W --wake Wake a sleeping device (ACPI D0 state) + + Less commonly used options are + -B --bus Show only devices on BUS. + -A --set-addresses Set PCI address register 1 to the ADDR. + -D --debug Show details of operations + -f --force Override checks and perform the operation + -u --usage Show this long usage message + -v --verbose Verbose mode + -V --version Display this program's version information + +"; + +#include +#include +#include +#include +#include +#include +#include +#if defined(__linux__) && __GNU_LIBRARY__ == 1 +#include /* Newer libraries use instead. */ +#else +#include +#endif +#if !defined(__OPTIMIZE__) +#error You must compile this driver with "-O"! +#endif + +struct option longopts[] = { + {"show-addresses", 0, 0, 'a'}, /* Show PCI address registers. */ + {"set-addresses", 1, 0, 'A'}, /* Show PCI address registers. */ + {"bus", 1, 0, 'B'}, /* Show only devices on BUS. */ + {"debug", 0, 0, 'D'}, /* Increase debug level. */ + {"force", 0, 0, 'f'}, /* Force operation. */ + {"set-WOL", 0, 0, 'M'}, /* Set to Wake-On-LAN mode. */ + {"sleep", 0, 0, 'S'}, /* Put device to sleep (ACPI D3 state). */ + {"usage", 0, 0, 'u'}, /* Show the long usage message. */ + {"verbose", 0, 0, 'v'}, /* Verbose mode */ + {"version", 0, 0, 'V'}, /* Display version number */ + {"wake-on-lan", 0, 0, 'W'}, /* Wake (set to D0 state) the device. */ + {"device-index", 1, 0, '#'}, /* Operate only on device INDEX. */ + { 0, 0, 0, 0 } +}; + +static int verbose=1, opt_a=0, opt_f=0, opt_wake=0, opt_set_WOL=0, debug=0; +static int opt_sleep = 0; +static long set_address = -1; + +static void show_addr_config(unsigned char pci_bus, unsigned char pci_dev_fn); +static void show_ext_caps(unsigned int *cfg_space, unsigned char pci_bus, + unsigned char pci_dev_fn); +static void show_one_device(unsigned char pci_bus, unsigned char pci_dev_fn, + int dev_num); + +static void cyclone_WOL(int pci_bus, int pci_dev_fn, void *pci_config_space); +static void acpi_wake(unsigned char pci_bus, unsigned char pci_dev_fn, + void *config); +static void acpi_sleep(unsigned char bus, unsigned char devfn, void *pci_cfg); +static int dump_mem_region(long addr); + +extern int pcibios_read_config_byte (unsigned char bus, unsigned char dev_fn, + unsigned char where, unsigned char *val); +int pcibios_read_config_word (unsigned char bus, unsigned char dev_fn, + unsigned char regnum, unsigned short *val); +extern int pcibios_read_config_dword (unsigned char bus, unsigned char dev_fn, + unsigned char regnum, unsigned int *val); +void pcibios_write_config_byte (unsigned char bus, unsigned char dev_fn, + unsigned char regnum, unsigned char val); +void pcibios_write_config_word (unsigned char bus, unsigned char dev_fn, + unsigned char regnum, unsigned short val); +void pcibios_write_config_dword (unsigned char bus, unsigned char dev_fn, + unsigned char regnum, unsigned int val); + + +int main(int argc, char **argv) +{ + int pci_bus = 0, pci_dev_fn; + int errflag = 0, show_version = 0; + int c, longind, card_num = 0; + int dev_num = 0; + + while ((c = getopt_long(argc, argv, "#:aA:b:B:DfMSuvVW", + longopts, &longind)) + != -1) + switch (c) { + case 'a': opt_a++; break; + case 'A': set_address = strtol(optarg, NULL, 16); break; + case 'b': printf("Setting bus to %s.\n", optarg); + case 'B': pci_bus = strtol(optarg, NULL, 0); break; + case 'D': debug++; break; + case 'f': opt_f++; break; + case 'M': opt_set_WOL++; break; + case 'S': opt_sleep++; break; + case 'u': printf("%s%s", usage_msg, long_usage_msg); return 0; + case 'v': verbose++; break; + case 'V': show_version++; break; + case 'W': opt_wake++; break; + case '#': card_num = atoi(optarg); break; + case '?': + errflag++; + } + if (errflag) { + fprintf(stderr, "%s%s", usage_msg, " Use -u for more information.\n"); + return 3; + } + + if (verbose) + printf(version_msg); + + /* Get access to all of I/O space. */ + if (iopl(3) < 0) { + perror("pci-config: iopl()"); + fprintf(stderr, "This program must be run as root.\n"); + return 2; + } + for (pci_dev_fn = 0; pci_dev_fn < 256; pci_dev_fn++) { + /*unsigned char cb;*/ + unsigned int pci_id; + + pcibios_read_config_dword(pci_bus, pci_dev_fn, 0, &pci_id); + if (pci_id == 0xffffffff) + continue; + dev_num++; + if (card_num == 0) { + printf("Device #%d at bus %d device/function %d/%d, %8.8x.\n", + dev_num, pci_bus, pci_dev_fn>>3, pci_dev_fn&7, pci_id); + } else if (card_num == dev_num) { + show_one_device(pci_bus, pci_dev_fn, dev_num); + } + if ((pci_dev_fn & 7) == 0) { + unsigned int cdw; + pcibios_read_config_dword(pci_bus, pci_dev_fn, 3*4, &cdw); + if ((cdw & 0x00800000) == 0) + pci_dev_fn += 7; + } + } + + return 0; +} + +static void show_one_device(unsigned char pci_bus, unsigned char pci_dev_fn, + int dev_num) +{ + unsigned int config[64]; + int i; + int pci_id; + + printf("Device #%d at bus %d device/function %d/%d.", + dev_num, pci_bus, pci_dev_fn>>3, pci_dev_fn&7); + for (i = 0; i < 64; i++) { + pcibios_read_config_dword(pci_bus, pci_dev_fn, i<<2, &config[i]); + printf("%s%8.8x", i % 8 == 0 ? "\n " : " ", config[i]); + } + printf("\n"); + for (i = 0; i < 5; i++) { + unsigned int pciaddr = config[4 + i]; + if (pciaddr) + printf(" Base Address %d: %s at %8.8x.\n", + i, pciaddr & 1 ? "I/O" : "Memory", pciaddr & ~1); + } + if (set_address >= 0) { + fprintf(stderr, "Setting PCI address register 1 to 0x%lx.\n", + set_address); + pcibios_write_config_dword(pci_bus, pci_dev_fn, 0x14, set_address); + pcibios_write_config_dword(pci_bus, pci_dev_fn, 0x04, config[1] | 3); + } + if (opt_a) + show_addr_config(pci_bus, pci_dev_fn); + pci_id = config[0]; + if (config[1] & 0x00100000) + show_ext_caps(config, pci_bus, pci_dev_fn); + if (config[10]) { + char *cis_addr_space[] = {"PCI configuration space", "BAR 0", "BAR 1", "BAR 2", "BAR 3", }; + int space = config[10] & 7; + printf(" CardBus CIS pointer 0x%4.4x (%s), address %x.\n", config[10], + cis_addr_space[space], config[4 + (space-1)]); + if (space > 0 && space < 4) + dump_mem_region(config[4 + ((space-1))]); + } + if (opt_sleep) + acpi_sleep(pci_bus, pci_dev_fn, config); + if (opt_wake) + acpi_wake(pci_bus, pci_dev_fn, config); + if (opt_set_WOL) + if (pci_id == 0x905510b7) + cyclone_WOL(pci_bus, pci_dev_fn, config); +} + +static void show_addr_config(unsigned char pci_bus, unsigned char pci_dev_fn) +{ + int i; + unsigned int pciaddr, cdw; + for (i = 0; i < 5; i++) { + int cfg_i = 0x10 + (i<<2); + pcibios_read_config_dword(pci_bus, pci_dev_fn, cfg_i, &pciaddr); + pcibios_write_config_dword(pci_bus, pci_dev_fn, cfg_i, 0xffffffff); + pcibios_read_config_dword(pci_bus, pci_dev_fn, cfg_i, &cdw); + if (cdw == 0) + break; + printf(" Address %d %s at %8.8x, decoded bits are %8.8x.\n", + i, cdw & 1 ? "is I/O" : "memory", pciaddr & ~1, ~cdw); + pcibios_write_config_dword(pci_bus, pci_dev_fn, cfg_i, pciaddr); + } + pcibios_read_config_dword(pci_bus, pci_dev_fn, 0x30, &pciaddr); + pcibios_write_config_dword(pci_bus, pci_dev_fn, 0x30, 0xfffffffe); + pcibios_read_config_dword(pci_bus, pci_dev_fn, 0x30, &cdw); + pcibios_write_config_dword(pci_bus, pci_dev_fn, 0x30, pciaddr); + if (cdw) + printf(" BIOS ROM at %8.8x, decoded bits are %8.8x.\n", pciaddr, cdw); + else + printf(" No BIOS extension (boot ROM).\n"); + return; +} +static void show_ext_caps(unsigned int *cfg_space, unsigned char pci_bus, + unsigned char pci_dev_fn) +{ + unsigned char *pcfg = (void *)cfg_space; + int cap_idx = cfg_space[13] & 0xff; + + printf(" Extended capabilities, first structure at offset 0x%x.\n", + cap_idx); + for (; cap_idx; cap_idx = pcfg[cap_idx + 1]) { + printf(" Extended PCI capability type %d at 0x%2.2x, next %d.\n", + pcfg[cap_idx], cap_idx, pcfg[cap_idx + 1]); + if (pcfg[cap_idx] == 1) { + printf(" Power management entry ver. %d: Capabilities %2.2x%2.2x" + ", Ctrl %2.2x%2.2x, Event %2.2x%2.2x.\n", + pcfg[cap_idx + 2] & 7, + pcfg[cap_idx + 3], pcfg[cap_idx + 2], + pcfg[cap_idx + 5], pcfg[cap_idx + 4], + pcfg[cap_idx + 7], pcfg[cap_idx + 6]); + printf(" Power state D%d.\n", pcfg[cap_idx + 4] & 3); + } + } +} + + +static int acpi_find(unsigned char pci_bus, unsigned char pci_dev_fn, + void *config) +{ + unsigned char *pcfg = (void *)config; + if (pcfg[6] & 0x10) { + int cap_idx = pcfg[0x34]; + + printf(" Extended capabilities, first structure at offset 0x%x.\n", + cap_idx); + for (; cap_idx; cap_idx = pcfg[cap_idx + 1]) { + if (pcfg[cap_idx] == 1) + return cap_idx; + } + } + return 0; +} + +static void acpi_wake(unsigned char pci_bus, unsigned char pci_dev_fn, + void *pci_config_space) +{ + unsigned char *config = pci_config_space; + unsigned short *configw = pci_config_space; + unsigned int *configdw = pci_config_space; + int pwr_idx = acpi_find(pci_bus, pci_dev_fn, config); + unsigned short pwr_command = configw[(pwr_idx + 4)>>1]; + int i; + + if (debug) + printf("Power index is %#x.\n", pwr_idx); + + printf(" Waking up an ACPI device. Currently powered %s, " + "I/O %#x IRQ %d.\n" + " Updating the power state of %4.4x->%4.4x.\n", + pwr_command & 3 ? "down" : "up", configdw[0x10>>2], config[0x3c], + pwr_command, pwr_command & ~3); + pcibios_write_config_word(pci_bus, pci_dev_fn, pwr_idx + 4, + pwr_command & ~3); + /* Many devices must have their PCI register state restored when changing + from D3 state. */ + for (i = 0x10; i <= 0x20; i+=4) + pcibios_write_config_dword(pci_bus, pci_dev_fn, i, configdw[i >> 2]); + /* PCI_ROM_ADDRESS, interrupt line, cache line size, latency timer */ + pcibios_write_config_dword(pci_bus, pci_dev_fn, 0x30, configdw[0x30 >> 2]); + pcibios_write_config_byte(pci_bus, pci_dev_fn, 0x3c, config[0x3c]); + pcibios_write_config_byte(pci_bus, pci_dev_fn, 0x0c, config[0x0c]); + pcibios_write_config_byte(pci_bus, pci_dev_fn, 0x0d, config[0x0d]); + /* Finally, restore the command register. */ + pcibios_write_config_word(pci_bus, pci_dev_fn, 0x04, configw[4>>1]); +} + +static void acpi_sleep(unsigned char bus, unsigned char devfn, + void *pci_config_space) +{ + unsigned short *configw = pci_config_space; + int pwr_idx = acpi_find(bus, devfn, pci_config_space); + unsigned short pwr_command = configw[(pwr_idx + 4)>>1]; + pcibios_write_config_word(bus, devfn, pwr_idx + 4, pwr_command | 0x0103); +} + +/* Put the 3Com Cyclone e.g. 3c905B series into Wake On LAN mode. */ +static void cyclone_WOL(int pci_bus, int pci_dev_fn, void *config) +{ + int pwr_idx = acpi_find(pci_bus, pci_dev_fn, config); + unsigned short pwr_command = ((unsigned short *)config)[(pwr_idx + 4)>>1]; + long ioaddr = ((int *)config)[0x10]; + + acpi_wake(pci_bus, pci_dev_fn, config); + + outw(0x801f, ioaddr + 0x0e); /* Set RxFilter to accept frames. */ + outw((1<<11) + 7, ioaddr + 0x0e); + outw(7, ioaddr + 0x0c); + printf(" Window 7 Power Management Event is %4.4x.\n", + inw(ioaddr + 0x0c)); + printf(" Changing the power state from %4.4x to 0103.\n", pwr_command); + outw(0x2000, ioaddr + 0x0e); /* RxEnable. */ + pcibios_write_config_word(pci_bus, pci_dev_fn, 0xe0, pwr_command | 0x8103); +} + + +#define PCI_CONFIG_ADDR 0x0cf8 +#define PCI_CONFIG_DATA 0x0cfc + +int pcibios_read_config_byte (unsigned char bus, unsigned char dev_fn, + unsigned char regnum, unsigned char *val) +{ + outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), + PCI_CONFIG_ADDR); + *val = inb(PCI_CONFIG_DATA + (regnum & 3)); + return 0; +} +int pcibios_read_config_word (unsigned char bus, unsigned char dev_fn, + unsigned char regnum, unsigned short *val) +{ + outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), + PCI_CONFIG_ADDR); + *val = inw(PCI_CONFIG_DATA + (regnum & 2)); + return 0; +} +int pcibios_read_config_dword (unsigned char bus, unsigned char dev_fn, + unsigned char regnum, unsigned int *val) +{ + outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), + PCI_CONFIG_ADDR); + *val = inl(PCI_CONFIG_DATA); + return 0; +} +void pcibios_write_config_byte (unsigned char bus, unsigned char dev_fn, + unsigned char regnum, unsigned char val) +{ + outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), + PCI_CONFIG_ADDR); + outb(val, PCI_CONFIG_DATA + (regnum & 3)); + return; +} +void pcibios_write_config_word (unsigned char bus, unsigned char dev_fn, + unsigned char regnum, unsigned short val) +{ + outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), + PCI_CONFIG_ADDR); + outw(val, PCI_CONFIG_DATA + (regnum & 2)); + return; +} +void pcibios_write_config_dword (unsigned char bus, unsigned char dev_fn, + unsigned char regnum, unsigned int val) +{ + outl(0x80000000 | (bus<<16) | (dev_fn << 8) | (regnum & 0xfc), + PCI_CONFIG_ADDR); + outl(val, PCI_CONFIG_DATA); + return; +} + + +/* Map the board shared memory into our address space -- this code is + a good example of non-kernel access to devices on the PCI bus. */ +static int dump_mem_region(long addr) +{ + unsigned short *shared_mem; + int i; + int memfd = open("/dev/kmem", O_RDWR); + + if (memfd < 0) { + perror("/dev/kmem (shared memory)"); + return 2; + } else + printf("Opened /dev/kmem for PCI memory.\n"); + shared_mem = mmap(0, 0x8000, PROT_READ|PROT_WRITE, + MAP_SHARED, memfd, addr); + printf("Shared memory at %#x (%#x).\n", (int)addr, (int)shared_mem); + for (i = 0; i < 100; i++) + printf(" %4.4x", shared_mem[i]); + close(memfd); + printf(" ...\n"); + return 0; +} + +/* + * Local variables: + * compile-command: "cc -O -Wall -o pci-config pci-config.c" + * tab-width: 4 + * c-indent-level: 4 + * c-basic-offset: 4 + * End: + */