| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 1 | /* | 
 | 2 |  * driver.c - device id matching, driver model, etc. | 
 | 3 |  * | 
 | 4 |  * Copyright 2002 Adam Belay <ambx1@neo.rr.com> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 5 |  */ | 
 | 6 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 7 | #include <linux/string.h> | 
 | 8 | #include <linux/list.h> | 
 | 9 | #include <linux/module.h> | 
 | 10 | #include <linux/ctype.h> | 
 | 11 | #include <linux/slab.h> | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 12 | #include <linux/pnp.h> | 
 | 13 | #include "base.h" | 
 | 14 |  | 
 | 15 | static int compare_func(const char *ida, const char *idb) | 
 | 16 | { | 
 | 17 | 	int i; | 
| Bjorn Helgaas | 07d4e9a | 2007-07-26 10:41:21 -0700 | [diff] [blame] | 18 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 19 | 	/* we only need to compare the last 4 chars */ | 
| Bjorn Helgaas | 9dd7846 | 2007-07-26 10:41:20 -0700 | [diff] [blame] | 20 | 	for (i = 3; i < 7; i++) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 21 | 		if (ida[i] != 'X' && | 
| Bjorn Helgaas | 9dd7846 | 2007-07-26 10:41:20 -0700 | [diff] [blame] | 22 | 		    idb[i] != 'X' && toupper(ida[i]) != toupper(idb[i])) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 23 | 			return 0; | 
 | 24 | 	} | 
 | 25 | 	return 1; | 
 | 26 | } | 
 | 27 |  | 
 | 28 | int compare_pnp_id(struct pnp_id *pos, const char *id) | 
 | 29 | { | 
 | 30 | 	if (!pos || !id || (strlen(id) != 7)) | 
 | 31 | 		return 0; | 
| Bjorn Helgaas | 9dd7846 | 2007-07-26 10:41:20 -0700 | [diff] [blame] | 32 | 	if (memcmp(id, "ANYDEVS", 7) == 0) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 33 | 		return 1; | 
| Bjorn Helgaas | 9dd7846 | 2007-07-26 10:41:20 -0700 | [diff] [blame] | 34 | 	while (pos) { | 
 | 35 | 		if (memcmp(pos->id, id, 3) == 0) | 
 | 36 | 			if (compare_func(pos->id, id) == 1) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 37 | 				return 1; | 
 | 38 | 		pos = pos->next; | 
 | 39 | 	} | 
 | 40 | 	return 0; | 
 | 41 | } | 
 | 42 |  | 
| Bjorn Helgaas | 9dd7846 | 2007-07-26 10:41:20 -0700 | [diff] [blame] | 43 | static const struct pnp_device_id *match_device(struct pnp_driver *drv, | 
 | 44 | 						struct pnp_dev *dev) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 45 | { | 
 | 46 | 	const struct pnp_device_id *drv_id = drv->id_table; | 
| Bjorn Helgaas | 07d4e9a | 2007-07-26 10:41:21 -0700 | [diff] [blame] | 47 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 48 | 	if (!drv_id) | 
 | 49 | 		return NULL; | 
 | 50 |  | 
 | 51 | 	while (*drv_id->id) { | 
 | 52 | 		if (compare_pnp_id(dev->id, drv_id->id)) | 
 | 53 | 			return drv_id; | 
 | 54 | 		drv_id++; | 
 | 55 | 	} | 
 | 56 | 	return NULL; | 
 | 57 | } | 
 | 58 |  | 
 | 59 | int pnp_device_attach(struct pnp_dev *pnp_dev) | 
 | 60 | { | 
 | 61 | 	spin_lock(&pnp_lock); | 
| Bjorn Helgaas | 9dd7846 | 2007-07-26 10:41:20 -0700 | [diff] [blame] | 62 | 	if (pnp_dev->status != PNP_READY) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 63 | 		spin_unlock(&pnp_lock); | 
 | 64 | 		return -EBUSY; | 
 | 65 | 	} | 
 | 66 | 	pnp_dev->status = PNP_ATTACHED; | 
 | 67 | 	spin_unlock(&pnp_lock); | 
 | 68 | 	return 0; | 
 | 69 | } | 
 | 70 |  | 
 | 71 | void pnp_device_detach(struct pnp_dev *pnp_dev) | 
 | 72 | { | 
 | 73 | 	spin_lock(&pnp_lock); | 
 | 74 | 	if (pnp_dev->status == PNP_ATTACHED) | 
 | 75 | 		pnp_dev->status = PNP_READY; | 
 | 76 | 	spin_unlock(&pnp_lock); | 
 | 77 | 	pnp_disable_dev(pnp_dev); | 
 | 78 | } | 
 | 79 |  | 
 | 80 | static int pnp_device_probe(struct device *dev) | 
 | 81 | { | 
 | 82 | 	int error; | 
 | 83 | 	struct pnp_driver *pnp_drv; | 
 | 84 | 	struct pnp_dev *pnp_dev; | 
 | 85 | 	const struct pnp_device_id *dev_id = NULL; | 
 | 86 | 	pnp_dev = to_pnp_dev(dev); | 
 | 87 | 	pnp_drv = to_pnp_driver(dev->driver); | 
 | 88 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 89 | 	error = pnp_device_attach(pnp_dev); | 
 | 90 | 	if (error < 0) | 
 | 91 | 		return error; | 
 | 92 |  | 
 | 93 | 	if (pnp_dev->active == 0) { | 
 | 94 | 		if (!(pnp_drv->flags & PNP_DRIVER_RES_DO_NOT_CHANGE)) { | 
 | 95 | 			error = pnp_activate_dev(pnp_dev); | 
 | 96 | 			if (error < 0) | 
 | 97 | 				return error; | 
 | 98 | 		} | 
 | 99 | 	} else if ((pnp_drv->flags & PNP_DRIVER_RES_DISABLE) | 
| Bjorn Helgaas | 9dd7846 | 2007-07-26 10:41:20 -0700 | [diff] [blame] | 100 | 		   == PNP_DRIVER_RES_DISABLE) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 101 | 		error = pnp_disable_dev(pnp_dev); | 
 | 102 | 		if (error < 0) | 
 | 103 | 			return error; | 
 | 104 | 	} | 
 | 105 | 	error = 0; | 
 | 106 | 	if (pnp_drv->probe) { | 
 | 107 | 		dev_id = match_device(pnp_drv, pnp_dev); | 
 | 108 | 		if (dev_id != NULL) | 
 | 109 | 			error = pnp_drv->probe(pnp_dev, dev_id); | 
 | 110 | 	} | 
| Bjorn Helgaas | 9dd7846 | 2007-07-26 10:41:20 -0700 | [diff] [blame] | 111 | 	if (error >= 0) { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 112 | 		pnp_dev->driver = pnp_drv; | 
 | 113 | 		error = 0; | 
 | 114 | 	} else | 
 | 115 | 		goto fail; | 
| Bjorn Helgaas | a05d078 | 2007-10-16 23:31:10 -0700 | [diff] [blame] | 116 |  | 
 | 117 | 	dev_dbg(dev, "driver attached\n"); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 118 | 	return error; | 
 | 119 |  | 
| Bjorn Helgaas | 1e0aa9a | 2007-08-15 10:32:08 -0600 | [diff] [blame] | 120 | fail: | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 121 | 	pnp_device_detach(pnp_dev); | 
 | 122 | 	return error; | 
 | 123 | } | 
 | 124 |  | 
 | 125 | static int pnp_device_remove(struct device *dev) | 
 | 126 | { | 
| Bjorn Helgaas | 9dd7846 | 2007-07-26 10:41:20 -0700 | [diff] [blame] | 127 | 	struct pnp_dev *pnp_dev = to_pnp_dev(dev); | 
 | 128 | 	struct pnp_driver *drv = pnp_dev->driver; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 129 |  | 
 | 130 | 	if (drv) { | 
 | 131 | 		if (drv->remove) | 
 | 132 | 			drv->remove(pnp_dev); | 
 | 133 | 		pnp_dev->driver = NULL; | 
 | 134 | 	} | 
 | 135 | 	pnp_device_detach(pnp_dev); | 
 | 136 | 	return 0; | 
 | 137 | } | 
 | 138 |  | 
 | 139 | static int pnp_bus_match(struct device *dev, struct device_driver *drv) | 
 | 140 | { | 
| Bjorn Helgaas | 9dd7846 | 2007-07-26 10:41:20 -0700 | [diff] [blame] | 141 | 	struct pnp_dev *pnp_dev = to_pnp_dev(dev); | 
 | 142 | 	struct pnp_driver *pnp_drv = to_pnp_driver(drv); | 
| Bjorn Helgaas | 07d4e9a | 2007-07-26 10:41:21 -0700 | [diff] [blame] | 143 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 144 | 	if (match_device(pnp_drv, pnp_dev) == NULL) | 
 | 145 | 		return 0; | 
 | 146 | 	return 1; | 
 | 147 | } | 
 | 148 |  | 
| Takashi Iwai | 4c98cfe | 2005-11-29 09:09:32 +0100 | [diff] [blame] | 149 | static int pnp_bus_suspend(struct device *dev, pm_message_t state) | 
 | 150 | { | 
| Bjorn Helgaas | 9dd7846 | 2007-07-26 10:41:20 -0700 | [diff] [blame] | 151 | 	struct pnp_dev *pnp_dev = to_pnp_dev(dev); | 
 | 152 | 	struct pnp_driver *pnp_drv = pnp_dev->driver; | 
| Pierre Ossman | 68094e3 | 2005-11-29 09:09:32 +0100 | [diff] [blame] | 153 | 	int error; | 
| Takashi Iwai | 4c98cfe | 2005-11-29 09:09:32 +0100 | [diff] [blame] | 154 |  | 
| Pierre Ossman | 68094e3 | 2005-11-29 09:09:32 +0100 | [diff] [blame] | 155 | 	if (!pnp_drv) | 
 | 156 | 		return 0; | 
 | 157 |  | 
 | 158 | 	if (pnp_drv->suspend) { | 
 | 159 | 		error = pnp_drv->suspend(pnp_dev, state); | 
 | 160 | 		if (error) | 
 | 161 | 			return error; | 
 | 162 | 	} | 
 | 163 |  | 
| Rene Herman | 5d38998 | 2008-02-06 01:40:05 -0800 | [diff] [blame] | 164 | 	if (pnp_can_disable(pnp_dev)) { | 
| Bjorn Helgaas | 9dd7846 | 2007-07-26 10:41:20 -0700 | [diff] [blame] | 165 | 		error = pnp_stop_dev(pnp_dev); | 
 | 166 | 		if (error) | 
 | 167 | 			return error; | 
| Pierre Ossman | 68094e3 | 2005-11-29 09:09:32 +0100 | [diff] [blame] | 168 | 	} | 
 | 169 |  | 
| Bjorn Helgaas | 0bc11fd | 2008-04-28 02:15:57 -0700 | [diff] [blame] | 170 | 	if (pnp_dev->protocol->suspend) | 
| Shaohua Li | fc30e68 | 2007-07-20 10:03:20 +0800 | [diff] [blame] | 171 | 		pnp_dev->protocol->suspend(pnp_dev, state); | 
| Takashi Iwai | 4c98cfe | 2005-11-29 09:09:32 +0100 | [diff] [blame] | 172 | 	return 0; | 
 | 173 | } | 
 | 174 |  | 
| Pierre Ossman | 68094e3 | 2005-11-29 09:09:32 +0100 | [diff] [blame] | 175 | static int pnp_bus_resume(struct device *dev) | 
| Takashi Iwai | 4c98cfe | 2005-11-29 09:09:32 +0100 | [diff] [blame] | 176 | { | 
| Bjorn Helgaas | 9dd7846 | 2007-07-26 10:41:20 -0700 | [diff] [blame] | 177 | 	struct pnp_dev *pnp_dev = to_pnp_dev(dev); | 
 | 178 | 	struct pnp_driver *pnp_drv = pnp_dev->driver; | 
| Pierre Ossman | 68094e3 | 2005-11-29 09:09:32 +0100 | [diff] [blame] | 179 | 	int error; | 
| Takashi Iwai | 4c98cfe | 2005-11-29 09:09:32 +0100 | [diff] [blame] | 180 |  | 
| Pierre Ossman | 68094e3 | 2005-11-29 09:09:32 +0100 | [diff] [blame] | 181 | 	if (!pnp_drv) | 
 | 182 | 		return 0; | 
 | 183 |  | 
| Bjorn Helgaas | 0bc11fd | 2008-04-28 02:15:57 -0700 | [diff] [blame] | 184 | 	if (pnp_dev->protocol->resume) | 
| Shaohua Li | fc30e68 | 2007-07-20 10:03:20 +0800 | [diff] [blame] | 185 | 		pnp_dev->protocol->resume(pnp_dev); | 
 | 186 |  | 
| Rene Herman | 5d38998 | 2008-02-06 01:40:05 -0800 | [diff] [blame] | 187 | 	if (pnp_can_write(pnp_dev)) { | 
| Pierre Ossman | 68094e3 | 2005-11-29 09:09:32 +0100 | [diff] [blame] | 188 | 		error = pnp_start_dev(pnp_dev); | 
 | 189 | 		if (error) | 
 | 190 | 			return error; | 
 | 191 | 	} | 
 | 192 |  | 
| Rene Herman | 5d38998 | 2008-02-06 01:40:05 -0800 | [diff] [blame] | 193 | 	if (pnp_drv->resume) { | 
 | 194 | 		error = pnp_drv->resume(pnp_dev); | 
 | 195 | 		if (error) | 
 | 196 | 			return error; | 
 | 197 | 	} | 
| Pierre Ossman | 68094e3 | 2005-11-29 09:09:32 +0100 | [diff] [blame] | 198 |  | 
 | 199 | 	return 0; | 
| Takashi Iwai | 4c98cfe | 2005-11-29 09:09:32 +0100 | [diff] [blame] | 200 | } | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 201 |  | 
 | 202 | struct bus_type pnp_bus_type = { | 
| Bjorn Helgaas | 07d4e9a | 2007-07-26 10:41:21 -0700 | [diff] [blame] | 203 | 	.name    = "pnp", | 
 | 204 | 	.match   = pnp_bus_match, | 
 | 205 | 	.probe   = pnp_device_probe, | 
 | 206 | 	.remove  = pnp_device_remove, | 
| Takashi Iwai | 4c98cfe | 2005-11-29 09:09:32 +0100 | [diff] [blame] | 207 | 	.suspend = pnp_bus_suspend, | 
| Bjorn Helgaas | 07d4e9a | 2007-07-26 10:41:21 -0700 | [diff] [blame] | 208 | 	.resume  = pnp_bus_resume, | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 209 | }; | 
 | 210 |  | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 211 | int pnp_register_driver(struct pnp_driver *drv) | 
 | 212 | { | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 213 | 	pnp_dbg("the driver '%s' has been registered", drv->name); | 
 | 214 |  | 
 | 215 | 	drv->driver.name = drv->name; | 
 | 216 | 	drv->driver.bus = &pnp_bus_type; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 217 |  | 
| Bjorn Helgaas | 982c609 | 2006-03-27 01:17:08 -0800 | [diff] [blame] | 218 | 	return driver_register(&drv->driver); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 219 | } | 
 | 220 |  | 
 | 221 | void pnp_unregister_driver(struct pnp_driver *drv) | 
 | 222 | { | 
 | 223 | 	driver_unregister(&drv->driver); | 
 | 224 | 	pnp_dbg("the driver '%s' has been unregistered", drv->name); | 
 | 225 | } | 
 | 226 |  | 
 | 227 | /** | 
 | 228 |  * pnp_add_id - adds an EISA id to the specified device | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 229 |  * @dev: pointer to the desired device | 
| Bjorn Helgaas | 772defc | 2008-04-28 16:33:52 -0600 | [diff] [blame] | 230 |  * @id: pointer to an EISA id string | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 231 |  */ | 
| Bjorn Helgaas | 772defc | 2008-04-28 16:33:52 -0600 | [diff] [blame] | 232 | struct pnp_id *pnp_add_id(struct pnp_dev *dev, char *id) | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 233 | { | 
| Bjorn Helgaas | 772defc | 2008-04-28 16:33:52 -0600 | [diff] [blame] | 234 | 	struct pnp_id *dev_id, *ptr; | 
| Bjorn Helgaas | 07d4e9a | 2007-07-26 10:41:21 -0700 | [diff] [blame] | 235 |  | 
| Bjorn Helgaas | 772defc | 2008-04-28 16:33:52 -0600 | [diff] [blame] | 236 | 	dev_id = kzalloc(sizeof(struct pnp_id), GFP_KERNEL); | 
 | 237 | 	if (!dev_id) | 
 | 238 | 		return NULL; | 
 | 239 |  | 
 | 240 | 	dev_id->id[0] = id[0]; | 
 | 241 | 	dev_id->id[1] = id[1]; | 
 | 242 | 	dev_id->id[2] = id[2]; | 
 | 243 | 	dev_id->id[3] = tolower(id[3]); | 
 | 244 | 	dev_id->id[4] = tolower(id[4]); | 
 | 245 | 	dev_id->id[5] = tolower(id[5]); | 
 | 246 | 	dev_id->id[6] = tolower(id[6]); | 
 | 247 | 	dev_id->id[7] = '\0'; | 
 | 248 |  | 
 | 249 | 	dev_id->next = NULL; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 250 | 	ptr = dev->id; | 
 | 251 | 	while (ptr && ptr->next) | 
 | 252 | 		ptr = ptr->next; | 
 | 253 | 	if (ptr) | 
| Bjorn Helgaas | 772defc | 2008-04-28 16:33:52 -0600 | [diff] [blame] | 254 | 		ptr->next = dev_id; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 255 | 	else | 
| Bjorn Helgaas | 772defc | 2008-04-28 16:33:52 -0600 | [diff] [blame] | 256 | 		dev->id = dev_id; | 
 | 257 |  | 
 | 258 | 	return dev_id; | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 259 | } | 
 | 260 |  | 
 | 261 | EXPORT_SYMBOL(pnp_register_driver); | 
 | 262 | EXPORT_SYMBOL(pnp_unregister_driver); | 
| Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 263 | EXPORT_SYMBOL(pnp_device_attach); | 
 | 264 | EXPORT_SYMBOL(pnp_device_detach); |