/* SPDX-License-Identifier: GPL-2.0-only */ /* * Channel Subsystem tests * * Copyright (c) 2020 IBM Corp * * Authors: * Pierre Morel */ #include #include #include #include #include #include #include #include #include #include #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_ptr->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_ptr->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 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 }, { NULL, NULL } }; int main(int argc, char *argv[]) { int i; report_prefix_push("Channel Subsystem"); 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(); } report_prefix_pop(); return report_summary(); }