mirror of
https://github.com/ibm-s390-linux/s390-tools.git
synced 2026-08-05 02:14:52 +00:00
- Move zipl/boot/error.h to include/boot - Adjust include statements in zipb/boot and genprotimg/boot - Remove error.h from tunedasd/src/tunedasd.c as not needed - Fix tunedasd/src/Makefile Signed-off-by: Mikhail Zaslonko <zaslonko@linux.ibm.com> Reviewed-by: Marc Hartmayer <mhartmay@linux.ibm.com> Signed-off-by: Jan Höppner <hoeppner@linux.ibm.com>
584 lines
11 KiB
C
584 lines
11 KiB
C
/*
|
|
* zipl - zSeries Initial Program Loader tool
|
|
*
|
|
* Mini libc implementation
|
|
*
|
|
* Copyright IBM Corp. 2013, 2017
|
|
*
|
|
* s390-tools is free software; you can redistribute it and/or modify
|
|
* it under the terms of the MIT license. See LICENSE for details.
|
|
*/
|
|
|
|
#include "libc.h"
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include "boot/error.h"
|
|
#include "boot/s390.h"
|
|
|
|
#include "sclp.h"
|
|
#include "ebcdic.h"
|
|
|
|
extern char __heap_start[];
|
|
extern char __heap_stop[];
|
|
extern char __bss_start[];
|
|
extern char __bss_stop[];
|
|
extern char __ex_table_start[];
|
|
extern char __ex_table_stop[];
|
|
|
|
struct ex_table_entry {
|
|
unsigned int fault;
|
|
unsigned int target;
|
|
};
|
|
|
|
#define MEM_ALLOC_START ((unsigned long) __heap_start)
|
|
#define MEM_ALLOC_END ((unsigned long) __heap_stop)
|
|
#define MEM_ALLOC_MAX 4
|
|
|
|
static uint8_t mem_page_alloc_vec[MEM_ALLOC_MAX];
|
|
|
|
/*
|
|
* Initialize memory with value
|
|
*/
|
|
void *memset(void *s, int c, unsigned long n)
|
|
{
|
|
char *xs;
|
|
|
|
xs = s;
|
|
while (n--)
|
|
*xs++ = c;
|
|
return s;
|
|
}
|
|
|
|
/*
|
|
* Copy memory
|
|
*/
|
|
void *memcpy(void *dest, const void *src, unsigned long n)
|
|
{
|
|
const char *s = src;
|
|
char *d = dest;
|
|
|
|
while (n--)
|
|
*d++ = *s++;
|
|
return dest;
|
|
}
|
|
|
|
/*
|
|
* Move @n bytes of memory from @src to @dest. The memory regions may overlap.
|
|
*/
|
|
void *memmove(void *dest, const void *src, unsigned long n)
|
|
{
|
|
const char *s = src;
|
|
char *d = dest;
|
|
|
|
if (s < d) {
|
|
d += n;
|
|
s += n;
|
|
while (n--)
|
|
*--d = *--s;
|
|
} else {
|
|
while (n--)
|
|
*d++ = *s++;
|
|
}
|
|
return dest;
|
|
}
|
|
|
|
/*
|
|
* Copy string
|
|
*/
|
|
char *strcpy(char *dest, const char *src)
|
|
{
|
|
const char *s = src;
|
|
char *d = dest;
|
|
|
|
while (*s)
|
|
*d++ = *s++;
|
|
*d = 0;
|
|
return dest;
|
|
}
|
|
|
|
/**
|
|
* Copy \a src to buffer \a dest of size \a size. At most size - 1
|
|
* chars will be copied. \a dest will always be NUL terminated.
|
|
*
|
|
* Note: If the return value is greater than or equal to size truncation
|
|
* occurred.
|
|
*
|
|
* @param[in] dest Destination buffer
|
|
* @param[in] src Source string
|
|
* @param[in] size Size of destination buffer
|
|
*
|
|
* @returns strlen Length of \a src string
|
|
*/
|
|
size_t strlcpy(char *dest, const char *src, size_t size)
|
|
{
|
|
size_t str_len = strlen(src);
|
|
size_t len;
|
|
|
|
if (size) {
|
|
len = MIN(size - 1, str_len);
|
|
memcpy(dest, src, len);
|
|
dest[len] = '\0';
|
|
}
|
|
|
|
return str_len;
|
|
}
|
|
|
|
/*
|
|
* Return string length
|
|
*/
|
|
int strlen(const char *s)
|
|
{
|
|
int len = 0;
|
|
|
|
while (*s++)
|
|
len++;
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* Concatenate two strings
|
|
*/
|
|
char *strcat(char *dest, const char *src)
|
|
{
|
|
strcpy(dest + strlen(dest), src);
|
|
return dest;
|
|
}
|
|
|
|
/*
|
|
* Compare two strings
|
|
*/
|
|
int strncmp(const char *s1, const char *s2, unsigned long count)
|
|
{
|
|
while (count--)
|
|
if (*s1++ != *s2++)
|
|
return *(unsigned char *)(s1 - 1) -
|
|
*(unsigned char *)(s2 - 1);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Find the first occurrence of character in string.
|
|
* STR: address of the string, which is different from NULL!
|
|
*/
|
|
char *strchr(const char *str, char c)
|
|
{
|
|
while (*str) {
|
|
if (*str == c)
|
|
return (char *)str;
|
|
str++;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Minimalistic hash function on "extended" strings with optional
|
|
* logical prefixes.
|
|
* It doesn't provide perfect quality of distribution.
|
|
*
|
|
* STR: null-terminated string
|
|
* PREFIX: Pointer to a one-character logical prefix (can be NULL)
|
|
* HASH_SIZE: number of buckets
|
|
*/
|
|
unsigned int strhash(unsigned char *str, char *prefix, unsigned int hash_size)
|
|
{
|
|
unsigned int sum = 0;
|
|
|
|
if (prefix)
|
|
sum += 5 * *prefix;
|
|
while (*str)
|
|
sum += 5 * *(str++);
|
|
|
|
return sum % hash_size;
|
|
}
|
|
|
|
static int skip_atoi(const char **c)
|
|
{
|
|
int i = 0;
|
|
|
|
do {
|
|
i = i*10 + *((*c)++) - '0';
|
|
} while (isdigit(**c));
|
|
|
|
return i;
|
|
}
|
|
|
|
enum format_type {
|
|
FORMAT_TYPE_NONE,
|
|
FORMAT_TYPE_STR,
|
|
FORMAT_TYPE_ULONG,
|
|
};
|
|
|
|
struct printf_spec {
|
|
unsigned int type:8; /* format_type enum */
|
|
signed int field_width:24; /* width of output field */
|
|
unsigned int zeropad:1; /* pad numbers with zero */
|
|
unsigned int base:8; /* number base, 8, 10 or 16 only */
|
|
signed int precision:16; /* # of digits/chars */
|
|
};
|
|
|
|
#define FIELD_WIDTH_MAX ((1 << 23) - 1)
|
|
|
|
static int format_decode(const char *fmt, struct printf_spec *spec)
|
|
{
|
|
const char *start = fmt;
|
|
|
|
spec->type = FORMAT_TYPE_NONE;
|
|
while (*fmt) {
|
|
if (*fmt == '%')
|
|
break;
|
|
fmt++;
|
|
}
|
|
|
|
/* return current non-format string */
|
|
if (fmt != start || !*fmt)
|
|
return fmt - start;
|
|
|
|
/* first char is '%', skip it */
|
|
fmt++;
|
|
if (*fmt == '0') {
|
|
spec->zeropad = 1;
|
|
fmt++;
|
|
}
|
|
|
|
spec->field_width = -1;
|
|
if (isdigit(*fmt))
|
|
spec->field_width = skip_atoi(&fmt);
|
|
|
|
spec->precision = -1;
|
|
if (*fmt == '.') {
|
|
fmt++;
|
|
if (isdigit(*fmt))
|
|
spec->precision = skip_atoi(&fmt);
|
|
}
|
|
|
|
/* always use long form, i.e. ignore long qualifier */
|
|
if (*fmt == 'l')
|
|
fmt++;
|
|
|
|
switch (*fmt) {
|
|
case 's':
|
|
spec->type = FORMAT_TYPE_STR;
|
|
break;
|
|
|
|
case 'o':
|
|
spec->base = 8;
|
|
spec->type = FORMAT_TYPE_ULONG;
|
|
break;
|
|
|
|
case 'u':
|
|
spec->base = 10;
|
|
spec->type = FORMAT_TYPE_ULONG;
|
|
break;
|
|
|
|
case 'x':
|
|
spec->base = 16;
|
|
spec->type = FORMAT_TYPE_ULONG;
|
|
break;
|
|
|
|
default:
|
|
libc_stop(EINTERNAL);
|
|
}
|
|
|
|
return ++fmt - start;
|
|
}
|
|
|
|
static char *string(char *buf, char *end, const char *s,
|
|
struct printf_spec *spec)
|
|
{
|
|
int limit = spec->precision;
|
|
int len = 0;
|
|
int spaces;
|
|
|
|
/* Copy string to buffer */
|
|
while (limit--) {
|
|
char c = *s++;
|
|
if (!c)
|
|
break;
|
|
if (buf < end)
|
|
*buf = c;
|
|
buf++;
|
|
len++;
|
|
}
|
|
|
|
/* right align if necessary */
|
|
if (len < spec->field_width && buf < end) {
|
|
spaces = spec->field_width - len;
|
|
if (spaces >= end - buf)
|
|
spaces = end - buf;
|
|
memmove(buf + spaces, buf, len);
|
|
memset(buf, ' ', spaces);
|
|
buf += spaces;
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
static char *number(char *buf, char *end, unsigned long val,
|
|
struct printf_spec *spec)
|
|
{
|
|
/* temporary buffer to prepare the string.
|
|
* Worst case: base = 8 -> 3 bits per char -> 2.67 chars per byte */
|
|
char tmp[3 * sizeof(val)];
|
|
static const char vec[] = {'0', '1', '2', '3', '4', '5', '6', '7',
|
|
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
|
int field_width = spec->field_width;
|
|
int precision = spec->precision;
|
|
int len;
|
|
|
|
/* prepare string in reverse order */
|
|
len = 0;
|
|
do {
|
|
tmp[len++] = vec[val % spec->base];
|
|
val /= spec->base;
|
|
} while (val);
|
|
|
|
if (len > precision)
|
|
precision = len;
|
|
|
|
field_width -= precision;
|
|
while (field_width-- > 0) {
|
|
char c = spec->zeropad ? '0' : ' ';
|
|
if (buf < end)
|
|
*buf = c;
|
|
buf++;
|
|
}
|
|
|
|
/* needed if no field width but a precision is given */
|
|
while (len < precision--) {
|
|
if (buf < end)
|
|
*buf = '0';
|
|
buf++;
|
|
}
|
|
|
|
while (len-- > 0) {
|
|
if (buf < end)
|
|
*buf = tmp[len];
|
|
buf++;
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* vsnprintf - Format string and place in a buffer
|
|
*
|
|
* This funcion only supports a subset of format options defined in the
|
|
* C standard, i.e.
|
|
* specifiers:
|
|
* * %s (strings)
|
|
* * %o (unsigned int octal)
|
|
* * %u (unsigned int decimal)
|
|
* * %x (unsigned int hexadecimal)
|
|
*
|
|
* length modifier:
|
|
* * 'l' (ignored, see below)
|
|
*
|
|
* flag:
|
|
* * '0' (zero padding for integers)
|
|
*
|
|
* precision and field width as integers, i.e. _not_ by asterix '*'.
|
|
*
|
|
* The integer specifiers (o, u and, x) always use the long form, i.e.
|
|
* assume the argument to be of type 'unsigned long int'.
|
|
*
|
|
* Returns the number of characters the function would have generated for
|
|
* the given input (excluding the trailing '\0'. If the return value is
|
|
* greater than or equal @size the resulting string is trunctuated.
|
|
*/
|
|
static int vsnprintf(char *buf, unsigned long size, const char *fmt,
|
|
va_list args)
|
|
{
|
|
struct printf_spec spec = {0};
|
|
char *str, *end;
|
|
|
|
str = buf;
|
|
end = buf + size;
|
|
|
|
/* use negative (large positive) buffer sizes as indication for
|
|
* unknown/unlimited buffer sizes. */
|
|
if (end < buf) {
|
|
end = ((void *)-1);
|
|
size = end - buf;
|
|
}
|
|
|
|
while (*fmt) {
|
|
const char *old_fmt = fmt;
|
|
int read = format_decode(fmt, &spec);
|
|
int copy;
|
|
|
|
fmt += read;
|
|
|
|
switch (spec.type) {
|
|
case FORMAT_TYPE_NONE:
|
|
copy = read;
|
|
if (str < end) {
|
|
if (copy > end - str)
|
|
copy = end - str;
|
|
memcpy(str, old_fmt, copy);
|
|
}
|
|
str += read;
|
|
break;
|
|
|
|
case FORMAT_TYPE_STR:
|
|
str = string(str, end, va_arg(args, char *), &spec);
|
|
break;
|
|
|
|
case FORMAT_TYPE_ULONG:
|
|
str = number(str, end, va_arg(args, unsigned long),
|
|
&spec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (size) {
|
|
if (str < end)
|
|
*str = '\0';
|
|
else
|
|
end[-1] = '\0';
|
|
}
|
|
return str - buf;
|
|
}
|
|
|
|
/*
|
|
* Write formatted string to buffer
|
|
*/
|
|
void snprintf(char *buf, unsigned long size, const char *fmt, ...)
|
|
{
|
|
va_list va;
|
|
|
|
va_start(va, fmt);
|
|
vsnprintf(buf, size, fmt, va);
|
|
va_end(va);
|
|
}
|
|
|
|
/*
|
|
* Print formatted string to console
|
|
*/
|
|
void printf(const char *fmt, ...)
|
|
{
|
|
char buf[LINE_LENGTH + 1];
|
|
int len;
|
|
va_list va;
|
|
|
|
va_start(va, fmt);
|
|
len = vsnprintf(buf, sizeof(buf), fmt, va);
|
|
if (len > LINE_LENGTH) {
|
|
buf[LINE_LENGTH - 1] = '.';
|
|
buf[LINE_LENGTH - 2] = '.';
|
|
buf[LINE_LENGTH - 3] = '.';
|
|
}
|
|
va_end(va);
|
|
sclp_print(buf);
|
|
#ifdef ENABLE_SCLP_ASCII
|
|
sclp_print_ascii(buf);
|
|
#endif /* ENABLE_SCLP_ASCII */
|
|
}
|
|
|
|
/*
|
|
* Allocate one zero page
|
|
*/
|
|
unsigned long get_zeroed_page(void)
|
|
{
|
|
const int page_count = MIN(MEM_ALLOC_MAX, (int)((MEM_ALLOC_END - MEM_ALLOC_START) / PAGE_SIZE));
|
|
unsigned long addr;
|
|
int i;
|
|
|
|
for (i = 0; i < page_count; i++) {
|
|
if (mem_page_alloc_vec[i] != 0)
|
|
continue;
|
|
addr = MEM_ALLOC_START + i * PAGE_SIZE;
|
|
memset((void *) addr, 0, PAGE_SIZE);
|
|
mem_page_alloc_vec[i] = 1;
|
|
return addr;
|
|
}
|
|
libc_stop(EINTERNAL);
|
|
}
|
|
|
|
/*
|
|
* Free page
|
|
*/
|
|
void free_page(unsigned long addr)
|
|
{
|
|
if (addr < MEM_ALLOC_START || addr >= MEM_ALLOC_END)
|
|
libc_stop(EINTERNAL);
|
|
|
|
mem_page_alloc_vec[(addr - MEM_ALLOC_START) / PAGE_SIZE] = 0;
|
|
}
|
|
|
|
/*
|
|
* Program check handler
|
|
*/
|
|
void pgm_check_handler_fn(void)
|
|
{
|
|
struct ex_table_entry *ex_table = (void *) __ex_table_start;
|
|
struct psw_t *psw_old = &S390_lowcore.program_old_psw;
|
|
int i, ex_table_cnt;
|
|
|
|
ex_table_cnt = (__ex_table_stop - __ex_table_start)
|
|
/ sizeof(struct ex_table_entry);
|
|
|
|
for (i = 0; i < ex_table_cnt; i++) {
|
|
if (ex_table[i].fault == psw_old->addr) {
|
|
psw_old->addr = ex_table[i].target;
|
|
return;
|
|
}
|
|
}
|
|
libc_stop(psw_old->addr);
|
|
}
|
|
|
|
void __noinline load_wait_psw(uint64_t psw_mask, struct psw_t *psw)
|
|
{
|
|
struct psw_t wait_psw = { .mask = psw_mask, .addr = 0 };
|
|
struct psw_t old_psw, *wait_psw_ptr = &wait_psw;
|
|
unsigned long addr;
|
|
|
|
old_psw = *psw;
|
|
psw->mask = 0x0000000180000000ULL;
|
|
asm volatile(
|
|
" larl %[addr],.Lwait\n"
|
|
" stg %[addr],8(%[wait_psw_ptr])\n"
|
|
" stg %[addr],8(%[psw])\n"
|
|
" lpswe %[wait_psw]\n"
|
|
".Lwait: \n"
|
|
: [addr] "=&d" (addr)
|
|
: [wait_psw] "Q" (wait_psw), [wait_psw_ptr] "a" (wait_psw_ptr),
|
|
[psw] "a" (psw)
|
|
: "cc", "memory");
|
|
*psw = old_psw;
|
|
}
|
|
|
|
/*
|
|
* The libc startup function
|
|
*/
|
|
void initialize(void)
|
|
{
|
|
struct psw_t *psw_pgm = &S390_lowcore.program_new_psw;
|
|
|
|
/* Setup program check handler */
|
|
psw_pgm->mask = 0x0000000180000000ULL;
|
|
psw_pgm->addr = (unsigned long) pgm_check_handler;
|
|
|
|
/* Clear bss section */
|
|
memset(__bss_start, 0, __bss_stop - __bss_start);
|
|
start();
|
|
}
|
|
|
|
/*
|
|
* Load disabled wait PSW with reason code in address field
|
|
*/
|
|
void libc_stop(unsigned long reason)
|
|
{
|
|
struct psw_t psw;
|
|
|
|
psw.mask = 0x0002000080000000ULL;
|
|
psw.addr = reason;
|
|
|
|
asm volatile(
|
|
" lpswe %[psw]\n"
|
|
: : [psw] "Q" (psw) : "cc"
|
|
);
|
|
|
|
while(1);
|
|
}
|