0a87c5cfc0
There are several s390 diagnose calls, which must be executed below the 2GB memory boundary. In order to enforce this, those diagnoses must be compiled into the kernel. Currently diag 14 can be called within the vmur kernel module from addresses above 2GB. This leads to specification exceptions. This patch moves diag10, diag14 and diag210 into the new diag.c file. Signed-off-by: Michael Holzheu <holzheu@de.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
308 lines
8.3 KiB
C
308 lines
8.3 KiB
C
/*
|
|
* drivers/s390/cio/device_id.c
|
|
*
|
|
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
|
|
* IBM Corporation
|
|
* Author(s): Cornelia Huck (cornelia.huck@de.ibm.com)
|
|
* Martin Schwidefsky (schwidefsky@de.ibm.com)
|
|
*
|
|
* Sense ID functions.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
|
|
#include <asm/ccwdev.h>
|
|
#include <asm/delay.h>
|
|
#include <asm/cio.h>
|
|
#include <asm/lowcore.h>
|
|
#include <asm/diag.h>
|
|
|
|
#include "cio.h"
|
|
#include "cio_debug.h"
|
|
#include "css.h"
|
|
#include "device.h"
|
|
#include "ioasm.h"
|
|
|
|
/*
|
|
* Input :
|
|
* devno - device number
|
|
* ps - pointer to sense ID data area
|
|
* Output : none
|
|
*/
|
|
static void
|
|
VM_virtual_device_info (__u16 devno, struct senseid *ps)
|
|
{
|
|
static struct {
|
|
int vrdcvcla, vrdcvtyp, cu_type;
|
|
} vm_devices[] = {
|
|
{ 0x08, 0x01, 0x3480 },
|
|
{ 0x08, 0x02, 0x3430 },
|
|
{ 0x08, 0x10, 0x3420 },
|
|
{ 0x08, 0x42, 0x3424 },
|
|
{ 0x08, 0x44, 0x9348 },
|
|
{ 0x08, 0x81, 0x3490 },
|
|
{ 0x08, 0x82, 0x3422 },
|
|
{ 0x10, 0x41, 0x1403 },
|
|
{ 0x10, 0x42, 0x3211 },
|
|
{ 0x10, 0x43, 0x3203 },
|
|
{ 0x10, 0x45, 0x3800 },
|
|
{ 0x10, 0x47, 0x3262 },
|
|
{ 0x10, 0x48, 0x3820 },
|
|
{ 0x10, 0x49, 0x3800 },
|
|
{ 0x10, 0x4a, 0x4245 },
|
|
{ 0x10, 0x4b, 0x4248 },
|
|
{ 0x10, 0x4d, 0x3800 },
|
|
{ 0x10, 0x4e, 0x3820 },
|
|
{ 0x10, 0x4f, 0x3820 },
|
|
{ 0x10, 0x82, 0x2540 },
|
|
{ 0x10, 0x84, 0x3525 },
|
|
{ 0x20, 0x81, 0x2501 },
|
|
{ 0x20, 0x82, 0x2540 },
|
|
{ 0x20, 0x84, 0x3505 },
|
|
{ 0x40, 0x01, 0x3278 },
|
|
{ 0x40, 0x04, 0x3277 },
|
|
{ 0x40, 0x80, 0x2250 },
|
|
{ 0x40, 0xc0, 0x5080 },
|
|
{ 0x80, 0x00, 0x3215 },
|
|
};
|
|
struct diag210 diag_data;
|
|
int ccode, i;
|
|
|
|
CIO_TRACE_EVENT (4, "VMvdinf");
|
|
|
|
diag_data = (struct diag210) {
|
|
.vrdcdvno = devno,
|
|
.vrdclen = sizeof (diag_data),
|
|
};
|
|
|
|
ccode = diag210 (&diag_data);
|
|
ps->reserved = 0xff;
|
|
|
|
/* Special case for bloody osa devices. */
|
|
if (diag_data.vrdcvcla == 0x02 &&
|
|
diag_data.vrdcvtyp == 0x20) {
|
|
ps->cu_type = 0x3088;
|
|
ps->cu_model = 0x60;
|
|
return;
|
|
}
|
|
for (i = 0; i < ARRAY_SIZE(vm_devices); i++)
|
|
if (diag_data.vrdcvcla == vm_devices[i].vrdcvcla &&
|
|
diag_data.vrdcvtyp == vm_devices[i].vrdcvtyp) {
|
|
ps->cu_type = vm_devices[i].cu_type;
|
|
return;
|
|
}
|
|
CIO_MSG_EVENT(0, "DIAG X'210' for device %04X returned (cc = %d):"
|
|
"vdev class : %02X, vdev type : %04X \n ... "
|
|
"rdev class : %02X, rdev type : %04X, "
|
|
"rdev model: %02X\n",
|
|
devno, ccode,
|
|
diag_data.vrdcvcla, diag_data.vrdcvtyp,
|
|
diag_data.vrdcrccl, diag_data.vrdccrty,
|
|
diag_data.vrdccrmd);
|
|
}
|
|
|
|
/*
|
|
* Start Sense ID helper function.
|
|
* Try to obtain the 'control unit'/'device type' information
|
|
* associated with the subchannel.
|
|
*/
|
|
static int
|
|
__ccw_device_sense_id_start(struct ccw_device *cdev)
|
|
{
|
|
struct subchannel *sch;
|
|
struct ccw1 *ccw;
|
|
int ret;
|
|
|
|
sch = to_subchannel(cdev->dev.parent);
|
|
/* Setup sense channel program. */
|
|
ccw = cdev->private->iccws;
|
|
if (sch->schib.pmcw.pim != 0x80) {
|
|
/* more than one path installed. */
|
|
ccw->cmd_code = CCW_CMD_SUSPEND_RECONN;
|
|
ccw->cda = 0;
|
|
ccw->count = 0;
|
|
ccw->flags = CCW_FLAG_SLI | CCW_FLAG_CC;
|
|
ccw++;
|
|
}
|
|
ccw->cmd_code = CCW_CMD_SENSE_ID;
|
|
ccw->cda = (__u32) __pa (&cdev->private->senseid);
|
|
ccw->count = sizeof (struct senseid);
|
|
ccw->flags = CCW_FLAG_SLI;
|
|
|
|
/* Reset device status. */
|
|
memset(&cdev->private->irb, 0, sizeof(struct irb));
|
|
|
|
/* Try on every path. */
|
|
ret = -ENODEV;
|
|
while (cdev->private->imask != 0) {
|
|
if ((sch->opm & cdev->private->imask) != 0 &&
|
|
cdev->private->iretry > 0) {
|
|
cdev->private->iretry--;
|
|
/* Reset internal retry indication. */
|
|
cdev->private->flags.intretry = 0;
|
|
ret = cio_start (sch, cdev->private->iccws,
|
|
cdev->private->imask);
|
|
/* ret is 0, -EBUSY, -EACCES or -ENODEV */
|
|
if (ret != -EACCES)
|
|
return ret;
|
|
}
|
|
cdev->private->imask >>= 1;
|
|
cdev->private->iretry = 5;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void
|
|
ccw_device_sense_id_start(struct ccw_device *cdev)
|
|
{
|
|
int ret;
|
|
|
|
memset (&cdev->private->senseid, 0, sizeof (struct senseid));
|
|
cdev->private->senseid.cu_type = 0xFFFF;
|
|
cdev->private->imask = 0x80;
|
|
cdev->private->iretry = 5;
|
|
ret = __ccw_device_sense_id_start(cdev);
|
|
if (ret && ret != -EBUSY)
|
|
ccw_device_sense_id_done(cdev, ret);
|
|
}
|
|
|
|
/*
|
|
* Called from interrupt context to check if a valid answer
|
|
* to Sense ID was received.
|
|
*/
|
|
static int
|
|
ccw_device_check_sense_id(struct ccw_device *cdev)
|
|
{
|
|
struct subchannel *sch;
|
|
struct irb *irb;
|
|
|
|
sch = to_subchannel(cdev->dev.parent);
|
|
irb = &cdev->private->irb;
|
|
/* Did we get a proper answer ? */
|
|
if (cdev->private->senseid.cu_type != 0xFFFF &&
|
|
cdev->private->senseid.reserved == 0xFF) {
|
|
if (irb->scsw.count < sizeof (struct senseid) - 8)
|
|
cdev->private->flags.esid = 1;
|
|
return 0; /* Success */
|
|
}
|
|
/* Check the error cases. */
|
|
if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) {
|
|
/* Retry Sense ID if requested. */
|
|
if (cdev->private->flags.intretry) {
|
|
cdev->private->flags.intretry = 0;
|
|
return -EAGAIN;
|
|
}
|
|
return -ETIME;
|
|
}
|
|
if (irb->esw.esw0.erw.cons && (irb->ecw[0] & SNS0_CMD_REJECT)) {
|
|
/*
|
|
* if the device doesn't support the SenseID
|
|
* command further retries wouldn't help ...
|
|
* NB: We don't check here for intervention required like we
|
|
* did before, because tape devices with no tape inserted
|
|
* may present this status *in conjunction with* the
|
|
* sense id information. So, for intervention required,
|
|
* we use the "whack it until it talks" strategy...
|
|
*/
|
|
CIO_MSG_EVENT(2, "SenseID : device %04x on Subchannel "
|
|
"0.%x.%04x reports cmd reject\n",
|
|
cdev->private->dev_id.devno, sch->schid.ssid,
|
|
sch->schid.sch_no);
|
|
return -EOPNOTSUPP;
|
|
}
|
|
if (irb->esw.esw0.erw.cons) {
|
|
CIO_MSG_EVENT(2, "SenseID : UC on dev 0.%x.%04x, "
|
|
"lpum %02X, cnt %02d, sns :"
|
|
" %02X%02X%02X%02X %02X%02X%02X%02X ...\n",
|
|
cdev->private->dev_id.ssid,
|
|
cdev->private->dev_id.devno,
|
|
irb->esw.esw0.sublog.lpum,
|
|
irb->esw.esw0.erw.scnt,
|
|
irb->ecw[0], irb->ecw[1],
|
|
irb->ecw[2], irb->ecw[3],
|
|
irb->ecw[4], irb->ecw[5],
|
|
irb->ecw[6], irb->ecw[7]);
|
|
return -EAGAIN;
|
|
}
|
|
if (irb->scsw.cc == 3) {
|
|
if ((sch->orb.lpm &
|
|
sch->schib.pmcw.pim & sch->schib.pmcw.pam) != 0)
|
|
CIO_MSG_EVENT(2, "SenseID : path %02X for device %04x "
|
|
"on subchannel 0.%x.%04x is "
|
|
"'not operational'\n", sch->orb.lpm,
|
|
cdev->private->dev_id.devno,
|
|
sch->schid.ssid, sch->schid.sch_no);
|
|
return -EACCES;
|
|
}
|
|
/* Hmm, whatever happened, try again. */
|
|
CIO_MSG_EVENT(2, "SenseID : start_IO() for device %04x on "
|
|
"subchannel 0.%x.%04x returns status %02X%02X\n",
|
|
cdev->private->dev_id.devno, sch->schid.ssid,
|
|
sch->schid.sch_no,
|
|
irb->scsw.dstat, irb->scsw.cstat);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/*
|
|
* Got interrupt for Sense ID.
|
|
*/
|
|
void
|
|
ccw_device_sense_id_irq(struct ccw_device *cdev, enum dev_event dev_event)
|
|
{
|
|
struct subchannel *sch;
|
|
struct irb *irb;
|
|
int ret;
|
|
|
|
sch = to_subchannel(cdev->dev.parent);
|
|
irb = (struct irb *) __LC_IRB;
|
|
/* Retry sense id, if needed. */
|
|
if (irb->scsw.stctl ==
|
|
(SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
|
|
if ((irb->scsw.cc == 1) || !irb->scsw.actl) {
|
|
ret = __ccw_device_sense_id_start(cdev);
|
|
if (ret && ret != -EBUSY)
|
|
ccw_device_sense_id_done(cdev, ret);
|
|
}
|
|
return;
|
|
}
|
|
if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
|
|
return;
|
|
ret = ccw_device_check_sense_id(cdev);
|
|
memset(&cdev->private->irb, 0, sizeof(struct irb));
|
|
switch (ret) {
|
|
/* 0, -ETIME, -EOPNOTSUPP, -EAGAIN or -EACCES */
|
|
case 0: /* Sense id succeeded. */
|
|
case -ETIME: /* Sense id stopped by timeout. */
|
|
ccw_device_sense_id_done(cdev, ret);
|
|
break;
|
|
case -EACCES: /* channel is not operational. */
|
|
sch->lpm &= ~cdev->private->imask;
|
|
cdev->private->imask >>= 1;
|
|
cdev->private->iretry = 5;
|
|
/* fall through. */
|
|
case -EAGAIN: /* try again. */
|
|
ret = __ccw_device_sense_id_start(cdev);
|
|
if (ret == 0 || ret == -EBUSY)
|
|
break;
|
|
/* fall through. */
|
|
default: /* Sense ID failed. Try asking VM. */
|
|
if (MACHINE_IS_VM) {
|
|
VM_virtual_device_info (cdev->private->dev_id.devno,
|
|
&cdev->private->senseid);
|
|
if (cdev->private->senseid.cu_type != 0xFFFF) {
|
|
/* Got the device information from VM. */
|
|
ccw_device_sense_id_done(cdev, 0);
|
|
return;
|
|
}
|
|
}
|
|
/*
|
|
* If we can't couldn't identify the device type we
|
|
* consider the device "not operational".
|
|
*/
|
|
ccw_device_sense_id_done(cdev, -ENODEV);
|
|
break;
|
|
}
|
|
}
|