Files
s390-tools/zipl/boot/libc.c
Mikhail Zaslonko eb06ebe245 include/boot: Move zipl/boot/error.h to include/boot
- 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>
2023-07-20 21:45:27 +02:00

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);
}