diff mbox series

[kvm-unit-tests,v7,4/4] s390x: SCLP unit test

Message ID 20200110184050.191506-5-imbrenda@linux.ibm.com (mailing list archive)
State New, archived
Headers show
Series s390x: SCLP Unit test | expand

Commit Message

Claudio Imbrenda Jan. 10, 2020, 6:40 p.m. UTC
SCLP unit test. Testing the following:

* Correctly ignoring instruction bits that should be ignored
* Privileged instruction check
* Check for addressing exceptions
* Specification exceptions:
  - SCCB size less than 8
  - SCCB unaligned
  - SCCB overlaps prefix or lowcore
  - SCCB address higher than 2GB
* Return codes for
  - Invalid command
  - SCCB too short (but at least 8)
  - SCCB page boundary violation

Signed-off-by: Claudio Imbrenda <imbrenda@linux.ibm.com>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Acked-by: Janosch Frank <frankja@linux.ibm.com>
---
 s390x/Makefile      |   1 +
 s390x/sclp.c        | 472 ++++++++++++++++++++++++++++++++++++++++++++
 s390x/unittests.cfg |   8 +
 3 files changed, 481 insertions(+)
 create mode 100644 s390x/sclp.c

Comments

David Hildenbrand Jan. 13, 2020, 11 a.m. UTC | #1
> +/**
> + * Test some bits in the instruction format that are specified to be ignored.
> + */
> +static void test_instbits(void)
> +{
> +	SCCBHeader *h = (SCCBHeader *)pagebuf;
> +	int cc;
> +
> +	expect_pgm_int();
> +	sclp_mark_busy();
> +	h->length = 8;
> +	sclp_setup_int();
> +
> +	asm volatile(
> +		"       .insn   rre,0xb2204200,%1,%2\n"  /* servc %1,%2 */
> +		"       ipm     %0\n"
> +		"       srl     %0,28"
> +		: "=&d" (cc) : "d" (valid_code), "a" (__pa(pagebuf))
> +		: "cc", "memory");
> +	if (lc->pgm_int_code) {
> +		sclp_handle_ext();
> +		cc = 1;
> +	} else if (!cc)
> +		

I wonder if something like the following would be possible:

expect_pgm_int();
...
asm volatiole();
...
sclp_wait_busy();
check_pgm_int_code(PGM_INT_CODE_SPECIFICATION);

We would have to clear "sclp_busy" when we get a progam interrupt on a
servc instruction - shouldn't be too hard to add to the program
exception handler.

> +	report(cc == 0, "Instruction format ignored bits");
> +}
> +
> +/**
> + * Find a valid READ INFO command code; not all codes are always allowed, and
> + * probing should be performed in the right order.
> + */
> +static void find_valid_sclp_code(void)
> +{
> +	const unsigned int commands[] = { SCLP_CMDW_READ_SCP_INFO_FORCED,
> +					  SCLP_CMDW_READ_SCP_INFO };
> +	SCCBHeader *h = (SCCBHeader *)pagebuf;
> +	int i, cc;
> +
> +	for (i = 0; i < ARRAY_SIZE(commands); i++) {
> +		sclp_mark_busy();
> +		memset(h, 0, sizeof(*h));
> +		h->length = 4096;
> +
> +		valid_code = commands[i];
> +		cc = sclp_service_call(commands[i], h);
> +		if (cc)
> +			break;
> +		if (h->response_code == SCLP_RC_NORMAL_READ_COMPLETION)
> +			return;
> +		if (h->response_code != SCLP_RC_INVALID_SCLP_COMMAND)
> +			break;
> +	}
> +	valid_code = 0;

This can be dropped because ...

> +	report_abort("READ_SCP_INFO failed");

... you abort here.
Claudio Imbrenda Jan. 13, 2020, 12:33 p.m. UTC | #2
On Mon, 13 Jan 2020 12:00:00 +0100
David Hildenbrand <david@redhat.com> wrote:

> > +/**
> > + * Test some bits in the instruction format that are specified to
> > be ignored.
> > + */
> > +static void test_instbits(void)
> > +{
> > +	SCCBHeader *h = (SCCBHeader *)pagebuf;
> > +	int cc;
> > +
> > +	expect_pgm_int();
> > +	sclp_mark_busy();
> > +	h->length = 8;
> > +	sclp_setup_int();
> > +
> > +	asm volatile(
> > +		"       .insn   rre,0xb2204200,%1,%2\n"  /* servc
> > %1,%2 */
> > +		"       ipm     %0\n"
> > +		"       srl     %0,28"
> > +		: "=&d" (cc) : "d" (valid_code), "a"
> > (__pa(pagebuf))
> > +		: "cc", "memory");
> > +	if (lc->pgm_int_code) {
> > +		sclp_handle_ext();
> > +		cc = 1;
> > +	} else if (!cc)
> > +		  
> 
> I wonder if something like the following would be possible:
> 
> expect_pgm_int();
> ...
> asm volatiole();
> ...
> sclp_wait_busy();
> check_pgm_int_code(PGM_INT_CODE_SPECIFICATION);

we do not expect a specification exception, if that happens it's
a bug and the test should rightfully fail.

> We would have to clear "sclp_busy" when we get a progam interrupt on a
> servc instruction - shouldn't be too hard to add to the program
> exception handler.

Sure that could be done, but is it worth it to rework the program
interrupt handler only for one unit test?

[...]

> > +	valid_code = 0;  
> 
> This can be dropped because ...
> 
> > +	report_abort("READ_SCP_INFO failed");  
> 
> ... you abort here.

will fix
David Hildenbrand Jan. 13, 2020, 12:48 p.m. UTC | #3
On 13.01.20 13:33, Claudio Imbrenda wrote:
> On Mon, 13 Jan 2020 12:00:00 +0100
> David Hildenbrand <david@redhat.com> wrote:
> 
>>> +/**
>>> + * Test some bits in the instruction format that are specified to
>>> be ignored.
>>> + */
>>> +static void test_instbits(void)
>>> +{
>>> +	SCCBHeader *h = (SCCBHeader *)pagebuf;
>>> +	int cc;
>>> +
>>> +	expect_pgm_int();
>>> +	sclp_mark_busy();
>>> +	h->length = 8;
>>> +	sclp_setup_int();
>>> +
>>> +	asm volatile(
>>> +		"       .insn   rre,0xb2204200,%1,%2\n"  /* servc
>>> %1,%2 */
>>> +		"       ipm     %0\n"
>>> +		"       srl     %0,28"
>>> +		: "=&d" (cc) : "d" (valid_code), "a"
>>> (__pa(pagebuf))
>>> +		: "cc", "memory");
>>> +	if (lc->pgm_int_code) {
>>> +		sclp_handle_ext();
>>> +		cc = 1;
>>> +	} else if (!cc)
>>> +		  
>>
>> I wonder if something like the following would be possible:
>>
>> expect_pgm_int();
>> ...
>> asm volatiole();
>> ...
>> sclp_wait_busy();
>> check_pgm_int_code(PGM_INT_CODE_SPECIFICATION);
> 
> we do not expect a specification exception, if that happens it's
> a bug and the test should rightfully fail.

Which one do we expect? (you're not checking for a specific one, should
you?)

> 
>> We would have to clear "sclp_busy" when we get a progam interrupt on a
>> servc instruction - shouldn't be too hard to add to the program
>> exception handler.
> 
> Sure that could be done, but is it worth it to rework the program
> interrupt handler only for one unit test?

Good point. I don't like this particular code, but I can live with it :)
Claudio Imbrenda Jan. 13, 2020, 12:58 p.m. UTC | #4
On Mon, 13 Jan 2020 13:48:17 +0100
David Hildenbrand <david@redhat.com> wrote:

[...]
	    
> >>
> >> I wonder if something like the following would be possible:
> >>
> >> expect_pgm_int();
> >> ...
> >> asm volatiole();
> >> ...
> >> sclp_wait_busy();
> >> check_pgm_int_code(PGM_INT_CODE_SPECIFICATION);  
> > 
> > we do not expect a specification exception, if that happens it's
> > a bug and the test should rightfully fail.  
> 
> Which one do we expect? (you're not checking for a specific one,
> should you?)

nothing, the call should succeed :)

> >   
> >> We would have to clear "sclp_busy" when we get a progam interrupt
> >> on a servc instruction - shouldn't be too hard to add to the
> >> program exception handler.  
> > 
> > Sure that could be done, but is it worth it to rework the program
> > interrupt handler only for one unit test?  
> 
> Good point. I don't like this particular code, but I can live with it
> :)
>
David Hildenbrand Jan. 13, 2020, 1:10 p.m. UTC | #5
On 13.01.20 13:58, Claudio Imbrenda wrote:
> On Mon, 13 Jan 2020 13:48:17 +0100
> David Hildenbrand <david@redhat.com> wrote:
> 
> [...]
> 	    
>>>>
>>>> I wonder if something like the following would be possible:
>>>>
>>>> expect_pgm_int();
>>>> ...
>>>> asm volatiole();
>>>> ...
>>>> sclp_wait_busy();
>>>> check_pgm_int_code(PGM_INT_CODE_SPECIFICATION);  
>>>
>>> we do not expect a specification exception, if that happens it's
>>> a bug and the test should rightfully fail.  
>>
>> Which one do we expect? (you're not checking for a specific one,
>> should you?)
> 
> nothing, the call should succeed :)

:) I'm confused by the fact that you "expect_pgm_int()" but actually
don't expect one ...

Please enlighten me why this isn't

+	sclp_mark_busy();
+	h->length = 8;
+	sclp_setup_int();
+
+	asm volatile(
+		"       .insn   rre,0xb2204200,%1,%2\n"  /* servc %1,%2 */
+		"       ipm     %0\n"
+		"       srl     %0,28"
+		: "=&d" (cc) : "d" (valid_code), "a" (__pa(pagebuf))
+		: "cc", "memory");
+	if (!cc)
+		sclp_wait_busy();
+	report(cc == 0, "Instruction format ignored bits");

I feel like I am missing something important.
Claudio Imbrenda Jan. 13, 2020, 2:05 p.m. UTC | #6
On Mon, 13 Jan 2020 14:10:10 +0100
David Hildenbrand <david@redhat.com> wrote:

[...]

> :) I'm confused by the fact that you "expect_pgm_int()" but actually
> don't expect one ...
> 
> Please enlighten me why this isn't
> 
> +	sclp_mark_busy();
> +	h->length = 8;
> +	sclp_setup_int();
> +
> +	asm volatile(
> +		"       .insn   rre,0xb2204200,%1,%2\n"  /* servc
> %1,%2 */
> +		"       ipm     %0\n"
> +		"       srl     %0,28"
> +		: "=&d" (cc) : "d" (valid_code), "a" (__pa(pagebuf))
> +		: "cc", "memory");
> +	if (!cc)
> +		sclp_wait_busy();
> +	report(cc == 0, "Instruction format ignored bits");
> 
> I feel like I am missing something important.

because if we take an unexpected pgm interrupt:
* the interrupt handler will write stuff on the console using SCLP
* it will wait for the busy flag to be cleared before doing so
* thus it will hang.

this would be solved by adding special logic to the pgm interrupt
handler (as we have discussed in your previous email)
David Hildenbrand Jan. 13, 2020, 2:43 p.m. UTC | #7
On 13.01.20 15:05, Claudio Imbrenda wrote:
> On Mon, 13 Jan 2020 14:10:10 +0100
> David Hildenbrand <david@redhat.com> wrote:
> 
> [...]
> 
>> :) I'm confused by the fact that you "expect_pgm_int()" but actually
>> don't expect one ...
>>
>> Please enlighten me why this isn't
>>
>> +	sclp_mark_busy();
>> +	h->length = 8;
>> +	sclp_setup_int();
>> +
>> +	asm volatile(
>> +		"       .insn   rre,0xb2204200,%1,%2\n"  /* servc
>> %1,%2 */
>> +		"       ipm     %0\n"
>> +		"       srl     %0,28"
>> +		: "=&d" (cc) : "d" (valid_code), "a" (__pa(pagebuf))
>> +		: "cc", "memory");
>> +	if (!cc)
>> +		sclp_wait_busy();
>> +	report(cc == 0, "Instruction format ignored bits");
>>
>> I feel like I am missing something important.
> 
> because if we take an unexpected pgm interrupt:
> * the interrupt handler will write stuff on the console using SCLP
> * it will wait for the busy flag to be cleared before doing so
> * thus it will hang.
> 
> this would be solved by adding special logic to the pgm interrupt
> handler (as we have discussed in your previous email)
> 

I see, so the issue should hold for all SCLP checks where we don't
expect an exception ... hmmm
Claudio Imbrenda Jan. 13, 2020, 3:24 p.m. UTC | #8
On Mon, 13 Jan 2020 15:43:28 +0100
David Hildenbrand <david@redhat.com> wrote:

> On 13.01.20 15:05, Claudio Imbrenda wrote:
> > On Mon, 13 Jan 2020 14:10:10 +0100
> > David Hildenbrand <david@redhat.com> wrote:
> > 
> > [...]
> >   
> >> :) I'm confused by the fact that you "expect_pgm_int()" but
> >> actually don't expect one ...
> >>
> >> Please enlighten me why this isn't
> >>
> >> +	sclp_mark_busy();
> >> +	h->length = 8;
> >> +	sclp_setup_int();
> >> +
> >> +	asm volatile(
> >> +		"       .insn   rre,0xb2204200,%1,%2\n"  /* servc
> >> %1,%2 */
> >> +		"       ipm     %0\n"
> >> +		"       srl     %0,28"
> >> +		: "=&d" (cc) : "d" (valid_code), "a"
> >> (__pa(pagebuf))
> >> +		: "cc", "memory");
> >> +	if (!cc)
> >> +		sclp_wait_busy();
> >> +	report(cc == 0, "Instruction format ignored bits");
> >>
> >> I feel like I am missing something important.  
> > 
> > because if we take an unexpected pgm interrupt:
> > * the interrupt handler will write stuff on the console using SCLP
> > * it will wait for the busy flag to be cleared before doing so
> > * thus it will hang.
> > 
> > this would be solved by adding special logic to the pgm interrupt
> > handler (as we have discussed in your previous email)
> >   
> 
> I see, so the issue should hold for all SCLP checks where we don't
> expect an exception ... hmmm
 
which is why my wrapper in the unit test is so complicated :)
David Hildenbrand Jan. 13, 2020, 4:06 p.m. UTC | #9
On 13.01.20 16:24, Claudio Imbrenda wrote:
> On Mon, 13 Jan 2020 15:43:28 +0100
> David Hildenbrand <david@redhat.com> wrote:
> 
>> On 13.01.20 15:05, Claudio Imbrenda wrote:
>>> On Mon, 13 Jan 2020 14:10:10 +0100
>>> David Hildenbrand <david@redhat.com> wrote:
>>>
>>> [...]
>>>   
>>>> :) I'm confused by the fact that you "expect_pgm_int()" but
>>>> actually don't expect one ...
>>>>
>>>> Please enlighten me why this isn't
>>>>
>>>> +	sclp_mark_busy();
>>>> +	h->length = 8;
>>>> +	sclp_setup_int();
>>>> +
>>>> +	asm volatile(
>>>> +		"       .insn   rre,0xb2204200,%1,%2\n"  /* servc
>>>> %1,%2 */
>>>> +		"       ipm     %0\n"
>>>> +		"       srl     %0,28"
>>>> +		: "=&d" (cc) : "d" (valid_code), "a"
>>>> (__pa(pagebuf))
>>>> +		: "cc", "memory");
>>>> +	if (!cc)
>>>> +		sclp_wait_busy();
>>>> +	report(cc == 0, "Instruction format ignored bits");
>>>>
>>>> I feel like I am missing something important.  
>>>
>>> because if we take an unexpected pgm interrupt:
>>> * the interrupt handler will write stuff on the console using SCLP
>>> * it will wait for the busy flag to be cleared before doing so
>>> * thus it will hang.
>>>
>>> this would be solved by adding special logic to the pgm interrupt
>>> handler (as we have discussed in your previous email)
>>>   
>>
>> I see, so the issue should hold for all SCLP checks where we don't
>> expect an exception ... hmmm
>  
> which is why my wrapper in the unit test is so complicated :)
> 

so .... if we would implement my suggestion (if we get an exception on a
servc instruction, clear sclp_busy) that code would get simplified as
well? :)

It would be really beneficial if we could rely on

expect_pgm_int()
...
check_pgm_int_code(whatever)

if we *expect an interrupt and none of that if we don't. So if it's a
matter of clearing sclp_busy on PGM exceptions, that shouldn't be too
hard ... but maybe I am missing something (haven't looked again at the
whole patch ...).
Claudio Imbrenda Jan. 13, 2020, 4:17 p.m. UTC | #10
On Mon, 13 Jan 2020 17:06:05 +0100
David Hildenbrand <david@redhat.com> wrote:

[...]

> >>> this would be solved by adding special logic to the pgm interrupt
> >>> handler (as we have discussed in your previous email)
> >>>     
> >>
> >> I see, so the issue should hold for all SCLP checks where we don't
> >> expect an exception ... hmmm  
> >  
> > which is why my wrapper in the unit test is so complicated :)
> >   
> 
> so .... if we would implement my suggestion (if we get an exception
> on a servc instruction, clear sclp_busy) that code would get
> simplified as well? :)

sure, as I said, that can be done. The question is if we really want to
change something in the interrupt handler (shared by all s390x unit
tests) just for the benefit of this one unit test.

Also consider that the changes to the interrupt handler would not
necessarily be trivial. i.e. you need to check that the origin of the
pgm interrupt is a SERVC instruction, and then act accordingly.
David Hildenbrand Jan. 15, 2020, 9:57 a.m. UTC | #11
On 13.01.20 17:17, Claudio Imbrenda wrote:
> On Mon, 13 Jan 2020 17:06:05 +0100
> David Hildenbrand <david@redhat.com> wrote:
> 
> [...]
> 
>>>>> this would be solved by adding special logic to the pgm interrupt
>>>>> handler (as we have discussed in your previous email)
>>>>>     
>>>>
>>>> I see, so the issue should hold for all SCLP checks where we don't
>>>> expect an exception ... hmmm  
>>>  
>>> which is why my wrapper in the unit test is so complicated :)
>>>   
>>
>> so .... if we would implement my suggestion (if we get an exception
>> on a servc instruction, clear sclp_busy) that code would get
>> simplified as well? :)
> 
> sure, as I said, that can be done. The question is if we really want to
> change something in the interrupt handler (shared by all s390x unit
> tests) just for the benefit of this one unit test.
> 
> Also consider that the changes to the interrupt handler would not
> necessarily be trivial. i.e. you need to check that the origin of the
> pgm interrupt is a SERVC instruction, and then act accordingly.
> 

I suggest something like the following:

diff --git a/lib/s390x/interrupt.c b/lib/s390x/interrupt.c
index 05f30be..d762e83 100644
--- a/lib/s390x/interrupt.c
+++ b/lib/s390x/interrupt.c
@@ -106,10 +106,17 @@ static void fixup_pgm_int(void)
 
 void handle_pgm_int(void)
 {
-       if (!pgm_int_expected)
+       if (!pgm_int_expected) {
+               /*
+                * If we get a PGM interrupt while having sclp_busy=true, we
+                * will loop forever. Just force sclp_busy=false to make
+                * progress here.
+                */
+               sclp_handle_ext();
                report_abort("Unexpected program interrupt: %d at %#lx, ilen %d\n",
                             lc->pgm_int_code, lc->pgm_old_psw.addr,
                             lc->pgm_int_id);
+       }
 
        pgm_int_expected = false;
        fixup_pgm_int();

Then this test could become something like (not sure about cc handling)

diff --git a/s390x/sclp.c b/s390x/sclp.c
index 10f0809..81c5a76 100644
--- a/s390x/sclp.c
+++ b/s390x/sclp.c
@@ -396,25 +396,18 @@ out:
 static void test_instbits(void)
 {
        SCCBHeader *h = (SCCBHeader *)pagebuf;
-       int cc;
 
-       expect_pgm_int();
        sclp_mark_busy();
        h->length = 8;
        sclp_setup_int();
 
        asm volatile(
-               "       .insn   rre,0xb2204200,%1,%2\n"  /* servc %1,%2 */
-               "       ipm     %0\n"
-               "       srl     %0,28"
-               : "=&d" (cc) : "d" (valid_code), "a" (__pa(pagebuf))
+               "       .insn   rre,0xb2204200,%0,%1\n"
+               :: "d" (valid_code), "a" (__pa(pagebuf))
                : "cc", "memory");
-       if (lc->pgm_int_code) {
-               sclp_handle_ext();
-               cc = 1;
-       } else if (!cc)
-               sclp_wait_busy();
-       report(cc == 0, "Instruction format ignored bits");
+       sclp_wait_busy();
+       report(true, "Instruction format ignored bits");
 }


This works correctly. E.g., adding a "*((uint8_t *)-50ul) = 2;"
after the sclp_setup_int(); - to quickly fake a PGM exception - makes the
test abort correctly:

FAIL sclp-1g (0 tests)
FAIL sclp-3g (0 tests)

ABORT: sclp: Unexpected program interrupt: 5 at 0x155e8, ilen 6
diff mbox series

Patch

diff --git a/s390x/Makefile b/s390x/Makefile
index 3744372..ddb4b48 100644
--- a/s390x/Makefile
+++ b/s390x/Makefile
@@ -16,6 +16,7 @@  tests += $(TEST_DIR)/diag288.elf
 tests += $(TEST_DIR)/stsi.elf
 tests += $(TEST_DIR)/skrf.elf
 tests += $(TEST_DIR)/smp.elf
+tests += $(TEST_DIR)/sclp.elf
 tests_binary = $(patsubst %.elf,%.bin,$(tests))
 
 all: directories test_cases test_cases_binary
diff --git a/s390x/sclp.c b/s390x/sclp.c
new file mode 100644
index 0000000..10f0809
--- /dev/null
+++ b/s390x/sclp.c
@@ -0,0 +1,472 @@ 
+/*
+ * Service Call tests
+ *
+ * Copyright (c) 2019 IBM Corp
+ *
+ * Authors:
+ *  Claudio Imbrenda <imbrenda@linux.ibm.com>
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2.
+ */
+
+#include <libcflat.h>
+#include <asm/page.h>
+#include <asm/asm-offsets.h>
+#include <asm/interrupt.h>
+#include <sclp.h>
+
+#define PGM_NONE	1
+#define PGM_BIT_SPEC	(1ULL << PGM_INT_CODE_SPECIFICATION)
+#define PGM_BIT_ADDR	(1ULL << PGM_INT_CODE_ADDRESSING)
+#define PGM_BIT_PRIV	(1ULL << PGM_INT_CODE_PRIVILEGED_OPERATION)
+#define MKPTR(x) ((void *)(uint64_t)(x))
+
+#define LC_SIZE (2 * PAGE_SIZE)
+
+static uint8_t pagebuf[LC_SIZE] __attribute__((aligned(LC_SIZE)));	/* scratch pages used for some tests */
+static uint8_t prefix_buf[LC_SIZE] __attribute__((aligned(LC_SIZE)));	/* temporary lowcore for test_sccb_prefix */
+static uint8_t sccb_template[PAGE_SIZE];				/* SCCB template to be used */
+static uint32_t valid_code;						/* valid command code for READ SCP INFO */
+static struct lowcore *lc;
+
+/**
+ * Perform one service call, handling exceptions and interrupts.
+ */
+static int sclp_service_call_test(unsigned int command, void *sccb)
+{
+	int cc;
+
+	sclp_mark_busy();
+	sclp_setup_int();
+	cc = servc(command, __pa(sccb));
+	if (lc->pgm_int_code) {
+		sclp_handle_ext();
+		return 0;
+	}
+	if (!cc)
+		sclp_wait_busy();
+	return cc;
+}
+
+/**
+ * Perform one test at the given address, optionally using the SCCB template,
+ * checking for the expected program interrupts and return codes.
+ *
+ * The parameter buf_len indicates the number of bytes of the template that
+ * should be copied to the test address, and should be 0 when the test
+ * address is invalid, in which case nothing is copied.
+ *
+ * The template is used to simplify tests where the same buffer content is
+ * used many times in a row, at different addresses.
+ *
+ * Returns true in case of success or false in case of failure
+ */
+static bool test_one_sccb(uint32_t cmd, uint8_t *addr, uint16_t buf_len, uint64_t exp_pgm, uint16_t exp_rc)
+{
+	SCCBHeader *h = (SCCBHeader *)addr;
+	int res, pgm;
+
+	/* Copy the template to the test address if needed */
+	if (buf_len)
+		memcpy(addr, sccb_template, buf_len);
+	expect_pgm_int();
+	/* perform the actual call */
+	res = sclp_service_call_test(cmd, h);
+	if (res) {
+		report_info("SCLP not ready (command %#x, address %p, cc %d)", cmd, addr, res);
+		return false;
+	}
+	pgm = clear_pgm_int();
+	/* Check if the program exception was one of the expected ones */
+	if (!((1ULL << pgm) & exp_pgm)) {
+		report_info("First failure at addr %p, buf_len %d, cmd %#x, pgm code %d",
+				addr, buf_len, cmd, pgm);
+		return false;
+	}
+	/* Check if the response code is the one expected */
+	if (exp_rc && exp_rc != h->response_code) {
+		report_info("First failure at addr %p, buf_len %d, cmd %#x, resp code %#x",
+				addr, buf_len, cmd, h->response_code);
+		return false;
+	}
+	return true;
+}
+
+/**
+ * Wrapper for test_one_sccb to be used when the template should not be
+ * copied and the memory address should not be touched.
+ */
+static bool test_one_ro(uint32_t cmd, uint8_t *addr, uint64_t exp_pgm, uint16_t exp_rc)
+{
+	return test_one_sccb(cmd, addr, 0, exp_pgm, exp_rc);
+}
+
+/**
+ * Wrapper for test_one_sccb to set up a simple SCCB template.
+ *
+ * The parameter sccb_len indicates the value that will be saved in the SCCB
+ * length field of the SCCB, buf_len indicates the number of bytes of
+ * template that need to be copied to the actual test address. In many cases
+ * it's enough to clear/copy the first 8 bytes of the buffer, while the SCCB
+ * itself can be larger.
+ *
+ * Returns true in case of success or false in case of failure
+ */
+static bool test_one_simple(uint32_t cmd, uint8_t *addr, uint16_t sccb_len,
+			uint16_t buf_len, uint64_t exp_pgm, uint16_t exp_rc)
+{
+	memset(sccb_template, 0, sizeof(sccb_template));
+	((SCCBHeader *)sccb_template)->length = sccb_len;
+	return test_one_sccb(cmd, addr, buf_len, exp_pgm, exp_rc);
+}
+
+/**
+ * Test SCCB lengths < 8.
+ */
+static void test_sccb_too_short(void)
+{
+	int len;
+
+	for (len = 0; len < 8; len++)
+		if (!test_one_simple(valid_code, pagebuf, len, 8, PGM_BIT_SPEC, 0))
+			break;
+
+	report(len == 8, "SCCB too short");
+}
+
+/**
+ * Test SCCBs that are not 64-bit aligned.
+ */
+static void test_sccb_unaligned(void)
+{
+	int offset;
+
+	for (offset = 1; offset < 8; offset++)
+		if (!test_one_simple(valid_code, offset + pagebuf, 8, 8, PGM_BIT_SPEC, 0))
+			break;
+	report(offset == 8, "SCCB unaligned");
+}
+
+/**
+ * Test SCCBs whose address is in the lowcore or prefix area.
+ */
+static void test_sccb_prefix(void)
+{
+	uint8_t scratch[LC_SIZE];
+	uint32_t prefix, new_prefix;
+	int offset;
+
+	/*
+	 * copy the current lowcore to the future new location, otherwise we
+	 * will not have a valid lowcore after setting the new prefix.
+	 */
+	memcpy(prefix_buf, 0, LC_SIZE);
+	/* save the current prefix (it's probably going to be 0) */
+	prefix = get_prefix();
+	/*
+	 * save the current content of absolute pages 0 and 1, so we can
+	 * restore them after we trash them later on
+	 */
+	memcpy(scratch, (void *)(intptr_t)prefix, LC_SIZE);
+	/* set the new prefix to prefix_buf */
+	new_prefix = (uint32_t)(intptr_t)prefix_buf;
+	set_prefix(new_prefix);
+
+	/*
+	 * testing with SCCB addresses in the lowcore; since we can't
+	 * actually trash the lowcore (unsurprisingly, things break if we
+	 * do), this will be a read-only test.
+	 */
+	for (offset = 0; offset < LC_SIZE; offset += 8)
+		if (!test_one_ro(valid_code, MKPTR(offset), PGM_BIT_SPEC, 0))
+			break;
+	report(offset == LC_SIZE, "SCCB low pages");
+
+	/*
+	 * the SCLP should not even touch the memory, but we will write the
+	 * SCCBs all over the two pages starting at absolute address 0, thus
+	 * trashing them; we will need to restore them later.
+	 */
+	for (offset = 0; offset < LC_SIZE; offset += 8)
+		if (!test_one_simple(valid_code, MKPTR(new_prefix + offset), 8, 8, PGM_BIT_SPEC, 0))
+			break;
+	report(offset == LC_SIZE, "SCCB prefix pages");
+
+	/* restore the previous contents of absolute pages 0 and 1 */
+	memcpy(prefix_buf, 0, LC_SIZE);
+	/* restore the prefix to the original value */
+	set_prefix(prefix);
+}
+
+/**
+ * Test SCCBs that are above 2GB. If outside of memory, an addressing
+ * exception is also allowed.
+ */
+static void test_sccb_high(void)
+{
+	SCCBHeader *h = (SCCBHeader *)pagebuf;
+	uintptr_t a[33 * 4 * 2 + 2];	/* for the list of addresses to test */
+
+	uint64_t maxram;
+	int i, pgm, len = 0;
+
+	h->length = 8;
+	/* addresses with 1 bit set in the first 33 bits */
+	for (i = 0; i < 33; i++)
+		a[len++] = 1UL << (i + 31);
+	/* addresses with 2 consecutive bits set in the first 33 bits */
+	for (i = 0; i < 33; i++)
+		a[len++] = 3UL << (i + 31);
+	/* addresses with all bits set in bits 0..N */
+	for (i = 0; i < 33; i++)
+		a[len++] = 0xffffffff80000000UL << i;
+	/* addresses with all bits set in bits N..33 */
+	a[len++] = 0x80000000;
+	for (i = 1; i < 33; i++, len++)
+		a[len] = a[len - 1] | (1UL << (i + 31));
+	/* all the addresses above, but adding the offset of a valid buffer */
+	for (i = 0; i < len; i++)
+		a[len + i] = a[i] + (intptr_t)h;
+	len += i;
+	/* two more hand-crafted addresses */
+	a[len++] = 0xdeadbeef00000000;
+	a[len++] = 0xdeaddeadbeef0000;
+
+	maxram = get_ram_size();
+	for (i = 0; i < len; i++) {
+		pgm = PGM_BIT_SPEC | (a[i] >= maxram ? PGM_BIT_ADDR : 0);
+		if (!test_one_ro(valid_code, (void *)a[i], pgm, 0))
+			break;
+	}
+	report(i == len, "SCCB high addresses");
+}
+
+/**
+ * Test invalid commands, both invalid command detail codes and valid
+ * ones with invalid command class code.
+ */
+static void test_inval(void)
+{
+	const uint16_t res = SCLP_RC_INVALID_SCLP_COMMAND;
+	uint32_t cmd;
+	int i;
+
+	report_prefix_push("Invalid command");
+	for (i = 0; i < 65536; i++) {
+		cmd = 0xdead0000 | i;
+		if (!test_one_simple(cmd, pagebuf, PAGE_SIZE, PAGE_SIZE, PGM_NONE, res))
+			break;
+	}
+	report(i == 65536, "Command detail code");
+
+	for (i = 0; i < 256; i++) {
+		cmd = (valid_code & ~0xff) | i;
+		if (cmd == valid_code)
+			continue;
+		if (!test_one_simple(cmd, pagebuf, PAGE_SIZE, PAGE_SIZE, PGM_NONE, res))
+			break;
+	}
+	report(i == 256, "Command class code");
+	report_prefix_pop();
+}
+
+
+/**
+ * Test short SCCBs (but larger than 8).
+ */
+static void test_short(void)
+{
+	const uint16_t res = SCLP_RC_INSUFFICIENT_SCCB_LENGTH;
+	int len;
+
+	for (len = 8; len < 144; len++)
+		if (!test_one_simple(valid_code, pagebuf, len, len, PGM_NONE, res))
+			break;
+	report(len == 144, "Insufficient SCCB length (Read SCP info)");
+
+	for (len = 8; len < 40; len++)
+		if (!test_one_simple(SCLP_READ_CPU_INFO, pagebuf, len, len, PGM_NONE, res))
+			break;
+	report(len == 40, "Insufficient SCCB length (Read CPU info)");
+}
+
+/**
+ * Test SCCB page boundary violations.
+ */
+static void test_boundary(void)
+{
+	const uint32_t cmd = SCLP_CMD_WRITE_EVENT_DATA;
+	const uint16_t res = SCLP_RC_SCCB_BOUNDARY_VIOLATION;
+	WriteEventData *sccb = (WriteEventData *)sccb_template;
+	int len, offset;
+
+	memset(sccb_template, 0, sizeof(sccb_template));
+	sccb->h.function_code = SCLP_FC_NORMAL_WRITE;
+	for (len = 32; len <= 4096; len++) {
+		offset = len & 7 ? len & ~7 : len - 8;
+		for (offset = 4096 - offset; offset < 4096; offset += 8) {
+			sccb->h.length = len;
+			if (!test_one_sccb(cmd, offset + pagebuf, len, PGM_NONE, res))
+				goto out;
+		}
+	}
+out:
+	report(len > 4096 && offset == 4096, "SCCB page boundary violation");
+}
+
+/**
+ * Test excessively long SCCBs.
+ */
+static void test_toolong(void)
+{
+	const uint32_t cmd = SCLP_CMD_WRITE_EVENT_DATA;
+	const uint16_t res = SCLP_RC_SCCB_BOUNDARY_VIOLATION;
+	WriteEventData *sccb = (WriteEventData *)sccb_template;
+	int len;
+
+	memset(sccb_template, 0, sizeof(sccb_template));
+	sccb->h.function_code = SCLP_FC_NORMAL_WRITE;
+	for (len = 4097; len < 8192; len++) {
+		sccb->h.length = len;
+		if (!test_one_sccb(cmd, pagebuf, PAGE_SIZE, PGM_NONE, res))
+			break;
+	}
+	report(len == 8192, "SCCB bigger than 4k");
+}
+
+/**
+ * Test privileged operation.
+ */
+static void test_priv(void)
+{
+	SCCBHeader *h = (SCCBHeader *)pagebuf;
+
+	report_prefix_push("Privileged operation");
+	h->length = 8;
+	expect_pgm_int();
+	enter_pstate();
+	servc(valid_code, __pa(h));
+	check_pgm_int_code(PGM_INT_CODE_PRIVILEGED_OPERATION);
+	report_prefix_pop();
+}
+
+/**
+ * Test addressing exceptions. We need to test SCCB addresses between the
+ * end of available memory and 2GB, because after 2GB a specification
+ * exception is also allowed.
+ * Only applicable if the VM has less than 2GB of memory
+ */
+static void test_addressing(void)
+{
+	unsigned long i, maxram = get_ram_size();
+
+	/* the VM has more than 2GB of memory */
+	if (maxram >= 0x80000000) {
+		report_skip("Invalid SCCB address");
+		return;
+	}
+	/* test all possible valid addresses immediately after the end of memory
+	 * up to 64KB after the end of memory
+	 */
+	for (i = 0; i < 0x10000 && i + maxram < 0x80000000; i += 8)
+		if (!test_one_ro(valid_code, MKPTR(i + maxram), PGM_BIT_ADDR, 0))
+			goto out;
+	/* test more addresses until we reach 1MB after end of memory;
+	 * increment by a prime number (times 8) in order to test all
+	 * possible valid offsets inside pages
+	 */
+	for (; i < 0x100000 && i + maxram < 0x80000000 ; i += 808)
+		if (!test_one_ro(valid_code, MKPTR(i + maxram), PGM_BIT_ADDR, 0))
+			goto out;
+	/* test the remaining addresses until we reach address 2GB;
+	 * increment by a prime number (times 8) in order to test all
+	 * possible valid offsets inside pages
+	 */
+	for (; i + maxram < 0x80000000; i += 800024)
+		if (!test_one_ro(valid_code, MKPTR(i + maxram), PGM_BIT_ADDR, 0))
+			goto out;
+out:
+	report(i + maxram >= 0x80000000, "Invalid SCCB address");
+}
+
+/**
+ * Test some bits in the instruction format that are specified to be ignored.
+ */
+static void test_instbits(void)
+{
+	SCCBHeader *h = (SCCBHeader *)pagebuf;
+	int cc;
+
+	expect_pgm_int();
+	sclp_mark_busy();
+	h->length = 8;
+	sclp_setup_int();
+
+	asm volatile(
+		"       .insn   rre,0xb2204200,%1,%2\n"  /* servc %1,%2 */
+		"       ipm     %0\n"
+		"       srl     %0,28"
+		: "=&d" (cc) : "d" (valid_code), "a" (__pa(pagebuf))
+		: "cc", "memory");
+	if (lc->pgm_int_code) {
+		sclp_handle_ext();
+		cc = 1;
+	} else if (!cc)
+		sclp_wait_busy();
+	report(cc == 0, "Instruction format ignored bits");
+}
+
+/**
+ * Find a valid READ INFO command code; not all codes are always allowed, and
+ * probing should be performed in the right order.
+ */
+static void find_valid_sclp_code(void)
+{
+	const unsigned int commands[] = { SCLP_CMDW_READ_SCP_INFO_FORCED,
+					  SCLP_CMDW_READ_SCP_INFO };
+	SCCBHeader *h = (SCCBHeader *)pagebuf;
+	int i, cc;
+
+	for (i = 0; i < ARRAY_SIZE(commands); i++) {
+		sclp_mark_busy();
+		memset(h, 0, sizeof(*h));
+		h->length = 4096;
+
+		valid_code = commands[i];
+		cc = sclp_service_call(commands[i], h);
+		if (cc)
+			break;
+		if (h->response_code == SCLP_RC_NORMAL_READ_COMPLETION)
+			return;
+		if (h->response_code != SCLP_RC_INVALID_SCLP_COMMAND)
+			break;
+	}
+	valid_code = 0;
+	report_abort("READ_SCP_INFO failed");
+}
+
+int main(void)
+{
+	report_prefix_push("sclp");
+	find_valid_sclp_code();
+
+	/* Test some basic things */
+	test_instbits();
+	test_priv();
+	test_addressing();
+
+	/* Test the specification exceptions */
+	test_sccb_too_short();
+	test_sccb_unaligned();
+	test_sccb_prefix();
+	test_sccb_high();
+
+	/* Test the expected response codes */
+	test_inval();
+	test_short();
+	test_boundary();
+	test_toolong();
+
+	return report_summary();
+}
diff --git a/s390x/unittests.cfg b/s390x/unittests.cfg
index f1b07cd..07013b2 100644
--- a/s390x/unittests.cfg
+++ b/s390x/unittests.cfg
@@ -75,3 +75,11 @@  file = stsi.elf
 [smp]
 file = smp.elf
 extra_params =-smp 2
+
+[sclp-1g]
+file = sclp.elf
+extra_params = -m 1G
+
+[sclp-3g]
+file = sclp.elf
+extra_params = -m 3G