/*
* S/390 common I/O routines -- channel subsystem call
*
* Copyright IBM Corp. 1999,2012
* Author(s): Ingo Adlung (adlung@de.ibm.com)
* Cornelia Huck (cornelia.huck@de.ibm.com)
* Arnd Bergmann (arndb@de.ibm.com)
*/
#define KMSG_COMPONENT "cio"
#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/pci.h>
#include <asm/cio.h>
#include <asm/chpid.h>
#include <asm/chsc.h>
#include <asm/crw.h>
#include <asm/isc.h>
#include "css.h"
#include "cio.h"
#include "cio_debug.h"
#include "ioasm.h"
#include "chp.h"
#include "chsc.h"
static void *sei_page;
static void *chsc_page;
static DEFINE_SPINLOCK(chsc_page_lock);
/**
* chsc_error_from_response() - convert a chsc response to an error
* @response: chsc response code
*
* Returns an appropriate Linux error code for @response.
*/
int chsc_error_from_response(int response)
{
switch (response) {
case 0x0001:
return 0;
case 0x0002:
case 0x0003:
case 0x0006:
case 0x0007:
case 0x0008:
case 0x000a:
case 0x0104:
return -EINVAL;
case 0x0004:
return -EOPNOTSUPP;
case 0x000b:
case 0x0107: /* "Channel busy" for the op 0x003d */
return -EBUSY;
case 0x0100:
case 0x0102:
return -ENOMEM;
default:
return -EIO;
}
}
EXPORT_SYMBOL_GPL(chsc_error_from_response);
struct chsc_ssd_area {
struct chsc_header request;
u16 :10;
u16 ssid:2;
u16 :4;
u16 f_sch; /* first subchannel */
u16 :16;
u16 l_sch; /* last subchannel */
u32 :32;
struct chsc_header response;
u32 :32;
u8 sch_valid : 1;
u8 dev_valid : 1;
u8 st : 3; /* subchannel type */
u8 zeroes : 3;
u8 unit_addr; /* unit address */
u16 devno; /* device number */
u8 path_mask;
u8 fla_valid_mask;
u16 sch; /* subchannel */
u8 chpid[8]; /* chpids 0-7 */
u16 fla[8]; /* full link addresses 0-7 */
} __attribute__ ((packed));
int chsc_get_ssd_info(struct subchannel_id schid, struct chsc_ssd_info *ssd)
{
struct chsc_ssd_area *ssd_area;
int ccode;
int ret;
int i;
int mask;
spin_lock_irq(&chsc_page_lock);
memset(chsc_page, 0, PAGE_SIZE);
ssd_area = chsc_page;
ssd_area->request.length = 0x0010;
ssd_area->request.code = 0x0004;
ssd_area->ssid = schid.ssid;
ssd_area->f_sch = schid.sch_no;
ssd_area->l_sch = schid.sch_no;
ccode = chsc(ssd_area);
/* Check response. */
if (ccode > 0) {
ret = (ccode == 3) ? -ENODEV : -EBUSY;
goto out;
}
ret = chsc_error_from_response(ssd_area->response.code);
if (ret != 0) {
CIO_MSG_EVENT(2, "chsc: ssd failed for 0.%x.%04x (rc=%04x)\n",
schid.ssid, schid.sch_no,
ssd_area->response.code);
goto out;
}
if (!ssd_area->sch_valid) {
ret = -ENODEV;
goto out;
}
/* Copy data */
ret = 0;
memset(ssd, 0, sizeof(struct chsc_ssd_info));
if ((ssd_area->st != SUBCHANNEL_TYPE_IO) &&
(ssd_area->st != SUBCHANNEL_TYPE_MSG))
goto out;
ssd->path_mask = ssd_area->path_mask;
ssd->fla_valid_mask = ssd_area->fla_valid_mask;
for (i = 0; i < 8; i++) {
mask = 0x80 >> i;
if (ssd_area->path_mask & mask) {
chp_id_init(&ssd->chpid[i]);
ssd->chpid[i].id = ssd_area->chpid[i];
}
if (ssd_area->fla_valid_mask & mask)
ssd->fla[i] = ssd_area->fla[i];
}
out:
spin_unlock_irq(&chsc_page_lock);
return ret;
}
/**
* chsc_ssqd() - store subchannel QDIO data (SSQD)
* @schid: id of the subchannel on which SSQD is performed
* @ssqd: request and response block for SSQD
*
* Returns 0 on success.
*/
int chsc_ssqd(struct subchannel_id schid, struct chsc_ssqd_area *ssqd)
{
memset(ssqd, 0, sizeof(*ssqd));
ssqd->request.length = 0x0010;
ssqd->request.code = 0x0024;
ssqd->first_sch = schid.sch_no;
ssqd->last_sch = schid.sch_no;
ssqd->ssid = schid.ssid;
if (chsc(ssqd))
return -EIO;
return chsc_error_from_response(ssqd->response.code);
}
EXPORT_SYMBOL_GPL(chsc_ssqd);
/**
* chsc_sadc() - set adapter device controls (SADC)
* @schid: id of the subchannel on which SADC is performed
* @scssc: request and response block for SADC
* @summary_indicator_addr: summary indicator address
* @subchannel_indicator_addr: subchannel indicator address
*
* Returns 0 on success.
*/
int chsc_sadc(struct subchannel_id schid, struct chsc_scssc_area *scssc,
u64 summary_indicator_addr, u64 subchannel_indicator_addr)
{
memset(scssc, 0, sizeof(*scssc));
scssc->request.length = 0x0fe0;
scssc->request.code = 0x0021;
scssc->operation_code = 0;
scssc->summary_indicator_addr = summary_indicator_addr;
scssc->subchannel_indicator_addr = subchannel_indicator_addr;
scssc->ks = PAGE_DEFAULT_KEY >> 4;
scssc->kc = PAGE_DEFAULT_KEY >> 4;
scssc->isc = QDIO_AIRQ_ISC;
scssc->schid = schid;
/* enable the time delay disablement facility */
if (css_general_characteristics.aif_tdd)
scssc->word_with_d_bit = 0x10000000;
if (chsc(scssc))
return -EIO;
return chsc_error_from_response(scssc->response.code);
}
EXPORT_SYMBOL_GPL(chsc_sadc);
static int s390_subchannel_remove_chpid(struct subchannel *sch, void *data)
{
spin_lock_irq(sch->lock);
if (sch->driver && sch->driver->chp_event)
if (sch->driver->chp_event(sch, data, CHP_OFFLINE) != 0)
goto out_unreg;
spin_unlock_irq(sch->lock);
return 0;
out_unreg:
sch->lpm = 0;
spin_unlock_irq(sch->lock);
css_schedule_eval(sch->schid);
return 0;
}
void chsc_chp_offline(struct chp_id chpid)
{
char dbf_txt[15];
struct chp_link link;
sprintf(dbf_txt, "chpr%x.%02x", chpid.cssid, chpid.id);
CIO_TRACE_EVENT(2, dbf_txt);
if (chp_get_status(chpid) <= 0)
return;
memset(&link, 0, sizeof(struct chp_link));
link.chpid = chpid;
/* Wait until previous actions have settled. */
css_wait_for_slow_path();
for_each_subchannel_staged(s390_subchannel_remove_chpid, NULL, &link);
}
static int __s390_process_res_acc(struct subchannel *sch, void *data)
{
spin_lock_irq(sch->lock);
if (sch->driver && sch->driver->chp_event)
sch->driver->chp_event(sch, data, CHP_ONLINE);
spin_unlock_irq(sch->lock);
return 0;
}
static void s390_process_res_acc(struct chp_link *link)
{
char dbf_txt[15];
sprintf(dbf_txt, "accpr%x.%02x", link->chpid.cssid,
link->chpid.id);
CIO_TRACE_EVENT( 2, dbf_txt);
if (link->fla != 0) {
sprintf(dbf_txt, "fla%x", link->fla);
CIO_TRACE_EVENT( 2, dbf_txt);
}
/* Wait until previous actions have settled. */
css_wait_for_slow_path();
/*
* I/O resources may have become accessible.
* Scan through all subchannels that may be concerned and
* do a validation on those.
* The more information we have (info), the less scanning
* will we have to do.
*/
for_each_subchannel_staged(__s390_process_res_acc, NULL, link);
css_schedule_reprobe();
}
static int
__get_chpid_from_lir(void *data)
{
struct lir {
u8 iq;
u8 ic;
u16 sci;
/* incident-node descriptor */
u32 indesc[28];
/* attached-node descriptor */
u32 andesc[28];
/* incident-specific information */
u32 isinfo[28];
} __attribute__ ((packed)) *lir;
lir = data;
if (!(lir->iq&0x80))
/* NULL link incident record */
return -EINVAL;
if (!(lir->indesc[0]&0xc0000000))
/* node descriptor not valid */
return -EINVAL;
if (!(lir->indesc[0]&0x10000000))
/* don't handle device-type nodes - FIXME */
return -EINVAL;
/* Byte 3 contains the chpid. Could also be CTCA, but we don't care */
return (u16) (lir->indesc[0]&0x000000ff);
}
struct chsc_sei_nt0_area {
u8 flags;
u8 vf; /* validity flags */
u8 rs; /* reporting source */
u8 cc; /* content code */
u16 fla; /* full link address */
u16 rsid; /* reporting source id */
u32 reserved1;
u32 reserved2;
/* ccdf has to be big enough for a link-incident record */
u8 ccdf[PAGE_SIZE - 24 - 16]; /* content-code dependent field */
} __packed;
struct chsc_sei_nt2_area {
u8 flags; /* p and v bit */
u8 reserved1;
u8 reserved2;
u8 cc; /* content code */
u32 reserved3[13];
u8 ccdf[PAGE_SIZE - 24 - 56]; /* content-code dependent field */
} __packed;
#define CHSC_SEI_NT0 (1ULL << 63)
#define CHSC_SEI_NT2