2018-05-15 13:28:53 +02:00
// SPDX-License-Identifier: GPL-2.0
2019-02-27 17:36:35 +01:00
# include <linux/kernel.h>
2018-05-15 13:28:53 +02:00
# include <linux/init.h>
# include <linux/ctype.h>
2020-06-08 21:32:42 -07:00
# include <linux/pgtable.h>
2018-05-15 13:28:53 +02:00
# include <asm/ebcdic.h>
# include <asm/sclp.h>
# include <asm/sections.h>
# include <asm/boot_data.h>
2019-02-27 16:52:42 +01:00
# include <asm/facility.h>
2019-04-01 19:11:04 +02:00
# include <asm/uv.h>
2018-05-15 13:28:53 +02:00
# include "boot.h"
char __bootdata ( early_command_line ) [ COMMAND_LINE_SIZE ] ;
2019-04-01 19:10:51 +02:00
struct ipl_parameter_block __bootdata_preserved ( ipl_block ) ;
int __bootdata_preserved ( ipl_block_valid ) ;
2020-01-30 22:16:27 -08:00
unsigned int __bootdata_preserved ( zlib_dfltcc_support ) = ZLIB_DFLTCC_FULL ;
2018-05-15 13:28:53 +02:00
2019-08-02 12:28:20 +02:00
unsigned long __bootdata ( vmalloc_size ) = VMALLOC_DEFAULT_SIZE ;
2017-11-17 18:44:28 +01:00
int __bootdata ( noexec_disabled ) ;
2018-05-15 13:28:53 +02:00
2020-10-19 11:01:33 +02:00
unsigned long memory_limit ;
2020-09-18 12:25:37 +02:00
int vmalloc_size_set ;
2020-09-02 16:52:06 +02:00
int kaslr_enabled ;
2019-02-03 21:37:20 +01:00
2018-05-15 13:28:53 +02:00
static inline int __diag308 ( unsigned long subcode , void * addr )
{
register unsigned long _addr asm ( " 0 " ) = ( unsigned long ) addr ;
register unsigned long _rc asm ( " 1 " ) = 0 ;
unsigned long reg1 , reg2 ;
psw_t old = S390_lowcore . program_new_psw ;
asm volatile (
" epsw %0,%1 \n "
" st %0,%[psw_pgm] \n "
" st %1,%[psw_pgm]+4 \n "
" larl %0,1f \n "
" stg %0,%[psw_pgm]+8 \n "
" diag %[addr],%[subcode],0x308 \n "
" 1: nopr %%r7 \n "
: " =&d " ( reg1 ) , " =&a " ( reg2 ) ,
[ psw_pgm ] " =Q " ( S390_lowcore . program_new_psw ) ,
[ addr ] " +d " ( _addr ) , " +d " ( _rc )
: [ subcode ] " d " ( subcode )
: " cc " , " memory " ) ;
S390_lowcore . program_new_psw = old ;
return _rc ;
}
void store_ipl_parmblock ( void )
{
int rc ;
2019-04-01 19:10:51 +02:00
rc = __diag308 ( DIAG308_STORE , & ipl_block ) ;
2018-05-15 13:28:53 +02:00
if ( rc = = DIAG308_RC_OK & &
2019-04-01 19:10:51 +02:00
ipl_block . hdr . version < = IPL_MAX_SUPPORTED_VERSION )
ipl_block_valid = 1 ;
2018-05-15 13:28:53 +02:00
}
2020-10-19 11:01:33 +02:00
bool is_ipl_block_dump ( void )
{
if ( ipl_block . pb0_hdr . pbt = = IPL_PBT_FCP & &
ipl_block . fcp . opt = = IPL_PB0_FCP_OPT_DUMP )
return true ;
if ( ipl_block . pb0_hdr . pbt = = IPL_PBT_NVME & &
ipl_block . nvme . opt = = IPL_PB0_NVME_OPT_DUMP )
return true ;
return false ;
}
2019-04-15 10:35:54 +02:00
static size_t scpdata_length ( const u8 * buf , size_t count )
2018-05-15 13:28:53 +02:00
{
while ( count ) {
if ( buf [ count - 1 ] ! = ' \0 ' & & buf [ count - 1 ] ! = ' ' )
break ;
count - - ;
}
return count ;
}
static size_t ipl_block_get_ascii_scpdata ( char * dest , size_t size ,
const struct ipl_parameter_block * ipb )
{
2020-09-29 20:23:17 +02:00
const __u8 * scp_data ;
__u32 scp_data_len ;
2018-05-15 13:28:53 +02:00
int has_lowercase ;
2020-09-29 20:23:17 +02:00
size_t count = 0 ;
size_t i ;
switch ( ipb - > pb0_hdr . pbt ) {
case IPL_PBT_FCP :
scp_data_len = ipb - > fcp . scp_data_len ;
scp_data = ipb - > fcp . scp_data ;
break ;
case IPL_PBT_NVME :
scp_data_len = ipb - > nvme . scp_data_len ;
scp_data = ipb - > nvme . scp_data ;
break ;
default :
goto out ;
}
2018-05-15 13:28:53 +02:00
2020-09-29 20:23:17 +02:00
count = min ( size - 1 , scpdata_length ( scp_data , scp_data_len ) ) ;
2018-05-15 13:28:53 +02:00
if ( ! count )
goto out ;
has_lowercase = 0 ;
for ( i = 0 ; i < count ; i + + ) {
2020-09-29 20:23:17 +02:00
if ( ! isascii ( scp_data [ i ] ) ) {
2018-05-15 13:28:53 +02:00
count = 0 ;
goto out ;
}
2020-09-29 20:23:17 +02:00
if ( ! has_lowercase & & islower ( scp_data [ i ] ) )
2018-05-15 13:28:53 +02:00
has_lowercase = 1 ;
}
if ( has_lowercase )
2020-09-29 20:23:17 +02:00
memcpy ( dest , scp_data , count ) ;
2018-05-15 13:28:53 +02:00
else
for ( i = 0 ; i < count ; i + + )
2020-09-29 20:23:17 +02:00
dest [ i ] = tolower ( scp_data [ i ] ) ;
2018-05-15 13:28:53 +02:00
out :
dest [ count ] = ' \0 ' ;
return count ;
}
static void append_ipl_block_parm ( void )
{
char * parm , * delim ;
size_t len , rc = 0 ;
len = strlen ( early_command_line ) ;
delim = early_command_line + len ; /* '\0' character position */
parm = early_command_line + len + 1 ; /* append right after '\0' */
2019-02-20 14:26:51 +01:00
switch ( ipl_block . pb0_hdr . pbt ) {
case IPL_PBT_CCW :
2018-05-15 13:28:53 +02:00
rc = ipl_block_get_ascii_vmparm (
2019-04-01 19:10:51 +02:00
parm , COMMAND_LINE_SIZE - len - 1 , & ipl_block ) ;
2018-05-15 13:28:53 +02:00
break ;
2019-02-20 14:26:51 +01:00
case IPL_PBT_FCP :
2020-09-29 20:23:17 +02:00
case IPL_PBT_NVME :
2018-05-15 13:28:53 +02:00
rc = ipl_block_get_ascii_scpdata (
2019-04-01 19:10:51 +02:00
parm , COMMAND_LINE_SIZE - len - 1 , & ipl_block ) ;
2018-05-15 13:28:53 +02:00
break ;
}
if ( rc ) {
if ( * parm = = ' = ' )
memmove ( early_command_line , parm + 1 , rc ) ;
else
* delim = ' ' ; /* replace '\0' with space */
}
}
static inline int has_ebcdic_char ( const char * str )
{
int i ;
for ( i = 0 ; str [ i ] ; i + + )
if ( str [ i ] & 0x80 )
return 1 ;
return 0 ;
}
void setup_boot_command_line ( void )
{
COMMAND_LINE [ ARCH_COMMAND_LINE_SIZE - 1 ] = 0 ;
/* convert arch command line to ascii if necessary */
if ( has_ebcdic_char ( COMMAND_LINE ) )
EBCASC ( COMMAND_LINE , ARCH_COMMAND_LINE_SIZE ) ;
/* copy arch command line */
strcpy ( early_command_line , strim ( COMMAND_LINE ) ) ;
/* append IPL PARM data to the boot command line */
2019-04-01 19:11:08 +02:00
if ( ! is_prot_virt_guest ( ) & & ipl_block_valid )
2018-05-15 13:28:53 +02:00
append_ipl_block_parm ( ) ;
}
2019-02-27 16:52:42 +01:00
static void modify_facility ( unsigned long nr , bool clear )
{
if ( clear )
__clear_facility ( nr , S390_lowcore . stfle_fac_list ) ;
else
__set_facility ( nr , S390_lowcore . stfle_fac_list ) ;
}
2019-02-27 17:36:35 +01:00
static void check_cleared_facilities ( void )
{
unsigned long als [ ] = { FACILITIES_ALS } ;
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( als ) ; i + + ) {
if ( ( S390_lowcore . stfle_fac_list [ i ] & als [ i ] ) ! = als [ i ] ) {
sclp_early_printk ( " Warning: The Linux kernel requires facilities cleared via command line option \n " ) ;
print_missing_facilities ( ) ;
break ;
}
}
}
2019-02-27 16:52:42 +01:00
static void modify_fac_list ( char * str )
{
unsigned long val , endval ;
char * endp ;
bool clear ;
while ( * str ) {
clear = false ;
if ( * str = = ' ! ' ) {
clear = true ;
str + + ;
}
val = simple_strtoull ( str , & endp , 0 ) ;
if ( str = = endp )
break ;
str = endp ;
if ( * str = = ' - ' ) {
str + + ;
endval = simple_strtoull ( str , & endp , 0 ) ;
if ( str = = endp )
break ;
str = endp ;
while ( val < = endval ) {
modify_facility ( val , clear ) ;
val + + ;
}
} else {
modify_facility ( val , clear ) ;
}
if ( * str ! = ' , ' )
break ;
str + + ;
}
2019-02-27 17:36:35 +01:00
check_cleared_facilities ( ) ;
2019-02-27 16:52:42 +01:00
}
2020-09-02 16:52:06 +02:00
static char command_line_buf [ COMMAND_LINE_SIZE ] ;
2019-02-27 16:52:42 +01:00
void parse_boot_command_line ( void )
2018-05-15 13:28:53 +02:00
{
char * param , * val ;
2017-11-17 18:44:28 +01:00
bool enabled ;
char * args ;
int rc ;
2018-05-15 13:28:53 +02:00
2019-02-03 21:37:20 +01:00
kaslr_enabled = IS_ENABLED ( CONFIG_RANDOMIZE_BASE ) ;
2018-05-15 13:28:53 +02:00
args = strcpy ( command_line_buf , early_command_line ) ;
while ( * args ) {
args = next_arg ( args , & param , & val ) ;
2020-10-19 11:01:33 +02:00
if ( ! strcmp ( param , " mem " ) & & val )
memory_limit = round_down ( memparse ( val , NULL ) , PAGE_SIZE ) ;
2017-11-17 18:44:28 +01:00
2020-09-18 12:25:37 +02:00
if ( ! strcmp ( param , " vmalloc " ) & & val ) {
2019-08-02 12:28:20 +02:00
vmalloc_size = round_up ( memparse ( val , NULL ) , PAGE_SIZE ) ;
2020-09-18 12:25:37 +02:00
vmalloc_size_set = 1 ;
}
2019-08-02 12:28:20 +02:00
2020-09-25 22:42:30 +02:00
if ( ! strcmp ( param , " dfltcc " ) & & val ) {
2020-01-30 22:16:27 -08:00
if ( ! strcmp ( val , " off " ) )
zlib_dfltcc_support = ZLIB_DFLTCC_DISABLED ;
else if ( ! strcmp ( val , " on " ) )
zlib_dfltcc_support = ZLIB_DFLTCC_FULL ;
else if ( ! strcmp ( val , " def_only " ) )
zlib_dfltcc_support = ZLIB_DFLTCC_DEFLATE_ONLY ;
else if ( ! strcmp ( val , " inf_only " ) )
zlib_dfltcc_support = ZLIB_DFLTCC_INFLATE_ONLY ;
else if ( ! strcmp ( val , " always " ) )
zlib_dfltcc_support = ZLIB_DFLTCC_FULL_DEBUG ;
}
2017-11-17 18:44:28 +01:00
if ( ! strcmp ( param , " noexec " ) ) {
rc = kstrtobool ( val , & enabled ) ;
if ( ! rc & & ! enabled )
noexec_disabled = 1 ;
}
2019-02-27 16:52:42 +01:00
2019-08-19 10:49:53 +02:00
if ( ! strcmp ( param , " facilities " ) & & val )
2019-02-27 16:52:42 +01:00
modify_fac_list ( val ) ;
2019-02-03 21:37:20 +01:00
if ( ! strcmp ( param , " nokaslr " ) )
kaslr_enabled = 0 ;
2020-09-11 11:38:21 +02:00
# if IS_ENABLED(CONFIG_KVM)
if ( ! strcmp ( param , " prot_virt " ) ) {
rc = kstrtobool ( val , & enabled ) ;
if ( ! rc & & enabled )
prot_virt_host = 1 ;
}
# endif
2018-05-15 13:28:53 +02:00
}
}