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