blob: 4b6e31b3b80374979bd06b21883cca87eb6d176f [file] [log] [blame]
/* 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();
}