| /* 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; | |
| } |