diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/s390/cio/device_pgid.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/s390/cio/device_pgid.c')
-rw-r--r-- | drivers/s390/cio/device_pgid.c | 448 |
1 files changed, 448 insertions, 0 deletions
diff --git a/drivers/s390/cio/device_pgid.c b/drivers/s390/cio/device_pgid.c new file mode 100644 index 000000000000..0adac8a67331 --- /dev/null +++ b/drivers/s390/cio/device_pgid.c | |||
@@ -0,0 +1,448 @@ | |||
1 | /* | ||
2 | * drivers/s390/cio/device_pgid.c | ||
3 | * | ||
4 | * Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, | ||
5 | * IBM Corporation | ||
6 | * Author(s): Cornelia Huck(cohuck@de.ibm.com) | ||
7 | * Martin Schwidefsky (schwidefsky@de.ibm.com) | ||
8 | * | ||
9 | * Path Group ID functions. | ||
10 | */ | ||
11 | |||
12 | #include <linux/config.h> | ||
13 | #include <linux/module.h> | ||
14 | #include <linux/init.h> | ||
15 | |||
16 | #include <asm/ccwdev.h> | ||
17 | #include <asm/cio.h> | ||
18 | #include <asm/delay.h> | ||
19 | #include <asm/lowcore.h> | ||
20 | |||
21 | #include "cio.h" | ||
22 | #include "cio_debug.h" | ||
23 | #include "css.h" | ||
24 | #include "device.h" | ||
25 | |||
26 | /* | ||
27 | * Start Sense Path Group ID helper function. Used in ccw_device_recog | ||
28 | * and ccw_device_sense_pgid. | ||
29 | */ | ||
30 | static int | ||
31 | __ccw_device_sense_pgid_start(struct ccw_device *cdev) | ||
32 | { | ||
33 | struct subchannel *sch; | ||
34 | struct ccw1 *ccw; | ||
35 | int ret; | ||
36 | |||
37 | sch = to_subchannel(cdev->dev.parent); | ||
38 | /* Setup sense path group id channel program. */ | ||
39 | ccw = cdev->private->iccws; | ||
40 | ccw->cmd_code = CCW_CMD_SENSE_PGID; | ||
41 | ccw->cda = (__u32) __pa (&cdev->private->pgid); | ||
42 | ccw->count = sizeof (struct pgid); | ||
43 | ccw->flags = CCW_FLAG_SLI; | ||
44 | |||
45 | /* Reset device status. */ | ||
46 | memset(&cdev->private->irb, 0, sizeof(struct irb)); | ||
47 | /* Try on every path. */ | ||
48 | ret = -ENODEV; | ||
49 | while (cdev->private->imask != 0) { | ||
50 | /* Try every path multiple times. */ | ||
51 | if (cdev->private->iretry > 0) { | ||
52 | cdev->private->iretry--; | ||
53 | ret = cio_start (sch, cdev->private->iccws, | ||
54 | cdev->private->imask); | ||
55 | /* ret is 0, -EBUSY, -EACCES or -ENODEV */ | ||
56 | if (ret != -EACCES) | ||
57 | return ret; | ||
58 | CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel " | ||
59 | "%04x, lpm %02X, became 'not " | ||
60 | "operational'\n", | ||
61 | cdev->private->devno, sch->irq, | ||
62 | cdev->private->imask); | ||
63 | |||
64 | } | ||
65 | cdev->private->imask >>= 1; | ||
66 | cdev->private->iretry = 5; | ||
67 | } | ||
68 | return ret; | ||
69 | } | ||
70 | |||
71 | void | ||
72 | ccw_device_sense_pgid_start(struct ccw_device *cdev) | ||
73 | { | ||
74 | int ret; | ||
75 | |||
76 | cdev->private->state = DEV_STATE_SENSE_PGID; | ||
77 | cdev->private->imask = 0x80; | ||
78 | cdev->private->iretry = 5; | ||
79 | memset (&cdev->private->pgid, 0, sizeof (struct pgid)); | ||
80 | ret = __ccw_device_sense_pgid_start(cdev); | ||
81 | if (ret && ret != -EBUSY) | ||
82 | ccw_device_sense_pgid_done(cdev, ret); | ||
83 | } | ||
84 | |||
85 | /* | ||
86 | * Called from interrupt context to check if a valid answer | ||
87 | * to Sense Path Group ID was received. | ||
88 | */ | ||
89 | static int | ||
90 | __ccw_device_check_sense_pgid(struct ccw_device *cdev) | ||
91 | { | ||
92 | struct subchannel *sch; | ||
93 | struct irb *irb; | ||
94 | |||
95 | sch = to_subchannel(cdev->dev.parent); | ||
96 | irb = &cdev->private->irb; | ||
97 | if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) | ||
98 | return -ETIME; | ||
99 | if (irb->esw.esw0.erw.cons && | ||
100 | (irb->ecw[0]&(SNS0_CMD_REJECT|SNS0_INTERVENTION_REQ))) { | ||
101 | /* | ||
102 | * If the device doesn't support the Sense Path Group ID | ||
103 | * command further retries wouldn't help ... | ||
104 | */ | ||
105 | return -EOPNOTSUPP; | ||
106 | } | ||
107 | if (irb->esw.esw0.erw.cons) { | ||
108 | CIO_MSG_EVENT(2, "SNID - device %04x, unit check, " | ||
109 | "lpum %02X, cnt %02d, sns : " | ||
110 | "%02X%02X%02X%02X %02X%02X%02X%02X ...\n", | ||
111 | cdev->private->devno, | ||
112 | irb->esw.esw0.sublog.lpum, | ||
113 | irb->esw.esw0.erw.scnt, | ||
114 | irb->ecw[0], irb->ecw[1], | ||
115 | irb->ecw[2], irb->ecw[3], | ||
116 | irb->ecw[4], irb->ecw[5], | ||
117 | irb->ecw[6], irb->ecw[7]); | ||
118 | return -EAGAIN; | ||
119 | } | ||
120 | if (irb->scsw.cc == 3) { | ||
121 | CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel " | ||
122 | "%04x, lpm %02X, became 'not operational'\n", | ||
123 | cdev->private->devno, sch->irq, sch->orb.lpm); | ||
124 | return -EACCES; | ||
125 | } | ||
126 | if (cdev->private->pgid.inf.ps.state2 == SNID_STATE2_RESVD_ELSE) { | ||
127 | CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel %04x " | ||
128 | "is reserved by someone else\n", | ||
129 | cdev->private->devno, sch->irq); | ||
130 | return -EUSERS; | ||
131 | } | ||
132 | return 0; | ||
133 | } | ||
134 | |||
135 | /* | ||
136 | * Got interrupt for Sense Path Group ID. | ||
137 | */ | ||
138 | void | ||
139 | ccw_device_sense_pgid_irq(struct ccw_device *cdev, enum dev_event dev_event) | ||
140 | { | ||
141 | struct subchannel *sch; | ||
142 | struct irb *irb; | ||
143 | int ret; | ||
144 | |||
145 | irb = (struct irb *) __LC_IRB; | ||
146 | /* Retry sense pgid for cc=1. */ | ||
147 | if (irb->scsw.stctl == | ||
148 | (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) { | ||
149 | if (irb->scsw.cc == 1) { | ||
150 | ret = __ccw_device_sense_pgid_start(cdev); | ||
151 | if (ret && ret != -EBUSY) | ||
152 | ccw_device_sense_pgid_done(cdev, ret); | ||
153 | } | ||
154 | return; | ||
155 | } | ||
156 | if (ccw_device_accumulate_and_sense(cdev, irb) != 0) | ||
157 | return; | ||
158 | sch = to_subchannel(cdev->dev.parent); | ||
159 | ret = __ccw_device_check_sense_pgid(cdev); | ||
160 | memset(&cdev->private->irb, 0, sizeof(struct irb)); | ||
161 | switch (ret) { | ||
162 | /* 0, -ETIME, -EOPNOTSUPP, -EAGAIN, -EACCES or -EUSERS */ | ||
163 | case 0: /* Sense Path Group ID successful. */ | ||
164 | if (cdev->private->pgid.inf.ps.state1 == SNID_STATE1_RESET) | ||
165 | memcpy(&cdev->private->pgid, &global_pgid, | ||
166 | sizeof(struct pgid)); | ||
167 | ccw_device_sense_pgid_done(cdev, 0); | ||
168 | break; | ||
169 | case -EOPNOTSUPP: /* Sense Path Group ID not supported */ | ||
170 | ccw_device_sense_pgid_done(cdev, -EOPNOTSUPP); | ||
171 | break; | ||
172 | case -ETIME: /* Sense path group id stopped by timeout. */ | ||
173 | ccw_device_sense_pgid_done(cdev, -ETIME); | ||
174 | break; | ||
175 | case -EACCES: /* channel is not operational. */ | ||
176 | sch->lpm &= ~cdev->private->imask; | ||
177 | cdev->private->imask >>= 1; | ||
178 | cdev->private->iretry = 5; | ||
179 | /* Fall through. */ | ||
180 | case -EAGAIN: /* Try again. */ | ||
181 | ret = __ccw_device_sense_pgid_start(cdev); | ||
182 | if (ret != 0 && ret != -EBUSY) | ||
183 | ccw_device_sense_pgid_done(cdev, -ENODEV); | ||
184 | break; | ||
185 | case -EUSERS: /* device is reserved for someone else. */ | ||
186 | ccw_device_sense_pgid_done(cdev, -EUSERS); | ||
187 | break; | ||
188 | } | ||
189 | } | ||
190 | |||
191 | /* | ||
192 | * Path Group ID helper function. | ||
193 | */ | ||
194 | static int | ||
195 | __ccw_device_do_pgid(struct ccw_device *cdev, __u8 func) | ||
196 | { | ||
197 | struct subchannel *sch; | ||
198 | struct ccw1 *ccw; | ||
199 | int ret; | ||
200 | |||
201 | sch = to_subchannel(cdev->dev.parent); | ||
202 | |||
203 | /* Setup sense path group id channel program. */ | ||
204 | cdev->private->pgid.inf.fc = func; | ||
205 | ccw = cdev->private->iccws; | ||
206 | if (!cdev->private->flags.pgid_single) { | ||
207 | cdev->private->pgid.inf.fc |= SPID_FUNC_MULTI_PATH; | ||
208 | ccw->cmd_code = CCW_CMD_SUSPEND_RECONN; | ||
209 | ccw->cda = 0; | ||
210 | ccw->count = 0; | ||
211 | ccw->flags = CCW_FLAG_SLI | CCW_FLAG_CC; | ||
212 | ccw++; | ||
213 | } else | ||
214 | cdev->private->pgid.inf.fc |= SPID_FUNC_SINGLE_PATH; | ||
215 | |||
216 | ccw->cmd_code = CCW_CMD_SET_PGID; | ||
217 | ccw->cda = (__u32) __pa (&cdev->private->pgid); | ||
218 | ccw->count = sizeof (struct pgid); | ||
219 | ccw->flags = CCW_FLAG_SLI; | ||
220 | |||
221 | /* Reset device status. */ | ||
222 | memset(&cdev->private->irb, 0, sizeof(struct irb)); | ||
223 | |||
224 | /* Try multiple times. */ | ||
225 | ret = -ENODEV; | ||
226 | if (cdev->private->iretry > 0) { | ||
227 | cdev->private->iretry--; | ||
228 | ret = cio_start (sch, cdev->private->iccws, | ||
229 | cdev->private->imask); | ||
230 | /* ret is 0, -EBUSY, -EACCES or -ENODEV */ | ||
231 | if ((ret != -EACCES) && (ret != -ENODEV)) | ||
232 | return ret; | ||
233 | } | ||
234 | /* PGID command failed on this path. Switch it off. */ | ||
235 | sch->lpm &= ~cdev->private->imask; | ||
236 | sch->vpm &= ~cdev->private->imask; | ||
237 | CIO_MSG_EVENT(2, "SPID - Device %04x on Subchannel " | ||
238 | "%04x, lpm %02X, became 'not operational'\n", | ||
239 | cdev->private->devno, sch->irq, cdev->private->imask); | ||
240 | return ret; | ||
241 | } | ||
242 | |||
243 | /* | ||
244 | * Called from interrupt context to check if a valid answer | ||
245 | * to Set Path Group ID was received. | ||
246 | */ | ||
247 | static int | ||
248 | __ccw_device_check_pgid(struct ccw_device *cdev) | ||
249 | { | ||
250 | struct subchannel *sch; | ||
251 | struct irb *irb; | ||
252 | |||
253 | sch = to_subchannel(cdev->dev.parent); | ||
254 | irb = &cdev->private->irb; | ||
255 | if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) | ||
256 | return -ETIME; | ||
257 | if (irb->esw.esw0.erw.cons) { | ||
258 | if (irb->ecw[0] & SNS0_CMD_REJECT) | ||
259 | return -EOPNOTSUPP; | ||
260 | /* Hmm, whatever happened, try again. */ | ||
261 | CIO_MSG_EVENT(2, "SPID - device %04x, unit check, cnt %02d, " | ||
262 | "sns : %02X%02X%02X%02X %02X%02X%02X%02X ...\n", | ||
263 | cdev->private->devno, irb->esw.esw0.erw.scnt, | ||
264 | irb->ecw[0], irb->ecw[1], | ||
265 | irb->ecw[2], irb->ecw[3], | ||
266 | irb->ecw[4], irb->ecw[5], | ||
267 | irb->ecw[6], irb->ecw[7]); | ||
268 | return -EAGAIN; | ||
269 | } | ||
270 | if (irb->scsw.cc == 3) { | ||
271 | CIO_MSG_EVENT(2, "SPID - Device %04x on Subchannel " | ||
272 | "%04x, lpm %02X, became 'not operational'\n", | ||
273 | cdev->private->devno, sch->irq, | ||
274 | cdev->private->imask); | ||
275 | return -EACCES; | ||
276 | } | ||
277 | return 0; | ||
278 | } | ||
279 | |||
280 | static void | ||
281 | __ccw_device_verify_start(struct ccw_device *cdev) | ||
282 | { | ||
283 | struct subchannel *sch; | ||
284 | __u8 imask, func; | ||
285 | int ret; | ||
286 | |||
287 | sch = to_subchannel(cdev->dev.parent); | ||
288 | while (sch->vpm != sch->lpm) { | ||
289 | /* Find first unequal bit in vpm vs. lpm */ | ||
290 | for (imask = 0x80; imask != 0; imask >>= 1) | ||
291 | if ((sch->vpm & imask) != (sch->lpm & imask)) | ||
292 | break; | ||
293 | cdev->private->imask = imask; | ||
294 | func = (sch->vpm & imask) ? | ||
295 | SPID_FUNC_RESIGN : SPID_FUNC_ESTABLISH; | ||
296 | ret = __ccw_device_do_pgid(cdev, func); | ||
297 | if (ret == 0 || ret == -EBUSY) | ||
298 | return; | ||
299 | cdev->private->iretry = 5; | ||
300 | } | ||
301 | ccw_device_verify_done(cdev, (sch->lpm != 0) ? 0 : -ENODEV); | ||
302 | } | ||
303 | |||
304 | /* | ||
305 | * Got interrupt for Set Path Group ID. | ||
306 | */ | ||
307 | void | ||
308 | ccw_device_verify_irq(struct ccw_device *cdev, enum dev_event dev_event) | ||
309 | { | ||
310 | struct subchannel *sch; | ||
311 | struct irb *irb; | ||
312 | int ret; | ||
313 | |||
314 | irb = (struct irb *) __LC_IRB; | ||
315 | /* Retry set pgid for cc=1. */ | ||
316 | if (irb->scsw.stctl == | ||
317 | (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) { | ||
318 | if (irb->scsw.cc == 1) | ||
319 | __ccw_device_verify_start(cdev); | ||
320 | return; | ||
321 | } | ||
322 | if (ccw_device_accumulate_and_sense(cdev, irb) != 0) | ||
323 | return; | ||
324 | sch = to_subchannel(cdev->dev.parent); | ||
325 | ret = __ccw_device_check_pgid(cdev); | ||
326 | memset(&cdev->private->irb, 0, sizeof(struct irb)); | ||
327 | switch (ret) { | ||
328 | /* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */ | ||
329 | case 0: | ||
330 | /* Establish or Resign Path Group done. Update vpm. */ | ||
331 | if ((sch->lpm & cdev->private->imask) != 0) | ||
332 | sch->vpm |= cdev->private->imask; | ||
333 | else | ||
334 | sch->vpm &= ~cdev->private->imask; | ||
335 | cdev->private->iretry = 5; | ||
336 | __ccw_device_verify_start(cdev); | ||
337 | break; | ||
338 | case -EOPNOTSUPP: | ||
339 | /* | ||
340 | * One of those strange devices which claim to be able | ||
341 | * to do multipathing but not for Set Path Group ID. | ||
342 | */ | ||
343 | if (cdev->private->flags.pgid_single) { | ||
344 | ccw_device_verify_done(cdev, -EOPNOTSUPP); | ||
345 | break; | ||
346 | } | ||
347 | cdev->private->flags.pgid_single = 1; | ||
348 | /* fall through. */ | ||
349 | case -EAGAIN: /* Try again. */ | ||
350 | __ccw_device_verify_start(cdev); | ||
351 | break; | ||
352 | case -ETIME: /* Set path group id stopped by timeout. */ | ||
353 | ccw_device_verify_done(cdev, -ETIME); | ||
354 | break; | ||
355 | case -EACCES: /* channel is not operational. */ | ||
356 | sch->lpm &= ~cdev->private->imask; | ||
357 | sch->vpm &= ~cdev->private->imask; | ||
358 | cdev->private->iretry = 5; | ||
359 | __ccw_device_verify_start(cdev); | ||
360 | break; | ||
361 | } | ||
362 | } | ||
363 | |||
364 | void | ||
365 | ccw_device_verify_start(struct ccw_device *cdev) | ||
366 | { | ||
367 | cdev->private->flags.pgid_single = 0; | ||
368 | cdev->private->iretry = 5; | ||
369 | __ccw_device_verify_start(cdev); | ||
370 | } | ||
371 | |||
372 | static void | ||
373 | __ccw_device_disband_start(struct ccw_device *cdev) | ||
374 | { | ||
375 | struct subchannel *sch; | ||
376 | int ret; | ||
377 | |||
378 | sch = to_subchannel(cdev->dev.parent); | ||
379 | while (cdev->private->imask != 0) { | ||
380 | if (sch->lpm & cdev->private->imask) { | ||
381 | ret = __ccw_device_do_pgid(cdev, SPID_FUNC_DISBAND); | ||
382 | if (ret == 0) | ||
383 | return; | ||
384 | } | ||
385 | cdev->private->iretry = 5; | ||
386 | cdev->private->imask >>= 1; | ||
387 | } | ||
388 | ccw_device_verify_done(cdev, (sch->lpm != 0) ? 0 : -ENODEV); | ||
389 | } | ||
390 | |||
391 | /* | ||
392 | * Got interrupt for Unset Path Group ID. | ||
393 | */ | ||
394 | void | ||
395 | ccw_device_disband_irq(struct ccw_device *cdev, enum dev_event dev_event) | ||
396 | { | ||
397 | struct subchannel *sch; | ||
398 | struct irb *irb; | ||
399 | int ret; | ||
400 | |||
401 | irb = (struct irb *) __LC_IRB; | ||
402 | /* Retry set pgid for cc=1. */ | ||
403 | if (irb->scsw.stctl == | ||
404 | (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) { | ||
405 | if (irb->scsw.cc == 1) | ||
406 | __ccw_device_disband_start(cdev); | ||
407 | return; | ||
408 | } | ||
409 | if (ccw_device_accumulate_and_sense(cdev, irb) != 0) | ||
410 | return; | ||
411 | sch = to_subchannel(cdev->dev.parent); | ||
412 | ret = __ccw_device_check_pgid(cdev); | ||
413 | memset(&cdev->private->irb, 0, sizeof(struct irb)); | ||
414 | switch (ret) { | ||
415 | /* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */ | ||
416 | case 0: /* disband successful. */ | ||
417 | sch->vpm = 0; | ||
418 | ccw_device_disband_done(cdev, ret); | ||
419 | break; | ||
420 | case -EOPNOTSUPP: | ||
421 | /* | ||
422 | * One of those strange devices which claim to be able | ||
423 | * to do multipathing but not for Unset Path Group ID. | ||
424 | */ | ||
425 | cdev->private->flags.pgid_single = 1; | ||
426 | /* fall through. */ | ||
427 | case -EAGAIN: /* Try again. */ | ||
428 | __ccw_device_disband_start(cdev); | ||
429 | break; | ||
430 | case -ETIME: /* Set path group id stopped by timeout. */ | ||
431 | ccw_device_disband_done(cdev, -ETIME); | ||
432 | break; | ||
433 | case -EACCES: /* channel is not operational. */ | ||
434 | cdev->private->imask >>= 1; | ||
435 | cdev->private->iretry = 5; | ||
436 | __ccw_device_disband_start(cdev); | ||
437 | break; | ||
438 | } | ||
439 | } | ||
440 | |||
441 | void | ||
442 | ccw_device_disband_start(struct ccw_device *cdev) | ||
443 | { | ||
444 | cdev->private->flags.pgid_single = 0; | ||
445 | cdev->private->iretry = 5; | ||
446 | cdev->private->imask = 0x80; | ||
447 | __ccw_device_disband_start(cdev); | ||
448 | } | ||