| /* 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; | |
| } |