/* SPDX-License-Identifier: GPL-2.0-only */ /* * Channel Subsystem tests library * * Copyright (c) 2020 IBM Corp * * Authors: * Pierre Morel */ #include #include #include #include #include #include #include #include #include #include #include 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; }