blob: 2f902e39e785ff4e139a39be2ffe11b5fa01edc0 [file] [log] [blame]
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* s390x SCLP driver
*
* Copyright (c) 2017 Red Hat Inc
*
* Authors:
* David Hildenbrand <david@redhat.com>
*/
#include <libcflat.h>
#include <asm/page.h>
#include <asm/arch_def.h>
#include <asm/interrupt.h>
#include <asm/barrier.h>
#include <asm/spinlock.h>
#include "sclp.h"
#include <alloc_phys.h>
#include <alloc_page.h>
#include <asm/facility.h>
extern unsigned long stacktop;
static uint64_t storage_increment_size;
static uint64_t max_ram_size;
static uint64_t ram_size;
char _read_info[2 * PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
static ReadInfo *read_info;
struct sclp_facilities sclp_facilities;
char _sccb[PAGE_SIZE] __attribute__((__aligned__(4096)));
static volatile bool sclp_busy;
static struct spinlock sclp_lock;
static void mem_init(phys_addr_t mem_end)
{
phys_addr_t freemem_start = (phys_addr_t)&stacktop;
phys_addr_t base, top;
phys_alloc_init(freemem_start, mem_end - freemem_start);
phys_alloc_get_unused(&base, &top);
base = PAGE_ALIGN(base) >> PAGE_SHIFT;
top = top >> PAGE_SHIFT;
/* Make the pages available to the physical allocator */
page_alloc_init_area(AREA_ANY_NUMBER, base, top);
page_alloc_ops_enable();
}
void sclp_setup_int(void)
{
ctl_set_bit(0, CTL0_SERVICE_SIGNAL);
psw_mask_set_bits(PSW_MASK_EXT);
}
void sclp_handle_ext(void)
{
ctl_clear_bit(0, CTL0_SERVICE_SIGNAL);
sclp_clear_busy();
}
void sclp_wait_busy(void)
{
while (sclp_busy)
mb();
}
void sclp_mark_busy(void)
{
/*
* With multiple CPUs we might need to wait for another CPU's
* request before grabbing the busy indication.
*/
while (true) {
sclp_wait_busy();
spin_lock(&sclp_lock);
if (!sclp_busy) {
sclp_busy = true;
spin_unlock(&sclp_lock);
return;
}
spin_unlock(&sclp_lock);
}
}
void sclp_clear_busy(void)
{
spin_lock(&sclp_lock);
sclp_busy = false;
spin_unlock(&sclp_lock);
}
static void sclp_read_scp_info(ReadInfo *ri, int length)
{
unsigned int commands[] = { SCLP_CMDW_READ_SCP_INFO_FORCED,
SCLP_CMDW_READ_SCP_INFO };
int i, cc;
for (i = 0; i < ARRAY_SIZE(commands); i++) {
sclp_mark_busy();
memset(&ri->h, 0, sizeof(ri->h));
ri->h.length = length;
cc = sclp_service_call(commands[i], ri);
if (cc)
break;
if (ri->h.response_code == SCLP_RC_NORMAL_READ_COMPLETION)
return;
if (ri->h.response_code != SCLP_RC_INVALID_SCLP_COMMAND)
break;
}
report_abort("READ_SCP_INFO failed");
}
void sclp_read_info(void)
{
sclp_read_scp_info((void *)_read_info,
test_facility(140) ? sizeof(_read_info) : SCCB_SIZE);
read_info = (ReadInfo *)_read_info;
}
int sclp_get_cpu_num(void)
{
if (read_info)
return read_info->entries_cpu;
/*
* Don't abort here if read_info is NULL since abort() calls
* smp_teardown() which eventually calls this function and thus
* causes an infinite abort() chain, causing the test to hang.
* Since we obviously have at least one CPU, just return one.
*/
return 1;
}
CPUEntry *sclp_get_cpu_entries(void)
{
assert(read_info);
return (CPUEntry *)(_read_info + read_info->offset_cpu);
}
static bool sclp_feat_check(int byte, int bit)
{
uint8_t *rib = (uint8_t *)read_info;
return !!(rib[byte] & (0x80 >> bit));
}
void sclp_facilities_setup(void)
{
unsigned short cpu0_addr = stap();
CPUEntry *cpu;
int i;
assert(read_info);
cpu = sclp_get_cpu_entries();
if (read_info->offset_cpu > 134)
sclp_facilities.has_diag318 = read_info->byte_134_diag318;
sclp_facilities.has_sop = sclp_feat_check(80, SCLP_FEAT_80_BIT_SOP);
sclp_facilities.has_gsls = sclp_feat_check(85, SCLP_FEAT_85_BIT_GSLS);
sclp_facilities.has_esop = sclp_feat_check(85, SCLP_FEAT_85_BIT_ESOP);
sclp_facilities.has_kss = sclp_feat_check(98, SCLP_FEAT_98_BIT_KSS);
sclp_facilities.has_cmma = sclp_feat_check(116, SCLP_FEAT_116_BIT_CMMA);
sclp_facilities.has_64bscao = sclp_feat_check(116, SCLP_FEAT_116_BIT_64BSCAO);
sclp_facilities.has_esca = sclp_feat_check(116, SCLP_FEAT_116_BIT_ESCA);
sclp_facilities.has_ibs = sclp_feat_check(117, SCLP_FEAT_117_BIT_IBS);
sclp_facilities.has_pfmfi = sclp_feat_check(117, SCLP_FEAT_117_BIT_PFMFI);
for (i = 0; i < read_info->entries_cpu; i++, cpu++) {
/*
* The logic for only reading the facilities from the
* boot cpu comes from the kernel. I haven't yet found
* documentation that explains why this is necessary
* but I figure there's a reason behind doing it this
* way.
*/
if (cpu->address == cpu0_addr) {
sclp_facilities.has_sief2 = cpu->feat_sief2;
sclp_facilities.has_skeyi = cpu->feat_skeyi;
sclp_facilities.has_siif = cpu->feat_siif;
sclp_facilities.has_sigpif = cpu->feat_sigpif;
sclp_facilities.has_ib = cpu->feat_ib;
sclp_facilities.has_cei = cpu->feat_cei;
break;
}
}
}
/* Perform service call. Return 0 on success, non-zero otherwise. */
int sclp_service_call(unsigned int command, void *sccb)
{
int cc;
sclp_setup_int();
cc = servc(command, __pa(sccb));
sclp_wait_busy();
if (cc == 3)
return -1;
if (cc == 2)
return -1;
return 0;
}
void sclp_memory_setup(void)
{
uint64_t rnmax, rnsize;
enum tprot_permission permission;
assert(read_info);
/* calculate the storage increment size */
rnsize = read_info->rnsize;
if (!rnsize) {
rnsize = read_info->rnsize2;
}
storage_increment_size = rnsize << 20;
/* calculate the maximum memory size */
rnmax = read_info->rnmax;
if (!rnmax) {
rnmax = read_info->rnmax2;
}
max_ram_size = rnmax * storage_increment_size;
/* lowcore is always accessible, so the first increment is accessible */
ram_size = storage_increment_size;
/* probe for r/w memory up to max memory size */
while (ram_size < max_ram_size) {
expect_pgm_int();
permission = tprot(ram_size + storage_increment_size - 1, 0);
/* stop once we receive an exception or have protected memory */
if (clear_pgm_int() || permission != TPROT_READ_WRITE)
break;
ram_size += storage_increment_size;
}
mem_init(ram_size);
}
uint64_t get_ram_size(void)
{
return ram_size;
}
uint64_t get_max_ram_size(void)
{
return max_ram_size;
}
uint64_t sclp_get_stsi_mnest(void)
{
assert(read_info);
return read_info->stsi_parm;
}