| /* SPDX-License-Identifier: GPL-2.0-only */ |
| /* |
| * Channel Subsystem tests |
| * |
| * Copyright (c) 2020 IBM Corp |
| * |
| * Authors: |
| * Pierre Morel <pmorel@linux.ibm.com> |
| */ |
| |
| #include <libcflat.h> |
| #include <interrupt.h> |
| #include <hardware.h> |
| |
| #include <asm/arch_def.h> |
| #include <asm/page.h> |
| |
| #include <malloc_io.h> |
| #include <css.h> |
| |
| #define DEFAULT_CU_TYPE 0x3832 /* virtio-ccw */ |
| static unsigned long cu_type = DEFAULT_CU_TYPE; |
| |
| static int test_device_sid; |
| static struct senseid *senseid; |
| struct ccw1 *ccw; |
| |
| char alignment_test_page[PAGE_SIZE] __attribute__((aligned(PAGE_SIZE))); |
| |
| static void test_enumerate(void) |
| { |
| test_device_sid = css_enumerate(); |
| if (test_device_sid & SCHID_ONE) { |
| report_pass("Schid of first I/O device: 0x%08x", test_device_sid); |
| return; |
| } |
| report_fail("No I/O device found"); |
| } |
| |
| static void test_enable(void) |
| { |
| int cc; |
| |
| if (!test_device_sid) { |
| report_skip("No device"); |
| return; |
| } |
| |
| cc = css_enable(test_device_sid, IO_SCH_ISC); |
| |
| report(cc == 0, "Enable subchannel %08x", test_device_sid); |
| } |
| |
| /* |
| * test_sense |
| * Pre-requisites: |
| * - We need the test device as the first recognized |
| * device by the enumeration. |
| */ |
| static void test_sense(void) |
| { |
| int ret; |
| int len; |
| |
| if (!test_device_sid) { |
| report_skip("No device"); |
| return; |
| } |
| |
| ret = css_enable(test_device_sid, IO_SCH_ISC); |
| if (ret) { |
| report_fail("Could not enable the subchannel: %08x", |
| test_device_sid); |
| return; |
| } |
| |
| lowcore.io_int_param = 0; |
| |
| senseid = alloc_io_mem(sizeof(*senseid), 0); |
| if (!senseid) { |
| report_fail("Allocation of senseid"); |
| return; |
| } |
| |
| ccw = ccw_alloc(CCW_CMD_SENSE_ID, senseid, sizeof(*senseid), CCW_F_SLI); |
| if (!ccw) { |
| report_fail("Allocation of CCW"); |
| goto error_ccw; |
| } |
| |
| ret = start_ccw1_chain(test_device_sid, ccw); |
| if (ret) { |
| report_fail("Starting CCW chain"); |
| goto error; |
| } |
| |
| if (wait_and_check_io_completion(test_device_sid) < 0) |
| goto error; |
| |
| /* Test transfer completion */ |
| report_prefix_push("ssch transfer completion"); |
| |
| ret = css_residual_count(test_device_sid); |
| |
| if (ret < 0) { |
| report_info("no valid residual count"); |
| } else if (ret != 0) { |
| len = sizeof(*senseid) - ret; |
| if (ret && len < CSS_SENSEID_COMMON_LEN) { |
| report_fail("transferred a too short length: %d", ret); |
| goto error; |
| } else if (ret && len) |
| report_info("transferred a shorter length: %d", len); |
| } |
| |
| if (senseid->reserved != 0xff) { |
| report_fail("transferred garbage: 0x%02x", senseid->reserved); |
| goto error; |
| } |
| |
| report_prefix_pop(); |
| |
| report_info("reserved 0x%02x cu_type 0x%04x cu_model 0x%02x dev_type 0x%04x dev_model 0x%02x", |
| senseid->reserved, senseid->cu_type, senseid->cu_model, |
| senseid->dev_type, senseid->dev_model); |
| |
| report(senseid->cu_type == cu_type, "cu_type expected 0x%04x got 0x%04x", |
| (uint16_t)cu_type, senseid->cu_type); |
| |
| error: |
| free_io_mem(ccw, sizeof(*ccw)); |
| error_ccw: |
| free_io_mem(senseid, sizeof(*senseid)); |
| } |
| |
| static void sense_id(void) |
| { |
| assert(!start_ccw1_chain(test_device_sid, ccw)); |
| |
| assert(wait_and_check_io_completion(test_device_sid) >= 0); |
| } |
| |
| static void css_init(void) |
| { |
| assert(register_io_int_func(css_irq_io) == 0); |
| lowcore.io_int_param = 0; |
| |
| report(get_chsc_scsc(), "Store Channel Characteristics"); |
| } |
| |
| static void test_schm(void) |
| { |
| if (css_test_general_feature(CSSC_EXTENDED_MEASUREMENT_BLOCK)) |
| report_info("Extended measurement block available"); |
| |
| /* bits 59-63 of MB address must be 0 if MBU is defined */ |
| report_prefix_push("Unaligned operand"); |
| expect_pgm_int(); |
| schm((void *)0x01, SCHM_MBU); |
| check_pgm_int_code(PGM_INT_CODE_OPERAND); |
| report_prefix_pop(); |
| |
| /* bits 36-61 of register 1 (flags) must be 0 */ |
| report_prefix_push("Bad flags"); |
| expect_pgm_int(); |
| schm(NULL, 0xfffffffc); |
| check_pgm_int_code(PGM_INT_CODE_OPERAND); |
| report_prefix_pop(); |
| |
| /* SCHM is a privilege operation */ |
| report_prefix_push("Privilege"); |
| enter_pstate(); |
| expect_pgm_int(); |
| schm(NULL, SCHM_MBU); |
| check_pgm_int_code(PGM_INT_CODE_PRIVILEGED_OPERATION); |
| report_prefix_pop(); |
| |
| /* Normal operation */ |
| report_prefix_push("Normal operation"); |
| schm(NULL, SCHM_MBU); |
| report_pass("SCHM call without address"); |
| report_prefix_pop(); |
| } |
| |
| #define SCHM_UPDATE_CNT 10 |
| static bool start_measuring(uint64_t mbo, uint16_t mbi, bool fmt1) |
| { |
| int i; |
| |
| senseid = alloc_io_mem(sizeof(*senseid), 0); |
| assert(senseid); |
| |
| ccw = ccw_alloc(CCW_CMD_SENSE_ID, senseid, sizeof(*senseid), CCW_F_SLI); |
| assert(ccw); |
| |
| if (!css_enable_mb(test_device_sid, mbo, mbi, PMCW_MBUE, fmt1)) { |
| report_abort("Enabling measurement block failed"); |
| return false; |
| } |
| |
| for (i = 0; i < SCHM_UPDATE_CNT; i++) |
| sense_id(); |
| |
| free_io_mem(ccw, sizeof(*ccw)); |
| free_io_mem(senseid, sizeof(*senseid)); |
| |
| return true; |
| } |
| |
| /* |
| * test_schm_fmt0: |
| * With measurement block format 0 a memory space is shared |
| * by all subchannels, each subchannel can provide an index |
| * for the measurement block facility to store the measurements. |
| */ |
| static void test_schm_fmt0(void) |
| { |
| struct measurement_block_format0 *mb0; |
| int shared_mb_size = 2 * sizeof(struct measurement_block_format0); |
| |
| if (!test_device_sid) { |
| report_skip("No device"); |
| return; |
| } |
| |
| /* Allocate zeroed Measurement block */ |
| mb0 = alloc_io_mem(shared_mb_size, 0); |
| if (!mb0) { |
| report_abort("measurement_block_format0 allocation failed"); |
| return; |
| } |
| |
| schm(NULL, 0); /* Stop any previous measurement */ |
| schm(mb0, SCHM_MBU); |
| |
| /* Expect success */ |
| report_prefix_push("Valid MB address and index 0"); |
| report(start_measuring(0, 0, false) && |
| mb0->ssch_rsch_count == SCHM_UPDATE_CNT, |
| "SSCH measured %d", mb0->ssch_rsch_count); |
| report_prefix_pop(); |
| |
| /* Clear the measurement block for the next test */ |
| memset(mb0, 0, shared_mb_size); |
| |
| /* Expect success */ |
| report_prefix_push("Valid MB address and index 1"); |
| if (start_measuring(0, 1, false)) |
| report(mb0[1].ssch_rsch_count == SCHM_UPDATE_CNT, |
| "SSCH measured %d", mb0[1].ssch_rsch_count); |
| report_prefix_pop(); |
| |
| /* Stop the measurement */ |
| css_disable_mb(test_device_sid); |
| schm(NULL, 0); |
| |
| free_io_mem(mb0, shared_mb_size); |
| } |
| |
| static void msch_with_wrong_fmt1_mbo(unsigned int schid, uint64_t mb) |
| { |
| struct pmcw *pmcw = &schib.pmcw; |
| int cc; |
| |
| /* Read the SCHIB for this subchannel */ |
| cc = stsch(schid, &schib); |
| if (cc) { |
| report_fail("stsch: sch %08x failed with cc=%d", schid, cc); |
| return; |
| } |
| |
| /* Update the SCHIB to enable the measurement block */ |
| pmcw->flags |= PMCW_MBUE; |
| pmcw->flags2 |= PMCW_MBF1; |
| schib.mbo = mb; |
| |
| /* Tell the CSS we want to modify the subchannel */ |
| expect_pgm_int(); |
| cc = msch(schid, &schib); |
| check_pgm_int_code(PGM_INT_CODE_OPERAND); |
| } |
| |
| /* |
| * test_schm_fmt1: |
| * With measurement block format 1 the measurement block is |
| * dedicated to a subchannel. |
| */ |
| static void test_schm_fmt1(void) |
| { |
| struct measurement_block_format1 *mb1; |
| |
| if (!test_device_sid) { |
| report_skip("No device"); |
| return; |
| } |
| |
| if (!css_test_general_feature(CSSC_EXTENDED_MEASUREMENT_BLOCK)) { |
| report_skip("Extended measurement block not available"); |
| return; |
| } |
| |
| /* Allocate zeroed Measurement block */ |
| mb1 = alloc_io_mem(sizeof(struct measurement_block_format1), 0); |
| if (!mb1) { |
| report_abort("measurement_block_format1 allocation failed"); |
| return; |
| } |
| |
| schm(NULL, 0); /* Stop any previous measurement */ |
| schm(0, SCHM_MBU); |
| |
| /* Expect error for non aligned MB */ |
| report_prefix_push("Unaligned MB origin"); |
| msch_with_wrong_fmt1_mbo(test_device_sid, (uint64_t)mb1 + 1); |
| report_prefix_pop(); |
| |
| /* Clear the measurement block for the next test */ |
| memset(mb1, 0, sizeof(*mb1)); |
| |
| /* Expect success */ |
| report_prefix_push("Valid MB origin"); |
| if (start_measuring((u64)mb1, 0, true)) |
| report(mb1->ssch_rsch_count == SCHM_UPDATE_CNT, |
| "SSCH measured %d", mb1->ssch_rsch_count); |
| report_prefix_pop(); |
| |
| /* Stop the measurement */ |
| css_disable_mb(test_device_sid); |
| schm(NULL, 0); |
| |
| free_io_mem(mb1, sizeof(struct measurement_block_format1)); |
| } |
| |
| static void test_msch(void) |
| { |
| int invalid_pmcw_flags[] = {0, 1, 6, 7}; |
| struct schib test_schib; |
| uint16_t old_pmcw_flags; |
| const int align_to = 4; |
| int invalid_flag; |
| int cc; |
| |
| if (!test_device_sid) { |
| report_skip("No device"); |
| return; |
| } |
| |
| cc = stsch(test_device_sid, &schib); |
| if (cc) { |
| report_fail("stsch: sch %08x failed with cc=%d", test_device_sid, cc); |
| return; |
| } |
| |
| report_prefix_push("Unaligned"); |
| for (int i = 1; i < align_to; i *= 2) { |
| report_prefix_pushf("%d", i); |
| |
| expect_pgm_int(); |
| msch(test_device_sid, (struct schib *)(alignment_test_page + i)); |
| check_pgm_int_code(PGM_INT_CODE_SPECIFICATION); |
| |
| report_prefix_pop(); |
| } |
| report_prefix_pop(); |
| |
| report_prefix_push("Invalid SCHIB"); |
| old_pmcw_flags = schib.pmcw.flags; |
| for (int i = 0; i < ARRAY_SIZE(invalid_pmcw_flags); i++) { |
| invalid_flag = invalid_pmcw_flags[i]; |
| |
| report_prefix_pushf("PMCW flag bit %d set", invalid_flag); |
| |
| schib.pmcw.flags = old_pmcw_flags | BIT(15 - invalid_flag); |
| expect_pgm_int(); |
| msch(test_device_sid, &schib); |
| check_pgm_int_code(PGM_INT_CODE_OPERAND); |
| |
| cc = stsch(test_device_sid, &test_schib); |
| report(!cc, "STSCH succeeded"); |
| report(!(test_schib.pmcw.flags & BIT(15 - invalid_flag)), "Clear on STSCH"); |
| |
| report_prefix_pop(); |
| } |
| report_prefix_pop(); |
| |
| schib.pmcw.flags = old_pmcw_flags; |
| } |
| |
| static void check_stcrw_no_crw_available(void) |
| { |
| uint32_t crw = 0xfeedc0fe; |
| int cc; |
| |
| report_prefix_push("No CRW available"); |
| cc = stcrw(&crw); |
| report(cc == 1, "cc == 1"); |
| report(!crw, "stored zeroes in crw"); |
| report_prefix_pop(); |
| } |
| |
| static int check_stcrw_crw_available(void) |
| { |
| const uint32_t magic = 0xfeedc0fe; |
| uint32_t crw = magic; |
| int cc; |
| |
| report_prefix_push("CRW available"); |
| cc = stcrw(&crw); |
| report(!cc, "cc is zero"); |
| report(crw != magic, "stored crw"); |
| report_prefix_pop(); |
| |
| return crw; |
| } |
| |
| static uint32_t crw_get_rsc(uint32_t crw) |
| { |
| const int rsc_begin = 4; |
| const int rsc_end = 8; |
| |
| return (crw & GENMASK(31 - rsc_begin, 31 - rsc_end)) >> 24; |
| } |
| |
| #define CRW_RSC_CHP 4 |
| static void test_stcrw(void) |
| { |
| const int align_to = 4; |
| int res; |
| uint32_t crw; |
| |
| if (!test_device_sid) { |
| report_skip("No device"); |
| return; |
| } |
| |
| report_prefix_push("Unaligned"); |
| for (int i = 1; i < align_to; i *= 2) { |
| report_prefix_pushf("%d", i); |
| |
| expect_pgm_int(); |
| stcrw((uint32_t *)(alignment_test_page + i)); |
| check_pgm_int_code(PGM_INT_CODE_SPECIFICATION); |
| |
| report_prefix_pop(); |
| } |
| report_prefix_pop(); |
| |
| report_prefix_push("No CRW available initally"); |
| check_stcrw_no_crw_available(); |
| report_prefix_pop(); |
| |
| res = css_generate_crw(test_device_sid); |
| if (res) { |
| report_skip("Couldn't generate CRW"); |
| report_prefix_pop(); |
| return; |
| } |
| |
| crw = check_stcrw_crw_available(); |
| |
| report_prefix_push("CRW available"); |
| report(crw_get_rsc(crw) == CRW_RSC_CHP, "CRW has Channel Path RSC"); |
| report_prefix_pop(); |
| |
| report_prefix_push("No more CRWs pending"); |
| check_stcrw_no_crw_available(); |
| report_prefix_pop(); |
| } |
| |
| static void test_ssch(void) |
| { |
| const int align_to = 4; |
| struct orb orb; |
| |
| if (!test_device_sid) { |
| report_skip("No device"); |
| return; |
| } |
| |
| report_prefix_push("Unaligned"); |
| for (int i = 1; i < align_to; i *= 2) { |
| report_prefix_pushf("%d", i); |
| |
| expect_pgm_int(); |
| ssch(test_device_sid, (struct orb *)(alignment_test_page + i)); |
| check_pgm_int_code(PGM_INT_CODE_SPECIFICATION); |
| |
| report_prefix_pop(); |
| } |
| report_prefix_pop(); |
| |
| report_prefix_push("Invalid ORB"); |
| |
| memset(&orb, 0xff, sizeof(orb)); |
| expect_pgm_int(); |
| ssch(test_device_sid, &orb); |
| check_pgm_int_code(PGM_INT_CODE_OPERAND); |
| |
| report_prefix_pop(); |
| } |
| |
| static void test_stsch(void) |
| { |
| const int align_to = 4; |
| struct schib schib; |
| int cc; |
| |
| if (!test_device_sid) { |
| report_skip("No device"); |
| return; |
| } |
| |
| report_prefix_push("Unaligned"); |
| for (int i = 1; i < align_to; i *= 2) { |
| report_prefix_pushf("%d", i); |
| |
| expect_pgm_int(); |
| stsch(test_device_sid, (struct schib *)(alignment_test_page + i)); |
| check_pgm_int_code(PGM_INT_CODE_SPECIFICATION); |
| |
| report_prefix_pop(); |
| } |
| report_prefix_pop(); |
| |
| report_prefix_push("Invalid subchannel number"); |
| cc = stsch(0x0001ffff, &schib); |
| report(cc == 3, "Channel not operational"); |
| report_prefix_pop(); |
| |
| /* |
| * No matter if multiple-subchannel-set facility is installed, bit 47 |
| * always needs to be 1. |
| */ |
| report_prefix_push("Bit 47 in SID is zero"); |
| expect_pgm_int(); |
| stsch(0x0000ffff, &schib); |
| check_pgm_int_code(PGM_INT_CODE_OPERAND); |
| report_prefix_pop(); |
| } |
| |
| /* |
| * According to architecture MSCH does ignore bit 5 of the second word |
| * but STSCH will store bit 5 as zero. |
| */ |
| static void test_pmcw_bit5(void) |
| { |
| int cc; |
| uint16_t old_pmcw_flags; |
| |
| cc = stsch(test_device_sid, &schib); |
| if (cc) { |
| report_fail("stsch: sch %08x failed with cc=%d", test_device_sid, cc); |
| return; |
| } |
| old_pmcw_flags = schib.pmcw.flags; |
| |
| report_prefix_push("Bit 5 set"); |
| |
| schib.pmcw.flags = old_pmcw_flags | BIT(15 - 5); |
| cc = msch(test_device_sid, &schib); |
| report(!cc, "MSCH cc == 0"); |
| |
| cc = stsch(test_device_sid, &schib); |
| report(!cc, "STSCH cc == 0"); |
| report(!(schib.pmcw.flags & BIT(15 - 5)), "STSCH PMCW Bit 5 is clear"); |
| |
| report_prefix_pop(); |
| |
| report_prefix_push("Bit 5 clear"); |
| |
| schib.pmcw.flags = old_pmcw_flags & ~BIT(15 - 5); |
| cc = msch(test_device_sid, &schib); |
| report(!cc, "MSCH cc == 0"); |
| |
| cc = stsch(test_device_sid, &schib); |
| report(!cc, "STSCH cc == 0"); |
| report(!(schib.pmcw.flags & BIT(15 - 5)), "STSCH PMCW Bit 5 is clear"); |
| |
| report_prefix_pop(); |
| } |
| |
| static void test_tsch(void) |
| { |
| const int align_to = 4; |
| struct irb irb; |
| int cc; |
| |
| if (!test_device_sid) { |
| report_skip("No device"); |
| return; |
| } |
| |
| report_prefix_push("Unaligned"); |
| for (int i = 1; i < align_to; i *= 2) { |
| report_prefix_pushf("%d", i); |
| |
| expect_pgm_int(); |
| tsch(test_device_sid, (struct irb *)(alignment_test_page + i)); |
| check_pgm_int_code(PGM_INT_CODE_SPECIFICATION); |
| |
| report_prefix_pop(); |
| } |
| report_prefix_pop(); |
| |
| report_prefix_push("Invalid subchannel number"); |
| cc = tsch(0x0001ffff, &irb); |
| report(cc == 3, "Channel not operational"); |
| report_prefix_pop(); |
| |
| report_prefix_push("Bit 47 in SID is zero"); |
| expect_pgm_int(); |
| tsch(0x0000ffff, &irb); |
| check_pgm_int_code(PGM_INT_CODE_OPERAND); |
| report_prefix_pop(); |
| } |
| |
| static struct { |
| const char *name; |
| void (*func)(void); |
| } tests[] = { |
| /* The css_init test is needed to initialize the CSS Characteristics */ |
| { "initialize CSS (chsc)", css_init }, |
| { "enumerate (stsch)", test_enumerate }, |
| { "enable (msch)", test_enable }, |
| { "sense (ssch/tsch)", test_sense }, |
| { "measurement block (schm)", test_schm }, |
| { "measurement block format0", test_schm_fmt0 }, |
| { "measurement block format1", test_schm_fmt1 }, |
| { "msch", test_msch }, |
| { "stcrw", test_stcrw }, |
| { "ssch", test_ssch }, |
| { "stsch", test_stsch }, |
| { "pmcw bit 5 ignored", test_pmcw_bit5 }, |
| { "tsch", test_tsch }, |
| { NULL, NULL } |
| }; |
| |
| int main(int argc, char *argv[]) |
| { |
| int i; |
| |
| report_prefix_push("Channel Subsystem"); |
| |
| /* There's no guarantee where our devices are without qemu */ |
| if (!host_is_qemu()) { |
| report_skip("Not running under QEMU"); |
| goto done; |
| } |
| |
| enable_io_isc(0x80 >> IO_SCH_ISC); |
| for (i = 0; tests[i].name; i++) { |
| report_prefix_push(tests[i].name); |
| tests[i].func(); |
| report_prefix_pop(); |
| } |
| |
| done: |
| report_prefix_pop(); |
| return report_summary(); |
| } |