blob: b4d1f08628429e23a5d07601e1ede5cd8f880ba3 [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Channel Subsystem tests library
*
* Copyright (c) 2020 IBM Corp
*
* Authors:
* Pierre Morel <pmorel@linux.ibm.com>
*/
#include <libcflat.h>
#include <alloc_phys.h>
#include <asm/page.h>
#include <string.h>
#include <interrupt.h>
#include <asm/arch_def.h>
#include <asm/time.h>
#include <asm/arch_def.h>
#include <alloc_page.h>
#include <malloc_io.h>
#include <css.h>
struct schib schib;
struct chsc_scsc *chsc_scsc;
static const char * const chsc_rsp_description[] = {
"CHSC unknown error",
"Command executed",
"Invalid command",
"Request-block error",
"Command not installed",
"Data not available",
"Absolute address of channel-subsystem communication block exceeds 2G - 1.",
"Invalid command format",
"Invalid channel-subsystem identification (CSSID)",
"The command-request block specified an invalid format for the command response block.",
"Invalid subchannel-set identification (SSID)",
"A busy condition precludes execution.",
};
static bool check_response(void *p)
{
struct chsc_header *h = p;
if (h->code == CHSC_RSP_OK)
return true;
if (h->code > CHSC_RSP_MAX)
h->code = 0;
report_abort("Response code %04x: %s", h->code,
chsc_rsp_description[h->code]);
return false;
}
bool chsc(void *p, uint16_t code, uint16_t len)
{
struct chsc_header *h = p;
h->code = code;
h->len = len;
switch (_chsc(p)) {
case 3:
report_abort("Subchannel invalid or not enabled.");
break;
case 2:
report_abort("CHSC subchannel busy.");
break;
case 1:
report_abort("Subchannel invalid or not enabled.");
break;
case 0:
return check_response(p + len);
}
return false;
}
bool get_chsc_scsc(void)
{
int i, n;
char buffer[510];
char *p;
if (chsc_scsc) /* chsc_scsc already initialized */
return true;
chsc_scsc = alloc_page();
if (!chsc_scsc) {
report_abort("could not allocate chsc_scsc page!");
return false;
}
if (!chsc(chsc_scsc, CHSC_SCSC, CHSC_SCSC_LEN))
return false;
for (i = 0, p = buffer; i < CSS_GENERAL_FEAT_BITLEN; i++) {
if (css_test_general_feature(i)) {
n = snprintf(p, sizeof(buffer), "%d,", i);
p += n;
}
}
report_info("General features: %s", buffer);
for (i = 0, p = buffer; i < CSS_CHSC_FEAT_BITLEN; i++) {
if (css_test_chsc_feature(i)) {
n = snprintf(p, sizeof(buffer), "%d,", i);
p += n;
}
}
report_info("CHSC features: %s", buffer);
return true;
}
/*
* css_enumerate:
* On success return the first subchannel ID found.
* On error return an invalid subchannel ID containing cc
*/
int css_enumerate(void)
{
struct pmcw *pmcw = &schib.pmcw;
int scn_found = 0;
int dev_found = 0;
int schid = 0;
int cc;
int scn;
for (scn = 0; scn < 0xffff; scn++) {
cc = stsch(scn | SCHID_ONE, &schib);
switch (cc) {
case 0: /* 0 means SCHIB stored */
break;
case 3: /* 3 means no more channels */
goto out;
default: /* 1 or 2 should never happen for STSCH */
report_abort("Unexpected error %d on subchannel %08x",
cc, scn | SCHID_ONE);
return cc;
}
/* We currently only support type 0, a.k.a. I/O channels */
if (PMCW_CHANNEL_TYPE(pmcw) != 0)
continue;
/* We ignore I/O channels without valid devices */
scn_found++;
if (!(pmcw->flags & PMCW_DNV))
continue;
/* We keep track of the first device as our test device */
if (!schid)
schid = scn | SCHID_ONE;
report_info("Found subchannel %08x", scn | SCHID_ONE);
dev_found++;
}
out:
report_info("Tested subchannels: %d, I/O subchannels: %d, I/O devices: %d",
scn, scn_found, dev_found);
return schid;
}
/*
* css_enabled: report if the subchannel is enabled
* @schid: Subchannel Identifier
* Return value:
* true if the subchannel is enabled
* false otherwise
*/
bool css_enabled(int schid)
{
struct pmcw *pmcw = &schib.pmcw;
int cc;
cc = stsch(schid, &schib);
if (cc) {
report_info("stsch: updating sch %08x failed with cc=%d",
schid, cc);
return false;
}
if (!(pmcw->flags & PMCW_ENABLE)) {
report_info("stsch: sch %08x not enabled", schid);
return false;
}
return true;
}
/*
* css_enable: enable the subchannel with the specified ISC
* @schid: Subchannel Identifier
* @isc : number of the interruption subclass to use
* Return value:
* On success: 0
* On error the CC of the faulty instruction
* or -1 if the retry count is exceeded.
*/
int css_enable(int schid, int isc)
{
struct pmcw *pmcw = &schib.pmcw;
int retry_count = 0;
uint16_t flags;
int cc;
/* Read the SCHIB for this subchannel */
cc = stsch(schid, &schib);
if (cc) {
report_info("stsch: sch %08x failed with cc=%d", schid, cc);
return cc;
}
flags = PMCW_ENABLE | (isc << PMCW_ISC_SHIFT);
if ((pmcw->flags & (PMCW_ISC_MASK | PMCW_ENABLE)) == flags) {
report_info("stsch: sch %08x already enabled", schid);
return 0;
}
retry:
/* Update the SCHIB to enable the channel and set the ISC */
pmcw->flags &= ~PMCW_ISC_MASK;
pmcw->flags |= flags;
/* Tell the CSS we want to modify the subchannel */
cc = msch(schid, &schib);
if (cc) {
/*
* If the subchannel is status pending or
* if a function is in progress,
* we consider both cases as errors.
*/
report_info("msch: sch %08x failed with cc=%d", schid, cc);
return cc;
}
/*
* Read the SCHIB again to verify the enablement
*/
if (css_enabled(schid))
return 0;
if (retry_count++ < MAX_ENABLE_RETRIES) {
mdelay(10); /* the hardware was not ready, give it some time */
goto retry;
}
report_info("msch: modifying sch %08x failed after %d retries. pmcw flags: %04x",
schid, retry_count, pmcw->flags);
return -1;
}
/*
* schib_update_mb: update the subchannel Measurement Block
* @schid: Subchannel Identifier
* @mb : 64bit address of the measurement block
* @mbi : the measurement block offset
* @flags : PMCW_MBUE to enable measurement block update
* PMCW_DCTME to enable device connect time
* 0 to disable measurement
* @format1: set if format 1 is to be used
*/
static bool schib_update_mb(int schid, uint64_t mb, uint16_t mbi,
uint16_t flags, bool format1)
{
struct pmcw *pmcw = &schib.pmcw;
int cc;
/* Read the SCHIB for this subchannel */
cc = stsch(schid, &schib);
if (cc) {
report_info("stsch: sch %08x failed with cc=%d", schid, cc);
return false;
}
/* Update the SCHIB to enable the measurement block */
if (flags) {
pmcw->flags |= flags;
if (format1)
pmcw->flags2 |= PMCW_MBF1;
else
pmcw->flags2 &= ~PMCW_MBF1;
pmcw->mbi = mbi;
schib.mbo = mb & ~0x3f;
} else {
pmcw->flags &= ~(PMCW_MBUE | PMCW_DCTME);
}
/* Tell the CSS we want to modify the subchannel */
cc = msch(schid, &schib);
if (cc) {
/*
* If the subchannel is status pending or
* if a function is in progress,
* we consider both cases as errors.
*/
report_info("msch: sch %08x failed with cc=%d", schid, cc);
return false;
}
/*
* Read the SCHIB again
*/
cc = stsch(schid, &schib);
if (cc) {
report_info("stsch: updating sch %08x failed with cc=%d",
schid, cc);
return false;
}
return true;
}
/*
* css_enable_mb: enable the subchannel Measurement Block
* @schid: Subchannel Identifier
* @mb : 64bit address of the measurement block
* @format1: set if format 1 is to be used
* @mbi : the measurement block offset
* @flags : PMCW_MBUE to enable measurement block update
* PMCW_DCTME to enable device connect time
*/
bool css_enable_mb(int schid, uint64_t mb, uint16_t mbi, uint16_t flags,
bool format1)
{
int retry_count = MAX_ENABLE_RETRIES;
struct pmcw *pmcw = &schib.pmcw;
while (retry_count-- &&
!schib_update_mb(schid, mb, mbi, flags, format1))
mdelay(10); /* the hardware was not ready, give it some time */
return schib.mbo == mb && pmcw->mbi == mbi;
}
/*
* css_disable_mb: disable the subchannel Measurement Block
* @schid: Subchannel Identifier
*/
bool css_disable_mb(int schid)
{
int retry_count = MAX_ENABLE_RETRIES;
while (retry_count-- &&
!schib_update_mb(schid, 0, 0, 0, 0))
mdelay(10); /* the hardware was not ready, give it some time */
return retry_count > 0;
}
static struct irb irb;
void css_irq_io(void)
{
int ret = 0;
char *flags;
int sid;
report_prefix_push("Interrupt");
sid = lowcore.subsys_id_word;
/* Lowlevel set the SID as interrupt parameter. */
if (lowcore.io_int_param != sid) {
report_fail("io_int_param: %x differs from subsys_id_word: %x",
lowcore.io_int_param, sid);
goto pop;
}
report_prefix_pop();
report_prefix_push("tsch");
ret = tsch(sid, &irb);
switch (ret) {
case 1:
dump_irb(&irb);
flags = dump_scsw_flags(irb.scsw.ctrl);
report_fail("I/O interrupt, but tsch returns CC 1 for subchannel %08x.SCSW flags: %s",
sid, flags);
break;
case 2:
report_fail("tsch returns unexpected CC 2");
break;
case 3:
report_fail("tsch reporting sch %08x as not operational", sid);
break;
case 0:
/* Stay humble on success */
break;
}
pop:
report_prefix_pop();
lowcore.io_old_psw.mask &= ~PSW_MASK_WAIT;
}
int start_ccw1_chain(unsigned int sid, struct ccw1 *ccw)
{
struct orb orb = {
.intparm = sid,
.ctrl = ORB_CTRL_ISIC|ORB_CTRL_FMT|ORB_LPM_DFLT,
.cpa = (unsigned int) (unsigned long)ccw,
};
return ssch(sid, &orb);
}
struct ccw1 *ccw_alloc(int code, void *data, int count, unsigned char flags)
{
struct ccw1 *ccw;
ccw = alloc_io_mem(sizeof(*ccw), 0);
if (!ccw)
return NULL;
ccw->code = code;
ccw->flags = flags;
ccw->count = count;
ccw->data_address = (int)(unsigned long)data;
return ccw;
}
/* wait_and_check_io_completion:
* @schid: the subchannel ID
*
* Makes the most common check to validate a successful I/O
* completion.
* Only report failures.
*/
int wait_and_check_io_completion(int schid)
{
int ret = 0;
wait_for_interrupt(PSW_MASK_IO);
report_prefix_push("check I/O completion");
if (lowcore.io_int_param != schid) {
report_fail("interrupt parameter: expected %08x got %08x",
schid, lowcore.io_int_param);
ret = -1;
goto end;
}
/* Verify that device status is valid */
if (!(irb.scsw.ctrl & SCSW_SC_PENDING)) {
report_fail("No status pending after interrupt. Subch Ctrl: %08x",
irb.scsw.ctrl);
ret = -1;
goto end;
}
if (!(irb.scsw.ctrl & (SCSW_SC_SECONDARY | SCSW_SC_PRIMARY))) {
report_fail("Primary or secondary status missing. Subch Ctrl: %08x",
irb.scsw.ctrl);
ret = -1;
goto end;
}
if (!(irb.scsw.dev_stat & (SCSW_DEVS_DEV_END | SCSW_DEVS_SCH_END))) {
report_fail("No device end or sch end. Dev. status: %02x",
irb.scsw.dev_stat);
ret = -1;
goto end;
}
if (irb.scsw.sch_stat & ~SCSW_SCHS_IL) {
report_info("Unexpected Subch. status %02x", irb.scsw.sch_stat);
ret = -1;
goto end;
}
end:
report_prefix_pop();
return ret;
}
/*
* css_residual_count
* Return the residual count, if it is valid.
*
* Return value:
* Success: the residual count
* Not meaningful: -1 (-1 can not be a valid count)
*/
int css_residual_count(unsigned int schid)
{
if (!(irb.scsw.ctrl & (SCSW_SC_PENDING | SCSW_SC_PRIMARY)))
return -1;
if (irb.scsw.dev_stat)
if (irb.scsw.sch_stat & ~(SCSW_SCHS_PCI | SCSW_SCHS_IL))
return -1;
return irb.scsw.count;
}
/*
* enable_io_isc: setup ISC in Control Register 6
* @isc: The interruption Sub Class as a bitfield
*/
void enable_io_isc(uint8_t isc)
{
uint64_t value;
value = (uint64_t)isc << 24;
lctlg(6, value);
}
static int is_path_installed(struct schib *schib, int chp_idx)
{
return schib->pmcw.pim & BIT(7 - chp_idx);
}
/*
* css_find_installed_chpid: find any installed CHPID
* @sid: subsystem-identification word
* @chpid_out: store the found chpid here, left alone if none found
*
* returns 0 on success, -1 if no chpid found any other value
* indicates the condition code of a failing STSCH instruction
*/
int css_find_installed_chpid(int sid, uint8_t *chpid_out)
{
int cc;
cc = stsch(sid, &schib);
if (cc) {
report_fail("%s: sch %08x failed with cc=%d", __func__, sid, cc);
return cc;
}
for (int i = 0; i < ARRAY_SIZE(schib.pmcw.chpid); i++) {
if (is_path_installed(&schib, i)) {
*chpid_out = schib.pmcw.chpid[i];
return 0;
}
}
return -1;
}
/*
* css_generate_crw: Generate a CRW by issuing RCHP on any channel path
* @sid: subsystem-identification word
*
* returns 0 when a CRW was generated, -1 if no chpid found.
*/
int css_generate_crw(int sid)
{
int ret, cc;
uint8_t chpid;
report_prefix_push("Generate CRW");
ret = css_find_installed_chpid(sid, &chpid);
if (ret) {
report_fail("No CHPID found: ret=%d", ret);
return -1;
}
cc = rchp(chpid);
report(!cc, "rhcp cc != 0");
report_prefix_pop();
return 0;
}