patch-2_6_7-vs1_9_1_12
[linux-2.6.git] / drivers / s390 / cio / device_pgid.c
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         /*
147          * Unsolicited interrupts may pertain to an earlier status pending or
148          * busy condition on the subchannel. Retry sense pgid.
149          */
150         if (irb->scsw.stctl ==
151             (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
152                 ret = __ccw_device_sense_pgid_start(cdev);
153                 if (ret && ret != -EBUSY)
154                         ccw_device_sense_pgid_done(cdev, ret);
155                 return;
156         }
157         if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
158                 return;
159         sch = to_subchannel(cdev->dev.parent);
160         switch (__ccw_device_check_sense_pgid(cdev)) {
161         /* 0, -ETIME, -EOPNOTSUPP, -EAGAIN, -EACCES or -EUSERS */
162         case 0:                 /* Sense Path Group ID successful. */
163                 if (cdev->private->pgid.inf.ps.state1 == SNID_STATE1_RESET)
164                         memcpy(&cdev->private->pgid, &global_pgid,
165                                sizeof(struct pgid));
166                 ccw_device_sense_pgid_done(cdev, 0);
167                 break;
168         case -EOPNOTSUPP:       /* Sense Path Group ID not supported */
169                 ccw_device_sense_pgid_done(cdev, -EOPNOTSUPP);
170                 break;
171         case -ETIME:            /* Sense path group id stopped by timeout. */
172                 ccw_device_sense_pgid_done(cdev, -ETIME);
173                 break;
174         case -EACCES:           /* channel is not operational. */
175                 sch->lpm &= ~cdev->private->imask;
176                 cdev->private->imask >>= 1;
177                 cdev->private->iretry = 5;
178                 /* Fall through. */
179         case -EAGAIN:           /* Try again. */
180                 ret = __ccw_device_sense_pgid_start(cdev);
181                 if (ret != 0 && ret != -EBUSY)
182                         ccw_device_sense_pgid_done(cdev, -ENODEV);
183                 break;
184         case -EUSERS:           /* device is reserved for someone else. */
185                 ccw_device_sense_pgid_done(cdev, -EUSERS);
186                 break;
187         }
188 }
189
190 /*
191  * Path Group ID helper function.
192  */
193 static int
194 __ccw_device_do_pgid(struct ccw_device *cdev, __u8 func)
195 {
196         struct subchannel *sch;
197         struct ccw1 *ccw;
198         int ret;
199
200         sch = to_subchannel(cdev->dev.parent);
201
202         /* Setup sense path group id channel program. */
203         cdev->private->pgid.inf.fc = func;
204         ccw = cdev->private->iccws;
205         if (!cdev->private->flags.pgid_single) {
206                 cdev->private->pgid.inf.fc |= SPID_FUNC_MULTI_PATH;
207                 ccw->cmd_code = CCW_CMD_SUSPEND_RECONN;
208                 ccw->cda = 0;
209                 ccw->count = 0;
210                 ccw->flags = CCW_FLAG_SLI | CCW_FLAG_CC;
211                 ccw++;
212         } else
213                 cdev->private->pgid.inf.fc |= SPID_FUNC_SINGLE_PATH;
214
215         ccw->cmd_code = CCW_CMD_SET_PGID;
216         ccw->cda = (__u32) __pa (&cdev->private->pgid);
217         ccw->count = sizeof (struct pgid);
218         ccw->flags = CCW_FLAG_SLI;
219
220         /* Reset device status. */
221         memset(&cdev->private->irb, 0, sizeof(struct irb));
222
223         /* Try multiple times. */
224         ret = -ENODEV;
225         if (cdev->private->iretry > 0) {
226                 cdev->private->iretry--;
227                 ret = cio_start (sch, cdev->private->iccws,
228                                  cdev->private->imask);
229                 /* ret is 0, -EBUSY, -EACCES or -ENODEV */
230                 if ((ret != -EACCES) && (ret != -ENODEV))
231                         return ret;
232         }
233         /* PGID command failed on this path. Switch it off. */
234         sch->lpm &= ~cdev->private->imask;
235         sch->vpm &= ~cdev->private->imask;
236         CIO_MSG_EVENT(2, "SPID - Device %04x on Subchannel "
237                       "%04x, lpm %02X, became 'not operational'\n",
238                       cdev->private->devno, sch->irq, cdev->private->imask);
239         return ret;
240 }
241
242 /*
243  * Called from interrupt context to check if a valid answer
244  * to Set Path Group ID was received.
245  */
246 static int
247 __ccw_device_check_pgid(struct ccw_device *cdev)
248 {
249         struct subchannel *sch;
250         struct irb *irb;
251
252         sch = to_subchannel(cdev->dev.parent);
253         irb = &cdev->private->irb;
254         if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC))
255                 return -ETIME;
256         if (irb->esw.esw0.erw.cons) {
257                 if (irb->ecw[0] & SNS0_CMD_REJECT)
258                         return -EOPNOTSUPP;
259                 /* Hmm, whatever happened, try again. */
260                 CIO_MSG_EVENT(2, "SPID - device %04x, unit check, cnt %02d, "
261                               "sns : %02X%02X%02X%02X %02X%02X%02X%02X ...\n",
262                               cdev->private->devno, irb->esw.esw0.erw.scnt,
263                               irb->ecw[0], irb->ecw[1],
264                               irb->ecw[2], irb->ecw[3],
265                               irb->ecw[4], irb->ecw[5],
266                               irb->ecw[6], irb->ecw[7]);
267                 return -EAGAIN;
268         }
269         if (irb->scsw.cc == 3) {
270                 CIO_MSG_EVENT(2, "SPID - Device %04x on Subchannel "
271                               "%04x, lpm %02X, became 'not operational'\n",
272                               cdev->private->devno, sch->irq,
273                               cdev->private->imask);
274                 return -EACCES;
275         }
276         return 0;
277 }
278
279 static void
280 __ccw_device_verify_start(struct ccw_device *cdev)
281 {
282         struct subchannel *sch;
283         __u8 imask, func;
284         int ret;
285
286         sch = to_subchannel(cdev->dev.parent);
287         while (sch->vpm != sch->lpm) {
288                 /* Find first unequal bit in vpm vs. lpm */
289                 for (imask = 0x80; imask != 0; imask >>= 1)
290                         if ((sch->vpm & imask) != (sch->lpm & imask))
291                                 break;
292                 cdev->private->imask = imask;
293                 func = (sch->vpm & imask) ?
294                         SPID_FUNC_RESIGN : SPID_FUNC_ESTABLISH;
295                 ret = __ccw_device_do_pgid(cdev, func);
296                 if (ret == 0 || ret == -EBUSY)
297                         return;
298                 cdev->private->iretry = 5;
299         }
300         ccw_device_verify_done(cdev, (sch->lpm != 0) ? 0 : -ENODEV);
301 }
302                 
303 /*
304  * Got interrupt for Set Path Group ID.
305  */
306 void
307 ccw_device_verify_irq(struct ccw_device *cdev, enum dev_event dev_event)
308 {
309         struct subchannel *sch;
310         struct irb *irb;
311
312         irb = (struct irb *) __LC_IRB;
313         /*
314          * Unsolicited interrupts may pertain to an earlier status pending or
315          * busy condition on the subchannel. Restart path verification.
316          */
317         if (irb->scsw.stctl ==
318             (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
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         switch (__ccw_device_check_pgid(cdev)) {
326         /* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */
327         case 0:
328                 /* Establish or Resign Path Group done. Update vpm. */
329                 if ((sch->lpm & cdev->private->imask) != 0)
330                         sch->vpm |= cdev->private->imask;
331                 else
332                         sch->vpm &= ~cdev->private->imask;
333                 cdev->private->iretry = 5;
334                 __ccw_device_verify_start(cdev);
335                 break;
336         case -EOPNOTSUPP:
337                 /*
338                  * One of those strange devices which claim to be able
339                  * to do multipathing but not for Set Path Group ID.
340                  */
341                 if (cdev->private->flags.pgid_single) {
342                         ccw_device_verify_done(cdev, -EOPNOTSUPP);
343                         break;
344                 }
345                 cdev->private->flags.pgid_single = 1;
346                 /* fall through. */
347         case -EAGAIN:           /* Try again. */
348                 __ccw_device_verify_start(cdev);
349                 break;
350         case -ETIME:            /* Set path group id stopped by timeout. */
351                 ccw_device_verify_done(cdev, -ETIME);
352                 break;
353         case -EACCES:           /* channel is not operational. */
354                 sch->lpm &= ~cdev->private->imask;
355                 sch->vpm &= ~cdev->private->imask;
356                 cdev->private->iretry = 5;
357                 __ccw_device_verify_start(cdev);
358                 break;
359         }
360 }
361
362 void
363 ccw_device_verify_start(struct ccw_device *cdev)
364 {
365         cdev->private->flags.pgid_single = 0;
366         cdev->private->iretry = 5;
367         __ccw_device_verify_start(cdev);
368 }
369
370 static void
371 __ccw_device_disband_start(struct ccw_device *cdev)
372 {
373         struct subchannel *sch;
374         int ret;
375
376         sch = to_subchannel(cdev->dev.parent);
377         while (cdev->private->imask != 0) {
378                 if (sch->lpm & cdev->private->imask) {
379                         ret = __ccw_device_do_pgid(cdev, SPID_FUNC_DISBAND);
380                         if (ret == 0)
381                                 return;
382                 }
383                 cdev->private->iretry = 5;
384                 cdev->private->imask >>= 1;
385         }
386         ccw_device_verify_done(cdev, (sch->lpm != 0) ? 0 : -ENODEV);
387 }
388
389 /*
390  * Got interrupt for Unset Path Group ID.
391  */
392 void
393 ccw_device_disband_irq(struct ccw_device *cdev, enum dev_event dev_event)
394 {
395         struct subchannel *sch;
396         struct irb *irb;
397         int ret;
398
399         irb = (struct irb *) __LC_IRB;
400         /* Ignore unsolicited interrupts. */
401         if (irb->scsw.stctl ==
402                         (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS))
403                 return;
404         if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
405                 return;
406         sch = to_subchannel(cdev->dev.parent);
407         ret = __ccw_device_check_pgid(cdev);
408         switch (ret) {
409         /* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */
410         case 0:                 /* disband successful. */
411                 sch->vpm = 0;
412                 ccw_device_disband_done(cdev, ret);
413                 break;
414         case -EOPNOTSUPP:
415                 /*
416                  * One of those strange devices which claim to be able
417                  * to do multipathing but not for Unset Path Group ID.
418                  */
419                 cdev->private->flags.pgid_single = 1;
420                 /* fall through. */
421         case -EAGAIN:           /* Try again. */
422                 __ccw_device_disband_start(cdev);
423                 break;
424         case -ETIME:            /* Set path group id stopped by timeout. */
425                 ccw_device_disband_done(cdev, -ETIME);
426                 break;
427         case -EACCES:           /* channel is not operational. */
428                 cdev->private->imask >>= 1;
429                 cdev->private->iretry = 5;
430                 __ccw_device_disband_start(cdev);
431                 break;
432         }
433 }
434
435 void
436 ccw_device_disband_start(struct ccw_device *cdev)
437 {
438         cdev->private->flags.pgid_single = 0;
439         cdev->private->iretry = 5;
440         cdev->private->imask = 0x80;
441         __ccw_device_disband_start(cdev);
442 }