Commit | Line | Data |
---|---|---|
a6a8d9f8 CS |
1 | /* |
2 | * SCSI device handler infrastruture. | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify it | |
5 | * under the terms of the GNU General Public License as published by the | |
6 | * Free Software Foundation; either version 2 of the License, or (at your | |
7 | * option) any later version. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, but | |
10 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
12 | * General Public License for more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License along | |
15 | * with this program; if not, write to the Free Software Foundation, Inc., | |
16 | * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
17 | * | |
18 | * Copyright IBM Corporation, 2007 | |
19 | * Authors: | |
20 | * Chandra Seetharaman <sekharan@us.ibm.com> | |
21 | * Mike Anderson <andmike@linux.vnet.ibm.com> | |
22 | */ | |
23 | ||
24 | #include <scsi/scsi_dh.h> | |
25 | #include "../scsi_priv.h" | |
26 | ||
27 | static DEFINE_SPINLOCK(list_lock); | |
28 | static LIST_HEAD(scsi_dh_list); | |
29 | ||
30 | static struct scsi_device_handler *get_device_handler(const char *name) | |
31 | { | |
32 | struct scsi_device_handler *tmp, *found = NULL; | |
33 | ||
34 | spin_lock(&list_lock); | |
35 | list_for_each_entry(tmp, &scsi_dh_list, list) { | |
765cbc6d | 36 | if (!strncmp(tmp->name, name, strlen(tmp->name))) { |
a6a8d9f8 CS |
37 | found = tmp; |
38 | break; | |
39 | } | |
40 | } | |
41 | spin_unlock(&list_lock); | |
42 | return found; | |
43 | } | |
44 | ||
765cbc6d HR |
45 | static int device_handler_match(struct scsi_device_handler *tmp, |
46 | struct scsi_device *sdev) | |
a6a8d9f8 | 47 | { |
765cbc6d HR |
48 | int i; |
49 | ||
50 | for(i = 0; tmp->devlist[i].vendor; i++) { | |
51 | if (!strncmp(sdev->vendor, tmp->devlist[i].vendor, | |
52 | strlen(tmp->devlist[i].vendor)) && | |
53 | !strncmp(sdev->model, tmp->devlist[i].model, | |
54 | strlen(tmp->devlist[i].model))) { | |
55 | return 1; | |
56 | } | |
57 | } | |
a6a8d9f8 | 58 | |
a6a8d9f8 CS |
59 | return 0; |
60 | } | |
61 | ||
62 | /* | |
765cbc6d HR |
63 | * scsi_dh_handler_attach - Attach a device handler to a device |
64 | * @sdev - SCSI device the device handler should attach to | |
65 | * @scsi_dh - The device handler to attach | |
66 | */ | |
67 | static int scsi_dh_handler_attach(struct scsi_device *sdev, | |
68 | struct scsi_device_handler *scsi_dh) | |
69 | { | |
70 | int err = 0; | |
71 | ||
72 | if (sdev->scsi_dh_data) { | |
73 | if (sdev->scsi_dh_data->scsi_dh != scsi_dh) | |
74 | err = -EBUSY; | |
75 | } else if (scsi_dh->attach) | |
76 | err = scsi_dh->attach(sdev); | |
77 | ||
78 | return err; | |
79 | } | |
80 | ||
81 | /* | |
82 | * scsi_dh_handler_detach - Detach a device handler from a device | |
83 | * @sdev - SCSI device the device handler should be detached from | |
84 | * @scsi_dh - Device handler to be detached | |
a6a8d9f8 | 85 | * |
765cbc6d HR |
86 | * Detach from a device handler. If a device handler is specified, |
87 | * only detach if the currently attached handler is equal to it. | |
a6a8d9f8 | 88 | */ |
765cbc6d HR |
89 | static void scsi_dh_handler_detach(struct scsi_device *sdev, |
90 | struct scsi_device_handler *scsi_dh) | |
a6a8d9f8 | 91 | { |
765cbc6d HR |
92 | if (!sdev->scsi_dh_data) |
93 | return; | |
a6a8d9f8 | 94 | |
765cbc6d HR |
95 | if (scsi_dh && scsi_dh != sdev->scsi_dh_data->scsi_dh) |
96 | return; | |
a6a8d9f8 | 97 | |
765cbc6d HR |
98 | if (!scsi_dh) |
99 | scsi_dh = sdev->scsi_dh_data->scsi_dh; | |
100 | ||
101 | if (scsi_dh && scsi_dh->detach) | |
102 | scsi_dh->detach(sdev); | |
103 | } | |
104 | ||
105 | /* | |
106 | * scsi_dh_notifier - notifier chain callback | |
107 | */ | |
108 | static int scsi_dh_notifier(struct notifier_block *nb, | |
109 | unsigned long action, void *data) | |
110 | { | |
111 | struct device *dev = data; | |
112 | struct scsi_device *sdev; | |
113 | int err = 0; | |
114 | struct scsi_device_handler *tmp, *devinfo = NULL; | |
115 | ||
116 | if (!scsi_is_sdev_device(dev)) | |
117 | return 0; | |
118 | ||
119 | sdev = to_scsi_device(dev); | |
a6a8d9f8 | 120 | |
a6a8d9f8 | 121 | spin_lock(&list_lock); |
765cbc6d HR |
122 | list_for_each_entry(tmp, &scsi_dh_list, list) { |
123 | if (device_handler_match(tmp, sdev)) { | |
124 | devinfo = tmp; | |
125 | break; | |
126 | } | |
127 | } | |
a6a8d9f8 CS |
128 | spin_unlock(&list_lock); |
129 | ||
765cbc6d HR |
130 | if (!devinfo) |
131 | goto out; | |
132 | ||
133 | if (action == BUS_NOTIFY_ADD_DEVICE) { | |
134 | err = scsi_dh_handler_attach(sdev, devinfo); | |
135 | } else if (action == BUS_NOTIFY_DEL_DEVICE) { | |
136 | scsi_dh_handler_detach(sdev, NULL); | |
137 | } | |
138 | out: | |
139 | return err; | |
a6a8d9f8 | 140 | } |
a6a8d9f8 | 141 | |
765cbc6d HR |
142 | /* |
143 | * scsi_dh_notifier_add - Callback for scsi_register_device_handler | |
144 | */ | |
145 | static int scsi_dh_notifier_add(struct device *dev, void *data) | |
146 | { | |
147 | struct scsi_device_handler *scsi_dh = data; | |
148 | struct scsi_device *sdev; | |
149 | ||
150 | if (!scsi_is_sdev_device(dev)) | |
151 | return 0; | |
152 | ||
153 | if (!get_device(dev)) | |
154 | return 0; | |
155 | ||
156 | sdev = to_scsi_device(dev); | |
157 | ||
158 | if (device_handler_match(scsi_dh, sdev)) | |
159 | scsi_dh_handler_attach(sdev, scsi_dh); | |
160 | ||
161 | put_device(dev); | |
162 | ||
163 | return 0; | |
164 | } | |
165 | ||
166 | /* | |
167 | * scsi_dh_notifier_remove - Callback for scsi_unregister_device_handler | |
168 | */ | |
a6a8d9f8 CS |
169 | static int scsi_dh_notifier_remove(struct device *dev, void *data) |
170 | { | |
171 | struct scsi_device_handler *scsi_dh = data; | |
765cbc6d HR |
172 | struct scsi_device *sdev; |
173 | ||
174 | if (!scsi_is_sdev_device(dev)) | |
175 | return 0; | |
176 | ||
177 | if (!get_device(dev)) | |
178 | return 0; | |
179 | ||
180 | sdev = to_scsi_device(dev); | |
181 | ||
182 | scsi_dh_handler_detach(sdev, scsi_dh); | |
183 | ||
184 | put_device(dev); | |
a6a8d9f8 | 185 | |
a6a8d9f8 CS |
186 | return 0; |
187 | } | |
188 | ||
765cbc6d HR |
189 | /* |
190 | * scsi_register_device_handler - register a device handler personality | |
191 | * module. | |
192 | * @scsi_dh - device handler to be registered. | |
193 | * | |
194 | * Returns 0 on success, -EBUSY if handler already registered. | |
195 | */ | |
196 | int scsi_register_device_handler(struct scsi_device_handler *scsi_dh) | |
197 | { | |
198 | if (get_device_handler(scsi_dh->name)) | |
199 | return -EBUSY; | |
200 | ||
201 | spin_lock(&list_lock); | |
202 | list_add(&scsi_dh->list, &scsi_dh_list); | |
203 | spin_unlock(&list_lock); | |
204 | bus_for_each_dev(&scsi_bus_type, NULL, scsi_dh, scsi_dh_notifier_add); | |
205 | printk(KERN_INFO "%s: device handler registered\n", scsi_dh->name); | |
206 | ||
207 | return SCSI_DH_OK; | |
208 | } | |
209 | EXPORT_SYMBOL_GPL(scsi_register_device_handler); | |
210 | ||
a6a8d9f8 CS |
211 | /* |
212 | * scsi_unregister_device_handler - register a device handler personality | |
213 | * module. | |
214 | * @scsi_dh - device handler to be unregistered. | |
215 | * | |
216 | * Returns 0 on success, -ENODEV if handler not registered. | |
217 | */ | |
218 | int scsi_unregister_device_handler(struct scsi_device_handler *scsi_dh) | |
219 | { | |
765cbc6d HR |
220 | if (!get_device_handler(scsi_dh->name)) |
221 | return -ENODEV; | |
a6a8d9f8 CS |
222 | |
223 | bus_for_each_dev(&scsi_bus_type, NULL, scsi_dh, | |
765cbc6d HR |
224 | scsi_dh_notifier_remove); |
225 | ||
a6a8d9f8 CS |
226 | spin_lock(&list_lock); |
227 | list_del(&scsi_dh->list); | |
228 | spin_unlock(&list_lock); | |
765cbc6d | 229 | printk(KERN_INFO "%s: device handler unregistered\n", scsi_dh->name); |
a6a8d9f8 | 230 | |
765cbc6d | 231 | return SCSI_DH_OK; |
a6a8d9f8 CS |
232 | } |
233 | EXPORT_SYMBOL_GPL(scsi_unregister_device_handler); | |
234 | ||
235 | /* | |
236 | * scsi_dh_activate - activate the path associated with the scsi_device | |
237 | * corresponding to the given request queue. | |
238 | * @q - Request queue that is associated with the scsi_device to be | |
239 | * activated. | |
240 | */ | |
241 | int scsi_dh_activate(struct request_queue *q) | |
242 | { | |
243 | int err = 0; | |
244 | unsigned long flags; | |
245 | struct scsi_device *sdev; | |
246 | struct scsi_device_handler *scsi_dh = NULL; | |
247 | ||
248 | spin_lock_irqsave(q->queue_lock, flags); | |
249 | sdev = q->queuedata; | |
250 | if (sdev && sdev->scsi_dh_data) | |
251 | scsi_dh = sdev->scsi_dh_data->scsi_dh; | |
252 | if (!scsi_dh || !get_device(&sdev->sdev_gendev)) | |
253 | err = SCSI_DH_NOSYS; | |
254 | spin_unlock_irqrestore(q->queue_lock, flags); | |
255 | ||
256 | if (err) | |
257 | return err; | |
258 | ||
259 | if (scsi_dh->activate) | |
260 | err = scsi_dh->activate(sdev); | |
261 | put_device(&sdev->sdev_gendev); | |
262 | return err; | |
263 | } | |
264 | EXPORT_SYMBOL_GPL(scsi_dh_activate); | |
265 | ||
266 | /* | |
267 | * scsi_dh_handler_exist - Return TRUE(1) if a device handler exists for | |
268 | * the given name. FALSE(0) otherwise. | |
269 | * @name - name of the device handler. | |
270 | */ | |
271 | int scsi_dh_handler_exist(const char *name) | |
272 | { | |
273 | return (get_device_handler(name) != NULL); | |
274 | } | |
275 | EXPORT_SYMBOL_GPL(scsi_dh_handler_exist); | |
276 | ||
765cbc6d HR |
277 | static struct notifier_block scsi_dh_nb = { |
278 | .notifier_call = scsi_dh_notifier | |
279 | }; | |
280 | ||
281 | static int __init scsi_dh_init(void) | |
282 | { | |
283 | int r; | |
284 | ||
285 | r = bus_register_notifier(&scsi_bus_type, &scsi_dh_nb); | |
286 | ||
287 | return r; | |
288 | } | |
289 | ||
290 | static void __exit scsi_dh_exit(void) | |
291 | { | |
292 | bus_unregister_notifier(&scsi_bus_type, &scsi_dh_nb); | |
293 | } | |
294 | ||
295 | module_init(scsi_dh_init); | |
296 | module_exit(scsi_dh_exit); | |
297 | ||
a6a8d9f8 CS |
298 | MODULE_DESCRIPTION("SCSI device handler"); |
299 | MODULE_AUTHOR("Chandra Seetharaman <sekharan@us.ibm.com>"); | |
300 | MODULE_LICENSE("GPL"); |