summaryrefslogtreecommitdiff
path: root/gpxe/src/drivers/bus/isa.c
diff options
context:
space:
mode:
Diffstat (limited to 'gpxe/src/drivers/bus/isa.c')
-rw-r--r--gpxe/src/drivers/bus/isa.c175
1 files changed, 175 insertions, 0 deletions
diff --git a/gpxe/src/drivers/bus/isa.c b/gpxe/src/drivers/bus/isa.c
new file mode 100644
index 00000000..a4105fd0
--- /dev/null
+++ b/gpxe/src/drivers/bus/isa.c
@@ -0,0 +1,175 @@
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <io.h>
+#include <gpxe/isa.h>
+
+/*
+ * isa.c implements a "classical" port-scanning method of ISA device
+ * detection. The driver must provide a list of probe addresses
+ * (probe_addrs), together with a function (probe_addr) that can be
+ * used to test for the physical presence of a device at any given
+ * address.
+ *
+ * Note that this should probably be considered the "last resort" for
+ * device probing. If the card supports ISAPnP or EISA, use that
+ * instead. Some cards (e.g. the 3c509) implement a proprietary
+ * ISAPnP-like mechanism.
+ *
+ * The ISA probe address list can be overridden by config.h; if the
+ * user specifies ISA_PROBE_ADDRS then that list will be used first.
+ * (If ISA_PROBE_ONLY is defined, the driver's own list will never be
+ * used).
+ */
+
+/*
+ * User-supplied probe address list
+ *
+ */
+static isa_probe_addr_t isa_extra_probe_addrs[] = {
+#ifdef ISA_PROBE_ADDRS
+ ISA_PROBE_ADDRS
+#endif
+};
+#define ISA_EXTRA_PROBE_ADDR_COUNT \
+ ( sizeof ( isa_extra_probe_addrs ) / sizeof ( isa_extra_probe_addrs[0] ) )
+
+#define ISA_IOIDX_MIN( driver ) ( -ISA_EXTRA_PROBE_ADDR_COUNT )
+#ifdef ISA_PROBE_ONLY
+#define ISA_IOIDX_MAX( driver ) ( -1 )
+#else
+#define ISA_IOIDX_MAX( driver ) ( (int) (driver)->addr_count - 1 )
+#endif
+
+#define ISA_IOADDR( driver, ioidx ) \
+ ( ( (ioidx) < 0 ) ? \
+ isa_extra_probe_addrs[ (ioidx) + ISA_EXTRA_PROBE_ADDR_COUNT ] : \
+ (driver)->probe_addrs[(ioidx)] )
+
+static struct isa_driver isa_drivers[0]
+ __table_start ( struct isa_driver, isa_driver );
+static struct isa_driver isa_drivers_end[0]
+ __table_end ( struct isa_driver, isa_driver );
+
+static void isabus_remove ( struct root_device *rootdev );
+
+/**
+ * Probe an ISA device
+ *
+ * @v isa ISA device
+ * @ret rc Return status code
+ */
+static int isa_probe ( struct isa_device *isa ) {
+ int rc;
+
+ DBG ( "Trying ISA driver %s at I/O %04x\n",
+ isa->driver->name, isa->ioaddr );
+
+ if ( ( rc = isa->driver->probe ( isa ) ) != 0 ) {
+ DBG ( "...probe failed\n" );
+ return rc;
+ }
+
+ DBG ( "...device found\n" );
+ return 0;
+}
+
+/**
+ * Remove an ISA device
+ *
+ * @v isa ISA device
+ */
+static void isa_remove ( struct isa_device *isa ) {
+ isa->driver->remove ( isa );
+ DBG ( "Removed ISA%04x\n", isa->ioaddr );
+}
+
+/**
+ * Probe ISA root bus
+ *
+ * @v rootdev ISA bus root device
+ *
+ * Scans the ISA bus for devices and registers all devices it can
+ * find.
+ */
+static int isabus_probe ( struct root_device *rootdev ) {
+ struct isa_device *isa = NULL;
+ struct isa_driver *driver;
+ int ioidx;
+ int rc;
+
+ for ( driver = isa_drivers ; driver < isa_drivers_end ; driver++ ) {
+ for ( ioidx = ISA_IOIDX_MIN ( driver ) ;
+ ioidx <= ISA_IOIDX_MAX ( driver ) ; ioidx++ ) {
+ /* Allocate struct isa_device */
+ if ( ! isa )
+ isa = malloc ( sizeof ( *isa ) );
+ if ( ! isa ) {
+ rc = -ENOMEM;
+ goto err;
+ }
+ memset ( isa, 0, sizeof ( *isa ) );
+ isa->driver = driver;
+ isa->ioaddr = ISA_IOADDR ( driver, ioidx );
+
+ /* Add to device hierarchy */
+ snprintf ( isa->dev.name, sizeof ( isa->dev.name ),
+ "ISA%04x", isa->ioaddr );
+ isa->dev.desc.bus_type = BUS_TYPE_ISA;
+ isa->dev.desc.vendor = driver->vendor_id;
+ isa->dev.desc.device = driver->prod_id;
+ isa->dev.parent = &rootdev->dev;
+ list_add ( &isa->dev.siblings,
+ &rootdev->dev.children );
+ INIT_LIST_HEAD ( &isa->dev.children );
+
+ /* Try probing at this I/O address */
+ if ( isa_probe ( isa ) == 0 ) {
+ /* isadev registered, we can drop our ref */
+ isa = NULL;
+ } else {
+ /* Not registered; re-use struct */
+ list_del ( &isa->dev.siblings );
+ }
+ }
+ }
+
+ free ( isa );
+ return 0;
+
+ err:
+ free ( isa );
+ isabus_remove ( rootdev );
+ return rc;
+}
+
+/**
+ * Remove ISA root bus
+ *
+ * @v rootdev ISA bus root device
+ */
+static void isabus_remove ( struct root_device *rootdev ) {
+ struct isa_device *isa;
+ struct isa_device *tmp;
+
+ list_for_each_entry_safe ( isa, tmp, &rootdev->dev.children,
+ dev.siblings ) {
+ isa_remove ( isa );
+ list_del ( &isa->dev.siblings );
+ free ( isa );
+ }
+}
+
+/** ISA bus root device driver */
+static struct root_driver isa_root_driver = {
+ .probe = isabus_probe,
+ .remove = isabus_remove,
+};
+
+/** ISA bus root device */
+struct root_device isa_root_device __root_device = {
+ .dev = { .name = "ISA" },
+ .driver = &isa_root_driver,
+};