summaryrefslogtreecommitdiff
path: root/gpxe/src/drivers/bus/isa.c
blob: f458826d88d3b40b744e00ca29324937a2955479 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <gpxe/io.h>
#include <gpxe/isa.h>

FILE_LICENCE ( GPL2_OR_LATER );

/*
 * 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 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_each_table_entry ( driver, ISA_DRIVERS ) {
		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,
};