[PATCH] PnP BIOS update #8

Thomas Hood (jdthood@mail.com)
02 Nov 2001 18:11:21 -0500


Here's a new improved version of the patch to the
PnP BIOS driver (13-ac6) which again does:

1) Minor documentation changes
2) One minor bugfix (devs count wrong if error encountered)
3) Yet more paranoid checking of PnP BIOS results and
more verbose error reporting
4) Slap spinlocks around device list accesses.
Added function pnpbios_get_device,
to replace pnpbios_find_device

This has been tested by me; and there are no tricky changes,
so this should be safe to put in.

TODO: locking of driver list accesses?
// Thomas

The patch:
--- linux-2.4.13-ac6/drivers/pnp/pnp_bios.c Fri Oct 26 18:13:48 2001
+++ linux-2.4.13-ac6-fix/drivers/pnp/pnp_bios.c Fri Nov 2 16:45:11 2001
@@ -4,7 +4,7 @@
* Originally (C) 1998 Christian Schmidt (chr.schmidt@tu-bs.de)
* Modifications (c) 1998 Tom Lees <tom@lpsg.demon.co.uk>
* Minor reorganizations by David Hinds <dahinds@users.sourceforge.net>
- * More modifications by Thomas Hood <jdthood_AT_yahoo.co.uk>
+ * Modifications (c) 2001 by Thomas Hood <jdthood@mail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
@@ -46,19 +46,16 @@
#include <linux/spinlock.h>
#include <asm/system.h>

-/* PnP bios signature: "$PnP" */
-#define PNP_SIGNATURE (('$' << 0) + ('P' << 8) + ('n' << 16) + ('P' << 24))

/*
- * Forward declarations
+ *
+ * PNP BIOS LOW LEVEL INTERFACE
+ *
*/

-static void pnpbios_update_devlist( u8 nodenum, struct pnp_bios_node *data );
+/* PnP BIOS signature: "$PnP" */
+#define PNP_SIGNATURE (('$' << 0) + ('P' << 8) + ('n' << 16) + ('P' << 24))

-/*
- * This is the standard structure used to identify the entry point
- * to the Plug and Play bios
- */
#pragma pack(1)
union pnp_bios_expansion_header {
struct {
@@ -81,9 +78,6 @@
};
#pragma pack()

-/*
- * Local Variables
- */
static struct {
u16 offset;
u16 segment;
@@ -91,13 +85,13 @@

static union pnp_bios_expansion_header * pnp_bios_hdr = NULL;

-/* The PnP entries in the GDT */
-#define PNP_GDT 0x0060
-#define PNP_CS32 (PNP_GDT+0x00) /* segment for calling fn */
-#define PNP_CS16 (PNP_GDT+0x08) /* code segment for bios */
-#define PNP_DS (PNP_GDT+0x10) /* data segment for bios */
-#define PNP_TS1 (PNP_GDT+0x18) /* transfer data segment */
-#define PNP_TS2 (PNP_GDT+0x20) /* another data segment */
+/* The PnP BIOS entries in the GDT */
+#define PNP_GDT (0x0060)
+#define PNP_CS32 (PNP_GDT+0x00) /* segment for calling fn */
+#define PNP_CS16 (PNP_GDT+0x08) /* code segment for BIOS */
+#define PNP_DS (PNP_GDT+0x10) /* data segment for BIOS */
+#define PNP_TS1 (PNP_GDT+0x18) /* transfer data segment */
+#define PNP_TS2 (PNP_GDT+0x20) /* another data segment */

/*
* These are some opcodes for a "static asmlinkage"
@@ -135,24 +129,6 @@
set_limit (gdt [(selname) >> 3], size)

/*
- * Callable PnP BIOS functions
- */
-#define PNP_GET_NUM_SYS_DEV_NODES 0x00
-#define PNP_GET_SYS_DEV_NODE 0x01
-#define PNP_SET_SYS_DEV_NODE 0x02
-#define PNP_GET_EVENT 0x03
-#define PNP_SEND_MESSAGE 0x04
-#define PNP_GET_DOCKING_STATION_INFORMATION 0x05
-#define PNP_SET_STATIC_ALLOCED_RES_INFO 0x09
-#define PNP_GET_STATIC_ALLOCED_RES_INFO 0x0a
-#define PNP_GET_APM_ID_TABLE 0x0b
-#define PNP_GET_PNP_ISA_CONFIG_STRUC 0x40
-#define PNP_GET_ESCD_INFO 0x41
-#define PNP_READ_ESCD 0x42
-#define PNP_WRITE_ESCD 0x43
-
-
-/*
* At some point we want to use this stack frame pointer to unwind
* after PnP BIOS oopses.
*/
@@ -161,7 +137,7 @@
u32 pnp_bios_fault_eip;
u32 pnp_bios_is_utter_crap = 0;

-static spinlock_t pnp_bios_lock;
+static spinlock_t pnp_bios_lock = SPIN_LOCK_UNLOCKED;

static inline u16 call_pnp_bios(u16 func, u16 arg1, u16 arg2, u16 arg3,
u16 arg4, u16 arg5, u16 arg6, u16 arg7)
@@ -170,15 +146,13 @@
u16 status;

/*
- * PnPBIOS is generally not terribly re-entrant.
- * Also don't rely on it to save everything correctly
- *
- * On some boxes IRQ's during PnP bios calls seem fatal
+ * PnP BIOSes are generally not terribly re-entrant.
+ * Also, don't rely on them to save everything correctly.
*/
-
if(pnp_bios_is_utter_crap)
return PNP_FUNCTION_NOT_SUPPORTED;
-
+
+ /* On some boxes IRQ's during PnP BIOS calls are deadly. */
spin_lock_irqsave(&pnp_bios_lock, flags);
__cli();
__asm__ __volatile__(
@@ -212,7 +186,7 @@
);
spin_unlock_irqrestore(&pnp_bios_lock, flags);

- /* If we got here and this is set the pnp bios faulted on us.. */
+ /* If we get here and this is set then the PnP BIOS faulted on us. */
if(pnp_bios_is_utter_crap)
{
printk(KERN_ERR "PnPBIOS: Warning! Your PnP BIOS caused a fatal error. Attempting to continue.\n");
@@ -223,6 +197,7 @@
return status;
}

+
/*
*
* UTILITY FUNCTIONS
@@ -245,12 +220,30 @@
return (pnp_bios_hdr != NULL);
}

+/* Forward declaration */
+static void pnpbios_update_devlist( u8 nodenum, struct pnp_bios_node *data );
+
+
/*
*
* PnP BIOS ACCESS FUNCTIONS
*
*/

+#define PNP_GET_NUM_SYS_DEV_NODES 0x00
+#define PNP_GET_SYS_DEV_NODE 0x01
+#define PNP_SET_SYS_DEV_NODE 0x02
+#define PNP_GET_EVENT 0x03
+#define PNP_SEND_MESSAGE 0x04
+#define PNP_GET_DOCKING_STATION_INFORMATION 0x05
+#define PNP_SET_STATIC_ALLOCED_RES_INFO 0x09
+#define PNP_GET_STATIC_ALLOCED_RES_INFO 0x0a
+#define PNP_GET_APM_ID_TABLE 0x0b
+#define PNP_GET_PNP_ISA_CONFIG_STRUC 0x40
+#define PNP_GET_ESCD_INFO 0x41
+#define PNP_READ_ESCD 0x42
+#define PNP_WRITE_ESCD 0x43
+
/*
* Call PnP BIOS with function 0x00, "get number of system device nodes"
*/
@@ -274,8 +267,8 @@
}

/*
- * Note that some PnP BIOSes (on Sony Vaio laptops) die a horrible
- * death if they are asked to access the "current" configuration
+ * Note that some PnP BIOSes (e.g., on Sony Vaio laptops) die a horrible
+ * death if they are asked to access the "current" configuration.
* Therefore, if it's a matter of indifference, it's better to call
* get_dev_node() and set_dev_node() with boot=1 rather than with boot=0.
*/
@@ -309,6 +302,7 @@
return status;
}

+
/*
* Call PnP BIOS with function 0x02, "set system device node"
* Input: *nodenum = desired node,
@@ -504,9 +498,10 @@
}
#endif

+
/*
*
- * PnP DOCKING FUNCTIONS
+ * DOCKING FUNCTIONS
*
*/

@@ -626,7 +621,8 @@
complete_and_exit(&unload_sem, 0);
}

-#endif
+#endif /* CONFIG_HOTPLUG */
+

/*
*
@@ -781,18 +777,43 @@
return;
}

+
/*
*
- * PnP BIOS PUBLIC DEVICE MANAGEMENT LAYER FUNCTIONS
+ * DEVICE LIST MANAGEMENT FUNCTIONS
+ *
*
+ * Some of these are exported to give public access
+ *
+ * Question: Why maintain a device list when the PnP BIOS can
+ * list devices for us? Answer: Some PnP BIOSes can't report
+ * the current configuration, only the boot configuration.
+ * The boot configuration can be changed, so we need to keep
+ * a record of what the configuration was when we booted;
+ * presumably it continues to describe the current config.
+ * For those BIOSes that can change the current config, we
+ * keep the information in the devlist up to date.
+ *
+ * Note that it is currently assumed that the list does not
+ * grow or shrink in size after init time, and slot_name
+ * never changes.
*/

static LIST_HEAD(pnpbios_devices);

+static spinlock_t pnpbios_devices_lock = SPIN_LOCK_UNLOCKED;
+
static int inline pnpbios_insert_device(struct pci_dev *dev)
{
- /* FIXME: Need to check for re-add of existing node */
+
+ /*
+ * FIXME: Check for re-add of existing node;
+ * return -1 if node already present
+ */
+
+ /* We don't lock because we only do this at init time */
list_add_tail(&dev->global_list, &pnpbios_devices);
+
return 0;
}

@@ -819,6 +840,10 @@
#undef CHAR
#undef HEX

+/*
+ * Build a linked list of pci_devs in order of ascending node number.
+ * Called only at init time.
+ */
static void __init pnpbios_build_devlist(void)
{
int i;
@@ -839,14 +864,20 @@
if (!node)
return;

- for(i=0,nodenum=0;i<0xff && nodenum!=0xff;i++) {
+ for(i=0,nodenum=0; i<0xff && nodenum!=0xff; i++) {
int thisnodenum = nodenum;
/* For now we build the list from the "boot" config
* because asking for the "current" config causes
- * some BIOSes to crash.
- */
- if (pnp_bios_get_dev_node((u8 *)&nodenum, (char )1 , node))
+ * some BIOSes to crash. */
+ if (pnp_bios_get_dev_node((u8 *)&nodenum, (char )1 , node)) {
+ printk(KERN_WARNING "PnPBIOS: PnP BIOS reported error on attempt to get dev node.\n");
+ break;
+ }
+ /* The BIOS returns with nodenum = the next node number */
+ if (nodenum < thisnodenum) {
+ printk(KERN_WARNING "PnPBIOS: Node number is out of sequence. Naughty BIOS!\n");
break;
+ }
nodes_got++;
dev = pnpbios_kmalloc(sizeof (struct pci_dev), GFP_KERNEL);
if (!dev)
@@ -858,7 +889,8 @@
pnpbios_node_resource_data_to_dev(node,dev);
if(pnpbios_insert_device(dev)<0)
kfree(dev);
- devs++;
+ else
+ devs++;
}
kfree(node);

@@ -866,66 +898,107 @@
nodes_got, nodes_got != 1 ? "s" : "", devs);
}

-static struct pci_dev *pnpbios_find_device_by_nodenum( u8 nodenum )
+/*
+ * Return pointer to device node after *prev with name pnpid[]
+ *
+ * Deprecated for external use, since the information in
+ * the node may change. Use pnpbios_get_device instead.
+ */
+struct pci_dev *pnpbios_find_device(char *pnpid, struct pci_dev *prev)
{
struct pci_dev *dev;
+ int nodenum;
+
+ nodenum = 0;
+ if(prev)
+ nodenum=prev->devfn + 1;

+ /*
+ * We don't lock. We assume that the list can be
+ * traversed and slot_name searched at any time,
+ * since this is static information.
+ */
pnpbios_for_each_dev(dev) {
- if(dev->devfn == nodenum)
- return dev;
+ if(dev->devfn >= nodenum) {
+ if(memcmp(dev->slot_name, pnpid, 7)==0)
+ goto out;
+ }
}
+ dev = (struct pci_dev *)NULL;

- return NULL;
+out:
+ return dev;
}

-static void pnpbios_update_devlist( u8 nodenum, struct pnp_bios_node *data )
-{
- struct pci_dev *dev = pnpbios_find_device_by_nodenum( nodenum );
- if ( dev ) {
- pnpbios_node_resource_data_to_dev(data,dev);
- }
-
- return;
-}
+EXPORT_SYMBOL(pnpbios_find_device);

/*
+ * Copy device node after *prev with name pnpid[] to *dev
+ * Return 1 for success, 0 for failure.
*
- * PUBLIC INTERFACE FUNCTIONS to PnP BIOS ENUMERATION
- *
+ * Recommended for external use
*/
+int pnpbios_get_device(char *pnpid, struct pci_dev *prev, struct pci_dev *dev)
+{
+ unsigned long flags;
+ struct pci_dev *node = pnpbios_find_device(pnpid, prev);

-/*
- * Find device in list
- */
-struct pci_dev *pnpbios_find_device(char *pnpid, struct pci_dev *prev)
+ if (node == NULL) return 0;
+
+ /* We lock to prevent updates while we are making a copy */
+ spin_lock_irqsave(&pnpbios_devices_lock, flags);
+ memcpy(dev,node,sizeof(struct pci_dev));
+ spin_unlock_irqrestore(&pnpbios_devices_lock, flags);
+
+ return 1;
+}
+
+EXPORT_SYMBOL(pnpbios_get_device);
+
+static struct pci_dev *pnpbios_find_device_by_nodenum( u8 nodenum )
{
struct pci_dev *dev;
- int nodenum;
-
- nodenum = 0;
- if(prev)
- nodenum=prev->devfn + 1; /* Encode node number here */

pnpbios_for_each_dev(dev) {
- if(dev->devfn >= nodenum) {
- if(memcmp(dev->slot_name, pnpid, 7)==0)
- return dev;
- }
+ if(dev->devfn == nodenum)
+ goto out;
}
+ dev = (struct pci_dev *)NULL;

- return NULL;
+out:
+ return dev;
+}
+
+static void pnpbios_update_devlist( u8 nodenum, struct pnp_bios_node *data )
+{
+ struct pci_dev *dev;
+
+ dev = pnpbios_find_device_by_nodenum( nodenum );
+ if ( dev ) {
+ unsigned long flags;
+ spin_lock_irqsave(&pnpbios_devices_lock, flags);
+ pnpbios_node_resource_data_to_dev(data,dev);
+ spin_unlock_irqrestore(&pnpbios_devices_lock, flags);
+ }
+
+ return;
}

-EXPORT_SYMBOL(pnpbios_find_device);

/*
- * Registration of PnPBIOS drivers and handling of hot-pluggable devices.
+ *
+ * DRIVER REGISTRATION FUNCTIONS
+ *
+ *
+ * Exported to give public access
+ *
*/

static LIST_HEAD(pnpbios_drivers);

/**
- * pnpbios_match_device - Tell if a PnPBIOS device structure has a matching PnPBIOS device id structure
+ * pnpbios_match_device - Tell if a PnPBIOS device structure has
+ * a matching PnPBIOS device id structure
* @ids: array of PnPBIOS device id structures to search in
* @dev: the PnPBIOS device structure to match against
*
@@ -1007,7 +1080,6 @@
* each device it was responsible for, and marks those devices as
* driverless.
*/
-
void
pnpbios_unregister_driver(struct pnpbios_driver *drv)
{
@@ -1025,10 +1097,14 @@

EXPORT_SYMBOL(pnpbios_unregister_driver);

+
/*
*
* RESOURCE RESERVATION FUNCTIONS
*
+ *
+ * Used only at init time
+ *
*/

static void __init pnpbios_reserve_ioport_range(char *pnpid, int start, int end)
@@ -1093,11 +1169,11 @@
dev->resource[i].end
);
} else if (dev->resource[i].flags & IORESOURCE_MEM) {
- /* memory */
+ /* iomem */
/* For now do nothing */
continue;
} else {
- /* Neither ioport nor memory */
+ /* Neither ioport nor iomem */
/* Do nothing */
continue;
}
@@ -1106,13 +1182,7 @@
return;
}

-/*
- * Reserve resources used by system board devices
- *
- * We really shouldn't just _reserve_ these regions since
- * that prevents the device drivers from claiming them.
- */
-static void __init pnpbios_reserve_resources( void )
+static void __init pnpbios_reserve_board_resources( void )
{
struct pci_dev *dev;

@@ -1129,11 +1199,11 @@
return;
}

+
/*
*
* INIT AND EXIT
*
- *
*/

extern int is_sony_vaio_laptop;
@@ -1210,7 +1280,7 @@
}

pnpbios_build_devlist();
- pnpbios_reserve_resources();
+ pnpbios_reserve_board_resources();
#ifdef CONFIG_PROC_FS
pnpbios_proc_init();
#endif
@@ -1225,7 +1295,7 @@

MODULE_LICENSE("GPL");

-/* We have to run it early and specifically in non modular.. */
+/* We have to run it early and not as a module. */
module_init(pnpbios_init);

#ifdef CONFIG_HOTPLUG

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/