diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2023-02-22 02:09:17 +0300 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2023-02-22 02:09:17 +0300 |
commit | bcf5470eb4a13e5670fefb21525b43ef385c6fc6 (patch) | |
tree | 33395b005d3642873f385a628b7b7385608739d1 | |
parent | 877934769e5b91798d304d4641647900ee614ce8 (diff) | |
parent | 6472a2dcc4274452bb46fb5a0d968a1c1ed772ee (diff) | |
download | linux-bcf5470eb4a13e5670fefb21525b43ef385c6fc6.tar.xz |
Merge tag 's390-6.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/s390/linux
Pull s390 updates from Heiko Carstens:
- Large cleanup of the con3270/tty3270 driver. Among others this fixes:
- Background Color Support
- ASCII Line Character Support
- VT100 Support
- Geometries other than 80x24
- Cleanup and improve cmpxchg() code. Also add cmpxchg_user_key() to
uaccess functions, which will be used by KVM to access KVM guest
memory with a specific storage key
- Add support for user space events counting to CPUMF
- Cleanup the vfio/ccw code, which also allows now to properly support
2K Format-2 IDALs
- Move kernel page table allocation and initialization to decompressor,
which finally allows to enter the kernel with dynamic address
translation enabled. This in turn allows to get rid of code with
special handling in the kernel, which has to distinguish if DAT is on
or off
- Replace kretprobe with rethook
- Various improvements to vfio/ap queue resets:
- Use TAPQ to verify completion of a reset in progress rather than
multiple invocations of ZAPQ.
- Check TAPQ response codes when verifying successful completion of
ZAPQ.
- Fix erroneous handling of some error response codes.
- Increase the maximum amount of time to wait for successful
completion of ZAPQ
- Rework system call wrappers to get rid of alias functions, which were
only left on s390
- Cleanup diag288_wdt watchdog driver. It has been agreed on with
Guenter Roeck that this goes upstream via the s390 tree
- Add missing loadparm parameter handling for list-directed ECKD
ipl/reipl
- Various improvements to memory detection code
- Remove arch_cpu_idle_time() since the current implementation is
broken, and allows user space observable accounted idle times which
can temporarily decrease
- Add Reset DAT-Protection support: (only) allow to change PTEs from RO
to RW with a new RDP instruction. Unlike the currently used IPTE
instruction, this does not necessarily guarantee that TLBs of all
CPUs are synchronously flushed; and that remote CPUs can see spurious
protection faults. The overall improvement for not requiring an all
CPU synchronization, like it is required with IPTE, should be
beneficial
- Fix KFENCE page fault reporting
- Smaller cleanups and improvement all over the place
* tag 's390-6.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/s390/linux: (182 commits)
s390/irq,idle: simplify idle check
s390/processor: add test_and_set_cpu_flag() and test_and_clear_cpu_flag()
s390/processor: let cpu helper functions return boolean values
s390/kfence: fix page fault reporting
s390/zcrypt: introduce ctfm field in struct CPRBX
s390: remove confusing comment from uapi types header file
vfio/ccw: remove WARN_ON during shutdown
s390/entry: remove toolchain dependent micro-optimization
s390/mem_detect: do not truncate online memory ranges info
s390/vx: remove __uint128_t type from __vector128 struct again
s390/mm: add support for RDP (Reset DAT-Protection)
s390/mm: define private VM_FAULT_* reasons from top bits
Documentation: s390: correct spelling
s390/ap: fix status returned by ap_qact()
s390/ap: fix status returned by ap_aqic()
s390: vfio-ap: tighten the NIB validity check
Revert "s390/mem_detect: do not update output parameters on failure"
s390/idle: remove arch_cpu_idle_time() and corresponding code
s390/vx: use simple assignments to access __vector128 members
s390/vx: add 64 and 128 bit members to __vector128 struct
...
102 files changed, 4542 insertions, 4544 deletions
diff --git a/Documentation/ABI/testing/sysfs-bus-css b/Documentation/ABI/testing/sysfs-bus-css index 12a733fe357f..d4d5cfb63b90 100644 --- a/Documentation/ABI/testing/sysfs-bus-css +++ b/Documentation/ABI/testing/sysfs-bus-css @@ -1,22 +1,19 @@ What: /sys/bus/css/devices/.../type Date: March 2008 -Contact: Cornelia Huck <cornelia.huck@de.ibm.com> - linux-s390@vger.kernel.org +Contact: linux-s390@vger.kernel.org Description: Contains the subchannel type, as reported by the hardware. This attribute is present for all subchannel types. What: /sys/bus/css/devices/.../modalias Date: March 2008 -Contact: Cornelia Huck <cornelia.huck@de.ibm.com> - linux-s390@vger.kernel.org +Contact: linux-s390@vger.kernel.org Description: Contains the module alias as reported with uevents. It is of the format css:t<type> and present for all subchannel types. What: /sys/bus/css/drivers/io_subchannel/.../chpids Date: December 2002 -Contact: Cornelia Huck <cornelia.huck@de.ibm.com> - linux-s390@vger.kernel.org +Contact: linux-s390@vger.kernel.org Description: Contains the ids of the channel paths used by this subchannel, as reported by the channel subsystem during subchannel recognition. @@ -26,8 +23,7 @@ Users: s390-tools, HAL What: /sys/bus/css/drivers/io_subchannel/.../pimpampom Date: December 2002 -Contact: Cornelia Huck <cornelia.huck@de.ibm.com> - linux-s390@vger.kernel.org +Contact: linux-s390@vger.kernel.org Description: Contains the PIM/PAM/POM values, as reported by the channel subsystem when last queried by the common I/O layer (this implies that this attribute is not necessarily @@ -38,8 +34,7 @@ Users: s390-tools, HAL What: /sys/bus/css/devices/.../driver_override Date: June 2019 -Contact: Cornelia Huck <cohuck@redhat.com> - linux-s390@vger.kernel.org +Contact: linux-s390@vger.kernel.org Description: This file allows the driver for a device to be specified. When specified, only a driver with a name matching the value written to driver_override will have an opportunity to bind to the diff --git a/Documentation/s390/pci.rst b/Documentation/s390/pci.rst index 8157f0cddbc2..a1a72a47dc96 100644 --- a/Documentation/s390/pci.rst +++ b/Documentation/s390/pci.rst @@ -51,7 +51,7 @@ Entries specific to zPCI functions and entries that hold zPCI information. The slot entries are set up using the function identifier (FID) of the PCI function. The format depicted as XXXXXXXX above is 8 hexadecimal digits - with 0 padding and lower case hexadecimal digitis. + with 0 padding and lower case hexadecimal digits. - /sys/bus/pci/slots/XXXXXXXX/power @@ -66,7 +66,7 @@ Entries specific to zPCI functions and entries that hold zPCI information. - function_handle Low-level identifier used for a configured PCI function. - It might be useful for debuging. + It might be useful for debugging. - pchid Model-dependent location of the I/O adapter. diff --git a/Documentation/s390/vfio-ccw.rst b/Documentation/s390/vfio-ccw.rst index ea928a3806f4..37026fa18179 100644 --- a/Documentation/s390/vfio-ccw.rst +++ b/Documentation/s390/vfio-ccw.rst @@ -176,7 +176,7 @@ The process of how these work together. Use the 'mdev_create' sysfs file, we need to manually create one (and only one for our case) mediated device. 3. vfio_mdev.ko drives the mediated ccw device. - vfio_mdev is also the vfio device drvier. It will probe the mdev and + vfio_mdev is also the vfio device driver. It will probe the mdev and add it to an iommu_group and a vfio_group. Then we could pass through the mdev to a guest. @@ -219,8 +219,8 @@ values may occur: The operation was successful. ``-EOPNOTSUPP`` - The orb specified transport mode or an unidentified IDAW format, or the - scsw specified a function other than the start function. + The ORB specified transport mode or the + SCSW specified a function other than the start function. ``-EIO`` A request was issued while the device was not in a state ready to accept diff --git a/MAINTAINERS b/MAINTAINERS index ceced37525c5..27f3a8619afc 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18111,6 +18111,7 @@ F: Documentation/driver-api/s390-drivers.rst F: Documentation/s390/ F: arch/s390/ F: drivers/s390/ +F: drivers/watchdog/diag288_wdt.c S390 COMMON I/O LAYER M: Vineeth Vijayan <vneethv@linux.ibm.com> @@ -18171,6 +18172,13 @@ F: arch/s390/pci/ F: drivers/pci/hotplug/s390_pci_hpc.c F: Documentation/s390/pci.rst +S390 SCM DRIVER +M: Vineeth Vijayan <vneethv@linux.ibm.com> +L: linux-s390@vger.kernel.org +S: Supported +F: drivers/s390/block/scm* +F: drivers/s390/cio/scm.c + S390 VFIO AP DRIVER M: Tony Krowiak <akrowiak@linux.ibm.com> M: Halil Pasic <pasic@linux.ibm.com> diff --git a/arch/s390/Kconfig b/arch/s390/Kconfig index 7fd08755a1f9..933771b0b07a 100644 --- a/arch/s390/Kconfig +++ b/arch/s390/Kconfig @@ -187,6 +187,7 @@ config S390 select HAVE_KPROBES select HAVE_KPROBES_ON_FTRACE select HAVE_KRETPROBES + select HAVE_RETHOOK select HAVE_KVM select HAVE_LIVEPATCH select HAVE_MEMBLOCK_PHYS_MAP diff --git a/arch/s390/boot/Makefile b/arch/s390/boot/Makefile index d52c3e2e16bc..47a397da0498 100644 --- a/arch/s390/boot/Makefile +++ b/arch/s390/boot/Makefile @@ -35,7 +35,7 @@ endif CFLAGS_sclp_early_core.o += -I$(srctree)/drivers/s390/char -obj-y := head.o als.o startup.o mem_detect.o ipl_parm.o ipl_report.o +obj-y := head.o als.o startup.o mem_detect.o ipl_parm.o ipl_report.o vmem.o obj-y += string.o ebcdic.o sclp_early_core.o mem.o ipl_vmparm.o cmdline.o obj-y += version.o pgm_check_info.o ctype.o ipl_data.o machine_kexec_reloc.o obj-$(findstring y, $(CONFIG_PROTECTED_VIRTUALIZATION_GUEST) $(CONFIG_PGSTE)) += uv.o diff --git a/arch/s390/boot/boot.h b/arch/s390/boot/boot.h index 70418389414d..58ce701d6110 100644 --- a/arch/s390/boot/boot.h +++ b/arch/s390/boot/boot.h @@ -8,10 +8,36 @@ #ifndef __ASSEMBLY__ +struct machine_info { + unsigned char has_edat1 : 1; + unsigned char has_edat2 : 1; + unsigned char has_nx : 1; +}; + +struct vmlinux_info { + unsigned long default_lma; + unsigned long entry; + unsigned long image_size; /* does not include .bss */ + unsigned long bss_size; /* uncompressed image .bss size */ + unsigned long bootdata_off; + unsigned long bootdata_size; + unsigned long bootdata_preserved_off; + unsigned long bootdata_preserved_size; + unsigned long dynsym_start; + unsigned long rela_dyn_start; + unsigned long rela_dyn_end; + unsigned long amode31_size; + unsigned long init_mm_off; + unsigned long swapper_pg_dir_off; + unsigned long invalid_pg_dir_off; +}; + void startup_kernel(void); -unsigned long detect_memory(void); +unsigned long detect_memory(unsigned long *safe_addr); +void mem_detect_set_usable_limit(unsigned long limit); bool is_ipl_block_dump(void); void store_ipl_parmblock(void); +unsigned long read_ipl_report(unsigned long safe_addr); void setup_boot_command_line(void); void parse_boot_command_line(void); void verify_facilities(void); @@ -19,7 +45,12 @@ void print_missing_facilities(void); void sclp_early_setup_buffer(void); void print_pgm_check_info(void); unsigned long get_random_base(unsigned long safe_addr); +void setup_vmem(unsigned long asce_limit); +unsigned long vmem_estimate_memory_needs(unsigned long online_mem_total); void __printf(1, 2) decompressor_printk(const char *fmt, ...); +void error(char *m); + +extern struct machine_info machine; /* Symbols defined by linker scripts */ extern const char kernel_version[]; @@ -31,8 +62,13 @@ extern char __boot_data_start[], __boot_data_end[]; extern char __boot_data_preserved_start[], __boot_data_preserved_end[]; extern char _decompressor_syms_start[], _decompressor_syms_end[]; extern char _stack_start[], _stack_end[]; +extern char _end[]; +extern unsigned char _compressed_start[]; +extern unsigned char _compressed_end[]; +extern struct vmlinux_info _vmlinux_info; +#define vmlinux _vmlinux_info -unsigned long read_ipl_report(unsigned long safe_offset); +#define __abs_lowcore_pa(x) (((unsigned long)(x) - __abs_lowcore) % sizeof(struct lowcore)) #endif /* __ASSEMBLY__ */ #endif /* BOOT_BOOT_H */ diff --git a/arch/s390/boot/decompressor.c b/arch/s390/boot/decompressor.c index b519a1f045d8..d762733a0753 100644 --- a/arch/s390/boot/decompressor.c +++ b/arch/s390/boot/decompressor.c @@ -11,6 +11,7 @@ #include <linux/string.h> #include <asm/page.h> #include "decompressor.h" +#include "boot.h" /* * gzip declarations diff --git a/arch/s390/boot/decompressor.h b/arch/s390/boot/decompressor.h index f75cc31a77dd..92b81d2ea35d 100644 --- a/arch/s390/boot/decompressor.h +++ b/arch/s390/boot/decompressor.h @@ -2,37 +2,11 @@ #ifndef BOOT_COMPRESSED_DECOMPRESSOR_H #define BOOT_COMPRESSED_DECOMPRESSOR_H -#include <linux/stddef.h> - #ifdef CONFIG_KERNEL_UNCOMPRESSED static inline void *decompress_kernel(void) { return NULL; } #else void *decompress_kernel(void); #endif unsigned long mem_safe_offset(void); -void error(char *m); - -struct vmlinux_info { - unsigned long default_lma; - void (*entry)(void); - unsigned long image_size; /* does not include .bss */ - unsigned long bss_size; /* uncompressed image .bss size */ - unsigned long bootdata_off; - unsigned long bootdata_size; - unsigned long bootdata_preserved_off; - unsigned long bootdata_preserved_size; - unsigned long dynsym_start; - unsigned long rela_dyn_start; - unsigned long rela_dyn_end; - unsigned long amode31_size; -}; - -/* Symbols defined by linker scripts */ -extern char _end[]; -extern unsigned char _compressed_start[]; -extern unsigned char _compressed_end[]; -extern char _vmlinux_info[]; - -#define vmlinux (*(struct vmlinux_info *)_vmlinux_info) #endif /* BOOT_COMPRESSED_DECOMPRESSOR_H */ diff --git a/arch/s390/boot/kaslr.c b/arch/s390/boot/kaslr.c index e8d74d4f62aa..3e3d846400b4 100644 --- a/arch/s390/boot/kaslr.c +++ b/arch/s390/boot/kaslr.c @@ -132,7 +132,7 @@ static unsigned long count_valid_kernel_positions(unsigned long kernel_size, unsigned long start, end, pos = 0; int i; - for_each_mem_detect_block(i, &start, &end) { + for_each_mem_detect_usable_block(i, &start, &end) { if (_min >= end) continue; if (start >= _max) @@ -153,7 +153,7 @@ static unsigned long position_to_address(unsigned long pos, unsigned long kernel unsigned long start, end; int i; - for_each_mem_detect_block(i, &start, &end) { + for_each_mem_detect_usable_block(i, &start, &end) { if (_min >= end) continue; if (start >= _max) @@ -172,26 +172,20 @@ static unsigned long position_to_address(unsigned long pos, unsigned long kernel unsigned long get_random_base(unsigned long safe_addr) { + unsigned long usable_total = get_mem_detect_usable_total(); unsigned long memory_limit = get_mem_detect_end(); unsigned long base_pos, max_pos, kernel_size; - unsigned long kasan_needs; int i; - memory_limit = min(memory_limit, ident_map_size); - /* * Avoid putting kernel in the end of physical memory - * which kasan will use for shadow memory and early pgtable - * mapping allocations. + * which vmem and kasan code will use for shadow memory and + * pgtable mapping allocations. */ - memory_limit -= kasan_estimate_memory_needs(memory_limit); + memory_limit -= kasan_estimate_memory_needs(usable_total); + memory_limit -= vmem_estimate_memory_needs(usable_total); - if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && initrd_data.start && initrd_data.size) { - if (safe_addr < initrd_data.start + initrd_data.size) - safe_addr = initrd_data.start + initrd_data.size; - } safe_addr = ALIGN(safe_addr, THREAD_SIZE); - kernel_size = vmlinux.image_size + vmlinux.bss_size; if (safe_addr + kernel_size > memory_limit) return 0; diff --git a/arch/s390/boot/mem_detect.c b/arch/s390/boot/mem_detect.c index 7fa1a32ea0f3..35f4ba11f7fd 100644 --- a/arch/s390/boot/mem_detect.c +++ b/arch/s390/boot/mem_detect.c @@ -16,29 +16,10 @@ struct mem_detect_info __bootdata(mem_detect); #define ENTRIES_EXTENDED_MAX \ (256 * (1020 / 2) * sizeof(struct mem_detect_block)) -/* - * To avoid corrupting old kernel memory during dump, find lowest memory - * chunk possible either right after the kernel end (decompressed kernel) or - * after initrd (if it is present and there is no hole between the kernel end - * and initrd) - */ -static void *mem_detect_alloc_extended(void) -{ - unsigned long offset = ALIGN(mem_safe_offset(), sizeof(u64)); - - if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && initrd_data.start && initrd_data.size && - initrd_data.start < offset + ENTRIES_EXTENDED_MAX) - offset = ALIGN(initrd_data.start + initrd_data.size, sizeof(u64)); - - return (void *)offset; -} - static struct mem_detect_block *__get_mem_detect_block_ptr(u32 n) { if (n < MEM_INLINED_ENTRIES) return &mem_detect.entries[n]; - if (unlikely(!mem_detect.entries_extended)) - mem_detect.entries_extended = mem_detect_alloc_extended(); return &mem_detect.entries_extended[n - MEM_INLINED_ENTRIES]; } @@ -147,7 +128,7 @@ static int tprot(unsigned long addr) return rc; } -static void search_mem_end(void) +static unsigned long search_mem_end(void) { unsigned long range = 1 << (MAX_PHYSMEM_BITS - 20); /* in 1MB blocks */ unsigned long offset = 0; @@ -159,33 +140,52 @@ static void search_mem_end(void) if (!tprot(pivot << 20)) offset = pivot; } - - add_mem_detect_block(0, (offset + 1) << 20); + return (offset + 1) << 20; } -unsigned long detect_memory(void) +unsigned long detect_memory(unsigned long *safe_addr) { - unsigned long max_physmem_end; + unsigned long max_physmem_end = 0; sclp_early_get_memsize(&max_physmem_end); + mem_detect.entries_extended = (struct mem_detect_block *)ALIGN(*safe_addr, sizeof(u64)); if (!sclp_early_read_storage_info()) { mem_detect.info_source = MEM_DETECT_SCLP_STOR_INFO; - return max_physmem_end; - } - - if (!diag260()) { + } else if (!diag260()) { mem_detect.info_source = MEM_DETECT_DIAG260; - return max_physmem_end; - } - - if (max_physmem_end) { + max_physmem_end = max_physmem_end ?: get_mem_detect_end(); + } else if (max_physmem_end) { add_mem_detect_block(0, max_physmem_end); mem_detect.info_source = MEM_DETECT_SCLP_READ_INFO; - return max_physmem_end; + } else { + max_physmem_end = search_mem_end(); + add_mem_detect_block(0, max_physmem_end); + mem_detect.info_source = MEM_DETECT_BIN_SEARCH; } - search_mem_end(); - mem_detect.info_source = MEM_DETECT_BIN_SEARCH; - return get_mem_detect_end(); + if (mem_detect.count > MEM_INLINED_ENTRIES) { + *safe_addr += (mem_detect.count - MEM_INLINED_ENTRIES) * + sizeof(struct mem_detect_block); + } + + return max_physmem_end; +} + +void mem_detect_set_usable_limit(unsigned long limit) +{ + struct mem_detect_block *block; + int i; + + /* make sure mem_detect.usable ends up within online memory block */ + for (i = 0; i < mem_detect.count; i++) { + block = __get_mem_detect_block_ptr(i); + if (block->start >= limit) + break; + if (block->end >= limit) { + mem_detect.usable = limit; + break; + } + mem_detect.usable = block->end; + } } diff --git a/arch/s390/boot/startup.c b/arch/s390/boot/startup.c index 47ca3264c023..11413f0baabc 100644 --- a/arch/s390/boot/startup.c +++ b/arch/s390/boot/startup.c @@ -3,6 +3,7 @@ #include <linux/elf.h> #include <asm/boot_data.h> #include <asm/sections.h> +#include <asm/maccess.h> #include <asm/cpu_mf.h> #include <asm/setup.h> #include <asm/kasan.h> @@ -11,6 +12,7 @@ #include <asm/diag.h> #include <asm/uv.h> #include <asm/abs_lowcore.h> +#include <asm/mem_detect.h> #include "decompressor.h" #include "boot.h" #include "uv.h" @@ -18,6 +20,7 @@ unsigned long __bootdata_preserved(__kaslr_offset); unsigned long __bootdata_preserved(__abs_lowcore); unsigned long __bootdata_preserved(__memcpy_real_area); +pte_t *__bootdata_preserved(memcpy_real_ptep); unsigned long __bootdata(__amode31_base); unsigned long __bootdata_preserved(VMALLOC_START); unsigned long __bootdata_preserved(VMALLOC_END); @@ -33,6 +36,8 @@ u64 __bootdata_preserved(stfle_fac_list[16]); u64 __bootdata_preserved(alt_stfle_fac_list[16]); struct oldmem_data __bootdata_preserved(oldmem_data); +struct machine_info machine; + void error(char *x) { sclp_early_printk("\n\n"); @@ -42,6 +47,20 @@ void error(char *x) disabled_wait(); } +static void detect_facilities(void) +{ + if (test_facility(8)) { + machine.has_edat1 = 1; + __ctl_set_bit(0, 23); + } + if (test_facility(78)) + machine.has_edat2 = 1; + if (!noexec_disabled && test_facility(130)) { + machine.has_nx = 1; + __ctl_set_bit(0, 20); + } +} + static void setup_lpp(void) { S390_lowcore.current_pid = 0; @@ -57,16 +76,17 @@ unsigned long mem_safe_offset(void) } #endif -static void rescue_initrd(unsigned long addr) +static unsigned long rescue_initrd(unsigned long safe_addr) { if (!IS_ENABLED(CONFIG_BLK_DEV_INITRD)) - return; + return safe_addr; if (!initrd_data.start || !initrd_data.size) - return; - if (addr <= initrd_data.start) - return; - memmove((void *)addr, (void *)initrd_data.start, initrd_data.size); - initrd_data.start = addr; + return safe_addr; + if (initrd_data.start < safe_addr) { + memmove((void *)safe_addr, (void *)initrd_data.start, initrd_data.size); + initrd_data.start = safe_addr; + } + return initrd_data.start + initrd_data.size; } static void copy_bootdata(void) @@ -150,9 +170,10 @@ static void setup_ident_map_size(unsigned long max_physmem_end) #endif } -static void setup_kernel_memory_layout(void) +static unsigned long setup_kernel_memory_layout(void) { unsigned long vmemmap_start; + unsigned long asce_limit; unsigned long rte_size; unsigned long pages; unsigned long vmax; @@ -167,10 +188,10 @@ static void setup_kernel_memory_layout(void) vmalloc_size > _REGION2_SIZE || vmemmap_start + vmemmap_size + vmalloc_size + MODULES_LEN > _REGION2_SIZE) { - vmax = _REGION1_SIZE; + asce_limit = _REGION1_SIZE; rte_size = _REGION2_SIZE; } else { - vmax = _REGION2_SIZE; + asce_limit = _REGION2_SIZE; rte_size = _REGION3_SIZE; } /* @@ -178,7 +199,7 @@ static void setup_kernel_memory_layout(void) * secure storage limit, so that any vmalloc allocation * we do could be used to back secure guest storage. */ - vmax = adjust_to_uv_max(vmax); + vmax = adjust_to_uv_max(asce_limit); #ifdef CONFIG_KASAN /* force vmalloc and modules below kasan shadow */ vmax = min(vmax, KASAN_SHADOW_START); @@ -207,6 +228,8 @@ static void setup_kernel_memory_layout(void) /* make sure vmemmap doesn't overlay with vmalloc area */ VMALLOC_START = max(vmemmap_start + vmemmap_size, VMALLOC_START); vmemmap = (struct page *)vmemmap_start; + + return asce_limit; } /* @@ -240,19 +263,25 @@ static void offset_vmlinux_info(unsigned long offset) vmlinux.rela_dyn_start += offset; vmlinux.rela_dyn_end += offset; vmlinux.dynsym_start += offset; + vmlinux.init_mm_off += offset; + vmlinux.swapper_pg_dir_off += offset; + vmlinux.invalid_pg_dir_off += offset; } static unsigned long reserve_amode31(unsigned long safe_addr) { __amode31_base = PAGE_ALIGN(safe_addr); - return safe_addr + vmlinux.amode31_size; + return __amode31_base + vmlinux.amode31_size; } void startup_kernel(void) { + unsigned long max_physmem_end; unsigned long random_lma; unsigned long safe_addr; + unsigned long asce_limit; void *img; + psw_t psw; initrd_data.start = parmarea.initrd_start; initrd_data.size = parmarea.initrd_size; @@ -265,14 +294,17 @@ void startup_kernel(void) safe_addr = reserve_amode31(safe_addr); safe_addr = read_ipl_report(safe_addr); uv_query_info(); - rescue_initrd(safe_addr); + safe_addr = rescue_initrd(safe_addr); sclp_early_read_info(); setup_boot_command_line(); parse_boot_command_line(); + detect_facilities(); sanitize_prot_virt_host(); - setup_ident_map_size(detect_memory()); + max_physmem_end = detect_memory(&safe_addr); + setup_ident_map_size(max_physmem_end); setup_vmalloc_size(); - setup_kernel_memory_layout(); + asce_limit = setup_kernel_memory_layout(); + mem_detect_set_usable_limit(ident_map_size); if (IS_ENABLED(CONFIG_RANDOMIZE_BASE) && kaslr_enabled) { random_lma = get_random_base(safe_addr); @@ -289,9 +321,23 @@ void startup_kernel(void) } else if (__kaslr_offset) memcpy((void *)vmlinux.default_lma, img, vmlinux.image_size); + /* + * The order of the following operations is important: + * + * - handle_relocs() must follow clear_bss_section() to establish static + * memory references to data in .bss to be used by setup_vmem() + * (i.e init_mm.pgd) + * + * - setup_vmem() must follow handle_relocs() to be able using + * static memory references to data in .bss (i.e init_mm.pgd) + * + * - copy_bootdata() must follow setup_vmem() to propagate changes to + * bootdata made by setup_vmem() + */ clear_bss_section(); - copy_bootdata(); handle_relocs(__kaslr_offset); + setup_vmem(asce_limit); + copy_bootdata(); if (__kaslr_offset) { /* @@ -303,5 +349,11 @@ void startup_kernel(void) if (IS_ENABLED(CONFIG_KERNEL_UNCOMPRESSED)) memset(img, 0, vmlinux.image_size); } - vmlinux.entry(); + + /* + * Jump to the decompressed kernel entry point and switch DAT mode on. + */ + psw.addr = vmlinux.entry; + psw.mask = PSW_KERNEL_BITS; + __load_psw(psw); } diff --git a/arch/s390/boot/vmem.c b/arch/s390/boot/vmem.c new file mode 100644 index 000000000000..4d1d0d8e99cb --- /dev/null +++ b/arch/s390/boot/vmem.c @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/sched/task.h> +#include <linux/pgtable.h> +#include <asm/pgalloc.h> +#include <asm/facility.h> +#include <asm/sections.h> +#include <asm/mem_detect.h> +#include <asm/maccess.h> +#include <asm/abs_lowcore.h> +#include "decompressor.h" +#include "boot.h" + +#define init_mm (*(struct mm_struct *)vmlinux.init_mm_off) +#define swapper_pg_dir vmlinux.swapper_pg_dir_off +#define invalid_pg_dir vmlinux.invalid_pg_dir_off + +/* + * Mimic virt_to_kpte() in lack of init_mm symbol. Skip pmd NULL check though. + */ +static inline pte_t *__virt_to_kpte(unsigned long va) +{ + return pte_offset_kernel(pmd_offset(pud_offset(p4d_offset(pgd_offset_k(va), va), va), va), va); +} + +unsigned long __bootdata_preserved(s390_invalid_asce); +unsigned long __bootdata(pgalloc_pos); +unsigned long __bootdata(pgalloc_end); +unsigned long __bootdata(pgalloc_low); + +enum populate_mode { + POPULATE_NONE, + POPULATE_ONE2ONE, + POPULATE_ABS_LOWCORE, +}; + +static void boot_check_oom(void) +{ + if (pgalloc_pos < pgalloc_low) + error("out of memory on boot\n"); +} + +static void pgtable_populate_init(void) +{ + unsigned long initrd_end; + unsigned long kernel_end; + + kernel_end = vmlinux.default_lma + vmlinux.image_size + vmlinux.bss_size; + pgalloc_low = round_up(kernel_end, PAGE_SIZE); + if (IS_ENABLED(CONFIG_BLK_DEV_INITRD)) { + initrd_end = round_up(initrd_data.start + initrd_data.size, _SEGMENT_SIZE); + pgalloc_low = max(pgalloc_low, initrd_end); + } + + pgalloc_end = round_down(get_mem_detect_end(), PAGE_SIZE); + pgalloc_pos = pgalloc_end; + + boot_check_oom(); +} + +static void *boot_alloc_pages(unsigned int order) +{ + unsigned long size = PAGE_SIZE << order; + + pgalloc_pos -= size; + pgalloc_pos = round_down(pgalloc_pos, size); + + boot_check_oom(); + + return (void *)pgalloc_pos; +} + +static void *boot_crst_alloc(unsigned long val) +{ + unsigned long *table; + + table = boot_alloc_pages(CRST_ALLOC_ORDER); + if (table) + crst_table_init(table, val); + return table; +} + +static pte_t *boot_pte_alloc(void) +{ + static void *pte_leftover; + pte_t *pte; + + BUILD_BUG_ON(_PAGE_TABLE_SIZE * 2 != PAGE_SIZE); + + if (!pte_leftover) { + pte_leftover = boot_alloc_pages(0); + pte = pte_leftover + _PAGE_TABLE_SIZE; + } else { + pte = pte_leftover; + pte_leftover = NULL; + } + memset64((u64 *)pte, _PAGE_INVALID, PTRS_PER_PTE); + return pte; +} + +static unsigned long _pa(unsigned long addr, enum populate_mode mode) +{ + switch (mode) { + case POPULATE_NONE: + return -1; + case POPULATE_ONE2ONE: + return addr; + case POPULATE_ABS_LOWCORE: + return __abs_lowcore_pa(addr); + default: + return -1; + } +} + +static bool can_large_pud(pud_t *pu_dir, unsigned long addr, unsigned long end) +{ + return machine.has_edat2 && + IS_ALIGNED(addr, PUD_SIZE) && (end - addr) >= PUD_SIZE; +} + +static bool can_large_pmd(pmd_t *pm_dir, unsigned long addr, unsigned long end) +{ + return machine.has_edat1 && + IS_ALIGNED(addr, PMD_SIZE) && (end - addr) >= PMD_SIZE; +} + +static void pgtable_pte_populate(pmd_t *pmd, unsigned long addr, unsigned long end, + enum populate_mode mode) +{ + unsigned long next; + pte_t *pte, entry; + + pte = pte_offset_kernel(pmd, addr); + for (; addr < end; addr += PAGE_SIZE, pte++) { + if (pte_none(*pte)) { + entry = __pte(_pa(addr, mode)); + entry = set_pte_bit(entry, PAGE_KERNEL_EXEC); + set_pte(pte, entry); + } + } +} + +static void pgtable_pmd_populate(pud_t *pud, unsigned long addr, unsigned long end, + enum populate_mode mode) +{ + unsigned long next; + pmd_t *pmd, entry; + pte_t *pte; + + pmd = pmd_offset(pud, addr); + for (; addr < end; addr = next, pmd++) { + next = pmd_addr_end(addr, end); + if (pmd_none(*pmd)) { + if (can_large_pmd(pmd, addr, next)) { + entry = __pmd(_pa(addr, mode)); + entry = set_pmd_bit(entry, SEGMENT_KERNEL_EXEC); + set_pmd(pmd, entry); + continue; + } + pte = boot_pte_alloc(); + pmd_populate(&init_mm, pmd, pte); + } else if (pmd_large(*pmd)) { + continue; + } + pgtable_pte_populate(pmd, addr, next, mode); + } +} + +static void pgtable_pud_populate(p4d_t *p4d, unsigned long addr, unsigned long end, + enum populate_mode mode) +{ + unsigned long next; + pud_t *pud, entry; + pmd_t *pmd; + + pud = pud_offset(p4d, addr); + for (; addr < end; addr = next, pud++) { + next = pud_addr_end(addr, end); + if (pud_none(*pud)) { + if (can_large_pud(pud, addr, next)) { + entry = __pud(_pa(addr, mode)); + entry = set_pud_bit(entry, REGION3_KERNEL_EXEC); + set_pud(pud, entry); + continue; + } + pmd = boot_crst_alloc(_SEGMENT_ENTRY_EMPTY); + pud_populate(&init_mm, pud, pmd); + } else if (pud_large(*pud)) { + continue; + } + pgtable_pmd_populate(pud, addr, next, mode); + } +} + +static void pgtable_p4d_populate(pgd_t *pgd, unsigned long addr, unsigned long end, + enum populate_mode mode) +{ + unsigned long next; + p4d_t *p4d; + pud_t *pud; + + p4d = p4d_offset(pgd, addr); + for (; addr < end; addr = next, p4d++) { + next = p4d_addr_end(addr, end); + if (p4d_none(*p4d)) { + pud = boot_crst_alloc(_REGION3_ENTRY_EMPTY); + p4d_populate(&init_mm, p4d, pud); + } + pgtable_pud_populate(p4d, addr, next, mode); + } +} + +static void pgtable_populate(unsigned long addr, unsigned long end, enum populate_mode mode) +{ + unsigned long next; + pgd_t *pgd; + p4d_t *p4d; + + pgd = pgd_offset(&init_mm, addr); + for (; addr < end; addr = next, pgd++) { + next = pgd_addr_end(addr, end); + if (pgd_none(*pgd)) { + p4d = boot_crst_alloc(_REGION2_ENTRY_EMPTY); + pgd_populate(&init_mm, pgd, p4d); + } + pgtable_p4d_populate(pgd, addr, next, mode); + } +} + +void setup_vmem(unsigned long asce_limit) +{ + unsigned long start, end; + unsigned long asce_type; + unsigned long asce_bits; + int i; + + if (asce_limit == _REGION1_SIZE) { + asce_type = _REGION2_ENTRY_EMPTY; + asce_bits = _ASCE_TYPE_REGION2 | _ASCE_TABLE_LENGTH; + } else { + asce_type = _REGION3_ENTRY_EMPTY; + asce_bits = _ASCE_TYPE_REGION3 | _ASCE_TABLE_LENGTH; + } + s390_invalid_asce = invalid_pg_dir | _ASCE_TYPE_REGION3 | _ASCE_TABLE_LENGTH; + + crst_table_init((unsigned long *)swapper_pg_dir, asce_type); + crst_table_init((unsigned long *)invalid_pg_dir, _REGION3_ENTRY_EMPTY); + + /* + * To allow prefixing the lowcore must be mapped with 4KB pages. + * To prevent creation of a large page at address 0 first map + * the lowcore and create the identity mapping only afterwards. + */ + pgtable_populate_init(); + pgtable_populate(0, sizeof(struct lowcore), POPULATE_ONE2ONE); + for_each_mem_detect_usable_block(i, &start, &end) + pgtable_populate(start, end, POPULATE_ONE2ONE); + pgtable_populate(__abs_lowcore, __abs_lowcore + sizeof(struct lowcore), + POPULATE_ABS_LOWCORE); + pgtable_populate(__memcpy_real_area, __memcpy_real_area + PAGE_SIZE, + POPULATE_NONE); + memcpy_real_ptep = __virt_to_kpte(__memcpy_real_area); + + S390_lowcore.kernel_asce = swapper_pg_dir | asce_bits; + S390_lowcore.user_asce = s390_invalid_asce; + + __ctl_load(S390_lowcore.kernel_asce, 1, 1); + __ctl_load(S390_lowcore.user_asce, 7, 7); + __ctl_load(S390_lowcore.kernel_asce, 13, 13); + + init_mm.context.asce = S390_lowcore.kernel_asce; +} + +unsigned long vmem_estimate_memory_needs(unsigned long online_mem_total) +{ + unsigned long pages = DIV_ROUND_UP(online_mem_total, PAGE_SIZE); + + return DIV_ROUND_UP(pages, _PAGE_ENTRIES) * _PAGE_TABLE_SIZE * 2; +} diff --git a/arch/s390/crypto/arch_random.c b/arch/s390/crypto/arch_random.c index 1f2d40993c4d..a8a2407381af 100644 --- a/arch/s390/crypto/arch_random.c +++ b/arch/s390/crypto/arch_random.c @@ -10,6 +10,7 @@ #include <linux/atomic.h> #include <linux/random.h> #include <linux/static_key.h> +#include <asm/archrandom.h> #include <asm/cpacf.h> DEFINE_STATIC_KEY_FALSE(s390_arch_random_available); diff --git a/arch/s390/include/asm/abs_lowcore.h b/arch/s390/include/asm/abs_lowcore.h index 4c61b14ee928..6f264b79e377 100644 --- a/arch/s390/include/asm/abs_lowcore.h +++ b/arch/s390/include/asm/abs_lowcore.h @@ -7,11 +7,21 @@ #define ABS_LOWCORE_MAP_SIZE (NR_CPUS * sizeof(struct lowcore)) extern unsigned long __abs_lowcore; -extern bool abs_lowcore_mapped; -struct lowcore *get_abs_lowcore(unsigned long *flags); -void put_abs_lowcore(struct lowcore *lc, unsigned long flags); int abs_lowcore_map(int cpu, struct lowcore *lc, bool alloc); void abs_lowcore_unmap(int cpu); +static inline struct lowcore *get_abs_lowcore(void) +{ + int cpu; + + cpu = get_cpu(); + return ((struct lowcore *)__abs_lowcore) + cpu; +} + +static inline void put_abs_lowcore(struct lowcore *lc) +{ + put_cpu(); +} + #endif /* _ASM_S390_ABS_LOWCORE_H */ diff --git a/arch/s390/include/asm/ap.h b/arch/s390/include/asm/ap.h index f508f5025e38..57a2d6518d27 100644 --- a/arch/s390/include/asm/ap.h +++ b/arch/s390/include/asm/ap.h @@ -239,7 +239,10 @@ static inline struct ap_queue_status ap_aqic(ap_qid_t qid, union { unsigned long value; struct ap_qirq_ctrl qirqctrl; - struct ap_queue_status status; + struct { + u32 _pad; + struct ap_queue_status status; + }; } reg1; unsigned long reg2 = pa_ind; @@ -253,7 +256,7 @@ static inline struct ap_queue_status ap_aqic(ap_qid_t qid, " lgr %[reg1],1\n" /* gr1 (status) into reg1 */ : [reg1] "+&d" (reg1) : [reg0] "d" (reg0), [reg2] "d" (reg2) - : "cc", "0", "1", "2"); + : "cc", "memory", "0", "1", "2"); return reg1.status; } @@ -290,7 +293,10 @@ static inline struct ap_queue_status ap_qact(ap_qid_t qid, int ifbit, unsigned long reg0 = qid | (5UL << 24) | ((ifbit & 0x01) << 22); union { unsigned long value; - struct ap_queue_status status; + struct { + u32 _pad; + struct ap_queue_status status; + }; } reg1; unsigned long reg2; diff --git a/arch/s390/include/asm/asm-extable.h b/arch/s390/include/asm/asm-extable.h index b74f1070ddb2..55a02a153dfc 100644 --- a/arch/s390/include/asm/asm-extable.h +++ b/arch/s390/include/asm/asm-extable.h @@ -12,6 +12,7 @@ #define EX_TYPE_UA_STORE 3 #define EX_TYPE_UA_LOAD_MEM 4 #define EX_TYPE_UA_LOAD_REG 5 +#define EX_TYPE_UA_LOAD_REGPAIR 6 #define EX_DATA_REG_ERR_SHIFT 0 #define EX_DATA_REG_ERR GENMASK(3, 0) @@ -85,4 +86,7 @@ #define EX_TABLE_UA_LOAD_REG(_fault, _target, _regerr, _regzero) \ __EX_TABLE_UA(__ex_table, _fault, _target, EX_TYPE_UA_LOAD_REG, _regerr, _regzero, 0) +#define EX_TABLE_UA_LOAD_REGPAIR(_fault, _target, _regerr, _regzero) \ + __EX_TABLE_UA(__ex_table, _fault, _target, EX_TYPE_UA_LOAD_REGPAIR, _regerr, _regzero, 0) + #endif /* __ASM_EXTABLE_H */ diff --git a/arch/s390/include/asm/ccwdev.h b/arch/s390/include/asm/ccwdev.h index bd1596810cc1..91d261751d25 100644 --- a/arch/s390/include/asm/ccwdev.h +++ b/arch/s390/include/asm/ccwdev.h @@ -15,6 +15,7 @@ #include <asm/fcx.h> #include <asm/irq.h> #include <asm/schid.h> +#include <linux/mutex.h> /* structs from asm/cio.h */ struct irb; @@ -87,6 +88,7 @@ struct ccw_device { spinlock_t *ccwlock; /* private: */ struct ccw_device_private *private; /* cio private information */ + struct mutex reg_mutex; /* public: */ struct ccw_device_id id; struct ccw_driver *drv; diff --git a/arch/s390/include/asm/cmpxchg.h b/arch/s390/include/asm/cmpxchg.h index 84c3f0d576c5..3f26416c2ad8 100644 --- a/arch/s390/include/asm/cmpxchg.h +++ b/arch/s390/include/asm/cmpxchg.h @@ -88,67 +88,90 @@ static __always_inline unsigned long __cmpxchg(unsigned long address, unsigned long old, unsigned long new, int size) { - unsigned long prev, tmp; - int shift; - switch (size) { - case 1: + case 1: { + unsigned int prev, shift, mask; + shift = (3 ^ (address & 3)) << 3; address ^= address & 3; + old = (old & 0xff) << shift; + new = (new & 0xff) << shift; + mask = ~(0xff << shift); asm volatile( - " l %0,%2\n" - "0: nr %0,%5\n" - " lr %1,%0\n" - " or %0,%3\n" - " or %1,%4\n" - " cs %0,%1,%2\n" - " jnl 1f\n" - " xr %1,%0\n" - " nr %1,%5\n" - " jnz 0b\n" + " l %[prev],%[address]\n" + " nr %[prev],%[mask]\n" + " xilf %[mask],0xffffffff\n" + " or %[new],%[prev]\n" + " or %[prev],%[tmp]\n" + "0: lr %[tmp],%[prev]\n" + " cs %[prev],%[new],%[address]\n" + " jnl 1f\n" + " xr %[tmp],%[prev]\n" + " xr %[new],%[tmp]\n" + " nr %[tmp],%[mask]\n" + " jz 0b\n" "1:" - : "=&d" (prev), "=&d" (tmp), "+Q" (*(int *) address) - : "d" ((old & 0xff) << shift), - "d" ((new & 0xff) << shift), - "d" (~(0xff << shift)) - : "memory", "cc"); + : [prev] "=&d" (prev), + [address] "+Q" (*(int *)address), + [tmp] "+&d" (old), + [new] "+&d" (new), + [mask] "+&d" (mask) + :: "memory", "cc"); return prev >> shift; - case 2: + } + case 2: { + unsigned int prev, shift, mask; + shift = (2 ^ (address & 2)) << 3; address ^= address & 2; + old = (old & 0xffff) << shift; + new = (new & 0xffff) << shift; + mask = ~(0xffff << shift); asm volatile( - " l %0,%2\n" - "0: nr %0,%5\n" - " lr %1,%0\n" - " or %0,%3\n" - " or %1,%4\n" - " cs %0,%1,%2\n" - " jnl 1f\n" - " xr %1,%0\n" - " nr %1,%5\n" - " jnz 0b\n" + " l %[prev],%[address]\n" + " nr %[prev],%[mask]\n" + " xilf %[mask],0xffffffff\n" + " or %[new],%[prev]\n" + " or %[prev],%[tmp]\n" + "0: lr %[tmp],%[prev]\n" + " cs %[prev],%[new],%[address]\n" + " jnl 1f\n" + " xr %[tmp],%[prev]\n" + " xr %[new],%[tmp]\n" + " nr %[tmp],%[mask]\n" + " jz 0b\n" "1:" - : "=&d" (prev), "=&d" (tmp), "+Q" (*(int *) address) - : "d" ((old & 0xffff) << shift), - "d" ((new & 0xffff) << shift), - "d" (~(0xffff << shift)) - : "memory", "cc"); + : [prev] "=&d" (prev), + [address] "+Q" (*(int *)address), + [tmp] "+&d" (old), + [new] "+&d" (new), + [mask] "+&d" (mask) + :: "memory", "cc"); return prev >> shift; - case 4: + } + case 4: { + unsigned int prev = old; + asm volatile( - " cs %0,%3,%1\n" - : "=&d" (prev), "+Q" (*(int *) address) - : "0" (old), "d" (new) + " cs %[prev],%[new],%[address]\n" + : [prev] "+&d" (prev), + [address] "+Q" (*(int *)address) + : [new] "d" (new) : "memory", "cc"); return prev; - case 8: + } + case 8: { + unsigned long prev = old; + asm volatile( - " csg %0,%3,%1\n" - : "=&d" (prev), "+QS" (*(long *) address) - : "0" (old), "d" (new) + " csg %[prev],%[new],%[address]\n" + : [prev] "+&d" (prev), + [address] "+QS" (*(long *)address) + : [new] "d" (new) : "memory", "cc"); return prev; } + } __cmpxchg_called_with_bad_pointer(); return old; } diff --git a/arch/s390/include/asm/cpu_mcf.h b/arch/s390/include/asm/cpu_mcf.h deleted file mode 100644 index f87a4788c19c..000000000000 --- a/arch/s390/include/asm/cpu_mcf.h +++ /dev/null @@ -1,112 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Counter facility support definitions for the Linux perf - * - * Copyright IBM Corp. 2019 - * Author(s): Hendrik Brueckner <brueckner@linux.ibm.com> - */ -#ifndef _ASM_S390_CPU_MCF_H -#define _ASM_S390_CPU_MCF_H - -#include <linux/perf_event.h> -#include <asm/cpu_mf.h> - -enum cpumf_ctr_set { - CPUMF_CTR_SET_BASIC = 0, /* Basic Counter Set */ - CPUMF_CTR_SET_USER = 1, /* Problem-State Counter Set */ - CPUMF_CTR_SET_CRYPTO = 2, /* Crypto-Activity Counter Set */ - CPUMF_CTR_SET_EXT = 3, /* Extended Counter Set */ - CPUMF_CTR_SET_MT_DIAG = 4, /* MT-diagnostic Counter Set */ - - /* Maximum number of counter sets */ - CPUMF_CTR_SET_MAX, -}; - -#define CPUMF_LCCTL_ENABLE_SHIFT 16 -#define CPUMF_LCCTL_ACTCTL_SHIFT 0 - -static inline void ctr_set_enable(u64 *state, u64 ctrsets) -{ - *state |= ctrsets << CPUMF_LCCTL_ENABLE_SHIFT; -} - -static inline void ctr_set_disable(u64 *state, u64 ctrsets) -{ - *state &= ~(ctrsets << CPUMF_LCCTL_ENABLE_SHIFT); -} - -static inline void ctr_set_start(u64 *state, u64 ctrsets) -{ - *state |= ctrsets << CPUMF_LCCTL_ACTCTL_SHIFT; -} - -static inline void ctr_set_stop(u64 *state, u64 ctrsets) -{ - *state &= ~(ctrsets << CPUMF_LCCTL_ACTCTL_SHIFT); -} - -static inline int ctr_stcctm(enum cpumf_ctr_set set, u64 range, u64 *dest) -{ - switch (set) { - case CPUMF_CTR_SET_BASIC: - return stcctm(BASIC, range, dest); - case CPUMF_CTR_SET_USER: - return stcctm(PROBLEM_STATE, range, dest); - case CPUMF_CTR_SET_CRYPTO: - return stcctm(CRYPTO_ACTIVITY, range, dest); - case CPUMF_CTR_SET_EXT: - return stcctm(EXTENDED, range, dest); - case CPUMF_CTR_SET_MT_DIAG: - return stcctm(MT_DIAG_CLEARING, range, dest); - case CPUMF_CTR_SET_MAX: - return 3; - } - return 3; -} - -struct cpu_cf_events { - struct cpumf_ctr_info info; - atomic_t ctr_set[CPUMF_CTR_SET_MAX]; - atomic64_t alert; - u64 state; /* For perf_event_open SVC */ - u64 dev_state; /* For /dev/hwctr */ - unsigned int flags; - size_t used; /* Bytes used in data */ - size_t usedss; /* Bytes used in start/stop */ - unsigned char start[PAGE_SIZE]; /* Counter set at event add */ - unsigned char stop[PAGE_SIZE]; /* Counter set at event delete */ - unsigned char data[PAGE_SIZE]; /* Counter set at /dev/hwctr */ - unsigned int sets; /* # Counter set saved in memory */ -}; -DECLARE_PER_CPU(struct cpu_cf_events, cpu_cf_events); - -bool kernel_cpumcf_avail(void); -int __kernel_cpumcf_begin(void); -unsigned long kernel_cpumcf_alert(int clear); -void __kernel_cpumcf_end(void); - -static inline int kernel_cpumcf_begin(void) -{ - if (!cpum_cf_avail()) - return -ENODEV; - - preempt_disable(); - return __kernel_cpumcf_begin(); -} -static inline void kernel_cpumcf_end(void) -{ - __kernel_cpumcf_end(); - preempt_enable(); -} - -/* Return true if store counter set multiple instruction is available */ -static inline int stccm_avail(void) -{ - return test_facility(142); -} - -size_t cpum_cf_ctrset_size(enum cpumf_ctr_set ctrset, - struct cpumf_ctr_info *info); -int cfset_online_cpu(unsigned int cpu); -int cfset_offline_cpu(unsigned int cpu); -#endif /* _ASM_S390_CPU_MCF_H */ diff --git a/arch/s390/include/asm/cpu_mf.h b/arch/s390/include/asm/cpu_mf.h index efa103b52a1a..7e417d7de568 100644 --- a/arch/s390/include/asm/cpu_mf.h +++ b/arch/s390/include/asm/cpu_mf.h @@ -42,7 +42,6 @@ static inline int cpum_sf_avail(void) return test_facility(40) && test_facility(68); } - struct cpumf_ctr_info { u16 cfvn; u16 auth_ctl; @@ -275,56 +274,4 @@ static inline int lsctl(struct hws_lsctl_request_block *req) return cc ? -EINVAL : 0; } - -/* Sampling control helper functions */ - -#include <linux/time.h> - -static inline unsigned long freq_to_sample_rate(struct hws_qsi_info_block *qsi, - unsigned long freq) -{ - return (USEC_PER_SEC / freq) * qsi->cpu_speed; -} - -static inline unsigned long sample_rate_to_freq(struct hws_qsi_info_block *qsi, - unsigned long rate) -{ - return USEC_PER_SEC * qsi->cpu_speed / rate; -} - -/* Return TOD timestamp contained in an trailer entry */ -static inline unsigned long long trailer_timestamp(struct hws_trailer_entry *te) -{ - /* TOD in STCKE format */ - if (te->header.t) - return *((unsigned long long *) &te->timestamp[1]); - - /* TOD in STCK format */ - return *((unsigned long long *) &te->timestamp[0]); -} - -/* Return pointer to trailer entry of an sample data block */ -static inline unsigned long *trailer_entry_ptr(unsigned long v) -{ - void *ret; - - ret = (void *) v; - ret += PAGE_SIZE; - ret -= sizeof(struct hws_trailer_entry); - - return (unsigned long *) ret; -} - -/* Return true if the entry in the sample data block table (sdbt) - * is a link to the next sdbt */ -static inline int is_link_entry(unsigned long *s) -{ - return *s & 0x1ul ? 1 : 0; -} - -/* Return pointer to the linked sdbt */ -static inline unsigned long *get_next_sdbt(unsigned long *s) -{ - return (unsigned long *) (*s & ~0x1ul); -} #endif /* _ASM_S390_CPU_MF_H */ diff --git a/arch/s390/include/asm/cputime.h b/arch/s390/include/asm/cputime.h index 1d389847b588..30bb3ec4e5fc 100644 --- a/arch/s390/include/asm/cputime.h +++ b/arch/s390/include/asm/cputime.h @@ -11,30 +11,11 @@ #include <linux/types.h> #include <asm/timex.h> -#define CPUTIME_PER_USEC 4096ULL -#define CPUTIME_PER_SEC (CPUTIME_PER_USEC * USEC_PER_SEC) - -/* We want to use full resolution of the CPU timer: 2**-12 micro-seconds. */ - -#define cmpxchg_cputime(ptr, old, new) cmpxchg64(ptr, old, new) - -/* - * Convert cputime to microseconds. - */ -static inline u64 cputime_to_usecs(const u64 cputime) -{ - return cputime >> 12; -} - /* * Convert cputime to nanoseconds. */ #define cputime_to_nsecs(cputime) tod_to_ns(cputime) -u64 arch_cpu_idle_time(int cpu); - -#define arch_idle_time(cpu) arch_cpu_idle_time(cpu) - void account_idle_time_irq(void); #endif /* _S390_CPUTIME_H */ diff --git a/arch/s390/include/asm/diag.h b/arch/s390/include/asm/diag.h index 56e99c286d12..674a939f16ee 100644 --- a/arch/s390/include/asm/diag.h +++ b/arch/s390/include/asm/diag.h @@ -12,6 +12,7 @@ #include <linux/if_ether.h> #include <linux/percpu.h> #include <asm/asm-extable.h> +#include <asm/cio.h> enum diag_stat_enum { DIAG_STAT_X008, @@ -20,6 +21,7 @@ enum diag_stat_enum { DIAG_STAT_X014, DIAG_STAT_X044, DIAG_STAT_X064, + DIAG_STAT_X08C, DIAG_STAT_X09C, DIAG_STAT_X0DC, DIAG_STAT_X204, @@ -79,10 +81,20 @@ struct diag210 { u8 vrdccrty; /* real device type (output) */ u8 vrdccrmd; /* real device model (output) */ u8 vrdccrft; /* real device feature (output) */ -} __attribute__((packed, aligned(4))); +} __packed __aligned(4); extern int diag210(struct diag210 *addr); +struct diag8c { + u8 flags; + u8 num_partitions; + u16 width; + u16 height; + u8 data[0]; +} __packed __aligned(4); + +extern int diag8c(struct diag8c *out, struct ccw_dev_id *devno); + /* bit is set in flags, when physical cpu info is included in diag 204 data */ #define DIAG204_LPAR_PHYS_FLG 0x80 #define DIAG204_LPAR_NAME_LEN 8 /* lpar name len in diag 204 data */ @@ -318,6 +330,7 @@ struct diag_ops { int (*diag210)(struct diag210 *addr); int (*diag26c)(void *req, void *resp, enum diag26c_sc subcode); int (*diag14)(unsigned long rx, unsigned long ry1, unsigned long subcode); + int (*diag8c)(struct diag8c *addr, struct ccw_dev_id *devno, size_t len); void (*diag0c)(struct hypfs_diag0c_entry *entry); void (*diag308_reset)(void); }; @@ -330,5 +343,6 @@ int _diag26c_amode31(void *req, void *resp, enum diag26c_sc subcode); int _diag14_amode31(unsigned long rx, unsigned long ry1, unsigned long subcode); void _diag0c_amode31(struct hypfs_diag0c_entry *entry); void _diag308_reset_amode31(void); +int _diag8c_amode31(struct diag8c *addr, struct ccw_dev_id *devno, size_t len); #endif /* _ASM_S390_DIAG_H */ diff --git a/arch/s390/include/asm/fpu/internal.h b/arch/s390/include/asm/fpu/internal.h index 4a71dbbf76fb..bbdadb1c9efc 100644 --- a/arch/s390/include/asm/fpu/internal.h +++ b/arch/s390/include/asm/fpu/internal.h @@ -27,7 +27,7 @@ static inline void convert_vx_to_fp(freg_t *fprs, __vector128 *vxrs) int i; for (i = 0; i < __NUM_FPRS; i++) - fprs[i] = *(freg_t *)(vxrs + i); + fprs[i].ui = vxrs[i].high; } static inline void convert_fp_to_vx(__vector128 *vxrs, freg_t *fprs) @@ -35,7 +35,7 @@ static inline void convert_fp_to_vx(__vector128 *vxrs, freg_t *fprs) int i; for (i = 0; i < __NUM_FPRS; i++) - *(freg_t *)(vxrs + i) = fprs[i]; + vxrs[i].high = fprs[i].ui; } static inline void fpregs_store(_s390_fp_regs *fpregs, struct fpu *fpu) diff --git a/arch/s390/include/asm/idals.h b/arch/s390/include/asm/idals.h index 40eae2c08d61..59fcc3c72edf 100644 --- a/arch/s390/include/asm/idals.h +++ b/arch/s390/include/asm/idals.h @@ -23,6 +23,9 @@ #define IDA_SIZE_LOG 12 /* 11 for 2k , 12 for 4k */ #define IDA_BLOCK_SIZE (1L<<IDA_SIZE_LOG) +#define IDA_2K_SIZE_LOG 11 +#define IDA_2K_BLOCK_SIZE (1L << IDA_2K_SIZE_LOG) + /* * Test if an address/length pair needs an idal list. */ @@ -43,6 +46,15 @@ static inline unsigned int idal_nr_words(void *vaddr, unsigned int length) } /* + * Return the number of 2K IDA words needed for an address/length pair. + */ +static inline unsigned int idal_2k_nr_words(void *vaddr, unsigned int length) +{ + return ((__pa(vaddr) & (IDA_2K_BLOCK_SIZE - 1)) + length + + (IDA_2K_BLOCK_SIZE - 1)) >> IDA_2K_SIZE_LOG; +} + +/* * Create the list of idal words for an address/length pair. */ static inline unsigned long *idal_create_words(unsigned long *idaws, diff --git a/arch/s390/include/asm/idle.h b/arch/s390/include/asm/idle.h index 5cea629c548e..09f763b9eb40 100644 --- a/arch/s390/include/asm/idle.h +++ b/arch/s390/include/asm/idle.h @@ -10,16 +10,12 @@ #include <linux/types.h> #include <linux/device.h> -#include <linux/seqlock.h> struct s390_idle_data { - seqcount_t seqcount; unsigned long idle_count; unsigned long idle_time; unsigned long clock_idle_enter; - unsigned long clock_idle_exit; unsigned long timer_idle_enter; - unsigned long timer_idle_exit; unsigned long mt_cycles_enter[8]; }; @@ -27,6 +23,5 @@ extern struct device_attribute dev_attr_idle_count; extern struct device_attribute dev_attr_idle_time_us; void psw_idle(struct s390_idle_data *data, unsigned long psw_mask); -void psw_idle_exit(void); #endif /* _S390_IDLE_H */ diff --git a/arch/s390/include/asm/kasan.h b/arch/s390/include/asm/kasan.h index 2768d5db181f..e5cfc81d5b61 100644 --- a/arch/s390/include/asm/kasan.h +++ b/arch/s390/include/asm/kasan.h @@ -14,17 +14,15 @@ #define KASAN_SHADOW_END (KASAN_SHADOW_START + KASAN_SHADOW_SIZE) extern void kasan_early_init(void); -extern void kasan_copy_shadow_mapping(void); -extern void kasan_free_early_identity(void); /* * Estimate kasan memory requirements, which it will reserve * at the very end of available physical memory. To estimate * that, we take into account that kasan would require * 1/8 of available physical memory (for shadow memory) + - * creating page tables for the whole memory + shadow memory - * region (1 + 1/8). To keep page tables estimates simple take - * the double of combined ptes size. + * creating page tables for the shadow memory region. + * To keep page tables estimates simple take the double of + * combined ptes size. * * physmem parameter has to be already adjusted if not entire physical memory * would be used (e.g. due to effect of "mem=" option). @@ -36,15 +34,13 @@ static inline unsigned long kasan_estimate_memory_needs(unsigned long physmem) /* for shadow memory */ kasan_needs = round_up(physmem / 8, PAGE_SIZE); /* for paging structures */ - pages = DIV_ROUND_UP(physmem + kasan_needs, PAGE_SIZE); + pages = DIV_ROUND_UP(kasan_needs, PAGE_SIZE); kasan_needs += DIV_ROUND_UP(pages, _PAGE_ENTRIES) * _PAGE_TABLE_SIZE * 2; return kasan_needs; } #else static inline void kasan_early_init(void) { } -static inline void kasan_copy_shadow_mapping(void) { } -static inline void kasan_free_early_identity(void) { } static inline unsigned long kasan_estimate_memory_needs(unsigned long physmem) { return 0; } #endif diff --git a/arch/s390/include/asm/kprobes.h b/arch/s390/include/asm/kprobes.h index 598095f4b924..83f732ca3af4 100644 --- a/arch/s390/include/asm/kprobes.h +++ b/arch/s390/include/asm/kprobes.h @@ -70,8 +70,6 @@ struct kprobe_ctlblk { }; void arch_remove_kprobe(struct kprobe *p); -void __kretprobe_trampoline(void); -void trampoline_probe_handler(struct pt_regs *regs); int kprobe_fault_handler(struct pt_regs *regs, int trapnr); int kprobe_exceptions_notify(struct notifier_block *self, diff --git a/arch/s390/include/asm/maccess.h b/arch/s390/include/asm/maccess.h index c7fa838cf6b9..cfec3141fdba 100644 --- a/arch/s390/include/asm/maccess.h +++ b/arch/s390/include/asm/maccess.h @@ -7,7 +7,7 @@ struct iov_iter; extern unsigned long __memcpy_real_area; -void memcpy_real_init(void); +extern pte_t *memcpy_real_ptep; size_t memcpy_real_iter(struct iov_iter *iter, unsigned long src, size_t count); int memcpy_real(void *dest, unsigned long src, size_t count); #ifdef CONFIG_CRASH_DUMP diff --git a/arch/s390/include/asm/mem_detect.h b/arch/s390/include/asm/mem_detect.h index a7c922a69050..f9e7354036d2 100644 --- a/arch/s390/include/asm/mem_detect.h +++ b/arch/s390/include/asm/mem_detect.h @@ -30,6 +30,7 @@ struct mem_detect_block { struct mem_detect_info { u32 count; u8 info_source; + unsigned long usable; struct mem_detect_block entries[MEM_INLINED_ENTRIES]; struct mem_detect_block *entries_extended; }; @@ -38,7 +39,7 @@ extern struct mem_detect_info mem_detect; void add_mem_detect_block(u64 start, u64 end); static inline int __get_mem_detect_block(u32 n, unsigned long *start, - unsigned long *end) + unsigned long *end, bool respect_usable_limit) { if (n >= mem_detect.count) { *start = 0; @@ -53,21 +54,41 @@ static inline int __get_mem_detect_block(u32 n, unsigned long *start, *start = (unsigned long)mem_detect.entries_extended[n - MEM_INLINED_ENTRIES].start; *end = (unsigned long)mem_detect.entries_extended[n - MEM_INLINED_ENTRIES].end; } + + if (respect_usable_limit && mem_detect.usable) { + if (*start >= mem_detect.usable) + return -1; + if (*end > mem_detect.usable) + *end = mem_detect.usable; + } return 0; } /** - * for_each_mem_detect_block - early online memory range iterator + * for_each_mem_detect_usable_block - early online memory range iterator * @i: an integer used as loop variable * @p_start: ptr to unsigned long for start address of the range * @p_end: ptr to unsigned long for end address of the range * - * Walks over detected online memory ranges. + * Walks over detected online memory ranges below usable limit. */ -#define for_each_mem_detect_block(i, p_start, p_end) \ - for (i = 0, __get_mem_detect_block(i, p_start, p_end); \ - i < mem_detect.count; \ - i++, __get_mem_detect_block(i, p_start, p_end)) +#define for_each_mem_detect_usable_block(i, p_start, p_end) \ + for (i = 0; !__get_mem_detect_block(i, p_start, p_end, true); i++) + +/* Walks over all detected online memory ranges disregarding usable limit. */ +#define for_each_mem_detect_block(i, p_start, p_end) \ + for (i = 0; !__get_mem_detect_block(i, p_start, p_end, false); i++) + +static inline unsigned long get_mem_detect_usable_total(void) +{ + unsigned long start, end, total = 0; + int i; + + for_each_mem_detect_usable_block(i, &start, &end) + total += end - start; + + return total; +} static inline void get_mem_detect_reserved(unsigned long *start, unsigned long *size) @@ -84,8 +105,10 @@ static inline unsigned long get_mem_detect_end(void) unsigned long start; unsigned long end; + if (mem_detect.usable) + return mem_detect.usable; if (mem_detect.count) { - __get_mem_detect_block(mem_detect.count - 1, &start, &end); + __get_mem_detect_block(mem_detect.count - 1, &start, &end, false); return end; } return 0; diff --git a/arch/s390/include/asm/pgtable.h b/arch/s390/include/asm/pgtable.h index b26cbf1c533c..b87ca864d27d 100644 --- a/arch/s390/include/asm/pgtable.h +++ b/arch/s390/include/asm/pgtable.h @@ -23,6 +23,7 @@ #include <asm/uv.h> extern pgd_t swapper_pg_dir[]; +extern pgd_t invalid_pg_dir[]; extern void paging_init(void); extern unsigned long s390_invalid_asce; @@ -181,6 +182,8 @@ static inline int is_module_addr(void *addr) #define _PAGE_SOFT_DIRTY 0x000 #endif +#define _PAGE_SW_BITS 0xffUL /* All SW bits */ + #define _PAGE_SWP_EXCLUSIVE _PAGE_LARGE /* SW pte exclusive swap bit */ /* Set of bits not changed in pte_modify */ @@ -188,6 +191,12 @@ static inline int is_module_addr(void *addr) _PAGE_YOUNG | _PAGE_SOFT_DIRTY) /* + * Mask of bits that must not be changed with RDP. Allow only _PAGE_PROTECT + * HW bit and all SW bits. + */ +#define _PAGE_RDP_MASK ~(_PAGE_PROTECT | _PAGE_SW_BITS) + +/* * handle_pte_fault uses pte_present and pte_none to find out the pte type * WITHOUT holding the page table lock. The _PAGE_PRESENT bit is used to * distinguish present from not-present ptes. It is changed only with the page @@ -477,6 +486,12 @@ static inline int is_module_addr(void *addr) _REGION3_ENTRY_YOUNG | \ _REGION_ENTRY_PROTECT | \ _REGION_ENTRY_NOEXEC) +#define REGION3_KERNEL_EXEC __pgprot(_REGION_ENTRY_TYPE_R3 | \ + _REGION3_ENTRY_LARGE | \ + _REGION3_ENTRY_READ | \ + _REGION3_ENTRY_WRITE | \ + _REGION3_ENTRY_YOUNG | \ + _REGION3_ENTRY_DIRTY) static inline bool mm_p4d_folded(struct mm_struct *mm) { @@ -1045,6 +1060,19 @@ static inline pte_t pte_mkhuge(pte_t pte) #define IPTE_NODAT 0x400 #define IPTE_GUEST_ASCE 0x800 +static __always_inline void __ptep_rdp(unsigned long addr, pte_t *ptep, + unsigned long opt, unsigned long asce, + int local) +{ + unsigned long pto; + + pto = __pa(ptep) & ~(PTRS_PER_PTE * sizeof(pte_t) - 1); + asm volatile(".insn rrf,0xb98b0000,%[r1],%[r2],%[asce],%[m4]" + : "+m" (*ptep) + : [r1] "a" (pto), [r2] "a" ((addr & PAGE_MASK) | opt), + [asce] "a" (asce), [m4] "i" (local)); +} + static __always_inline void __ptep_ipte(unsigned long address, pte_t *ptep, unsigned long opt, unsigned long asce, int local) @@ -1195,6 +1223,42 @@ static inline void ptep_set_wrprotect(struct mm_struct *mm, ptep_xchg_lazy(mm, addr, ptep, pte_wrprotect(pte)); } +/* + * Check if PTEs only differ in _PAGE_PROTECT HW bit, but also allow SW PTE + * bits in the comparison. Those might change e.g. because of dirty and young + * tracking. + */ +static inline int pte_allow_rdp(pte_t old, pte_t new) +{ + /* + * Only allow changes from RO to RW + */ + if (!(pte_val(old) & _PAGE_PROTECT) || pte_val(new) & _PAGE_PROTECT) + return 0; + + return (pte_val(old) & _PAGE_RDP_MASK) == (pte_val(new) & _PAGE_RDP_MASK); +} + +static inline void flush_tlb_fix_spurious_fault(struct vm_area_struct *vma, + unsigned long address) +{ + /* + * RDP might not have propagated the PTE protection reset to all CPUs, + * so there could be spurious TLB protection faults. + * NOTE: This will also be called when a racing pagetable update on + * another thread already installed the correct PTE. Both cases cannot + * really be distinguished. + * Therefore, only do the local TLB flush when RDP can be used, to avoid + * unnecessary overhead. + */ + if (MACHINE_HAS_RDP) + asm volatile("ptlb" : : : "memory"); +} +#define flush_tlb_fix_spurious_fault flush_tlb_fix_spurious_fault + +void ptep_reset_dat_prot(struct mm_struct *mm, unsigned long addr, pte_t *ptep, + pte_t new); + #define __HAVE_ARCH_PTEP_SET_ACCESS_FLAGS static inline int ptep_set_access_flags(struct vm_area_struct *vma, unsigned long addr, pte_t *ptep, @@ -1202,7 +1266,10 @@ static inline int ptep_set_access_flags(struct vm_area_struct *vma, { if (pte_same(*ptep, entry)) return 0; - ptep_xchg_direct(vma->vm_mm, addr, ptep, entry); + if (MACHINE_HAS_RDP && !mm_has_pgste(vma->vm_mm) && pte_allow_rdp(*ptep, entry)) + ptep_reset_dat_prot(vma->vm_mm, addr, ptep, entry); + else + ptep_xchg_direct(vma->vm_mm, addr, ptep, entry); return 1; } diff --git a/arch/s390/include/asm/processor.h b/arch/s390/include/asm/processor.h index c907f747d2a0..e98d9650764b 100644 --- a/arch/s390/include/asm/processor.h +++ b/arch/s390/include/asm/processor.h @@ -44,29 +44,46 @@ typedef long (*sys_call_ptr_t)(struct pt_regs *regs); -static inline void set_cpu_flag(int flag) +static __always_inline void set_cpu_flag(int flag) { S390_lowcore.cpu_flags |= (1UL << flag); } -static inline void clear_cpu_flag(int flag) +static __always_inline void clear_cpu_flag(int flag) { S390_lowcore.cpu_flags &= ~(1UL << flag); } -static inline int test_cpu_flag(int flag) +static __always_inline bool test_cpu_flag(int flag) { - return !!(S390_lowcore.cpu_flags & (1UL << flag)); + return S390_lowcore.cpu_flags & (1UL << flag); +} + +static __always_inline bool test_and_set_cpu_flag(int flag) +{ + if (test_cpu_flag(flag)) + return true; + set_cpu_flag(flag); + return false; +} + +static __always_inline bool test_and_clear_cpu_flag(int flag) +{ + if (!test_cpu_flag(flag)) + return false; + clear_cpu_flag(flag); + return true; } /* * Test CIF flag of another CPU. The caller needs to ensure that * CPU hotplug can not happen, e.g. by disabling preemption. */ -static inline int test_cpu_flag_of(int flag, int cpu) +static __always_inline bool test_cpu_flag_of(int flag, int cpu) { struct lowcore *lc = lowcore_ptr[cpu]; - return !!(lc->cpu_flags & (1UL << flag)); + + return lc->cpu_flags & (1UL << flag); } #define arch_needs_cpu() test_cpu_flag(CIF_NOHZ_DELAY) diff --git a/arch/s390/include/asm/ptrace.h b/arch/s390/include/asm/ptrace.h index 8bae33ab320a..bfb8c3cb8aee 100644 --- a/arch/s390/include/asm/ptrace.h +++ b/arch/s390/include/asm/ptrace.h @@ -26,7 +26,7 @@ #ifndef __ASSEMBLY__ #define PSW_KERNEL_BITS (PSW_DEFAULT_KEY | PSW_MASK_BASE | PSW_ASC_HOME | \ - PSW_MASK_EA | PSW_MASK_BA) + PSW_MASK_EA | PSW_MASK_BA | PSW_MASK_DAT) #define PSW_USER_BITS (PSW_MASK_DAT | PSW_MASK_IO | PSW_MASK_EXT | \ PSW_DEFAULT_KEY | PSW_MASK_BASE | PSW_MASK_MCHECK | \ PSW_MASK_PSTATE | PSW_ASC_PRIMARY) diff --git a/arch/s390/include/asm/setup.h b/arch/s390/include/asm/setup.h index 77e6506898f5..3a1f8825bc7d 100644 --- a/arch/s390/include/asm/setup.h +++ b/arch/s390/include/asm/setup.h @@ -34,6 +34,7 @@ #define MACHINE_FLAG_GS BIT(16) #define MACHINE_FLAG_SCC BIT(17) #define MACHINE_FLAG_PCI_MIO BIT(18) +#define MACHINE_FLAG_RDP BIT(19) #define LPP_MAGIC BIT(31) #define LPP_PID_MASK _AC(0xffffffff, UL) @@ -73,6 +74,10 @@ extern unsigned int zlib_dfltcc_support; extern int noexec_disabled; extern unsigned long ident_map_size; +extern unsigned long pgalloc_pos; +extern unsigned long pgalloc_end; +extern unsigned long pgalloc_low; +extern unsigned long __amode31_base; /* The Write Back bit position in the physaddr is given by the SLPC PCI */ extern unsigned long mio_wb_bit_mask; @@ -95,6 +100,7 @@ extern unsigned long mio_wb_bit_mask; #define MACHINE_HAS_GS (S390_lowcore.machine_flags & MACHINE_FLAG_GS) #define MACHINE_HAS_SCC (S390_lowcore.machine_flags & MACHINE_FLAG_SCC) #define MACHINE_HAS_PCI_MIO (S390_lowcore.machine_flags & MACHINE_FLAG_PCI_MIO) +#define MACHINE_HAS_RDP (S390_lowcore.machine_flags & MACHINE_FLAG_RDP) /* * Console mode. Override with conmode= diff --git a/arch/s390/include/asm/syscall_wrapper.h b/arch/s390/include/asm/syscall_wrapper.h index fde7e6b1df48..9286430fe729 100644 --- a/arch/s390/include/asm/syscall_wrapper.h +++ b/arch/s390/include/asm/syscall_wrapper.h @@ -7,36 +7,13 @@ #ifndef _ASM_S390_SYSCALL_WRAPPER_H #define _ASM_S390_SYSCALL_WRAPPER_H -#define __SC_TYPE(t, a) t - -#define SYSCALL_PT_ARG6(regs, m, t1, t2, t3, t4, t5, t6)\ - SYSCALL_PT_ARG5(regs, m, t1, t2, t3, t4, t5), \ - m(t6, (regs->gprs[7])) - -#define SYSCALL_PT_ARG5(regs, m, t1, t2, t3, t4, t5) \ - SYSCALL_PT_ARG4(regs, m, t1, t2, t3, t4), \ - m(t5, (regs->gprs[6])) - -#define SYSCALL_PT_ARG4(regs, m, t1, t2, t3, t4) \ - SYSCALL_PT_ARG3(regs, m, t1, t2, t3), \ - m(t4, (regs->gprs[5])) - -#define SYSCALL_PT_ARG3(regs, m, t1, t2, t3) \ - SYSCALL_PT_ARG2(regs, m, t1, t2), \ - m(t3, (regs->gprs[4])) - -#define SYSCALL_PT_ARG2(regs, m, t1, t2) \ - SYSCALL_PT_ARG1(regs, m, t1), \ - m(t2, (regs->gprs[3])) - -#define SYSCALL_PT_ARG1(regs, m, t1) \ - m(t1, (regs->orig_gpr2)) - -#define SYSCALL_PT_ARGS(x, ...) SYSCALL_PT_ARG##x(__VA_ARGS__) +/* Mapping of registers to parameters for syscalls */ +#define SC_S390_REGS_TO_ARGS(x, ...) \ + __MAP(x, __SC_ARGS \ + ,, regs->orig_gpr2,, regs->gprs[3],, regs->gprs[4] \ + ,, regs->gprs[5],, regs->gprs[6],, regs->gprs[7]) #ifdef CONFIG_COMPAT -#define __SC_COMPAT_TYPE(t, a) \ - __typeof(__builtin_choose_expr(sizeof(t) > 4, 0L, (t)0)) a #define __SC_COMPAT_CAST(t, a) \ ({ \ @@ -56,34 +33,31 @@ (t)__ReS; \ }) -#define __S390_SYS_STUBx(x, name, ...) \ - long __s390_sys##name(struct pt_regs *regs); \ - ALLOW_ERROR_INJECTION(__s390_sys##name, ERRNO); \ - long __s390_sys##name(struct pt_regs *regs) \ - { \ - long ret = __do_sys##name(SYSCALL_PT_ARGS(x, regs, \ - __SC_COMPAT_CAST, __MAP(x, __SC_TYPE, __VA_ARGS__))); \ - __MAP(x,__SC_TEST,__VA_ARGS__); \ - return ret; \ - } - /* * To keep the naming coherent, re-define SYSCALL_DEFINE0 to create an alias * named __s390x_sys_*() */ #define COMPAT_SYSCALL_DEFINE0(sname) \ - SYSCALL_METADATA(_##sname, 0); \ long __s390_compat_sys_##sname(void); \ ALLOW_ERROR_INJECTION(__s390_compat_sys_##sname, ERRNO); \ long __s390_compat_sys_##sname(void) #define SYSCALL_DEFINE0(sname) \ SYSCALL_METADATA(_##sname, 0); \ + long __s390_sys_##sname(void); \ + ALLOW_ERROR_INJECTION(__s390_sys_##sname, ERRNO); \ long __s390x_sys_##sname(void); \ ALLOW_ERROR_INJECTION(__s390x_sys_##sname, ERRNO); \ + static inline long __do_sys_##sname(void); \ long __s390_sys_##sname(void) \ - __attribute__((alias(__stringify(__s390x_sys_##sname)))); \ - long __s390x_sys_##sname(void) + { \ + return __do_sys_##sname(); \ + } \ + long __s390x_sys_##sname(void) \ + { \ + return __do_sys_##sname(); \ + } \ + static inline long __do_sys_##sname(void) #define COND_SYSCALL(name) \ cond_syscall(__s390x_sys_##name); \ @@ -94,24 +68,20 @@ SYSCALL_ALIAS(__s390_sys_##name, sys_ni_posix_timers) #define COMPAT_SYSCALL_DEFINEx(x, name, ...) \ - __diag_push(); \ - __diag_ignore(GCC, 8, "-Wattribute-alias", \ - "Type aliasing is used to sanitize syscall arguments"); \ long __s390_compat_sys##name(struct pt_regs *regs); \ - long __s390_compat_sys##name(struct pt_regs *regs) \ - __attribute__((alias(__stringify(__se_compat_sys##name)))); \ ALLOW_ERROR_INJECTION(__s390_compat_sys##name, ERRNO); \ - static inline long __do_compat_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \ - long __se_compat_sys##name(struct pt_regs *regs); \ - long __se_compat_sys##name(struct pt_regs *regs) \ + static inline long __se_compat_sys##name(__MAP(x, __SC_LONG, __VA_ARGS__)); \ + static inline long __do_compat_sys##name(__MAP(x, __SC_DECL, __VA_ARGS__)); \ + long __s390_compat_sys##name(struct pt_regs *regs) \ { \ - long ret = __do_compat_sys##name(SYSCALL_PT_ARGS(x, regs, __SC_DELOUSE, \ - __MAP(x, __SC_TYPE, __VA_ARGS__))); \ - __MAP(x,__SC_TEST,__VA_ARGS__); \ - return ret; \ + return __se_compat_sys##name(SC_S390_REGS_TO_ARGS(x, __VA_ARGS__)); \ } \ - __diag_pop(); \ - static inline long __do_compat_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) + static inline long __se_compat_sys##name(__MAP(x, __SC_LONG, __VA_ARGS__)) \ + { \ + __MAP(x, __SC_TEST, __VA_ARGS__); \ + return __do_compat_sys##name(__MAP(x, __SC_DELOUSE, __VA_ARGS__)); \ + } \ + static inline long __do_compat_sys##name(__MAP(x, __SC_DECL, __VA_ARGS__)) /* * As some compat syscalls may not be implemented, we need to expand @@ -124,42 +94,58 @@ #define COMPAT_SYS_NI(name) \ SYSCALL_ALIAS(__s390_compat_sys_##name, sys_ni_posix_timers) -#else /* CONFIG_COMPAT */ +#define __S390_SYS_STUBx(x, name, ...) \ + long __s390_sys##name(struct pt_regs *regs); \ + ALLOW_ERROR_INJECTION(__s390_sys##name, ERRNO); \ + static inline long ___se_sys##name(__MAP(x, __SC_LONG, __VA_ARGS__)); \ + long __s390_sys##name(struct pt_regs *regs) \ + { \ + return ___se_sys##name(SC_S390_REGS_TO_ARGS(x, __VA_ARGS__)); \ + } \ + static inline long ___se_sys##name(__MAP(x, __SC_LONG, __VA_ARGS__)) \ + { \ + __MAP(x, __SC_TEST, __VA_ARGS__); \ + return __do_sys##name(__MAP(x, __SC_COMPAT_CAST, __VA_ARGS__)); \ + } -#define __S390_SYS_STUBx(x, fullname, name, ...) +#else /* CONFIG_COMPAT */ #define SYSCALL_DEFINE0(sname) \ SYSCALL_METADATA(_##sname, 0); \ long __s390x_sys_##sname(void); \ ALLOW_ERROR_INJECTION(__s390x_sys_##sname, ERRNO); \ - long __s390x_sys_##sname(void) + static inline long __do_sys_##sname(void); \ + long __s390x_sys_##sname(void) \ + { \ + return __do_sys_##sname(); \ + } \ + static inline long __do_sys_##sname(void) #define COND_SYSCALL(name) \ cond_syscall(__s390x_sys_##name) #define SYS_NI(name) \ - SYSCALL_ALIAS(__s390x_sys_##name, sys_ni_posix_timers); + SYSCALL_ALIAS(__s390x_sys_##name, sys_ni_posix_timers) + +#define __S390_SYS_STUBx(x, fullname, name, ...) #endif /* CONFIG_COMPAT */ -#define __SYSCALL_DEFINEx(x, name, ...) \ - __diag_push(); \ - __diag_ignore(GCC, 8, "-Wattribute-alias", \ - "Type aliasing is used to sanitize syscall arguments"); \ - long __s390x_sys##name(struct pt_regs *regs) \ - __attribute__((alias(__stringify(__se_sys##name)))); \ - ALLOW_ERROR_INJECTION(__s390x_sys##name, ERRNO); \ - static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \ - long __se_sys##name(struct pt_regs *regs); \ - __S390_SYS_STUBx(x, name, __VA_ARGS__) \ - long __se_sys##name(struct pt_regs *regs) \ - { \ - long ret = __do_sys##name(SYSCALL_PT_ARGS(x, regs, \ - __SC_CAST, __MAP(x, __SC_TYPE, __VA_ARGS__))); \ - __MAP(x,__SC_TEST,__VA_ARGS__); \ - return ret; \ - } \ - __diag_pop(); \ - static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) +#define __SYSCALL_DEFINEx(x, name, ...) \ + long __s390x_sys##name(struct pt_regs *regs); \ + ALLOW_ERROR_INJECTION(__s390x_sys##name, ERRNO); \ + static inline long __se_sys##name(__MAP(x, __SC_LONG, __VA_ARGS__)); \ + static inline long __do_sys##name(__MAP(x, __SC_DECL, __VA_ARGS__)); \ + __S390_SYS_STUBx(x, name, __VA_ARGS__); \ + long __s390x_sys##name(struct pt_regs *regs) \ + { \ + return __se_sys##name(SC_S390_REGS_TO_ARGS(x, __VA_ARGS__)); \ + } \ + static inline long __se_sys##name(__MAP(x, __SC_LONG, __VA_ARGS__)) \ + { \ + __MAP(x, __SC_TEST, __VA_ARGS__); \ + return __do_sys##name(__MAP(x, __SC_CAST, __VA_ARGS__)); \ + } \ + static inline long __do_sys##name(__MAP(x, __SC_DECL, __VA_ARGS__)) #endif /* _ASM_S390_SYSCALL_WRAPPER_H */ diff --git a/arch/s390/include/asm/uaccess.h b/arch/s390/include/asm/uaccess.h index f7038b800cc3..8a8c64a678c4 100644 --- a/arch/s390/include/asm/uaccess.h +++ b/arch/s390/include/asm/uaccess.h @@ -390,4 +390,212 @@ do { \ goto err_label; \ } while (0) +void __cmpxchg_user_key_called_with_bad_pointer(void); + +#define CMPXCHG_USER_KEY_MAX_LOOPS 128 + +static __always_inline int __cmpxchg_user_key(unsigned long address, void *uval, + __uint128_t old, __uint128_t new, + unsigned long key, int size) +{ + int rc = 0; + + switch (size) { + case 1: { + unsigned int prev, shift, mask, _old, _new; + unsigned long count; + + shift = (3 ^ (address & 3)) << 3; + address ^= address & 3; + _old = ((unsigned int)old & 0xff) << shift; + _new = ((unsigned int)new & 0xff) << shift; + mask = ~(0xff << shift); + asm volatile( + " spka 0(%[key])\n" + " sacf 256\n" + " llill %[count],%[max_loops]\n" + "0: l %[prev],%[address]\n" + "1: nr %[prev],%[mask]\n" + " xilf %[mask],0xffffffff\n" + " or %[new],%[prev]\n" + " or %[prev],%[tmp]\n" + "2: lr %[tmp],%[prev]\n" + "3: cs %[prev],%[new],%[address]\n" + "4: jnl 5f\n" + " xr %[tmp],%[prev]\n" + " xr %[new],%[tmp]\n" + " nr %[tmp],%[mask]\n" + " jnz 5f\n" + " brct %[count],2b\n" + "5: sacf 768\n" + " spka %[default_key]\n" + EX_TABLE_UA_LOAD_REG(0b, 5b, %[rc], %[prev]) + EX_TABLE_UA_LOAD_REG(1b, 5b, %[rc], %[prev]) + EX_TABLE_UA_LOAD_REG(3b, 5b, %[rc], %[prev]) + EX_TABLE_UA_LOAD_REG(4b, 5b, %[rc], %[prev]) + : [rc] "+&d" (rc), + [prev] "=&d" (prev), + [address] "+Q" (*(int *)address), + [tmp] "+&d" (_old), + [new] "+&d" (_new), + [mask] "+&d" (mask), + [count] "=a" (count) + : [key] "%[count]" (key << 4), + [default_key] "J" (PAGE_DEFAULT_KEY), + [max_loops] "J" (CMPXCHG_USER_KEY_MAX_LOOPS) + : "memory", "cc"); + *(unsigned char *)uval = prev >> shift; + if (!count) + rc = -EAGAIN; + return rc; + } + case 2: { + unsigned int prev, shift, mask, _old, _new; + unsigned long count; + + shift = (2 ^ (address & 2)) << 3; + address ^= address & 2; + _old = ((unsigned int)old & 0xffff) << shift; + _new = ((unsigned int)new & 0xffff) << shift; + mask = ~(0xffff << shift); + asm volatile( + " spka 0(%[key])\n" + " sacf 256\n" + " llill %[count],%[max_loops]\n" + "0: l %[prev],%[address]\n" + "1: nr %[prev],%[mask]\n" + " xilf %[mask],0xffffffff\n" + " or %[new],%[prev]\n" + " or %[prev],%[tmp]\n" + "2: lr %[tmp],%[prev]\n" + "3: cs %[prev],%[new],%[address]\n" + "4: jnl 5f\n" + " xr %[tmp],%[prev]\n" + " xr %[new],%[tmp]\n" + " nr %[tmp],%[mask]\n" + " jnz 5f\n" + " brct %[count],2b\n" + "5: sacf 768\n" + " spka %[default_key]\n" + EX_TABLE_UA_LOAD_REG(0b, 5b, %[rc], %[prev]) + EX_TABLE_UA_LOAD_REG(1b, 5b, %[rc], %[prev]) + EX_TABLE_UA_LOAD_REG(3b, 5b, %[rc], %[prev]) + EX_TABLE_UA_LOAD_REG(4b, 5b, %[rc], %[prev]) + : [rc] "+&d" (rc), + [prev] "=&d" (prev), + [address] "+Q" (*(int *)address), + [tmp] "+&d" (_old), + [new] "+&d" (_new), + [mask] "+&d" (mask), + [count] "=a" (count) + : [key] "%[count]" (key << 4), + [default_key] "J" (PAGE_DEFAULT_KEY), + [max_loops] "J" (CMPXCHG_USER_KEY_MAX_LOOPS) + : "memory", "cc"); + *(unsigned short *)uval = prev >> shift; + if (!count) + rc = -EAGAIN; + return rc; + } + case 4: { + unsigned int prev = old; + + asm volatile( + " spka 0(%[key])\n" + " sacf 256\n" + "0: cs %[prev],%[new],%[address]\n" + "1: sacf 768\n" + " spka %[default_key]\n" + EX_TABLE_UA_LOAD_REG(0b, 1b, %[rc], %[prev]) + EX_TABLE_UA_LOAD_REG(1b, 1b, %[rc], %[prev]) + : [rc] "+&d" (rc), + [prev] "+&d" (prev), + [address] "+Q" (*(int *)address) + : [new] "d" ((unsigned int)new), + [key] "a" (key << 4), + [default_key] "J" (PAGE_DEFAULT_KEY) + : "memory", "cc"); + *(unsigned int *)uval = prev; + return rc; + } + case 8: { + unsigned long prev = old; + + asm volatile( + " spka 0(%[key])\n" + " sacf 256\n" + "0: csg %[prev],%[new],%[address]\n" + "1: sacf 768\n" + " spka %[default_key]\n" + EX_TABLE_UA_LOAD_REG(0b, 1b, %[rc], %[prev]) + EX_TABLE_UA_LOAD_REG(1b, 1b, %[rc], %[prev]) + : [rc] "+&d" (rc), + [prev] "+&d" (prev), + [address] "+QS" (*(long *)address) + : [new] "d" ((unsigned long)new), + [key] "a" (key << 4), + [default_key] "J" (PAGE_DEFAULT_KEY) + : "memory", "cc"); + *(unsigned long *)uval = prev; + return rc; + } + case 16: { + __uint128_t prev = old; + + asm volatile( + " spka 0(%[key])\n" + " sacf 256\n" + "0: cdsg %[prev],%[new],%[address]\n" + "1: sacf 768\n" + " spka %[default_key]\n" + EX_TABLE_UA_LOAD_REGPAIR(0b, 1b, %[rc], %[prev]) + EX_TABLE_UA_LOAD_REGPAIR(1b, 1b, %[rc], %[prev]) + : [rc] "+&d" (rc), + [prev] "+&d" (prev), + [address] "+QS" (*(__int128_t *)address) + : [new] "d" (new), + [key] "a" (key << 4), + [default_key] "J" (PAGE_DEFAULT_KEY) + : "memory", "cc"); + *(__uint128_t *)uval = prev; + return rc; + } + } + __cmpxchg_user_key_called_with_bad_pointer(); + return rc; +} + +/** + * cmpxchg_user_key() - cmpxchg with user space target, honoring storage keys + * @ptr: User space address of value to compare to @old and exchange with + * @new. Must be aligned to sizeof(*@ptr). + * @uval: Address where the old value of *@ptr is written to. + * @old: Old value. Compared to the content pointed to by @ptr in order to + * determine if the exchange occurs. The old value read from *@ptr is + * written to *@uval. + * @new: New value to place at *@ptr. + * @key: Access key to use for checking storage key protection. + * + * Perform a cmpxchg on a user space target, honoring storage key protection. + * @key alone determines how key checking is performed, neither + * storage-protection-override nor fetch-protection-override apply. + * The caller must compare *@uval and @old to determine if values have been + * exchanged. In case of an exception *@uval is set to zero. + * + * Return: 0: cmpxchg executed + * -EFAULT: an exception happened when trying to access *@ptr + * -EAGAIN: maxed out number of retries (byte and short only) + */ +#define cmpxchg_user_key(ptr, uval, old, new, key) \ +({ \ + __typeof__(ptr) __ptr = (ptr); \ + __typeof__(uval) __uval = (uval); \ + \ + BUILD_BUG_ON(sizeof(*(__ptr)) != sizeof(*(__uval))); \ + might_fault(); \ + __chk_user_ptr(__ptr); \ + __cmpxchg_user_key((unsigned long)(__ptr), (void *)(__uval), \ + (old), (new), (key), sizeof(*(__ptr))); \ +}) + #endif /* __S390_UACCESS_H */ diff --git a/arch/s390/include/asm/unwind.h b/arch/s390/include/asm/unwind.h index 02462e7100c1..b8ecf04e3468 100644 --- a/arch/s390/include/asm/unwind.h +++ b/arch/s390/include/asm/unwind.h @@ -4,7 +4,7 @@ #include <linux/sched.h> #include <linux/ftrace.h> -#include <linux/kprobes.h> +#include <linux/rethook.h> #include <linux/llist.h> #include <asm/ptrace.h> #include <asm/stacktrace.h> @@ -43,13 +43,15 @@ struct unwind_state { bool error; }; -/* Recover the return address modified by kretprobe and ftrace_graph. */ +/* Recover the return address modified by rethook and ftrace_graph. */ static inline unsigned long unwind_recover_ret_addr(struct unwind_state *state, unsigned long ip) { ip = ftrace_graph_ret_addr(state->task, &state->graph_idx, ip, (void *)state->sp); - if (is_kretprobe_trampoline(ip)) - ip = kretprobe_find_ret_addr(state->task, (void *)state->sp, &state->kr_cur); +#ifdef CONFIG_RETHOOK + if (is_rethook_trampoline(ip)) + ip = rethook_find_ret_addr(state->task, state->sp, &state->kr_cur); +#endif return ip; } diff --git a/arch/s390/include/uapi/asm/fs3270.h b/arch/s390/include/uapi/asm/fs3270.h new file mode 100644 index 000000000000..c4bc1108af6a --- /dev/null +++ b/arch/s390/include/uapi/asm/fs3270.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __ASM_S390_UAPI_FS3270_H +#define __ASM_S390_UAPI_FS3270_H + +#include <linux/types.h> +#include <asm/ioctl.h> + +/* ioctls for fullscreen 3270 */ +#define TUBICMD _IO('3', 3) /* set ccw command for fs reads. */ +#define TUBOCMD _IO('3', 4) /* set ccw command for fs writes. */ +#define TUBGETI _IO('3', 7) /* get ccw command for fs reads. */ +#define TUBGETO _IO('3', 8) /* get ccw command for fs writes. */ +#define TUBGETMOD _IO('3', 13) /* get characteristics like model, cols, rows */ + +/* For TUBGETMOD */ +struct raw3270_iocb { + __u16 model; + __u16 line_cnt; + __u16 col_cnt; + __u16 pf_cnt; + __u16 re_cnt; + __u16 map; +}; + +#endif /* __ASM_S390_UAPI_FS3270_H */ diff --git a/arch/s390/include/uapi/asm/raw3270.h b/arch/s390/include/uapi/asm/raw3270.h new file mode 100644 index 000000000000..6676f102bd50 --- /dev/null +++ b/arch/s390/include/uapi/asm/raw3270.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +#ifndef __ASM_S390_UAPI_RAW3270_H +#define __ASM_S390_UAPI_RAW3270_H + +/* Local Channel Commands */ +#define TC_WRITE 0x01 /* Write */ +#define TC_RDBUF 0x02 /* Read Buffer */ +#define TC_EWRITE 0x05 /* Erase write */ +#define TC_READMOD 0x06 /* Read modified */ +#define TC_EWRITEA 0x0d /* Erase write alternate */ +#define TC_WRITESF 0x11 /* Write structured field */ + +/* Buffer Control Orders */ +#define TO_GE 0x08 /* Graphics Escape */ +#define TO_SF 0x1d /* Start field */ +#define TO_SBA 0x11 /* Set buffer address */ +#define TO_IC 0x13 /* Insert cursor */ +#define TO_PT 0x05 /* Program tab */ +#define TO_RA 0x3c /* Repeat to address */ +#define TO_SFE 0x29 /* Start field extended */ +#define TO_EUA 0x12 /* Erase unprotected to address */ +#define TO_MF 0x2c /* Modify field */ +#define TO_SA 0x28 /* Set attribute */ + +/* Field Attribute Bytes */ +#define TF_INPUT 0x40 /* Visible input */ +#define TF_INPUTN 0x4c /* Invisible input */ +#define TF_INMDT 0xc1 /* Visible, Set-MDT */ +#define TF_LOG 0x60 + +/* Character Attribute Bytes */ +#define TAT_RESET 0x00 +#define TAT_FIELD 0xc0 +#define TAT_EXTHI 0x41 +#define TAT_FGCOLOR 0x42 +#define TAT_CHARS 0x43 +#define TAT_BGCOLOR 0x45 +#define TAT_TRANS 0x46 + +/* Extended-Highlighting Bytes */ +#define TAX_RESET 0x00 +#define TAX_BLINK 0xf1 +#define TAX_REVER 0xf2 +#define TAX_UNDER 0xf4 + +/* Reset value */ +#define TAR_RESET 0x00 + +/* Color values */ +#define TAC_RESET 0x00 +#define TAC_BLUE 0xf1 +#define TAC_RED 0xf2 +#define TAC_PINK 0xf3 +#define TAC_GREEN 0xf4 +#define TAC_TURQ 0xf5 +#define TAC_YELLOW 0xf6 +#define TAC_WHITE 0xf7 +#define TAC_DEFAULT 0x00 + +/* Write Control Characters */ +#define TW_NONE 0x40 /* No particular action */ +#define TW_KR 0xc2 /* Keyboard restore */ +#define TW_PLUSALARM 0x04 /* Add this bit for alarm */ + +#define RAW3270_FIRSTMINOR 1 /* First minor number */ +#define RAW3270_MAXDEVS 255 /* Max number of 3270 devices */ + +#define AID_CLEAR 0x6d +#define AID_ENTER 0x7d +#define AID_PF3 0xf3 +#define AID_PF7 0xf7 +#define AID_PF8 0xf8 +#define AID_READ_PARTITION 0x88 + +#endif /* __ASM_S390_UAPI_RAW3270_H */ diff --git a/arch/s390/include/uapi/asm/types.h b/arch/s390/include/uapi/asm/types.h index da034c606314..84457dbb26b4 100644 --- a/arch/s390/include/uapi/asm/types.h +++ b/arch/s390/include/uapi/asm/types.h @@ -12,15 +12,18 @@ #ifndef __ASSEMBLY__ -/* A address type so that arithmetic can be done on it & it can be upgraded to - 64 bit when necessary -*/ -typedef unsigned long addr_t; +typedef unsigned long addr_t; typedef __signed__ long saddr_t; typedef struct { - __u32 u[4]; -} __vector128; + union { + struct { + __u64 high; + __u64 low; + }; + __u32 u[4]; + }; +} __attribute__((packed, aligned(4))) __vector128; #endif /* __ASSEMBLY__ */ diff --git a/arch/s390/include/uapi/asm/zcrypt.h b/arch/s390/include/uapi/asm/zcrypt.h index d83713f67530..f4785abe1b9f 100644 --- a/arch/s390/include/uapi/asm/zcrypt.h +++ b/arch/s390/include/uapi/asm/zcrypt.h @@ -85,7 +85,8 @@ struct ica_rsa_modexpo_crt { struct CPRBX { __u16 cprb_len; /* CPRB length 220 */ __u8 cprb_ver_id; /* CPRB version id. 0x02 */ - __u8 _pad_000[3]; /* Alignment pad bytes */ + __u8 ctfm; /* Command Type Filtering Mask */ + __u8 pad_000[2]; /* Alignment pad bytes */ __u8 func_id[2]; /* function id 0x5432 */ __u8 cprb_flags[4]; /* Flags */ __u32 req_parml; /* request parameter buffer len */ diff --git a/arch/s390/kernel/Makefile b/arch/s390/kernel/Makefile index 5e6a23299790..8983837b3565 100644 --- a/arch/s390/kernel/Makefile +++ b/arch/s390/kernel/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_EARLY_PRINTK) += early_printk.o obj-$(CONFIG_KPROBES) += kprobes.o obj-$(CONFIG_KPROBES) += kprobes_insn_page.o obj-$(CONFIG_KPROBES) += mcount.o +obj-$(CONFIG_RETHOOK) += rethook.o obj-$(CONFIG_FUNCTION_TRACER) += ftrace.o obj-$(CONFIG_FUNCTION_TRACER) += mcount.o obj-$(CONFIG_CRASH_DUMP) += crash_dump.o @@ -69,7 +70,7 @@ obj-$(CONFIG_KEXEC_FILE) += kexec_elf.o obj-$(CONFIG_IMA_SECURE_AND_OR_TRUSTED_BOOT) += ima_arch.o -obj-$(CONFIG_PERF_EVENTS) += perf_event.o perf_cpum_cf_common.o +obj-$(CONFIG_PERF_EVENTS) += perf_event.o obj-$(CONFIG_PERF_EVENTS) += perf_cpum_cf.o perf_cpum_sf.o obj-$(CONFIG_PERF_EVENTS) += perf_cpum_cf_events.o perf_regs.o obj-$(CONFIG_PERF_EVENTS) += perf_pai_crypto.o perf_pai_ext.o diff --git a/arch/s390/kernel/abs_lowcore.c b/arch/s390/kernel/abs_lowcore.c index fb92e8ed0525..f9efc54ec4b7 100644 --- a/arch/s390/kernel/abs_lowcore.c +++ b/arch/s390/kernel/abs_lowcore.c @@ -3,12 +3,7 @@ #include <linux/pgtable.h> #include <asm/abs_lowcore.h> -#define ABS_LOWCORE_UNMAPPED 1 -#define ABS_LOWCORE_LAP_ON 2 -#define ABS_LOWCORE_IRQS_ON 4 - unsigned long __bootdata_preserved(__abs_lowcore); -bool __ro_after_init abs_lowcore_mapped; int abs_lowcore_map(int cpu, struct lowcore *lc, bool alloc) { @@ -49,47 +44,3 @@ void abs_lowcore_unmap(int cpu) addr += PAGE_SIZE; } } - -struct lowcore *get_abs_lowcore(unsigned long *flags) -{ - unsigned long irq_flags; - union ctlreg0 cr0; - int cpu; - - *flags = 0; - cpu = get_cpu(); - if (abs_lowcore_mapped) { - return ((struct lowcore *)__abs_lowcore) + cpu; - } else { - if (cpu != 0) - panic("Invalid unmapped absolute lowcore access\n"); - local_irq_save(irq_flags); - if (!irqs_disabled_flags(irq_flags)) - *flags |= ABS_LOWCORE_IRQS_ON; - __ctl_store(cr0.val, 0, 0); - if (cr0.lap) { - *flags |= ABS_LOWCORE_LAP_ON; - __ctl_clear_bit(0, 28); - } - *flags |= ABS_LOWCORE_UNMAPPED; - return lowcore_ptr[0]; - } -} - -void put_abs_lowcore(struct lowcore *lc, unsigned long flags) -{ - if (abs_lowcore_mapped) { - if (flags) - panic("Invalid mapped absolute lowcore release\n"); - } else { - if (smp_processor_id() != 0) - panic("Invalid mapped absolute lowcore access\n"); - if (!(flags & ABS_LOWCORE_UNMAPPED)) - panic("Invalid unmapped absolute lowcore release\n"); - if (flags & ABS_LOWCORE_LAP_ON) - __ctl_set_bit(0, 28); - if (flags & ABS_LOWCORE_IRQS_ON) - local_irq_enable(); - } - put_cpu(); -} diff --git a/arch/s390/kernel/cache.c b/arch/s390/kernel/cache.c index 7ee3651d00ab..56254fa06f99 100644 --- a/arch/s390/kernel/cache.c +++ b/arch/s390/kernel/cache.c @@ -46,7 +46,7 @@ struct cache_info { #define CACHE_MAX_LEVEL 8 union cache_topology { struct cache_info ci[CACHE_MAX_LEVEL]; - unsigned long long raw; + unsigned long raw; }; static const char * const cache_type_string[] = { diff --git a/arch/s390/kernel/compat_signal.c b/arch/s390/kernel/compat_signal.c index eee1ad3e1b29..cecedd01d4ec 100644 --- a/arch/s390/kernel/compat_signal.c +++ b/arch/s390/kernel/compat_signal.c @@ -139,7 +139,7 @@ static int save_sigregs_ext32(struct pt_regs *regs, /* Save vector registers to signal stack */ if (MACHINE_HAS_VX) { for (i = 0; i < __NUM_VXRS_LOW; i++) - vxrs[i] = *((__u64 *)(current->thread.fpu.vxrs + i) + 1); + vxrs[i] = current->thread.fpu.vxrs[i].low; if (__copy_to_user(&sregs_ext->vxrs_low, vxrs, sizeof(sregs_ext->vxrs_low)) || __copy_to_user(&sregs_ext->vxrs_high, @@ -173,7 +173,7 @@ static int restore_sigregs_ext32(struct pt_regs *regs, sizeof(sregs_ext->vxrs_high))) return -EFAULT; for (i = 0; i < __NUM_VXRS_LOW; i++) - *((__u64 *)(current->thread.fpu.vxrs + i) + 1) = vxrs[i]; + current->thread.fpu.vxrs[i].low = vxrs[i]; } return 0; } diff --git a/arch/s390/kernel/crash_dump.c b/arch/s390/kernel/crash_dump.c index c13b1455ec8c..8a617be28bb4 100644 --- a/arch/s390/kernel/crash_dump.c +++ b/arch/s390/kernel/crash_dump.c @@ -110,7 +110,7 @@ void __init save_area_add_vxrs(struct save_area *sa, __vector128 *vxrs) /* Copy lower halves of vector registers 0-15 */ for (i = 0; i < 16; i++) - memcpy(&sa->vxrs_low[i], &vxrs[i].u[2], 8); + sa->vxrs_low[i] = vxrs[i].low; /* Copy vector registers 16-31 */ memcpy(sa->vxrs_high, vxrs + 16, 16 * sizeof(__vector128)); } diff --git a/arch/s390/kernel/diag.c b/arch/s390/kernel/diag.c index a778714e4d8b..82079f2d8583 100644 --- a/arch/s390/kernel/diag.c +++ b/arch/s390/kernel/diag.c @@ -35,6 +35,7 @@ static const struct diag_desc diag_map[NR_DIAG_STAT] = { [DIAG_STAT_X014] = { .code = 0x014, .name = "Spool File Services" }, [DIAG_STAT_X044] = { .code = 0x044, .name = "Voluntary Timeslice End" }, [DIAG_STAT_X064] = { .code = 0x064, .name = "NSS Manipulation" }, + [DIAG_STAT_X08C] = { .code = 0x08c, .name = "Access 3270 Display Device Information" }, [DIAG_STAT_X09C] = { .code = 0x09c, .name = "Relinquish Timeslice" }, [DIAG_STAT_X0DC] = { .code = 0x0dc, .name = "Appldata Control" }, [DIAG_STAT_X204] = { .code = 0x204, .name = "Logical-CPU Utilization" }, @@ -57,12 +58,16 @@ struct diag_ops __amode31_ref diag_amode31_ops = { .diag26c = _diag26c_amode31, .diag14 = _diag14_amode31, .diag0c = _diag0c_amode31, + .diag8c = _diag8c_amode31, .diag308_reset = _diag308_reset_amode31 }; static struct diag210 _diag210_tmp_amode31 __section(".amode31.data"); struct diag210 __amode31_ref *__diag210_tmp_amode31 = &_diag210_tmp_amode31; +static struct diag8c _diag8c_tmp_amode31 __section(".amode31.data"); +static struct diag8c __amode31_ref *__diag8c_tmp_amode31 = &_diag8c_tmp_amode31; + static int show_diag_stat(struct seq_file *m, void *v) { struct diag_stat *stat; @@ -194,6 +199,27 @@ int diag210(struct diag210 *addr) } EXPORT_SYMBOL(diag210); +/* + * Diagnose 210: Get information about a virtual device + */ +int diag8c(struct diag8c *addr, struct ccw_dev_id *devno) +{ + static DEFINE_SPINLOCK(diag8c_lock); + unsigned long flags; + int ccode; + + spin_lock_irqsave(&diag8c_lock, flags); + + diag_stat_inc(DIAG_STAT_X08C); + ccode = diag_amode31_ops.diag8c(__diag8c_tmp_amode31, devno, sizeof(*addr)); + + *addr = *__diag8c_tmp_amode31; + spin_unlock_irqrestore(&diag8c_lock, flags); + + return ccode; +} +EXPORT_SYMBOL(diag8c); + int diag224(void *ptr) { int rc = -EOPNOTSUPP; diff --git a/arch/s390/kernel/early.c b/arch/s390/kernel/early.c index 6030fdd6997b..59eba19ae0f2 100644 --- a/arch/s390/kernel/early.c +++ b/arch/s390/kernel/early.c @@ -18,6 +18,7 @@ #include <linux/uaccess.h> #include <linux/kernel.h> #include <asm/asm-extable.h> +#include <linux/memblock.h> #include <asm/diag.h> #include <asm/ebcdic.h> #include <asm/ipl.h> @@ -160,9 +161,7 @@ static noinline __init void setup_lowcore_early(void) psw_t psw; psw.addr = (unsigned long)early_pgm_check_handler; - psw.mask = PSW_MASK_BASE | PSW_DEFAULT_KEY | PSW_MASK_EA | PSW_MASK_BA; - if (IS_ENABLED(CONFIG_KASAN)) - psw.mask |= PSW_MASK_DAT; + psw.mask = PSW_KERNEL_BITS; S390_lowcore.program_new_psw = psw; S390_lowcore.preempt_count = INIT_PREEMPT_COUNT; } @@ -227,6 +226,8 @@ static __init void detect_machine_facilities(void) S390_lowcore.machine_flags |= MACHINE_FLAG_PCI_MIO; /* the control bit is set during PCI initialization */ } + if (test_facility(194)) + S390_lowcore.machine_flags |= MACHINE_FLAG_RDP; } static inline void save_vector_registers(void) @@ -288,7 +289,6 @@ static void __init sort_amode31_extable(void) void __init startup_init(void) { - sclp_early_adjust_va(); reset_tod_clock(); check_image_bootable(); time_early_init(); diff --git a/arch/s390/kernel/entry.S b/arch/s390/kernel/entry.S index 0f423e9df095..c8d8c9960936 100644 --- a/arch/s390/kernel/entry.S +++ b/arch/s390/kernel/entry.S @@ -137,19 +137,13 @@ _LPP_OFFSET = __LC_LPP lgr %r14,\reg larl %r13,\start slgr %r14,%r13 -#ifdef CONFIG_AS_IS_LLVM clgfrl %r14,.Lrange_size\@ -#else - clgfi %r14,\end - \start -#endif jhe \outside_label -#ifdef CONFIG_AS_IS_LLVM .section .rodata, "a" .align 4 .Lrange_size\@: .long \end - \start .previous -#endif .endm .macro SIEEXIT diff --git a/arch/s390/kernel/entry.h b/arch/s390/kernel/entry.h index 995ec7449feb..34674e38826b 100644 --- a/arch/s390/kernel/entry.h +++ b/arch/s390/kernel/entry.h @@ -73,6 +73,5 @@ extern struct exception_table_entry _stop_amode31_ex_table[]; #define __amode31_data __section(".amode31.data") #define __amode31_ref __section(".amode31.refs") extern long _start_amode31_refs[], _end_amode31_refs[]; -extern unsigned long __amode31_base; #endif /* _ENTRY_H */ diff --git a/arch/s390/kernel/head64.S b/arch/s390/kernel/head64.S index d7b8b6ad574d..3b3bf8329e6c 100644 --- a/arch/s390/kernel/head64.S +++ b/arch/s390/kernel/head64.S @@ -25,6 +25,7 @@ ENTRY(startup_continue) larl %r14,init_task stg %r14,__LC_CURRENT larl %r15,init_thread_union+THREAD_SIZE-STACK_FRAME_OVERHEAD-__PT_SIZE + brasl %r14,sclp_early_adjust_va # allow sclp_early_printk #ifdef CONFIG_KASAN brasl %r14,kasan_early_init #endif diff --git a/arch/s390/kernel/idle.c b/arch/s390/kernel/idle.c index b04fb418307c..38e267c7bff7 100644 --- a/arch/s390/kernel/idle.c +++ b/arch/s390/kernel/idle.c @@ -24,116 +24,61 @@ static DEFINE_PER_CPU(struct s390_idle_data, s390_idle); void account_idle_time_irq(void) { struct s390_idle_data *idle = this_cpu_ptr(&s390_idle); + unsigned long idle_time; u64 cycles_new[8]; int i; - clear_cpu_flag(CIF_ENABLED_WAIT); if (smp_cpu_mtid) { stcctm(MT_DIAG, smp_cpu_mtid, cycles_new); for (i = 0; i < smp_cpu_mtid; i++) this_cpu_add(mt_cycles[i], cycles_new[i] - idle->mt_cycles_enter[i]); } - idle->clock_idle_exit = S390_lowcore.int_clock; - idle->timer_idle_exit = S390_lowcore.sys_enter_timer; + idle_time = S390_lowcore.int_clock - idle->clock_idle_enter; S390_lowcore.steal_timer += idle->clock_idle_enter - S390_lowcore.last_update_clock; - S390_lowcore.last_update_clock = idle->clock_idle_exit; + S390_lowcore.last_update_clock = S390_lowcore.int_clock; S390_lowcore.system_timer += S390_lowcore.last_update_timer - idle->timer_idle_enter; - S390_lowcore.last_update_timer = idle->timer_idle_exit; + S390_lowcore.last_update_timer = S390_lowcore.sys_enter_timer; + + /* Account time spent with enabled wait psw loaded as idle time. */ + WRITE_ONCE(idle->idle_time, READ_ONCE(idle->idle_time) + idle_time); + WRITE_ONCE(idle->idle_count, READ_ONCE(idle->idle_count) + 1); + account_idle_time(cputime_to_nsecs(idle_time)); } -void arch_cpu_idle(void) +void noinstr arch_cpu_idle(void) { struct s390_idle_data *idle = this_cpu_ptr(&s390_idle); - unsigned long idle_time; unsigned long psw_mask; /* Wait for external, I/O or machine check interrupt. */ - psw_mask = PSW_KERNEL_BITS | PSW_MASK_WAIT | PSW_MASK_DAT | - PSW_MASK_IO | PSW_MASK_EXT | PSW_MASK_MCHECK; + psw_mask = PSW_KERNEL_BITS | PSW_MASK_WAIT | + PSW_MASK_IO | PSW_MASK_EXT | PSW_MASK_MCHECK; clear_cpu_flag(CIF_NOHZ_DELAY); /* psw_idle() returns with interrupts disabled. */ psw_idle(idle, psw_mask); - - /* Account time spent with enabled wait psw loaded as idle time. */ - raw_write_seqcount_begin(&idle->seqcount); - idle_time = idle->clock_idle_exit - idle->clock_idle_enter; - idle->clock_idle_enter = idle->clock_idle_exit = 0ULL; - idle->idle_time += idle_time; - idle->idle_count++; - account_idle_time(cputime_to_nsecs(idle_time)); - raw_write_seqcount_end(&idle->seqcount); } static ssize_t show_idle_count(struct device *dev, - struct device_attribute *attr, char *buf) + struct device_attribute *attr, char *buf) { struct s390_idle_data *idle = &per_cpu(s390_idle, dev->id); - unsigned long idle_count; - unsigned int seq; - - do { - seq = read_seqcount_begin(&idle->seqcount); - idle_count = READ_ONCE(idle->idle_count); - if (READ_ONCE(idle->clock_idle_enter)) - idle_count++; - } while (read_seqcount_retry(&idle->seqcount, seq)); - return sprintf(buf, "%lu\n", idle_count); + + return sysfs_emit(buf, "%lu\n", READ_ONCE(idle->idle_count)); } DEVICE_ATTR(idle_count, 0444, show_idle_count, NULL); static ssize_t show_idle_time(struct device *dev, - struct device_attribute *attr, char *buf) + struct device_attribute *attr, char *buf) { - unsigned long now, idle_time, idle_enter, idle_exit, in_idle; struct s390_idle_data *idle = &per_cpu(s390_idle, dev->id); - unsigned int seq; - - do { - seq = read_seqcount_begin(&idle->seqcount); - idle_time = READ_ONCE(idle->idle_time); - idle_enter = READ_ONCE(idle->clock_idle_enter); - idle_exit = READ_ONCE(idle->clock_idle_exit); - } while (read_seqcount_retry(&idle->seqcount, seq)); - in_idle = 0; - now = get_tod_clock(); - if (idle_enter) { - if (idle_exit) { - in_idle = idle_exit - idle_enter; - } else if (now > idle_enter) { - in_idle = now - idle_enter; - } - } - idle_time += in_idle; - return sprintf(buf, "%lu\n", idle_time >> 12); -} -DEVICE_ATTR(idle_time_us, 0444, show_idle_time, NULL); -u64 arch_cpu_idle_time(int cpu) -{ - struct s390_idle_data *idle = &per_cpu(s390_idle, cpu); - unsigned long now, idle_enter, idle_exit, in_idle; - unsigned int seq; - - do { - seq = read_seqcount_begin(&idle->seqcount); - idle_enter = READ_ONCE(idle->clock_idle_enter); - idle_exit = READ_ONCE(idle->clock_idle_exit); - } while (read_seqcount_retry(&idle->seqcount, seq)); - in_idle = 0; - now = get_tod_clock(); - if (idle_enter) { - if (idle_exit) { - in_idle = idle_exit - idle_enter; - } else if (now > idle_enter) { - in_idle = now - idle_enter; - } - } - return cputime_to_nsecs(in_idle); + return sysfs_emit(buf, "%lu\n", READ_ONCE(idle->idle_time) >> 12); } +DEVICE_ATTR(idle_time_us, 0444, show_idle_time, NULL); void arch_cpu_idle_enter(void) { diff --git a/arch/s390/kernel/ipl.c b/arch/s390/kernel/ipl.c index fbd646dbf440..5f0f5c86963a 100644 --- a/arch/s390/kernel/ipl.c +++ b/arch/s390/kernel/ipl.c @@ -593,6 +593,7 @@ static struct attribute *ipl_eckd_attrs[] = { &sys_ipl_type_attr.attr, &sys_ipl_eckd_bootprog_attr.attr, &sys_ipl_eckd_br_chr_attr.attr, + &sys_ipl_ccw_loadparm_attr.attr, &sys_ipl_device_attr.attr, &sys_ipl_secure_attr.attr, &sys_ipl_has_secure_attr.attr, @@ -888,23 +889,27 @@ static ssize_t reipl_generic_loadparm_store(struct ipl_parameter_block *ipb, return len; } -/* FCP wrapper */ -static ssize_t reipl_fcp_loadparm_show(struct kobject *kobj, - struct kobj_attribute *attr, char *page) -{ - return reipl_generic_loadparm_show(reipl_block_fcp, page); -} - -static ssize_t reipl_fcp_loadparm_store(struct kobject *kobj, - struct kobj_attribute *attr, - const char *buf, size_t len) -{ - return reipl_generic_loadparm_store(reipl_block_fcp, buf, len); -} - -static struct kobj_attribute sys_reipl_fcp_loadparm_attr = - __ATTR(loadparm, 0644, reipl_fcp_loadparm_show, - reipl_fcp_loadparm_store); +#define DEFINE_GENERIC_LOADPARM(name) \ +static ssize_t reipl_##name##_loadparm_show(struct kobject *kobj, \ + struct kobj_attribute *attr, char *page) \ +{ \ + return reipl_generic_loadparm_show(reipl_block_##name, page); \ +} \ +static ssize_t reipl_##name##_loadparm_store(struct kobject *kobj, \ + struct kobj_attribute *attr, \ + const char *buf, size_t len) \ +{ \ + return reipl_generic_loadparm_store(reipl_block_##name, buf, len); \ +} \ +static struct kobj_attribute sys_reipl_##name##_loadparm_attr = \ + __ATTR(loadparm, 0644, reipl_##name##_loadparm_show, \ + reipl_##name##_loadparm_store) + +DEFINE_GENERIC_LOADPARM(fcp); +DEFINE_GENERIC_LOADPARM(nvme); +DEFINE_GENERIC_LOADPARM(ccw); +DEFINE_GENERIC_LOADPARM(nss); +DEFINE_GENERIC_LOADPARM(eckd); static ssize_t reipl_fcp_clear_show(struct kobject *kobj, struct kobj_attribute *attr, char *page) @@ -994,24 +999,6 @@ DEFINE_IPL_ATTR_RW(reipl_nvme, bootprog, "%lld\n", "%lld\n", DEFINE_IPL_ATTR_RW(reipl_nvme, br_lba, "%lld\n", "%lld\n", reipl_block_nvme->nvme.br_lba); -/* nvme wrapper */ -static ssize_t reipl_nvme_loadparm_show(struct kobject *kobj, - struct kobj_attribute *attr, char *page) -{ - return reipl_generic_loadparm_show(reipl_block_nvme, page); -} - -static ssize_t reipl_nvme_loadparm_store(struct kobject *kobj, - struct kobj_attribute *attr, - const char *buf, size_t len) -{ - return reipl_generic_loadparm_store(reipl_block_nvme, buf, len); -} - -static struct kobj_attribute sys_reipl_nvme_loadparm_attr = - __ATTR(loadparm, 0644, reipl_nvme_loadparm_show, - reipl_nvme_loadparm_store); - static struct attribute *reipl_nvme_attrs[] = { &sys_reipl_nvme_fid_attr.attr, &sys_reipl_nvme_nsid_attr.attr, @@ -1047,38 +1034,6 @@ static struct kobj_attribute sys_reipl_nvme_clear_attr = /* CCW reipl device attributes */ DEFINE_IPL_CCW_ATTR_RW(reipl_ccw, device, reipl_block_ccw->ccw); -/* NSS wrapper */ -static ssize_t reipl_nss_loadparm_show(struct kobject *kobj, - struct kobj_attribute *attr, char *page) -{ - return reipl_generic_loadparm_show(reipl_block_nss, page); -} - -static ssize_t reipl_nss_loadparm_store(struct kobject *kobj, - struct kobj_attribute *attr, - const char *buf, size_t len) -{ - return reipl_generic_loadparm_store(reipl_block_nss, buf, len); -} - -/* CCW wrapper */ -static ssize_t reipl_ccw_loadparm_show(struct kobject *kobj, - struct kobj_attribute *attr, char *page) -{ - return reipl_generic_loadparm_show(reipl_block_ccw, page); -} - -static ssize_t reipl_ccw_loadparm_store(struct kobject *kobj, - struct kobj_attribute *attr, - const char *buf, size_t len) -{ - return reipl_generic_loadparm_store(reipl_block_ccw, buf, len); -} - -static struct kobj_attribute sys_reipl_ccw_loadparm_attr = - __ATTR(loadparm, 0644, reipl_ccw_loadparm_show, - reipl_ccw_loadparm_store); - static ssize_t reipl_ccw_clear_show(struct kobject *kobj, struct kobj_attribute *attr, char *page) { @@ -1176,6 +1131,7 @@ static struct attribute *reipl_eckd_attrs[] = { &sys_reipl_eckd_device_attr.attr, &sys_reipl_eckd_bootprog_attr.attr, &sys_reipl_eckd_br_chr_attr.attr, + &sys_reipl_eckd_loadparm_attr.attr, NULL, }; @@ -1194,7 +1150,7 @@ static ssize_t reipl_eckd_clear_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t len) { - if (strtobool(buf, &reipl_eckd_clear) < 0) + if (kstrtobool(buf, &reipl_eckd_clear) < 0) return -EINVAL; return len; } @@ -1251,10 +1207,6 @@ static struct kobj_attribute sys_reipl_nss_name_attr = __ATTR(name, 0644, reipl_nss_name_show, reipl_nss_name_store); -static struct kobj_attribute sys_reipl_nss_loadparm_attr = - __ATTR(loadparm, 0644, reipl_nss_loadparm_show, - reipl_nss_loadparm_store); - static struct attribute *reipl_nss_attrs[] = { &sys_reipl_nss_name_attr.attr, &sys_reipl_nss_loadparm_attr.attr, @@ -1986,15 +1938,14 @@ static void dump_reipl_run(struct shutdown_trigger *trigger) { unsigned long ipib = (unsigned long) reipl_block_actual; struct lowcore *abs_lc; - unsigned long flags; unsigned int csum; csum = (__force unsigned int) csum_partial(reipl_block_actual, reipl_block_actual->hdr.len, 0); - abs_lc = get_abs_lowcore(&flags); + abs_lc = get_abs_lowcore(); abs_lc->ipib = ipib; abs_lc->ipib_checksum = csum; - put_abs_lowcore(abs_lc, flags); + put_abs_lowcore(abs_lc); dump_run(trigger); } diff --git a/arch/s390/kernel/irq.c b/arch/s390/kernel/irq.c index 45393919fe61..b020ff17d206 100644 --- a/arch/s390/kernel/irq.c +++ b/arch/s390/kernel/irq.c @@ -136,7 +136,7 @@ void noinstr do_io_irq(struct pt_regs *regs) { irqentry_state_t state = irqentry_enter(regs); struct pt_regs *old_regs = set_irq_regs(regs); - int from_idle; + bool from_idle; irq_enter_rcu(); @@ -146,7 +146,7 @@ void noinstr do_io_irq(struct pt_regs *regs) current->thread.last_break = regs->last_break; } - from_idle = !user_mode(regs) && regs->psw.addr == (unsigned long)psw_idle_exit; + from_idle = test_and_clear_cpu_flag(CIF_ENABLED_WAIT); if (from_idle) account_idle_time_irq(); @@ -171,7 +171,7 @@ void noinstr do_ext_irq(struct pt_regs *regs) { irqentry_state_t state = irqentry_enter(regs); struct pt_regs *old_regs = set_irq_regs(regs); - int from_idle; + bool from_idle; irq_enter_rcu(); @@ -185,7 +185,7 @@ void noinstr do_ext_irq(struct pt_regs *regs) regs->int_parm = S390_lowcore.ext_params; regs->int_parm_long = S390_lowcore.ext_params2; - from_idle = !user_mode(regs) && regs->psw.addr == (unsigned long)psw_idle_exit; + from_idle = test_and_clear_cpu_flag(CIF_ENABLED_WAIT); if (from_idle) account_idle_time_irq(); diff --git a/arch/s390/kernel/kprobes.c b/arch/s390/kernel/kprobes.c index 401f9c933ff9..5e713f318de3 100644 --- a/arch/s390/kernel/kprobes.c +++ b/arch/s390/kernel/kprobes.c @@ -281,16 +281,6 @@ static void pop_kprobe(struct kprobe_ctlblk *kcb) } NOKPROBE_SYMBOL(pop_kprobe); -void arch_prepare_kretprobe(struct kretprobe_instance *ri, struct pt_regs *regs) -{ - ri->ret_addr = (kprobe_opcode_t *)regs->gprs[14]; - ri->fp = (void *)regs->gprs[15]; - - /* Replace the return addr with trampoline addr */ - regs->gprs[14] = (unsigned long)&__kretprobe_trampoline; -} -NOKPROBE_SYMBOL(arch_prepare_kretprobe); - static void kprobe_reenter_check(struct kprobe_ctlblk *kcb, struct kprobe *p) { switch (kcb->kprobe_status) { @@ -371,26 +361,6 @@ static int kprobe_handler(struct pt_regs *regs) } NOKPROBE_SYMBOL(kprobe_handler); -void arch_kretprobe_fixup_return(struct pt_regs *regs, - kprobe_opcode_t *correct_ret_addr) -{ - /* Replace fake return address with real one. */ - regs->gprs[14] = (unsigned long)correct_ret_addr; -} -NOKPROBE_SYMBOL(arch_kretprobe_fixup_return); - -/* - * Called from __kretprobe_trampoline - */ -void trampoline_probe_handler(struct pt_regs *regs) -{ - kretprobe_trampoline_handler(regs, (void *)regs->gprs[15]); -} -NOKPROBE_SYMBOL(trampoline_probe_handler); - -/* assembler function that handles the kretprobes must not be probed itself */ -NOKPROBE_SYMBOL(__kretprobe_trampoline); - /* * Called after single-stepping. p->addr is the address of the * instruction whose first byte has been replaced by the "breakpoint" diff --git a/arch/s390/kernel/machine_kexec.c b/arch/s390/kernel/machine_kexec.c index 4579b42286d5..2a8e73266428 100644 --- a/arch/s390/kernel/machine_kexec.c +++ b/arch/s390/kernel/machine_kexec.c @@ -224,7 +224,6 @@ void machine_kexec_cleanup(struct kimage *image) void arch_crash_save_vmcoreinfo(void) { struct lowcore *abs_lc; - unsigned long flags; VMCOREINFO_SYMBOL(lowcore_ptr); VMCOREINFO_SYMBOL(high_memory); @@ -232,9 +231,9 @@ void arch_crash_save_vmcoreinfo(void) vmcoreinfo_append_str("SAMODE31=%lx\n", __samode31); vmcoreinfo_append_str("EAMODE31=%lx\n", __eamode31); vmcoreinfo_append_str("KERNELOFFSET=%lx\n", kaslr_offset()); - abs_lc = get_abs_lowcore(&flags); + abs_lc = get_abs_lowcore(); abs_lc->vmcore_info = paddr_vmcoreinfo_note(); - put_abs_lowcore(abs_lc, flags); + put_abs_lowcore(abs_lc); } void machine_shutdown(void) diff --git a/arch/s390/kernel/mcount.S b/arch/s390/kernel/mcount.S index 4786bfe02144..43ff91073d2a 100644 --- a/arch/s390/kernel/mcount.S +++ b/arch/s390/kernel/mcount.S @@ -135,9 +135,9 @@ SYM_FUNC_END(return_to_handler) #endif #endif /* CONFIG_FUNCTION_TRACER */ -#ifdef CONFIG_KPROBES +#ifdef CONFIG_RETHOOK -SYM_FUNC_START(__kretprobe_trampoline) +SYM_FUNC_START(arch_rethook_trampoline) stg %r14,(__SF_GPRS+8*8)(%r15) lay %r15,-STACK_FRAME_SIZE(%r15) @@ -152,16 +152,16 @@ SYM_FUNC_START(__kretprobe_trampoline) epsw %r2,%r3 risbg %r3,%r2,0,31,32 stg %r3,STACK_PTREGS_PSW(%r15) - larl %r1,__kretprobe_trampoline + larl %r1,arch_rethook_trampoline stg %r1,STACK_PTREGS_PSW+8(%r15) lay %r2,STACK_PTREGS(%r15) - brasl %r14,trampoline_probe_handler + brasl %r14,arch_rethook_trampoline_callback mvc __SF_EMPTY(16,%r7),STACK_PTREGS_PSW(%r15) lmg %r0,%r15,STACK_PTREGS_GPRS(%r15) lpswe __SF_EMPTY(%r15) -SYM_FUNC_END(__kretprobe_trampoline) +SYM_FUNC_END(arch_rethook_trampoline) -#endif /* CONFIG_KPROBES */ +#endif /* CONFIG_RETHOOK */ diff --git a/arch/s390/kernel/os_info.c b/arch/s390/kernel/os_info.c index ec0bd9457e90..6e1824141b29 100644 --- a/arch/s390/kernel/os_info.c +++ b/arch/s390/kernel/os_info.c @@ -59,15 +59,14 @@ void os_info_entry_add(int nr, void *ptr, u64 size) void __init os_info_init(void) { struct lowcore *abs_lc; - unsigned long flags; os_info.version_major = OS_INFO_VERSION_MAJOR; os_info.version_minor = OS_INFO_VERSION_MINOR; os_info.magic = OS_INFO_MAGIC; os_info.csum = os_info_csum(&os_info); - abs_lc = get_abs_lowcore(&flags); + abs_lc = get_abs_lowcore(); abs_lc->os_info = __pa(&os_info); - put_abs_lowcore(abs_lc, flags); + put_abs_lowcore(abs_lc); } #ifdef CONFIG_CRASH_DUMP diff --git a/arch/s390/kernel/perf_cpum_cf.c b/arch/s390/kernel/perf_cpum_cf.c index aa38649c7c27..c9ab971498d6 100644 --- a/arch/s390/kernel/perf_cpum_cf.c +++ b/arch/s390/kernel/perf_cpum_cf.c @@ -2,7 +2,7 @@ /* * Performance event support for s390x - CPU-measurement Counter Facility * - * Copyright IBM Corp. 2012, 2021 + * Copyright IBM Corp. 2012, 2023 * Author(s): Hendrik Brueckner <brueckner@linux.ibm.com> * Thomas Richter <tmricht@linux.ibm.com> */ @@ -16,11 +16,82 @@ #include <linux/init.h> #include <linux/export.h> #include <linux/miscdevice.h> +#include <linux/perf_event.h> -#include <asm/cpu_mcf.h> +#include <asm/cpu_mf.h> #include <asm/hwctrset.h> #include <asm/debug.h> +enum cpumf_ctr_set { + CPUMF_CTR_SET_BASIC = 0, /* Basic Counter Set */ + CPUMF_CTR_SET_USER = 1, /* Problem-State Counter Set */ + CPUMF_CTR_SET_CRYPTO = 2, /* Crypto-Activity Counter Set */ + CPUMF_CTR_SET_EXT = 3, /* Extended Counter Set */ + CPUMF_CTR_SET_MT_DIAG = 4, /* MT-diagnostic Counter Set */ + + /* Maximum number of counter sets */ + CPUMF_CTR_SET_MAX, +}; + +#define CPUMF_LCCTL_ENABLE_SHIFT 16 +#define CPUMF_LCCTL_ACTCTL_SHIFT 0 + +static inline void ctr_set_enable(u64 *state, u64 ctrsets) +{ + *state |= ctrsets << CPUMF_LCCTL_ENABLE_SHIFT; +} + +static inline void ctr_set_disable(u64 *state, u64 ctrsets) +{ + *state &= ~(ctrsets << CPUMF_LCCTL_ENABLE_SHIFT); +} + +static inline void ctr_set_start(u64 *state, u64 ctrsets) +{ + *state |= ctrsets << CPUMF_LCCTL_ACTCTL_SHIFT; +} + +static inline void ctr_set_stop(u64 *state, u64 ctrsets) +{ + *state &= ~(ctrsets << CPUMF_LCCTL_ACTCTL_SHIFT); +} + +static inline int ctr_stcctm(enum cpumf_ctr_set set, u64 range, u64 *dest) +{ + switch (set) { + case CPUMF_CTR_SET_BASIC: + return stcctm(BASIC, range, dest); + case CPUMF_CTR_SET_USER: + return stcctm(PROBLEM_STATE, range, dest); + case CPUMF_CTR_SET_CRYPTO: + return stcctm(CRYPTO_ACTIVITY, range, dest); + case CPUMF_CTR_SET_EXT: + return stcctm(EXTENDED, range, dest); + case CPUMF_CTR_SET_MT_DIAG: + return stcctm(MT_DIAG_CLEARING, range, dest); + case CPUMF_CTR_SET_MAX: + return 3; + } + return 3; +} + +struct cpu_cf_events { + struct cpumf_ctr_info info; + atomic_t ctr_set[CPUMF_CTR_SET_MAX]; + u64 state; /* For perf_event_open SVC */ + u64 dev_state; /* For /dev/hwctr */ + unsigned int flags; + size_t used; /* Bytes used in data */ + size_t usedss; /* Bytes used in start/stop */ + unsigned char start[PAGE_SIZE]; /* Counter set at event add */ + unsigned char stop[PAGE_SIZE]; /* Counter set at event delete */ + unsigned char data[PAGE_SIZE]; /* Counter set at /dev/hwctr */ + unsigned int sets; /* # Counter set saved in memory */ +}; + +/* Per-CPU event structure for the counter facility */ +static DEFINE_PER_CPU(struct cpu_cf_events, cpu_cf_events); + static unsigned int cfdiag_cpu_speed; /* CPU speed for CF_DIAG trailer */ static debug_info_t *cf_dbg; @@ -112,6 +183,53 @@ static void cfdiag_trailer(struct cf_trailer_entry *te) te->timestamp = get_tod_clock_fast(); } +/* + * Return the maximum possible counter set size (in number of 8 byte counters) + * depending on type and model number. + */ +static size_t cpum_cf_ctrset_size(enum cpumf_ctr_set ctrset, + struct cpumf_ctr_info *info) +{ + size_t ctrset_size = 0; + + switch (ctrset) { + case CPUMF_CTR_SET_BASIC: + if (info->cfvn >= 1) + ctrset_size = 6; + break; + case CPUMF_CTR_SET_USER: + if (info->cfvn == 1) + ctrset_size = 6; + else if (info->cfvn >= 3) + ctrset_size = 2; + break; + case CPUMF_CTR_SET_CRYPTO: + if (info->csvn >= 1 && info->csvn <= 5) + ctrset_size = 16; + else if (info->csvn == 6 || info->csvn == 7) + ctrset_size = 20; + break; + case CPUMF_CTR_SET_EXT: + if (info->csvn == 1) + ctrset_size = 32; + else if (info->csvn == 2) + ctrset_size = 48; + else if (info->csvn >= 3 && info->csvn <= 5) + ctrset_size = 128; + else if (info->csvn == 6 || info->csvn == 7) + ctrset_size = 160; + break; + case CPUMF_CTR_SET_MT_DIAG: + if (info->csvn > 3) + ctrset_size = 48; + break; + case CPUMF_CTR_SET_MAX: + break; + } + + return ctrset_size; +} + /* Read a counter set. The counter set number determines the counter set and * the CPUM-CF first and second version number determine the number of * available counters in each counter set. @@ -388,6 +506,47 @@ static void cpumf_pmu_disable(struct pmu *pmu) cpuhw->flags &= ~PMU_F_ENABLED; } +#define PMC_INIT 0UL +#define PMC_RELEASE 1UL + +static void cpum_cf_setup_cpu(void *flags) +{ + struct cpu_cf_events *cpuhw = this_cpu_ptr(&cpu_cf_events); + + switch ((unsigned long)flags) { + case PMC_INIT: + memset(&cpuhw->info, 0, sizeof(cpuhw->info)); + qctri(&cpuhw->info); + cpuhw->flags |= PMU_F_RESERVED; + break; + + case PMC_RELEASE: + cpuhw->flags &= ~PMU_F_RESERVED; + break; + } + + /* Disable CPU counter sets */ + lcctl(0); + debug_sprintf_event(cf_dbg, 5, "%s flags %#x flags %#x state %#llx\n", + __func__, *(int *)flags, cpuhw->flags, + cpuhw->state); +} + +/* Initialize the CPU-measurement counter facility */ +static int __kernel_cpumcf_begin(void) +{ + on_each_cpu(cpum_cf_setup_cpu, (void *)PMC_INIT, 1); + irq_subclass_register(IRQ_SUBCLASS_MEASUREMENT_ALERT); + + return 0; +} + +/* Release the CPU-measurement counter facility */ +static void __kernel_cpumcf_end(void) +{ + on_each_cpu(cpum_cf_setup_cpu, (void *)PMC_RELEASE, 1); + irq_subclass_unregister(IRQ_SUBCLASS_MEASUREMENT_ALERT); +} /* Number of perf events counting hardware events */ static atomic_t num_events = ATOMIC_INIT(0); @@ -397,12 +556,10 @@ static DEFINE_MUTEX(pmc_reserve_mutex); /* Release the PMU if event is the last perf event */ static void hw_perf_event_destroy(struct perf_event *event) { - if (!atomic_add_unless(&num_events, -1, 1)) { - mutex_lock(&pmc_reserve_mutex); - if (atomic_dec_return(&num_events) == 0) - __kernel_cpumcf_end(); - mutex_unlock(&pmc_reserve_mutex); - } + mutex_lock(&pmc_reserve_mutex); + if (atomic_dec_return(&num_events) == 0) + __kernel_cpumcf_end(); + mutex_unlock(&pmc_reserve_mutex); } /* CPUMF <-> perf event mappings for kernel+userspace (basic set) */ @@ -434,6 +591,12 @@ static void cpumf_hw_inuse(void) mutex_unlock(&pmc_reserve_mutex); } +static int is_userspace_event(u64 ev) +{ + return cpumf_generic_events_user[PERF_COUNT_HW_CPU_CYCLES] == ev || + cpumf_generic_events_user[PERF_COUNT_HW_INSTRUCTIONS] == ev; +} + static int __hw_perf_event_init(struct perf_event *event, unsigned int type) { struct perf_event_attr *attr = &event->attr; @@ -456,19 +619,26 @@ static int __hw_perf_event_init(struct perf_event *event, unsigned int type) if (is_sampling_event(event)) /* No sampling support */ return -ENOENT; ev = attr->config; - /* Count user space (problem-state) only */ if (!attr->exclude_user && attr->exclude_kernel) { - if (ev >= ARRAY_SIZE(cpumf_generic_events_user)) - return -EOPNOTSUPP; - ev = cpumf_generic_events_user[ev]; - - /* No support for kernel space counters only */ + /* + * Count user space (problem-state) only + * Handle events 32 and 33 as 0:u and 1:u + */ + if (!is_userspace_event(ev)) { + if (ev >= ARRAY_SIZE(cpumf_generic_events_user)) + return -EOPNOTSUPP; + ev = cpumf_generic_events_user[ev]; + } } else if (!attr->exclude_kernel && attr->exclude_user) { + /* No support for kernel space counters only */ return -EOPNOTSUPP; - } else { /* Count user and kernel space */ - if (ev >= ARRAY_SIZE(cpumf_generic_events_basic)) - return -EOPNOTSUPP; - ev = cpumf_generic_events_basic[ev]; + } else { + /* Count user and kernel space, incl. events 32 + 33 */ + if (!is_userspace_event(ev)) { + if (ev >= ARRAY_SIZE(cpumf_generic_events_basic)) + return -EOPNOTSUPP; + ev = cpumf_generic_events_basic[ev]; + } } break; @@ -761,31 +931,120 @@ static struct pmu cpumf_pmu = { .read = cpumf_pmu_read, }; +static int cpum_cf_setup(unsigned int cpu, unsigned long flags) +{ + local_irq_disable(); + cpum_cf_setup_cpu((void *)flags); + local_irq_enable(); + return 0; +} + +static int cfset_online_cpu(unsigned int cpu); +static int cpum_cf_online_cpu(unsigned int cpu) +{ + debug_sprintf_event(cf_dbg, 4, "%s cpu %d in_irq %ld\n", __func__, + cpu, in_interrupt()); + cpum_cf_setup(cpu, PMC_INIT); + return cfset_online_cpu(cpu); +} + +static int cfset_offline_cpu(unsigned int cpu); +static int cpum_cf_offline_cpu(unsigned int cpu) +{ + debug_sprintf_event(cf_dbg, 4, "%s cpu %d\n", __func__, cpu); + cfset_offline_cpu(cpu); + return cpum_cf_setup(cpu, PMC_RELEASE); +} + +/* Return true if store counter set multiple instruction is available */ +static inline int stccm_avail(void) +{ + return test_facility(142); +} + +/* CPU-measurement alerts for the counter facility */ +static void cpumf_measurement_alert(struct ext_code ext_code, + unsigned int alert, unsigned long unused) +{ + struct cpu_cf_events *cpuhw; + + if (!(alert & CPU_MF_INT_CF_MASK)) + return; + + inc_irq_stat(IRQEXT_CMC); + cpuhw = this_cpu_ptr(&cpu_cf_events); + + /* + * Measurement alerts are shared and might happen when the PMU + * is not reserved. Ignore these alerts in this case. + */ + if (!(cpuhw->flags & PMU_F_RESERVED)) + return; + + /* counter authorization change alert */ + if (alert & CPU_MF_INT_CF_CACA) + qctri(&cpuhw->info); + + /* loss of counter data alert */ + if (alert & CPU_MF_INT_CF_LCDA) + pr_err("CPU[%i] Counter data was lost\n", smp_processor_id()); + + /* loss of MT counter data alert */ + if (alert & CPU_MF_INT_CF_MTDA) + pr_warn("CPU[%i] MT counter data was lost\n", + smp_processor_id()); +} + static int cfset_init(void); static int __init cpumf_pmu_init(void) { int rc; - if (!kernel_cpumcf_avail()) + if (!cpum_cf_avail()) return -ENODEV; + /* + * Clear bit 15 of cr0 to unauthorize problem-state to + * extract measurement counters + */ + ctl_clear_bit(0, 48); + + /* register handler for measurement-alert interruptions */ + rc = register_external_irq(EXT_IRQ_MEASURE_ALERT, + cpumf_measurement_alert); + if (rc) { + pr_err("Registering for CPU-measurement alerts failed with rc=%i\n", rc); + return rc; + } + /* Setup s390dbf facility */ cf_dbg = debug_register(KMSG_COMPONENT, 2, 1, 128); if (!cf_dbg) { pr_err("Registration of s390dbf(cpum_cf) failed\n"); - return -ENOMEM; + rc = -ENOMEM; + goto out1; } debug_register_view(cf_dbg, &debug_sprintf_view); cpumf_pmu.attr_groups = cpumf_cf_event_group(); rc = perf_pmu_register(&cpumf_pmu, "cpum_cf", -1); if (rc) { - debug_unregister_view(cf_dbg, &debug_sprintf_view); - debug_unregister(cf_dbg); pr_err("Registering the cpum_cf PMU failed with rc=%i\n", rc); + goto out2; } else if (stccm_avail()) { /* Setup counter set device */ cfset_init(); } + + rc = cpuhp_setup_state(CPUHP_AP_PERF_S390_CF_ONLINE, + "perf/s390/cf:online", + cpum_cf_online_cpu, cpum_cf_offline_cpu); + return rc; + +out2: + debug_unregister_view(cf_dbg, &debug_sprintf_view); + debug_unregister(cf_dbg); +out1: + unregister_external_irq(EXT_IRQ_MEASURE_ALERT, cpumf_measurement_alert); return rc; } @@ -1003,7 +1262,6 @@ static int cfset_all_start(struct cfset_request *req) return rc; } - /* Return the maximum required space for all possible CPUs in case one * CPU will be onlined during the START, READ, STOP cycles. * To find out the size of the counter sets, any one CPU will do. They @@ -1266,7 +1524,7 @@ static struct miscdevice cfset_dev = { /* Hotplug add of a CPU. Scan through all active processes and add * that CPU to the list of CPUs supplied with ioctl(..., START, ...). */ -int cfset_online_cpu(unsigned int cpu) +static int cfset_online_cpu(unsigned int cpu) { struct cfset_call_on_cpu_parm p; struct cfset_request *rp; @@ -1286,7 +1544,7 @@ int cfset_online_cpu(unsigned int cpu) /* Hotplug remove of a CPU. Scan through all active processes and clear * that CPU from the list of CPUs supplied with ioctl(..., START, ...). */ -int cfset_offline_cpu(unsigned int cpu) +static int cfset_offline_cpu(unsigned int cpu) { struct cfset_call_on_cpu_parm p; struct cfset_request *rp; diff --git a/arch/s390/kernel/perf_cpum_cf_common.c b/arch/s390/kernel/perf_cpum_cf_common.c deleted file mode 100644 index 8ee48672233f..000000000000 --- a/arch/s390/kernel/perf_cpum_cf_common.c +++ /dev/null @@ -1,233 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * CPU-Measurement Counter Facility Support - Common Layer - * - * Copyright IBM Corp. 2019 - * Author(s): Hendrik Brueckner <brueckner@linux.ibm.com> - */ -#define KMSG_COMPONENT "cpum_cf_common" -#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt - -#include <linux/kernel.h> -#include <linux/kernel_stat.h> -#include <linux/percpu.h> -#include <linux/notifier.h> -#include <linux/init.h> -#include <linux/export.h> -#include <asm/ctl_reg.h> -#include <asm/irq.h> -#include <asm/cpu_mcf.h> - -/* Per-CPU event structure for the counter facility */ -DEFINE_PER_CPU(struct cpu_cf_events, cpu_cf_events) = { - .ctr_set = { - [CPUMF_CTR_SET_BASIC] = ATOMIC_INIT(0), - [CPUMF_CTR_SET_USER] = ATOMIC_INIT(0), - [CPUMF_CTR_SET_CRYPTO] = ATOMIC_INIT(0), - [CPUMF_CTR_SET_EXT] = ATOMIC_INIT(0), - [CPUMF_CTR_SET_MT_DIAG] = ATOMIC_INIT(0), - }, - .alert = ATOMIC64_INIT(0), - .state = 0, - .dev_state = 0, - .flags = 0, - .used = 0, - .usedss = 0, - .sets = 0 -}; -/* Indicator whether the CPU-Measurement Counter Facility Support is ready */ -static bool cpum_cf_initalized; - -/* CPU-measurement alerts for the counter facility */ -static void cpumf_measurement_alert(struct ext_code ext_code, - unsigned int alert, unsigned long unused) -{ - struct cpu_cf_events *cpuhw; - - if (!(alert & CPU_MF_INT_CF_MASK)) - return; - - inc_irq_stat(IRQEXT_CMC); - cpuhw = this_cpu_ptr(&cpu_cf_events); - - /* Measurement alerts are shared and might happen when the PMU - * is not reserved. Ignore these alerts in this case. */ - if (!(cpuhw->flags & PMU_F_RESERVED)) - return; - - /* counter authorization change alert */ - if (alert & CPU_MF_INT_CF_CACA) - qctri(&cpuhw->info); - - /* loss of counter data alert */ - if (alert & CPU_MF_INT_CF_LCDA) - pr_err("CPU[%i] Counter data was lost\n", smp_processor_id()); - - /* loss of MT counter data alert */ - if (alert & CPU_MF_INT_CF_MTDA) - pr_warn("CPU[%i] MT counter data was lost\n", - smp_processor_id()); - - /* store alert for special handling by in-kernel users */ - atomic64_or(alert, &cpuhw->alert); -} - -#define PMC_INIT 0 -#define PMC_RELEASE 1 -static void cpum_cf_setup_cpu(void *flags) -{ - struct cpu_cf_events *cpuhw = this_cpu_ptr(&cpu_cf_events); - - switch (*((int *) flags)) { - case PMC_INIT: - memset(&cpuhw->info, 0, sizeof(cpuhw->info)); - qctri(&cpuhw->info); - cpuhw->flags |= PMU_F_RESERVED; - break; - - case PMC_RELEASE: - cpuhw->flags &= ~PMU_F_RESERVED; - break; - } - - /* Disable CPU counter sets */ - lcctl(0); -} - -bool kernel_cpumcf_avail(void) -{ - return cpum_cf_initalized; -} -EXPORT_SYMBOL(kernel_cpumcf_avail); - -/* Initialize the CPU-measurement counter facility */ -int __kernel_cpumcf_begin(void) -{ - int flags = PMC_INIT; - - on_each_cpu(cpum_cf_setup_cpu, &flags, 1); - irq_subclass_register(IRQ_SUBCLASS_MEASUREMENT_ALERT); - - return 0; -} -EXPORT_SYMBOL(__kernel_cpumcf_begin); - -/* Obtain the CPU-measurement alerts for the counter facility */ -unsigned long kernel_cpumcf_alert(int clear) -{ - struct cpu_cf_events *cpuhw = this_cpu_ptr(&cpu_cf_events); - unsigned long alert; - - alert = atomic64_read(&cpuhw->alert); - if (clear) - atomic64_set(&cpuhw->alert, 0); - - return alert; -} -EXPORT_SYMBOL(kernel_cpumcf_alert); - -/* Release the CPU-measurement counter facility */ -void __kernel_cpumcf_end(void) -{ - int flags = PMC_RELEASE; - - on_each_cpu(cpum_cf_setup_cpu, &flags, 1); - irq_subclass_unregister(IRQ_SUBCLASS_MEASUREMENT_ALERT); -} -EXPORT_SYMBOL(__kernel_cpumcf_end); - -static int cpum_cf_setup(unsigned int cpu, int flags) -{ - local_irq_disable(); - cpum_cf_setup_cpu(&flags); - local_irq_enable(); - return 0; -} - -static int cpum_cf_online_cpu(unsigned int cpu) -{ - cpum_cf_setup(cpu, PMC_INIT); - return cfset_online_cpu(cpu); -} - -static int cpum_cf_offline_cpu(unsigned int cpu) -{ - cfset_offline_cpu(cpu); - return cpum_cf_setup(cpu, PMC_RELEASE); -} - -/* Return the maximum possible counter set size (in number of 8 byte counters) - * depending on type and model number. - */ -size_t cpum_cf_ctrset_size(enum cpumf_ctr_set ctrset, - struct cpumf_ctr_info *info) -{ - size_t ctrset_size = 0; - - switch (ctrset) { - case CPUMF_CTR_SET_BASIC: - if (info->cfvn >= 1) - ctrset_size = 6; - break; - case CPUMF_CTR_SET_USER: - if (info->cfvn == 1) - ctrset_size = 6; - else if (info->cfvn >= 3) - ctrset_size = 2; - break; - case CPUMF_CTR_SET_CRYPTO: - if (info->csvn >= 1 && info->csvn <= 5) - ctrset_size = 16; - else if (info->csvn == 6 || info->csvn == 7) - ctrset_size = 20; - break; - case CPUMF_CTR_SET_EXT: - if (info->csvn == 1) - ctrset_size = 32; - else if (info->csvn == 2) - ctrset_size = 48; - else if (info->csvn >= 3 && info->csvn <= 5) - ctrset_size = 128; - else if (info->csvn == 6 || info->csvn == 7) - ctrset_size = 160; - break; - case CPUMF_CTR_SET_MT_DIAG: - if (info->csvn > 3) - ctrset_size = 48; - break; - case CPUMF_CTR_SET_MAX: - break; - } - - return ctrset_size; -} - -static int __init cpum_cf_init(void) -{ - int rc; - - if (!cpum_cf_avail()) - return -ENODEV; - - /* clear bit 15 of cr0 to unauthorize problem-state to - * extract measurement counters */ - ctl_clear_bit(0, 48); - - /* register handler for measurement-alert interruptions */ - rc = register_external_irq(EXT_IRQ_MEASURE_ALERT, - cpumf_measurement_alert); - if (rc) { - pr_err("Registering for CPU-measurement alerts " - "failed with rc=%i\n", rc); - return rc; - } - - rc = cpuhp_setup_state(CPUHP_AP_PERF_S390_CF_ONLINE, - "perf/s390/cf:online", - cpum_cf_online_cpu, cpum_cf_offline_cpu); - if (!rc) - cpum_cf_initalized = true; - - return rc; -} -early_initcall(cpum_cf_init); diff --git a/arch/s390/kernel/perf_cpum_sf.c b/arch/s390/kernel/perf_cpum_sf.c index ead6eea48be8..79904a839fb9 100644 --- a/arch/s390/kernel/perf_cpum_sf.c +++ b/arch/s390/kernel/perf_cpum_sf.c @@ -22,6 +22,7 @@ #include <asm/irq.h> #include <asm/debug.h> #include <asm/timex.h> +#include <asm-generic/io.h> /* Minimum number of sample-data-block-tables: * At least one table is required for the sampling buffer structure. @@ -99,6 +100,57 @@ static DEFINE_PER_CPU(struct cpu_hw_sf, cpu_hw_sf); /* Debug feature */ static debug_info_t *sfdbg; +/* Sampling control helper functions */ +static inline unsigned long freq_to_sample_rate(struct hws_qsi_info_block *qsi, + unsigned long freq) +{ + return (USEC_PER_SEC / freq) * qsi->cpu_speed; +} + +static inline unsigned long sample_rate_to_freq(struct hws_qsi_info_block *qsi, + unsigned long rate) +{ + return USEC_PER_SEC * qsi->cpu_speed / rate; +} + +/* Return TOD timestamp contained in an trailer entry */ +static inline unsigned long long trailer_timestamp(struct hws_trailer_entry *te) +{ + /* TOD in STCKE format */ + if (te->header.t) + return *((unsigned long long *)&te->timestamp[1]); + + /* TOD in STCK format */ + return *((unsigned long long *)&te->timestamp[0]); +} + +/* Return pointer to trailer entry of an sample data block */ +static inline struct hws_trailer_entry *trailer_entry_ptr(unsigned long v) +{ + void *ret; + + ret = (void *)v; + ret += PAGE_SIZE; + ret -= sizeof(struct hws_trailer_entry); + + return ret; +} + +/* + * Return true if the entry in the sample data block table (sdbt) + * is a link to the next sdbt + */ +static inline int is_link_entry(unsigned long *s) +{ + return *s & 0x1UL ? 1 : 0; +} + +/* Return pointer to the linked sdbt */ +static inline unsigned long *get_next_sdbt(unsigned long *s) +{ + return phys_to_virt(*s & ~0x1UL); +} + /* * sf_disable() - Switch off sampling facility */ @@ -150,7 +202,7 @@ static void free_sampling_buffer(struct sf_buffer *sfb) } else { /* Process SDB pointer */ if (*curr) { - free_page(*curr); + free_page((unsigned long)phys_to_virt(*curr)); curr++; } } @@ -170,11 +222,11 @@ static int alloc_sample_data_block(unsigned long *sdbt, gfp_t gfp_flags) sdb = get_zeroed_page(gfp_flags); if (!sdb) return -ENOMEM; - te = (struct hws_trailer_entry *)trailer_entry_ptr(sdb); + te = trailer_entry_ptr(sdb); te->header.a = 1; /* Link SDB into the sample-data-block-table */ - *sdbt = sdb; + *sdbt = virt_to_phys((void *)sdb); return 0; } @@ -233,7 +285,7 @@ static int realloc_sampling_buffer(struct sf_buffer *sfb, } sfb->num_sdbt++; /* Link current page to tail of chain */ - *tail = (unsigned long)(void *) new + 1; + *tail = virt_to_phys((void *)new) + 1; tail_prev = tail; tail = new; } @@ -263,7 +315,7 @@ static int realloc_sampling_buffer(struct sf_buffer *sfb, } /* Link sampling buffer to its origin */ - *tail = (unsigned long) sfb->sdbt + 1; + *tail = virt_to_phys(sfb->sdbt) + 1; sfb->tail = tail; debug_sprintf_event(sfdbg, 4, "%s: new buffer" @@ -301,7 +353,7 @@ static int alloc_sampling_buffer(struct sf_buffer *sfb, unsigned long num_sdb) * realloc_sampling_buffer() invocation. */ sfb->tail = sfb->sdbt; - *sfb->tail = (unsigned long)(void *) sfb->sdbt + 1; + *sfb->tail = virt_to_phys((void *)sfb->sdbt) + 1; /* Allocate requested number of sample-data-blocks */ rc = realloc_sampling_buffer(sfb, num_sdb, GFP_KERNEL); @@ -557,9 +609,6 @@ static void setup_pmc_cpu(void *flags) if (err) pr_err("Switching off the sampling facility failed " "with rc %i\n", err); - debug_sprintf_event(sfdbg, 5, - "%s: initialized: cpuhw %p\n", __func__, - cpusf); break; case PMC_RELEASE: cpusf->flags &= ~PMU_F_RESERVED; @@ -569,9 +618,6 @@ static void setup_pmc_cpu(void *flags) "with rc %i\n", err); } else deallocate_buffers(cpusf); - debug_sprintf_event(sfdbg, 5, - "%s: released: cpuhw %p\n", __func__, - cpusf); break; } if (err) @@ -1177,8 +1223,8 @@ static void hw_collect_samples(struct perf_event *event, unsigned long *sdbt, struct hws_trailer_entry *te; struct hws_basic_entry *sample; - te = (struct hws_trailer_entry *) trailer_entry_ptr(*sdbt); - sample = (struct hws_basic_entry *) *sdbt; + te = trailer_entry_ptr((unsigned long)sdbt); + sample = (struct hws_basic_entry *)sdbt; while ((unsigned long *) sample < (unsigned long *) te) { /* Check for an empty sample */ if (!sample->def || sample->LS) @@ -1259,7 +1305,7 @@ static void hw_perf_event_update(struct perf_event *event, int flush_all) union hws_trailer_header old, prev, new; struct hw_perf_event *hwc = &event->hw; struct hws_trailer_entry *te; - unsigned long *sdbt; + unsigned long *sdbt, sdb; int done; /* @@ -1276,7 +1322,8 @@ static void hw_perf_event_update(struct perf_event *event, int flush_all) done = event_overflow = sampl_overflow = num_sdb = 0; while (!done) { /* Get the trailer entry of the sample-data-block */ - te = (struct hws_trailer_entry *) trailer_entry_ptr(*sdbt); + sdb = (unsigned long)phys_to_virt(*sdbt); + te = trailer_entry_ptr(sdb); /* Leave loop if no more work to do (block full indicator) */ if (!te->header.f) { @@ -1294,16 +1341,17 @@ static void hw_perf_event_update(struct perf_event *event, int flush_all) sampl_overflow += te->header.overflow; /* Timestamps are valid for full sample-data-blocks only */ - debug_sprintf_event(sfdbg, 6, "%s: sdbt %#lx " + debug_sprintf_event(sfdbg, 6, "%s: sdbt %#lx/%#lx " "overflow %llu timestamp %#llx\n", - __func__, (unsigned long)sdbt, te->header.overflow, + __func__, sdb, (unsigned long)sdbt, + te->header.overflow, (te->header.f) ? trailer_timestamp(te) : 0ULL); /* Collect all samples from a single sample-data-block and * flag if an (perf) event overflow happened. If so, the PMU * is stopped and remaining samples will be discarded. */ - hw_collect_samples(event, sdbt, &event_overflow); + hw_collect_samples(event, (unsigned long *)sdb, &event_overflow); num_sdb++; /* Reset trailer (using compare-double-and-swap) */ @@ -1361,10 +1409,26 @@ static void hw_perf_event_update(struct perf_event *event, int flush_all) OVERFLOW_REG(hwc), num_sdb); } -#define AUX_SDB_INDEX(aux, i) ((i) % aux->sfb.num_sdb) -#define AUX_SDB_NUM(aux, start, end) (end >= start ? end - start + 1 : 0) -#define AUX_SDB_NUM_ALERT(aux) AUX_SDB_NUM(aux, aux->head, aux->alert_mark) -#define AUX_SDB_NUM_EMPTY(aux) AUX_SDB_NUM(aux, aux->head, aux->empty_mark) +static inline unsigned long aux_sdb_index(struct aux_buffer *aux, + unsigned long i) +{ + return i % aux->sfb.num_sdb; +} + +static inline unsigned long aux_sdb_num(unsigned long start, unsigned long end) +{ + return end >= start ? end - start + 1 : 0; +} + +static inline unsigned long aux_sdb_num_alert(struct aux_buffer *aux) +{ + return aux_sdb_num(aux->head, aux->alert_mark); +} + +static inline unsigned long aux_sdb_num_empty(struct aux_buffer *aux) +{ + return aux_sdb_num(aux->head, aux->empty_mark); +} /* * Get trailer entry by index of SDB. @@ -1374,9 +1438,9 @@ static struct hws_trailer_entry *aux_sdb_trailer(struct aux_buffer *aux, { unsigned long sdb; - index = AUX_SDB_INDEX(aux, index); + index = aux_sdb_index(aux, index); sdb = aux->sdb_index[index]; - return (struct hws_trailer_entry *)trailer_entry_ptr(sdb); + return trailer_entry_ptr(sdb); } /* @@ -1398,7 +1462,7 @@ static void aux_output_end(struct perf_output_handle *handle) if (!aux) return; - range_scan = AUX_SDB_NUM_ALERT(aux); + range_scan = aux_sdb_num_alert(aux); for (i = 0, idx = aux->head; i < range_scan; i++, idx++) { te = aux_sdb_trailer(aux, idx); if (!te->header.f) @@ -1428,9 +1492,7 @@ static int aux_output_begin(struct perf_output_handle *handle, struct aux_buffer *aux, struct cpu_hw_sf *cpuhw) { - unsigned long range; - unsigned long i, range_scan, idx; - unsigned long head, base, offset; + unsigned long range, i, range_scan, idx, head, base, offset; struct hws_trailer_entry *te; if (WARN_ON_ONCE(handle->head & ~PAGE_MASK)) @@ -1449,8 +1511,8 @@ static int aux_output_begin(struct perf_output_handle *handle, "%s: range %ld head %ld alert %ld empty %ld\n", __func__, range, aux->head, aux->alert_mark, aux->empty_mark); - if (range > AUX_SDB_NUM_EMPTY(aux)) { - range_scan = range - AUX_SDB_NUM_EMPTY(aux); + if (range > aux_sdb_num_empty(aux)) { + range_scan = range - aux_sdb_num_empty(aux); idx = aux->empty_mark + 1; for (i = 0; i < range_scan; i++, idx++) { te = aux_sdb_trailer(aux, idx); @@ -1468,11 +1530,11 @@ static int aux_output_begin(struct perf_output_handle *handle, te->header.a = 1; /* Reset hardware buffer head */ - head = AUX_SDB_INDEX(aux, aux->head); + head = aux_sdb_index(aux, aux->head); base = aux->sdbt_index[head / CPUM_SF_SDB_PER_TABLE]; offset = head % CPUM_SF_SDB_PER_TABLE; - cpuhw->lsctl.tear = base + offset * sizeof(unsigned long); - cpuhw->lsctl.dear = aux->sdb_index[head]; + cpuhw->lsctl.tear = virt_to_phys((void *)base) + offset * sizeof(unsigned long); + cpuhw->lsctl.dear = virt_to_phys((void *)aux->sdb_index[head]); debug_sprintf_event(sfdbg, 6, "%s: head %ld alert %ld empty %ld " "index %ld tear %#lx dear %#lx\n", __func__, @@ -1550,7 +1612,7 @@ static bool aux_reset_buffer(struct aux_buffer *aux, unsigned long range, debug_sprintf_event(sfdbg, 6, "%s: range %ld head %ld alert %ld " "empty %ld\n", __func__, range, aux->head, aux->alert_mark, aux->empty_mark); - if (range <= AUX_SDB_NUM_EMPTY(aux)) + if (range <= aux_sdb_num_empty(aux)) /* * No need to scan. All SDBs in range are marked as empty. * Just set alert indicator. Should check race with hardware @@ -1571,7 +1633,7 @@ static bool aux_reset_buffer(struct aux_buffer *aux, unsigned long range, * Start scanning from one SDB behind empty_mark. If the new alert * indicator fall into this range, set it. */ - range_scan = range - AUX_SDB_NUM_EMPTY(aux); + range_scan = range - aux_sdb_num_empty(aux); idx_old = idx = aux->empty_mark + 1; for (i = 0; i < range_scan; i++, idx++) { te = aux_sdb_trailer(aux, idx); @@ -1618,7 +1680,7 @@ static void hw_collect_aux(struct cpu_hw_sf *cpuhw) return; /* Inform user space new data arrived */ - size = AUX_SDB_NUM_ALERT(aux) << PAGE_SHIFT; + size = aux_sdb_num_alert(aux) << PAGE_SHIFT; debug_sprintf_event(sfdbg, 6, "%s: #alert %ld\n", __func__, size >> PAGE_SHIFT); perf_aux_output_end(handle, size); @@ -1660,7 +1722,7 @@ static void hw_collect_aux(struct cpu_hw_sf *cpuhw) "overflow %lld\n", __func__, aux->head, range, overflow); } else { - size = AUX_SDB_NUM_ALERT(aux) << PAGE_SHIFT; + size = aux_sdb_num_alert(aux) << PAGE_SHIFT; perf_aux_output_end(&cpuhw->handle, size); debug_sprintf_event(sfdbg, 6, "%s: head %ld alert %ld " "already full, try another\n", @@ -1702,7 +1764,7 @@ static void aux_sdb_init(unsigned long sdb) { struct hws_trailer_entry *te; - te = (struct hws_trailer_entry *)trailer_entry_ptr(sdb); + te = trailer_entry_ptr(sdb); /* Save clock base */ te->clock_base = 1; @@ -1782,18 +1844,18 @@ static void *aux_buffer_setup(struct perf_event *event, void **pages, goto no_sdbt; aux->sdbt_index[sfb->num_sdbt++] = (unsigned long)new; /* Link current page to tail of chain */ - *tail = (unsigned long)(void *) new + 1; + *tail = virt_to_phys(new) + 1; tail = new; } /* Tail is the entry in a SDBT */ - *tail = (unsigned long)pages[i]; + *tail = virt_to_phys(pages[i]); aux->sdb_index[i] = (unsigned long)pages[i]; aux_sdb_init((unsigned long)pages[i]); } sfb->num_sdb = nr_pages; /* Link the last entry in the SDBT to the first SDBT */ - *tail = (unsigned long) sfb->sdbt + 1; + *tail = virt_to_phys(sfb->sdbt) + 1; sfb->tail = tail; /* @@ -1933,7 +1995,7 @@ static int cpumsf_pmu_add(struct perf_event *event, int flags) cpuhw->lsctl.h = 1; cpuhw->lsctl.interval = SAMPL_RATE(&event->hw); if (!SAMPL_DIAG_MODE(&event->hw)) { - cpuhw->lsctl.tear = (unsigned long) cpuhw->sfb.sdbt; + cpuhw->lsctl.tear = virt_to_phys(cpuhw->sfb.sdbt); cpuhw->lsctl.dear = *(unsigned long *) cpuhw->sfb.sdbt; TEAR_REG(&event->hw) = (unsigned long) cpuhw->sfb.sdbt; } diff --git a/arch/s390/kernel/perf_pai_ext.c b/arch/s390/kernel/perf_pai_ext.c index 555597222bad..fcea307d7529 100644 --- a/arch/s390/kernel/perf_pai_ext.c +++ b/arch/s390/kernel/perf_pai_ext.c @@ -16,8 +16,8 @@ #include <linux/init.h> #include <linux/export.h> #include <linux/io.h> +#include <linux/perf_event.h> -#include <asm/cpu_mcf.h> #include <asm/ctl_reg.h> #include <asm/pai.h> #include <asm/debug.h> diff --git a/arch/s390/kernel/process.c b/arch/s390/kernel/process.c index 3f5d2db0b854..67df64ef4839 100644 --- a/arch/s390/kernel/process.c +++ b/arch/s390/kernel/process.c @@ -147,8 +147,8 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args) if (unlikely(args->fn)) { /* kernel thread */ memset(&frame->childregs, 0, sizeof(struct pt_regs)); - frame->childregs.psw.mask = PSW_KERNEL_BITS | PSW_MASK_DAT | - PSW_MASK_IO | PSW_MASK_EXT | PSW_MASK_MCHECK; + frame->childregs.psw.mask = PSW_KERNEL_BITS | PSW_MASK_IO | + PSW_MASK_EXT | PSW_MASK_MCHECK; frame->childregs.psw.addr = (unsigned long)__ret_from_fork; frame->childregs.gprs[9] = (unsigned long)args->fn; diff --git a/arch/s390/kernel/ptrace.c b/arch/s390/kernel/ptrace.c index 53e0209229f8..cf9659e13f03 100644 --- a/arch/s390/kernel/ptrace.c +++ b/arch/s390/kernel/ptrace.c @@ -990,7 +990,7 @@ static int s390_vxrs_low_get(struct task_struct *target, if (target == current) save_fpu_regs(); for (i = 0; i < __NUM_VXRS_LOW; i++) - vxrs[i] = *((__u64 *)(target->thread.fpu.vxrs + i) + 1); + vxrs[i] = target->thread.fpu.vxrs[i].low; return membuf_write(&to, vxrs, sizeof(vxrs)); } @@ -1008,12 +1008,12 @@ static int s390_vxrs_low_set(struct task_struct *target, save_fpu_regs(); for (i = 0; i < __NUM_VXRS_LOW; i++) - vxrs[i] = *((__u64 *)(target->thread.fpu.vxrs + i) + 1); + vxrs[i] = target->thread.fpu.vxrs[i].low; rc = user_regset_copyin(&pos, &count, &kbuf, &ubuf, vxrs, 0, -1); if (rc == 0) for (i = 0; i < __NUM_VXRS_LOW; i++) - *((__u64 *)(target->thread.fpu.vxrs + i) + 1) = vxrs[i]; + target->thread.fpu.vxrs[i].low = vxrs[i]; return rc; } diff --git a/arch/s390/kernel/rethook.c b/arch/s390/kernel/rethook.c new file mode 100644 index 000000000000..af10e6bdd34e --- /dev/null +++ b/arch/s390/kernel/rethook.c @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <linux/rethook.h> +#include <linux/kprobes.h> +#include "rethook.h" + +void arch_rethook_prepare(struct rethook_node *rh, struct pt_regs *regs, bool mcount) +{ + rh->ret_addr = regs->gprs[14]; + rh->frame = regs->gprs[15]; + + /* Replace the return addr with trampoline addr */ + regs->gprs[14] = (unsigned long)&arch_rethook_trampoline; +} +NOKPROBE_SYMBOL(arch_rethook_prepare); + +void arch_rethook_fixup_return(struct pt_regs *regs, + unsigned long correct_ret_addr) +{ + /* Replace fake return address with real one. */ + regs->gprs[14] = correct_ret_addr; +} +NOKPROBE_SYMBOL(arch_rethook_fixup_return); + +/* + * Called from arch_rethook_trampoline + */ +unsigned long arch_rethook_trampoline_callback(struct pt_regs *regs) +{ + return rethook_trampoline_handler(regs, regs->gprs[15]); +} +NOKPROBE_SYMBOL(arch_rethook_trampoline_callback); + +/* assembler function that handles the rethook must not be probed itself */ +NOKPROBE_SYMBOL(arch_rethook_trampoline); diff --git a/arch/s390/kernel/rethook.h b/arch/s390/kernel/rethook.h new file mode 100644 index 000000000000..32f069eed3f3 --- /dev/null +++ b/arch/s390/kernel/rethook.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef __S390_RETHOOK_H +#define __S390_RETHOOK_H + +unsigned long arch_rethook_trampoline_callback(struct pt_regs *regs); + +#endif diff --git a/arch/s390/kernel/setup.c b/arch/s390/kernel/setup.c index 696c9e007a36..8ec5cdf9dadc 100644 --- a/arch/s390/kernel/setup.c +++ b/arch/s390/kernel/setup.c @@ -149,6 +149,9 @@ int __bootdata(noexec_disabled); unsigned long __bootdata(ident_map_size); struct mem_detect_info __bootdata(mem_detect); struct initrd_data __bootdata(initrd_data); +unsigned long __bootdata(pgalloc_pos); +unsigned long __bootdata(pgalloc_end); +unsigned long __bootdata(pgalloc_low); unsigned long __bootdata_preserved(__kaslr_offset); unsigned long __bootdata(__amode31_base); @@ -411,15 +414,10 @@ void __init arch_call_rest_init(void) call_on_stack_noreturn(rest_init, stack); } -static void __init setup_lowcore_dat_off(void) +static void __init setup_lowcore(void) { - unsigned long int_psw_mask = PSW_KERNEL_BITS; - struct lowcore *abs_lc, *lc; + struct lowcore *lc, *abs_lc; unsigned long mcck_stack; - unsigned long flags; - - if (IS_ENABLED(CONFIG_KASAN)) - int_psw_mask |= PSW_MASK_DAT; /* * Setup lowcore for boot cpu @@ -430,17 +428,17 @@ static void __init setup_lowcore_dat_off(void) panic("%s: Failed to allocate %zu bytes align=%zx\n", __func__, sizeof(*lc), sizeof(*lc)); - lc->restart_psw.mask = PSW_KERNEL_BITS; - lc->restart_psw.addr = (unsigned long) restart_int_handler; - lc->external_new_psw.mask = int_psw_mask | PSW_MASK_MCHECK; + lc->restart_psw.mask = PSW_KERNEL_BITS & ~PSW_MASK_DAT; + lc->restart_psw.addr = __pa(restart_int_handler); + lc->external_new_psw.mask = PSW_KERNEL_BITS | PSW_MASK_MCHECK; lc->external_new_psw.addr = (unsigned long) ext_int_handler; - lc->svc_new_psw.mask = int_psw_mask | PSW_MASK_MCHECK; + lc->svc_new_psw.mask = PSW_KERNEL_BITS | PSW_MASK_MCHECK; lc->svc_new_psw.addr = (unsigned long) system_call; - lc->program_new_psw.mask = int_psw_mask | PSW_MASK_MCHECK; + lc->program_new_psw.mask = PSW_KERNEL_BITS | PSW_MASK_MCHECK; lc->program_new_psw.addr = (unsigned long) pgm_check_handler; - lc->mcck_new_psw.mask = int_psw_mask; + lc->mcck_new_psw.mask = PSW_KERNEL_BITS; lc->mcck_new_psw.addr = (unsigned long) mcck_int_handler; - lc->io_new_psw.mask = int_psw_mask | PSW_MASK_MCHECK; + lc->io_new_psw.mask = PSW_KERNEL_BITS | PSW_MASK_MCHECK; lc->io_new_psw.addr = (unsigned long) io_int_handler; lc->clock_comparator = clock_comparator_max; lc->nodat_stack = ((unsigned long) &init_thread_union) @@ -477,15 +475,7 @@ static void __init setup_lowcore_dat_off(void) lc->restart_fn = (unsigned long) do_restart; lc->restart_data = 0; lc->restart_source = -1U; - - abs_lc = get_abs_lowcore(&flags); - abs_lc->restart_stack = lc->restart_stack; - abs_lc->restart_fn = lc->restart_fn; - abs_lc->restart_data = lc->restart_data; - abs_lc->restart_source = lc->restart_source; - abs_lc->restart_psw = lc->restart_psw; - abs_lc->mcesad = lc->mcesad; - put_abs_lowcore(abs_lc, flags); + __ctl_store(lc->cregs_save_area, 0, 15); mcck_stack = (unsigned long)memblock_alloc(THREAD_SIZE, THREAD_SIZE); if (!mcck_stack) @@ -499,34 +489,25 @@ static void __init setup_lowcore_dat_off(void) lc->return_lpswe = gen_lpswe(__LC_RETURN_PSW); lc->return_mcck_lpswe = gen_lpswe(__LC_RETURN_MCCK_PSW); lc->preempt_count = PREEMPT_DISABLED; + lc->kernel_asce = S390_lowcore.kernel_asce; + lc->user_asce = S390_lowcore.user_asce; + + abs_lc = get_abs_lowcore(); + abs_lc->restart_stack = lc->restart_stack; + abs_lc->restart_fn = lc->restart_fn; + abs_lc->restart_data = lc->restart_data; + abs_lc->restart_source = lc->restart_source; + abs_lc->restart_psw = lc->restart_psw; + abs_lc->restart_flags = RESTART_FLAG_CTLREGS; + memcpy(abs_lc->cregs_save_area, lc->cregs_save_area, sizeof(abs_lc->cregs_save_area)); + abs_lc->program_new_psw = lc->program_new_psw; + abs_lc->mcesad = lc->mcesad; + put_abs_lowcore(abs_lc); set_prefix(__pa(lc)); lowcore_ptr[0] = lc; -} - -static void __init setup_lowcore_dat_on(void) -{ - struct lowcore *abs_lc; - unsigned long flags; - int i; - - __ctl_clear_bit(0, 28); - S390_lowcore.external_new_psw.mask |= PSW_MASK_DAT; - S390_lowcore.svc_new_psw.mask |= PSW_MASK_DAT; - S390_lowcore.program_new_psw.mask |= PSW_MASK_DAT; - S390_lowcore.mcck_new_psw.mask |= PSW_MASK_DAT; - S390_lowcore.io_new_psw.mask |= PSW_MASK_DAT; - __ctl_set_bit(0, 28); - __ctl_store(S390_lowcore.cregs_save_area, 0, 15); - if (abs_lowcore_map(0, lowcore_ptr[0], true)) + if (abs_lowcore_map(0, lowcore_ptr[0], false)) panic("Couldn't setup absolute lowcore"); - abs_lowcore_mapped = true; - abs_lc = get_abs_lowcore(&flags); - abs_lc->restart_flags = RESTART_FLAG_CTLREGS; - abs_lc->program_new_psw = S390_lowcore.program_new_psw; - for (i = 0; i < 16; i++) - abs_lc->cregs_save_area[i] = S390_lowcore.cregs_save_area[i]; - put_abs_lowcore(abs_lc, flags); } static struct resource code_resource = { @@ -619,7 +600,6 @@ static void __init setup_resources(void) static void __init setup_memory_end(void) { - memblock_remove(ident_map_size, PHYS_ADDR_MAX - ident_map_size); max_pfn = max_low_pfn = PFN_DOWN(ident_map_size); pr_notice("The maximum memory size is %luMB\n", ident_map_size >> 20); } @@ -651,6 +631,14 @@ static struct notifier_block kdump_mem_nb = { #endif /* + * Reserve page tables created by decompressor + */ +static void __init reserve_pgtables(void) +{ + memblock_reserve(pgalloc_pos, pgalloc_end - pgalloc_pos); +} + +/* * Reserve memory for kdump kernel to be loaded with kexec */ static void __init reserve_crashkernel(void) @@ -784,10 +772,10 @@ static void __init memblock_add_mem_detect_info(void) get_mem_info_source(), mem_detect.info_source); /* keep memblock lists close to the kernel */ memblock_set_bottom_up(true); - for_each_mem_detect_block(i, &start, &end) { + for_each_mem_detect_usable_block(i, &start, &end) memblock_add(start, end - start); + for_each_mem_detect_block(i, &start, &end) memblock_physmem_add(start, end - start); - } memblock_set_bottom_up(false); memblock_set_node(0, ULONG_MAX, &memblock.memory, 0); } @@ -1005,6 +993,7 @@ void __init setup_arch(char **cmdline_p) setup_control_program_code(); /* Do some memory reservations *before* memory is added to memblock */ + reserve_pgtables(); reserve_kernel(); reserve_initrd(); reserve_certificate_list(); @@ -1039,7 +1028,7 @@ void __init setup_arch(char **cmdline_p) #endif setup_resources(); - setup_lowcore_dat_off(); + setup_lowcore(); smp_fill_possible_mask(); cpu_detect_mhz_feature(); cpu_init(); @@ -1051,15 +1040,14 @@ void __init setup_arch(char **cmdline_p) static_branch_enable(&cpu_has_bear); /* - * Create kernel page tables and switch to virtual addressing. + * Create kernel page tables. */ paging_init(); - memcpy_real_init(); + /* * After paging_init created the kernel page table, the new PSWs * in lowcore can now run with DAT enabled. */ - setup_lowcore_dat_on(); #ifdef CONFIG_CRASH_DUMP smp_save_dump_ipl_cpu(); #endif diff --git a/arch/s390/kernel/signal.c b/arch/s390/kernel/signal.c index 38258f817048..d63557d3868c 100644 --- a/arch/s390/kernel/signal.c +++ b/arch/s390/kernel/signal.c @@ -184,7 +184,7 @@ static int save_sigregs_ext(struct pt_regs *regs, /* Save vector registers to signal stack */ if (MACHINE_HAS_VX) { for (i = 0; i < __NUM_VXRS_LOW; i++) - vxrs[i] = *((__u64 *)(current->thread.fpu.vxrs + i) + 1); + vxrs[i] = current->thread.fpu.vxrs[i].low; if (__copy_to_user(&sregs_ext->vxrs_low, vxrs, sizeof(sregs_ext->vxrs_low)) || __copy_to_user(&sregs_ext->vxrs_high, @@ -210,7 +210,7 @@ static int restore_sigregs_ext(struct pt_regs *regs, sizeof(sregs_ext->vxrs_high))) return -EFAULT; for (i = 0; i < __NUM_VXRS_LOW; i++) - *((__u64 *)(current->thread.fpu.vxrs + i) + 1) = vxrs[i]; + current->thread.fpu.vxrs[i].low = vxrs[i]; } return 0; } diff --git a/arch/s390/kernel/smp.c b/arch/s390/kernel/smp.c index 0031325ce4bc..23c427284773 100644 --- a/arch/s390/kernel/smp.c +++ b/arch/s390/kernel/smp.c @@ -323,11 +323,10 @@ static void pcpu_delegate(struct pcpu *pcpu, { struct lowcore *lc, *abs_lc; unsigned int source_cpu; - unsigned long flags; lc = lowcore_ptr[pcpu - pcpu_devices]; source_cpu = stap(); - __load_psw_mask(PSW_KERNEL_BITS | PSW_MASK_DAT); + if (pcpu->address == source_cpu) { call_on_stack(2, stack, void, __pcpu_delegate, pcpu_delegate_fn *, func, void *, data); @@ -341,12 +340,12 @@ static void pcpu_delegate(struct pcpu *pcpu, lc->restart_data = (unsigned long)data; lc->restart_source = source_cpu; } else { - abs_lc = get_abs_lowcore(&flags); + abs_lc = get_abs_lowcore(); abs_lc->restart_stack = stack; abs_lc->restart_fn = (unsigned long)func; abs_lc->restart_data = (unsigned long)data; abs_lc->restart_source = source_cpu; - put_abs_lowcore(abs_lc, flags); + put_abs_lowcore(abs_lc); } __bpon(); asm volatile( @@ -488,7 +487,7 @@ void smp_send_stop(void) int cpu; /* Disable all interrupts/machine checks */ - __load_psw_mask(PSW_KERNEL_BITS | PSW_MASK_DAT); + __load_psw_mask(PSW_KERNEL_BITS); trace_hardirqs_off(); debug_set_critical(); @@ -593,7 +592,6 @@ void smp_ctl_set_clear_bit(int cr, int bit, bool set) { struct ec_creg_mask_parms parms = { .cr = cr, }; struct lowcore *abs_lc; - unsigned long flags; u64 ctlreg; if (set) { @@ -604,11 +602,11 @@ void smp_ctl_set_clear_bit(int cr, int bit, bool set) parms.andval = ~(1UL << bit); } spin_lock(&ctl_lock); - abs_lc = get_abs_lowcore(&flags); + abs_lc = get_abs_lowcore(); ctlreg = abs_lc->cregs_save_area[cr]; ctlreg = (ctlreg & parms.andval) | parms.orval; abs_lc->cregs_save_area[cr] = ctlreg; - put_abs_lowcore(abs_lc, flags); + put_abs_lowcore(abs_lc); spin_unlock(&ctl_lock); on_each_cpu(smp_ctl_bit_callback, &parms, 1); } diff --git a/arch/s390/kernel/stacktrace.c b/arch/s390/kernel/stacktrace.c index 7ee455e8e3d5..0787010139f7 100644 --- a/arch/s390/kernel/stacktrace.c +++ b/arch/s390/kernel/stacktrace.c @@ -40,12 +40,12 @@ int arch_stack_walk_reliable(stack_trace_consume_fn consume_entry, if (!addr) return -EINVAL; -#ifdef CONFIG_KPROBES +#ifdef CONFIG_RETHOOK /* - * Mark stacktraces with kretprobed functions on them + * Mark stacktraces with krethook functions on them * as unreliable. */ - if (state.ip == (unsigned long)__kretprobe_trampoline) + if (state.ip == (unsigned long)arch_rethook_trampoline) return -EINVAL; #endif diff --git a/arch/s390/kernel/text_amode31.S b/arch/s390/kernel/text_amode31.S index 2c8b14cc5556..e0f01ce251f5 100644 --- a/arch/s390/kernel/text_amode31.S +++ b/arch/s390/kernel/text_amode31.S @@ -63,6 +63,19 @@ ENTRY(_diag210_amode31) ENDPROC(_diag210_amode31) /* + * int diag8c(struct diag8c *addr, struct ccw_dev_id *devno, size_t len) +*/ +ENTRY(_diag8c_amode31) + llgf %r3,0(%r3) + sam31 + diag %r2,%r4,0x8c +.Ldiag8c_ex: + sam64 + lgfr %r2,%r3 + BR_EX_AMODE31_r14 + EX_TABLE_AMODE31(.Ldiag8c_ex, .Ldiag8c_ex) +ENDPROC(_diag8c_amode31) +/* * int _diag26c_amode31(void *req, void *resp, enum diag26c_sc subcode) */ ENTRY(_diag26c_amode31) diff --git a/arch/s390/kernel/vmlinux.lds.S b/arch/s390/kernel/vmlinux.lds.S index 20262e3c0cff..b653ba8d51e6 100644 --- a/arch/s390/kernel/vmlinux.lds.S +++ b/arch/s390/kernel/vmlinux.lds.S @@ -216,6 +216,9 @@ SECTIONS QUAD(__rela_dyn_start) /* rela_dyn_start */ QUAD(__rela_dyn_end) /* rela_dyn_end */ QUAD(_eamode31 - _samode31) /* amode31_size */ + QUAD(init_mm) + QUAD(swapper_pg_dir) + QUAD(invalid_pg_dir) } :NONE /* Debugging sections. */ @@ -227,5 +230,6 @@ SECTIONS DISCARDS /DISCARD/ : { *(.eh_frame) + *(.interp) } } diff --git a/arch/s390/lib/test_unwind.c b/arch/s390/lib/test_unwind.c index 5a053b393d5c..7231bf97b93a 100644 --- a/arch/s390/lib/test_unwind.c +++ b/arch/s390/lib/test_unwind.c @@ -47,7 +47,7 @@ static void print_backtrace(char *bt) static noinline int test_unwind(struct task_struct *task, struct pt_regs *regs, unsigned long sp) { - int frame_count, prev_is_func2, seen_func2_func1, seen_kretprobe_trampoline; + int frame_count, prev_is_func2, seen_func2_func1, seen_arch_rethook_trampoline; const int max_frames = 128; struct unwind_state state; size_t bt_pos = 0; @@ -63,7 +63,7 @@ static noinline int test_unwind(struct task_struct *task, struct pt_regs *regs, frame_count = 0; prev_is_func2 = 0; seen_func2_func1 = 0; - seen_kretprobe_trampoline = 0; + seen_arch_rethook_trampoline = 0; unwind_for_each_frame(&state, task, regs, sp) { unsigned long addr = unwind_get_return_address(&state); char sym[KSYM_SYMBOL_LEN]; @@ -89,8 +89,8 @@ static noinline int test_unwind(struct task_struct *task, struct pt_regs *regs, if (prev_is_func2 && str_has_prefix(sym, "unwindme_func1")) seen_func2_func1 = 1; prev_is_func2 = str_has_prefix(sym, "unwindme_func2"); - if (str_has_prefix(sym, "__kretprobe_trampoline+0x0/")) - seen_kretprobe_trampoline = 1; + if (str_has_prefix(sym, "arch_rethook_trampoline+0x0/")) + seen_arch_rethook_trampoline = 1; } /* Check the results. */ @@ -106,8 +106,8 @@ static noinline int test_unwind(struct task_struct *task, struct pt_regs *regs, kunit_err(current_test, "Maximum number of frames exceeded\n"); ret = -EINVAL; } - if (seen_kretprobe_trampoline) { - kunit_err(current_test, "__kretprobe_trampoline+0x0 in unwinding results\n"); + if (seen_arch_rethook_trampoline) { + kunit_err(current_test, "arch_rethook_trampoline+0x0 in unwinding results\n"); ret = -EINVAL; } if (ret || force_bt) diff --git a/arch/s390/mm/dump_pagetables.c b/arch/s390/mm/dump_pagetables.c index 9953819d7959..ba5f80268878 100644 --- a/arch/s390/mm/dump_pagetables.c +++ b/arch/s390/mm/dump_pagetables.c @@ -33,10 +33,6 @@ enum address_markers_idx { #endif IDENTITY_AFTER_NR, IDENTITY_AFTER_END_NR, -#ifdef CONFIG_KASAN - KASAN_SHADOW_START_NR, - KASAN_SHADOW_END_NR, -#endif VMEMMAP_NR, VMEMMAP_END_NR, VMALLOC_NR, @@ -47,6 +43,10 @@ enum address_markers_idx { ABS_LOWCORE_END_NR, MEMCPY_REAL_NR, MEMCPY_REAL_END_NR, +#ifdef CONFIG_KASAN + KASAN_SHADOW_START_NR, + KASAN_SHADOW_END_NR, +#endif }; static struct addr_marker address_markers[] = { @@ -62,10 +62,6 @@ static struct addr_marker address_markers[] = { #endif [IDENTITY_AFTER_NR] = {(unsigned long)_end, "Identity Mapping Start"}, [IDENTITY_AFTER_END_NR] = {0, "Identity Mapping End"}, -#ifdef CONFIG_KASAN - [KASAN_SHADOW_START_NR] = {KASAN_SHADOW_START, "Kasan Shadow Start"}, - [KASAN_SHADOW_END_NR] = {KASAN_SHADOW_END, "Kasan Shadow End"}, -#endif [VMEMMAP_NR] = {0, "vmemmap Area Start"}, [VMEMMAP_END_NR] = {0, "vmemmap Area End"}, [VMALLOC_NR] = {0, "vmalloc Area Start"}, @@ -76,6 +72,10 @@ static struct addr_marker address_markers[] = { [ABS_LOWCORE_END_NR] = {0, "Lowcore Area End"}, [MEMCPY_REAL_NR] = {0, "Real Memory Copy Area Start"}, [MEMCPY_REAL_END_NR] = {0, "Real Memory Copy Area End"}, +#ifdef CONFIG_KASAN + [KASAN_SHADOW_START_NR] = {KASAN_SHADOW_START, "Kasan Shadow Start"}, + [KASAN_SHADOW_END_NR] = {KASAN_SHADOW_END, "Kasan Shadow End"}, +#endif { -1, NULL } }; diff --git a/arch/s390/mm/extable.c b/arch/s390/mm/extable.c index 1e4d2187541a..fe87291df95d 100644 --- a/arch/s390/mm/extable.c +++ b/arch/s390/mm/extable.c @@ -47,13 +47,16 @@ static bool ex_handler_ua_load_mem(const struct exception_table_entry *ex, struc return true; } -static bool ex_handler_ua_load_reg(const struct exception_table_entry *ex, struct pt_regs *regs) +static bool ex_handler_ua_load_reg(const struct exception_table_entry *ex, + bool pair, struct pt_regs *regs) { unsigned int reg_zero = FIELD_GET(EX_DATA_REG_ADDR, ex->data); unsigned int reg_err = FIELD_GET(EX_DATA_REG_ERR, ex->data); regs->gprs[reg_err] = -EFAULT; regs->gprs[reg_zero] = 0; + if (pair) + regs->gprs[reg_zero + 1] = 0; regs->psw.addr = extable_fixup(ex); return true; } @@ -75,7 +78,9 @@ bool fixup_exception(struct pt_regs *regs) case EX_TYPE_UA_LOAD_MEM: return ex_handler_ua_load_mem(ex, regs); case EX_TYPE_UA_LOAD_REG: - return ex_handler_ua_load_reg(ex, regs); + return ex_handler_ua_load_reg(ex, false, regs); + case EX_TYPE_UA_LOAD_REGPAIR: + return ex_handler_ua_load_reg(ex, true, regs); } panic("invalid exception table entry"); } diff --git a/arch/s390/mm/fault.c b/arch/s390/mm/fault.c index 9649d9382e0a..a2632fd97d00 100644 --- a/arch/s390/mm/fault.c +++ b/arch/s390/mm/fault.c @@ -46,11 +46,15 @@ #define __SUBCODE_MASK 0x0600 #define __PF_RES_FIELD 0x8000000000000000ULL -#define VM_FAULT_BADCONTEXT ((__force vm_fault_t) 0x010000) -#define VM_FAULT_BADMAP ((__force vm_fault_t) 0x020000) -#define VM_FAULT_BADACCESS ((__force vm_fault_t) 0x040000) -#define VM_FAULT_SIGNAL ((__force vm_fault_t) 0x080000) -#define VM_FAULT_PFAULT ((__force vm_fault_t) 0x100000) +/* + * Allocate private vm_fault_reason from top. Please make sure it won't + * collide with vm_fault_reason. + */ +#define VM_FAULT_BADCONTEXT ((__force vm_fault_t)0x80000000) +#define VM_FAULT_BADMAP ((__force vm_fault_t)0x40000000) +#define VM_FAULT_BADACCESS ((__force vm_fault_t)0x20000000) +#define VM_FAULT_SIGNAL ((__force vm_fault_t)0x10000000) +#define VM_FAULT_PFAULT ((__force vm_fault_t)0x8000000) enum fault_type { KERNEL_FAULT, @@ -96,6 +100,20 @@ static enum fault_type get_fault_type(struct pt_regs *regs) return KERNEL_FAULT; } +static unsigned long get_fault_address(struct pt_regs *regs) +{ + unsigned long trans_exc_code = regs->int_parm_long; + + return trans_exc_code & __FAIL_ADDR_MASK; +} + +static bool fault_is_write(struct pt_regs *regs) +{ + unsigned long trans_exc_code = regs->int_parm_long; + + return (trans_exc_code & store_indication) == 0x400; +} + static int bad_address(void *p) { unsigned long dummy; @@ -228,15 +246,26 @@ static noinline void do_sigsegv(struct pt_regs *regs, int si_code) (void __user *)(regs->int_parm_long & __FAIL_ADDR_MASK)); } -static noinline void do_no_context(struct pt_regs *regs) +static noinline void do_no_context(struct pt_regs *regs, vm_fault_t fault) { + enum fault_type fault_type; + unsigned long address; + bool is_write; + if (fixup_exception(regs)) return; + fault_type = get_fault_type(regs); + if ((fault_type == KERNEL_FAULT) && (fault == VM_FAULT_BADCONTEXT)) { + address = get_fault_address(regs); + is_write = fault_is_write(regs); + if (kfence_handle_page_fault(address, is_write, regs)) + return; + } /* * Oops. The kernel tried to access some bad page. We'll have to * terminate things with extreme prejudice. */ - if (get_fault_type(regs) == KERNEL_FAULT) + if (fault_type == KERNEL_FAULT) printk(KERN_ALERT "Unable to handle kernel pointer dereference" " in virtual kernel address space\n"); else @@ -255,7 +284,7 @@ static noinline void do_low_address(struct pt_regs *regs) die (regs, "Low-address protection"); } - do_no_context(regs); + do_no_context(regs, VM_FAULT_BADACCESS); } static noinline void do_sigbus(struct pt_regs *regs) @@ -286,28 +315,28 @@ static noinline void do_fault_error(struct pt_regs *regs, vm_fault_t fault) fallthrough; case VM_FAULT_BADCONTEXT: case VM_FAULT_PFAULT: - do_no_context(regs); + do_no_context(regs, fault); break; case VM_FAULT_SIGNAL: if (!user_mode(regs)) - do_no_context(regs); + do_no_context(regs, fault); break; default: /* fault & VM_FAULT_ERROR */ if (fault & VM_FAULT_OOM) { if (!user_mode(regs)) - do_no_context(regs); + do_no_context(regs, fault); else pagefault_out_of_memory(); } else if (fault & VM_FAULT_SIGSEGV) { /* Kernel mode? Handle exceptions or die */ if (!user_mode(regs)) - do_no_context(regs); + do_no_context(regs, fault); else do_sigsegv(regs, SEGV_MAPERR); } else if (fault & VM_FAULT_SIGBUS) { /* Kernel mode? Handle exceptions or die */ if (!user_mode(regs)) - do_no_context(regs); + do_no_context(regs, fault); else do_sigbus(regs); } else @@ -334,7 +363,6 @@ static inline vm_fault_t do_exception(struct pt_regs *regs, int access) struct mm_struct *mm; struct vm_area_struct *vma; enum fault_type type; - unsigned long trans_exc_code; unsigned long address; unsigned int flags; vm_fault_t fault; @@ -351,9 +379,8 @@ static inline vm_fault_t do_exception(struct pt_regs *regs, int access) return 0; mm = tsk->mm; - trans_exc_code = regs->int_parm_long; - address = trans_exc_code & __FAIL_ADDR_MASK; - is_write = (trans_exc_code & store_indication) == 0x400; + address = get_fault_address(regs); + is_write = fault_is_write(regs); /* * Verify that the fault happened in user space, that @@ -364,8 +391,6 @@ static inline vm_fault_t do_exception(struct pt_regs *regs, int access) type = get_fault_type(regs); switch (type) { case KERNEL_FAULT: - if (kfence_handle_page_fault(address, is_write, regs)) - return 0; goto out; case USER_FAULT: case GMAP_FAULT: diff --git a/arch/s390/mm/init.c b/arch/s390/mm/init.c index 30ab55f868f6..144447d5cb4c 100644 --- a/arch/s390/mm/init.c +++ b/arch/s390/mm/init.c @@ -52,9 +52,9 @@ #include <linux/virtio_config.h> pgd_t swapper_pg_dir[PTRS_PER_PGD] __section(".bss..swapper_pg_dir"); -static pgd_t invalid_pg_dir[PTRS_PER_PGD] __section(".bss..invalid_pg_dir"); +pgd_t invalid_pg_dir[PTRS_PER_PGD] __section(".bss..invalid_pg_dir"); -unsigned long s390_invalid_asce; +unsigned long __bootdata_preserved(s390_invalid_asce); unsigned long empty_zero_page, zero_page_mask; EXPORT_SYMBOL(empty_zero_page); @@ -93,37 +93,8 @@ static void __init setup_zero_pages(void) void __init paging_init(void) { unsigned long max_zone_pfns[MAX_NR_ZONES]; - unsigned long pgd_type, asce_bits; - psw_t psw; - - s390_invalid_asce = (unsigned long)invalid_pg_dir; - s390_invalid_asce |= _ASCE_TYPE_REGION3 | _ASCE_TABLE_LENGTH; - crst_table_init((unsigned long *)invalid_pg_dir, _REGION3_ENTRY_EMPTY); - init_mm.pgd = swapper_pg_dir; - if (VMALLOC_END > _REGION2_SIZE) { - asce_bits = _ASCE_TYPE_REGION2 | _ASCE_TABLE_LENGTH; - pgd_type = _REGION2_ENTRY_EMPTY; - } else { - asce_bits = _ASCE_TYPE_REGION3 | _ASCE_TABLE_LENGTH; - pgd_type = _REGION3_ENTRY_EMPTY; - } - init_mm.context.asce = (__pa(init_mm.pgd) & PAGE_MASK) | asce_bits; - S390_lowcore.kernel_asce = init_mm.context.asce; - S390_lowcore.user_asce = s390_invalid_asce; - crst_table_init((unsigned long *) init_mm.pgd, pgd_type); - vmem_map_init(); - kasan_copy_shadow_mapping(); - - /* enable virtual mapping in kernel mode */ - __ctl_load(S390_lowcore.kernel_asce, 1, 1); - __ctl_load(S390_lowcore.user_asce, 7, 7); - __ctl_load(S390_lowcore.kernel_asce, 13, 13); - psw.mask = __extract_psw(); - psw_bits(psw).dat = 1; - psw_bits(psw).as = PSW_BITS_AS_HOME; - __load_psw_mask(psw.mask); - kasan_free_early_identity(); + vmem_map_init(); sparse_init(); zone_dma_bits = 31; memset(max_zone_pfns, 0, sizeof(max_zone_pfns)); diff --git a/arch/s390/mm/kasan_init.c b/arch/s390/mm/kasan_init.c index 9f988d4582ed..ef89a5f26853 100644 --- a/arch/s390/mm/kasan_init.c +++ b/arch/s390/mm/kasan_init.c @@ -1,7 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 #include <linux/kasan.h> #include <linux/sched/task.h> -#include <linux/memblock.h> #include <linux/pgtable.h> #include <asm/pgalloc.h> #include <asm/kasan.h> @@ -15,16 +14,11 @@ static unsigned long segment_pos __initdata; static unsigned long segment_low __initdata; -static unsigned long pgalloc_pos __initdata; -static unsigned long pgalloc_low __initdata; -static unsigned long pgalloc_freeable __initdata; static bool has_edat __initdata; static bool has_nx __initdata; #define __sha(x) ((unsigned long)kasan_mem_to_shadow((void *)x)) -static pgd_t early_pg_dir[PTRS_PER_PGD] __initdata __aligned(PAGE_SIZE); - static void __init kasan_early_panic(const char *reason) { sclp_early_printk("The Linux kernel failed to boot with the KernelAddressSanitizer:\n"); @@ -39,7 +33,7 @@ static void * __init kasan_early_alloc_segment(void) if (segment_pos < segment_low) kasan_early_panic("out of memory during initialisation\n"); - return (void *)segment_pos; + return __va(segment_pos); } static void * __init kasan_early_alloc_pages(unsigned int order) @@ -49,7 +43,7 @@ static void * __init kasan_early_alloc_pages(unsigned int order) if (pgalloc_pos < pgalloc_low) kasan_early_panic("out of memory during initialisation\n"); - return (void *)pgalloc_pos; + return __va(pgalloc_pos); } static void * __init kasan_early_crst_alloc(unsigned long val) @@ -81,35 +75,37 @@ static pte_t * __init kasan_early_pte_alloc(void) } enum populate_mode { - POPULATE_ONE2ONE, POPULATE_MAP, POPULATE_ZERO_SHADOW, POPULATE_SHALLOW }; + +static inline pgprot_t pgprot_clear_bit(pgprot_t pgprot, unsigned long bit) +{ + return __pgprot(pgprot_val(pgprot) & ~bit); +} + static void __init kasan_early_pgtable_populate(unsigned long address, unsigned long end, enum populate_mode mode) { - unsigned long pgt_prot_zero, pgt_prot, sgt_prot; + pgprot_t pgt_prot_zero = PAGE_KERNEL_RO; + pgprot_t pgt_prot = PAGE_KERNEL; + pgprot_t sgt_prot = SEGMENT_KERNEL; pgd_t *pg_dir; p4d_t *p4_dir; pud_t *pu_dir; pmd_t *pm_dir; pte_t *pt_dir; + pmd_t pmd; + pte_t pte; - pgt_prot_zero = pgprot_val(PAGE_KERNEL_RO); - if (!has_nx) - pgt_prot_zero &= ~_PAGE_NOEXEC; - pgt_prot = pgprot_val(PAGE_KERNEL); - sgt_prot = pgprot_val(SEGMENT_KERNEL); - if (!has_nx || mode == POPULATE_ONE2ONE) { - pgt_prot &= ~_PAGE_NOEXEC; - sgt_prot &= ~_SEGMENT_ENTRY_NOEXEC; + if (!has_nx) { + pgt_prot_zero = pgprot_clear_bit(pgt_prot_zero, _PAGE_NOEXEC); + pgt_prot = pgprot_clear_bit(pgt_prot, _PAGE_NOEXEC); + sgt_prot = pgprot_clear_bit(sgt_prot, _SEGMENT_ENTRY_NOEXEC); } - /* - * The first 1MB of 1:1 mapping is mapped with 4KB pages - */ while (address < end) { pg_dir = pgd_offset_k(address); if (pgd_none(*pg_dir)) { @@ -166,16 +162,13 @@ static void __init kasan_early_pgtable_populate(unsigned long address, pmd_populate(&init_mm, pm_dir, kasan_early_shadow_pte); address = (address + PMD_SIZE) & PMD_MASK; continue; - } else if (has_edat && address) { - void *page; - - if (mode == POPULATE_ONE2ONE) { - page = (void *)address; - } else { - page = kasan_early_alloc_segment(); - memset(page, 0, _SEGMENT_SIZE); - } - set_pmd(pm_dir, __pmd(__pa(page) | sgt_prot)); + } else if (has_edat) { + void *page = kasan_early_alloc_segment(); + + memset(page, 0, _SEGMENT_SIZE); + pmd = __pmd(__pa(page)); + pmd = set_pmd_bit(pmd, sgt_prot); + set_pmd(pm_dir, pmd); address = (address + PMD_SIZE) & PMD_MASK; continue; } @@ -192,18 +185,18 @@ static void __init kasan_early_pgtable_populate(unsigned long address, void *page; switch (mode) { - case POPULATE_ONE2ONE: - page = (void *)address; - set_pte(pt_dir, __pte(__pa(page) | pgt_prot)); - break; case POPULATE_MAP: page = kasan_early_alloc_pages(0); memset(page, 0, PAGE_SIZE); - set_pte(pt_dir, __pte(__pa(page) | pgt_prot)); + pte = __pte(__pa(page)); + pte = set_pte_bit(pte, pgt_prot); + set_pte(pt_dir, pte); break; case POPULATE_ZERO_SHADOW: page = kasan_early_shadow_page; - set_pte(pt_dir, __pte(__pa(page) | pgt_prot_zero)); + pte = __pte(__pa(page)); + pte = set_pte_bit(pte, pgt_prot_zero); + set_pte(pt_dir, pte); break; case POPULATE_SHALLOW: /* should never happen */ @@ -214,29 +207,6 @@ static void __init kasan_early_pgtable_populate(unsigned long address, } } -static void __init kasan_set_pgd(pgd_t *pgd, unsigned long asce_type) -{ - unsigned long asce_bits; - - asce_bits = asce_type | _ASCE_TABLE_LENGTH; - S390_lowcore.kernel_asce = (__pa(pgd) & PAGE_MASK) | asce_bits; - S390_lowcore.user_asce = S390_lowcore.kernel_asce; - - __ctl_load(S390_lowcore.kernel_asce, 1, 1); - __ctl_load(S390_lowcore.kernel_asce, 7, 7); - __ctl_load(S390_lowcore.kernel_asce, 13, 13); -} - -static void __init kasan_enable_dat(void) -{ - psw_t psw; - - psw.mask = __extract_psw(); - psw_bits(psw).dat = 1; - psw_bits(psw).as = PSW_BITS_AS_HOME; - __load_psw_mask(psw.mask); -} - static void __init kasan_early_detect_facilities(void) { if (test_facility(8)) { @@ -251,153 +221,81 @@ static void __init kasan_early_detect_facilities(void) void __init kasan_early_init(void) { - unsigned long shadow_alloc_size; - unsigned long initrd_end; - unsigned long memsize; - unsigned long pgt_prot = pgprot_val(PAGE_KERNEL_RO); - pte_t pte_z; + pte_t pte_z = __pte(__pa(kasan_early_shadow_page) | pgprot_val(PAGE_KERNEL_RO)); pmd_t pmd_z = __pmd(__pa(kasan_early_shadow_pte) | _SEGMENT_ENTRY); pud_t pud_z = __pud(__pa(kasan_early_shadow_pmd) | _REGION3_ENTRY); p4d_t p4d_z = __p4d(__pa(kasan_early_shadow_pud) | _REGION2_ENTRY); + unsigned long untracked_end = MODULES_VADDR; + unsigned long shadow_alloc_size; + unsigned long start, end; + int i; kasan_early_detect_facilities(); if (!has_nx) - pgt_prot &= ~_PAGE_NOEXEC; - pte_z = __pte(__pa(kasan_early_shadow_page) | pgt_prot); - - memsize = get_mem_detect_end(); - if (!memsize) - kasan_early_panic("cannot detect physical memory size\n"); - /* - * Kasan currently supports standby memory but only if it follows - * online memory (default allocation), i.e. no memory holes. - * - memsize represents end of online memory - * - ident_map_size represents online + standby and memory limits - * accounted. - * Kasan maps "memsize" right away. - * [0, memsize] - as identity mapping - * [__sha(0), __sha(memsize)] - shadow memory for identity mapping - * The rest [memsize, ident_map_size] if memsize < ident_map_size - * could be mapped/unmapped dynamically later during memory hotplug. - */ - memsize = min(memsize, ident_map_size); + pte_z = clear_pte_bit(pte_z, __pgprot(_PAGE_NOEXEC)); BUILD_BUG_ON(!IS_ALIGNED(KASAN_SHADOW_START, P4D_SIZE)); BUILD_BUG_ON(!IS_ALIGNED(KASAN_SHADOW_END, P4D_SIZE)); - crst_table_init((unsigned long *)early_pg_dir, _REGION2_ENTRY_EMPTY); /* init kasan zero shadow */ - crst_table_init((unsigned long *)kasan_early_shadow_p4d, - p4d_val(p4d_z)); - crst_table_init((unsigned long *)kasan_early_shadow_pud, - pud_val(pud_z)); - crst_table_init((unsigned long *)kasan_early_shadow_pmd, - pmd_val(pmd_z)); + crst_table_init((unsigned long *)kasan_early_shadow_p4d, p4d_val(p4d_z)); + crst_table_init((unsigned long *)kasan_early_shadow_pud, pud_val(pud_z)); + crst_table_init((unsigned long *)kasan_early_shadow_pmd, pmd_val(pmd_z)); memset64((u64 *)kasan_early_shadow_pte, pte_val(pte_z), PTRS_PER_PTE); - shadow_alloc_size = memsize >> KASAN_SHADOW_SCALE_SHIFT; - pgalloc_low = round_up((unsigned long)_end, _SEGMENT_SIZE); - if (IS_ENABLED(CONFIG_BLK_DEV_INITRD)) { - initrd_end = - round_up(initrd_data.start + initrd_data.size, _SEGMENT_SIZE); - pgalloc_low = max(pgalloc_low, initrd_end); - } - - if (pgalloc_low + shadow_alloc_size > memsize) - kasan_early_panic("out of memory during initialisation\n"); - if (has_edat) { - segment_pos = round_down(memsize, _SEGMENT_SIZE); + shadow_alloc_size = get_mem_detect_usable_total() >> KASAN_SHADOW_SCALE_SHIFT; + segment_pos = round_down(pgalloc_pos, _SEGMENT_SIZE); segment_low = segment_pos - shadow_alloc_size; + segment_low = round_down(segment_low, _SEGMENT_SIZE); pgalloc_pos = segment_low; - } else { - pgalloc_pos = memsize; } - init_mm.pgd = early_pg_dir; /* * Current memory layout: - * +- 0 -------------+ +- shadow start -+ - * | 1:1 ram mapping | /| 1/8 ram | - * | | / | | - * +- end of ram ----+ / +----------------+ - * | ... gap ... | / | | - * | |/ | kasan | - * +- shadow start --+ | zero | - * | 1/8 addr space | | page | - * +- shadow end -+ | mapping | - * | ... gap ... |\ | (untracked) | - * +- vmalloc area -+ \ | | - * | vmalloc_size | \ | | - * +- modules vaddr -+ \ +----------------+ - * | 2Gb | \| unmapped | allocated per module - * +-----------------+ +- shadow end ---+ + * +- 0 -------------+ +- shadow start -+ + * |1:1 ident mapping| /|1/8 of ident map| + * | | / | | + * +-end of ident map+ / +----------------+ + * | ... gap ... | / | kasan | + * | | / | zero page | + * +- vmalloc area -+ / | mapping | + * | vmalloc_size | / | (untracked) | + * +- modules vaddr -+ / +----------------+ + * | 2Gb |/ | unmapped | allocated per module + * +- shadow start -+ +----------------+ + * | 1/8 addr space | | zero pg mapping| (untracked) + * +- shadow end ----+---------+- shadow end ---+ * * Current memory layout (KASAN_VMALLOC): - * +- 0 -------------+ +- shadow start -+ - * | 1:1 ram mapping | /| 1/8 ram | - * | | / | | - * +- end of ram ----+ / +----------------+ - * | ... gap ... | / | kasan | - * | |/ | zero | - * +- shadow start --+ | page | - * | 1/8 addr space | | mapping | - * +- shadow end -+ | (untracked) | - * | ... gap ... |\ | | - * +- vmalloc area -+ \ +- vmalloc area -+ - * | vmalloc_size | \ |shallow populate| - * +- modules vaddr -+ \ +- modules area -+ - * | 2Gb | \|shallow populate| - * +-----------------+ +- shadow end ---+ + * +- 0 -------------+ +- shadow start -+ + * |1:1 ident mapping| /|1/8 of ident map| + * | | / | | + * +-end of ident map+ / +----------------+ + * | ... gap ... | / | kasan zero page| (untracked) + * | | / | mapping | + * +- vmalloc area -+ / +----------------+ + * | vmalloc_size | / |shallow populate| + * +- modules vaddr -+ / +----------------+ + * | 2Gb |/ |shallow populate| + * +- shadow start -+ +----------------+ + * | 1/8 addr space | | zero pg mapping| (untracked) + * +- shadow end ----+---------+- shadow end ---+ */ /* populate kasan shadow (for identity mapping and zero page mapping) */ - kasan_early_pgtable_populate(__sha(0), __sha(memsize), POPULATE_MAP); + for_each_mem_detect_usable_block(i, &start, &end) + kasan_early_pgtable_populate(__sha(start), __sha(end), POPULATE_MAP); if (IS_ENABLED(CONFIG_KASAN_VMALLOC)) { + untracked_end = VMALLOC_START; /* shallowly populate kasan shadow for vmalloc and modules */ kasan_early_pgtable_populate(__sha(VMALLOC_START), __sha(MODULES_END), POPULATE_SHALLOW); } /* populate kasan shadow for untracked memory */ - kasan_early_pgtable_populate(__sha(ident_map_size), - IS_ENABLED(CONFIG_KASAN_VMALLOC) ? - __sha(VMALLOC_START) : - __sha(MODULES_VADDR), + kasan_early_pgtable_populate(__sha(ident_map_size), __sha(untracked_end), POPULATE_ZERO_SHADOW); kasan_early_pgtable_populate(__sha(MODULES_END), __sha(_REGION1_SIZE), POPULATE_ZERO_SHADOW); - /* memory allocated for identity mapping structs will be freed later */ - pgalloc_freeable = pgalloc_pos; - /* populate identity mapping */ - kasan_early_pgtable_populate(0, memsize, POPULATE_ONE2ONE); - kasan_set_pgd(early_pg_dir, _ASCE_TYPE_REGION2); - kasan_enable_dat(); /* enable kasan */ init_task.kasan_depth = 0; - memblock_reserve(pgalloc_pos, memsize - pgalloc_pos); sclp_early_printk("KernelAddressSanitizer initialized\n"); } - -void __init kasan_copy_shadow_mapping(void) -{ - /* - * At this point we are still running on early pages setup early_pg_dir, - * while swapper_pg_dir has just been initialized with identity mapping. - * Carry over shadow memory region from early_pg_dir to swapper_pg_dir. - */ - - pgd_t *pg_dir_src; - pgd_t *pg_dir_dst; - p4d_t *p4_dir_src; - p4d_t *p4_dir_dst; - - pg_dir_src = pgd_offset_raw(early_pg_dir, KASAN_SHADOW_START); - pg_dir_dst = pgd_offset_raw(init_mm.pgd, KASAN_SHADOW_START); - p4_dir_src = p4d_offset(pg_dir_src, KASAN_SHADOW_START); - p4_dir_dst = p4d_offset(pg_dir_dst, KASAN_SHADOW_START); - memcpy(p4_dir_dst, p4_dir_src, - (KASAN_SHADOW_SIZE >> P4D_SHIFT) * sizeof(p4d_t)); -} - -void __init kasan_free_early_identity(void) -{ - memblock_phys_free(pgalloc_pos, pgalloc_freeable - pgalloc_pos); -} diff --git a/arch/s390/mm/maccess.c b/arch/s390/mm/maccess.c index 4824d1cd33d8..d02a61620cfa 100644 --- a/arch/s390/mm/maccess.c +++ b/arch/s390/mm/maccess.c @@ -21,7 +21,7 @@ #include <asm/maccess.h> unsigned long __bootdata_preserved(__memcpy_real_area); -static __ro_after_init pte_t *memcpy_real_ptep; +pte_t *__bootdata_preserved(memcpy_real_ptep); static DEFINE_MUTEX(memcpy_real_mutex); static notrace long s390_kernel_write_odd(void *dst, const void *src, size_t size) @@ -68,28 +68,17 @@ notrace void *s390_kernel_write(void *dst, const void *src, size_t size) long copied; spin_lock_irqsave(&s390_kernel_write_lock, flags); - if (!(flags & PSW_MASK_DAT)) { - memcpy(dst, src, size); - } else { - while (size) { - copied = s390_kernel_write_odd(tmp, src, size); - tmp += copied; - src += copied; - size -= copied; - } + while (size) { + copied = s390_kernel_write_odd(tmp, src, size); + tmp += copied; + src += copied; + size -= copied; } spin_unlock_irqrestore(&s390_kernel_write_lock, flags); return dst; } -void __init memcpy_real_init(void) -{ - memcpy_real_ptep = vmem_get_alloc_pte(__memcpy_real_area, true); - if (!memcpy_real_ptep) - panic("Couldn't setup memcpy real area"); -} - size_t memcpy_real_iter(struct iov_iter *iter, unsigned long src, size_t count) { size_t len, copied, res = 0; @@ -162,7 +151,6 @@ void *xlate_dev_mem_ptr(phys_addr_t addr) void *ptr = phys_to_virt(addr); void *bounce = ptr; struct lowcore *abs_lc; - unsigned long flags; unsigned long size; int this_cpu, cpu; @@ -178,10 +166,10 @@ void *xlate_dev_mem_ptr(phys_addr_t addr) goto out; size = PAGE_SIZE - (addr & ~PAGE_MASK); if (addr < sizeof(struct lowcore)) { - abs_lc = get_abs_lowcore(&flags); + abs_lc = get_abs_lowcore(); ptr = (void *)abs_lc + addr; memcpy(bounce, ptr, size); - put_abs_lowcore(abs_lc, flags); + put_abs_lowcore(abs_lc); } else if (cpu == this_cpu) { ptr = (void *)(addr - virt_to_phys(lowcore_ptr[cpu])); memcpy(bounce, ptr, size); diff --git a/arch/s390/mm/pgtable.c b/arch/s390/mm/pgtable.c index 4909dcd762e8..6effb24de6d9 100644 --- a/arch/s390/mm/pgtable.c +++ b/arch/s390/mm/pgtable.c @@ -302,6 +302,31 @@ pte_t ptep_xchg_direct(struct mm_struct *mm, unsigned long addr, } EXPORT_SYMBOL(ptep_xchg_direct); +/* + * Caller must check that new PTE only differs in _PAGE_PROTECT HW bit, so that + * RDP can be used instead of IPTE. See also comments at pte_allow_rdp(). + */ +void ptep_reset_dat_prot(struct mm_struct *mm, unsigned long addr, pte_t *ptep, + pte_t new) +{ + preempt_disable(); + atomic_inc(&mm->context.flush_count); + if (cpumask_equal(mm_cpumask(mm), cpumask_of(smp_processor_id()))) + __ptep_rdp(addr, ptep, 0, 0, 1); + else + __ptep_rdp(addr, ptep, 0, 0, 0); + /* + * PTE is not invalidated by RDP, only _PAGE_PROTECT is cleared. That + * means it is still valid and active, and must not be changed according + * to the architecture. But writing a new value that only differs in SW + * bits is allowed. + */ + set_pte(ptep, new); + atomic_dec(&mm->context.flush_count); + preempt_enable(); +} +EXPORT_SYMBOL(ptep_reset_dat_prot); + pte_t ptep_xchg_lazy(struct mm_struct *mm, unsigned long addr, pte_t *ptep, pte_t new) { diff --git a/arch/s390/mm/vmem.c b/arch/s390/mm/vmem.c index ee1a97078527..4113a7ffa149 100644 --- a/arch/s390/mm/vmem.c +++ b/arch/s390/mm/vmem.c @@ -11,6 +11,7 @@ #include <linux/list.h> #include <linux/hugetlb.h> #include <linux/slab.h> +#include <linux/sort.h> #include <asm/cacheflush.h> #include <asm/nospec-branch.h> #include <asm/pgalloc.h> @@ -296,10 +297,7 @@ static void try_free_pmd_table(pud_t *pud, unsigned long start) /* Don't mess with any tables not fully in 1:1 mapping & vmemmap area */ if (end > VMALLOC_START) return; -#ifdef CONFIG_KASAN - if (start < KASAN_SHADOW_END && KASAN_SHADOW_START > end) - return; -#endif + pmd = pmd_offset(pud, start); for (i = 0; i < PTRS_PER_PMD; i++, pmd++) if (!pmd_none(*pmd)) @@ -371,10 +369,6 @@ static void try_free_pud_table(p4d_t *p4d, unsigned long start) /* Don't mess with any tables not fully in 1:1 mapping & vmemmap area */ if (end > VMALLOC_START) return; -#ifdef CONFIG_KASAN - if (start < KASAN_SHADOW_END && KASAN_SHADOW_START > end) - return; -#endif pud = pud_offset(p4d, start); for (i = 0; i < PTRS_PER_PUD; i++, pud++) { @@ -425,10 +419,6 @@ static void try_free_p4d_table(pgd_t *pgd, unsigned long start) /* Don't mess with any tables not fully in 1:1 mapping & vmemmap area */ if (end > VMALLOC_START) return; -#ifdef CONFIG_KASAN - if (start < KASAN_SHADOW_END && KASAN_SHADOW_START > end) - return; -#endif p4d = p4d_offset(pgd, start); for (i = 0; i < PTRS_PER_P4D; i++, p4d++) { @@ -657,6 +647,23 @@ void vmem_unmap_4k_page(unsigned long addr) mutex_unlock(&vmem_mutex); } +static int __init memblock_region_cmp(const void *a, const void *b) +{ + const struct memblock_region *r1 = a; + const struct memblock_region *r2 = b; + + if (r1->base < r2->base) + return -1; + if (r1->base > r2->base) + return 1; + return 0; +} + +static void __init memblock_region_swap(void *a, void *b, int size) +{ + swap(*(struct memblock_region *)a, *(struct memblock_region *)b); +} + /* * map whole physical memory to virtual memory (identity mapping) * we reserve enough space in the vmalloc area for vmemmap to hotplug @@ -664,11 +671,68 @@ void vmem_unmap_4k_page(unsigned long addr) */ void __init vmem_map_init(void) { + struct memblock_region memory_rwx_regions[] = { + { + .base = 0, + .size = sizeof(struct lowcore), + .flags = MEMBLOCK_NONE, +#ifdef CONFIG_NUMA + .nid = NUMA_NO_NODE, +#endif + }, + { + .base = __pa(_stext), + .size = _etext - _stext, + .flags = MEMBLOCK_NONE, +#ifdef CONFIG_NUMA + .nid = NUMA_NO_NODE, +#endif + }, + { + .base = __pa(_sinittext), + .size = _einittext - _sinittext, + .flags = MEMBLOCK_NONE, +#ifdef CONFIG_NUMA + .nid = NUMA_NO_NODE, +#endif + }, + { + .base = __stext_amode31, + .size = __etext_amode31 - __stext_amode31, + .flags = MEMBLOCK_NONE, +#ifdef CONFIG_NUMA + .nid = NUMA_NO_NODE, +#endif + }, + }; + struct memblock_type memory_rwx = { + .regions = memory_rwx_regions, + .cnt = ARRAY_SIZE(memory_rwx_regions), + .max = ARRAY_SIZE(memory_rwx_regions), + }; phys_addr_t base, end; u64 i; - for_each_mem_range(i, &base, &end) - vmem_add_range(base, end - base); + /* + * Set RW+NX attribute on all memory, except regions enumerated with + * memory_rwx exclude type. These regions need different attributes, + * which are enforced afterwards. + * + * __for_each_mem_range() iterate and exclude types should be sorted. + * The relative location of _stext and _sinittext is hardcoded in the + * linker script. However a location of __stext_amode31 and the kernel + * image itself are chosen dynamically. Thus, sort the exclude type. + */ + sort(&memory_rwx_regions, + ARRAY_SIZE(memory_rwx_regions), sizeof(memory_rwx_regions[0]), + memblock_region_cmp, memblock_region_swap); + __for_each_mem_range(i, &memblock.memory, &memory_rwx, + NUMA_NO_NODE, MEMBLOCK_NONE, &base, &end, NULL) { + __set_memory((unsigned long)__va(base), + (end - base) >> PAGE_SHIFT, + SET_MEMORY_RW | SET_MEMORY_NX); + } + __set_memory((unsigned long)_stext, (unsigned long)(_etext - _stext) >> PAGE_SHIFT, SET_MEMORY_RO | SET_MEMORY_X); @@ -678,15 +742,14 @@ void __init vmem_map_init(void) __set_memory((unsigned long)_sinittext, (unsigned long)(_einittext - _sinittext) >> PAGE_SHIFT, SET_MEMORY_RO | SET_MEMORY_X); - __set_memory(__stext_amode31, (__etext_amode31 - __stext_amode31) >> PAGE_SHIFT, + __set_memory(__stext_amode31, + (__etext_amode31 - __stext_amode31) >> PAGE_SHIFT, SET_MEMORY_RO | SET_MEMORY_X); - /* lowcore requires 4k mapping for real addresses / prefixing */ - set_memory_4k(0, LC_PAGES); - /* lowcore must be executable for LPSWE */ - if (!static_key_enabled(&cpu_has_bear)) - set_memory_x(0, 1); + if (static_key_enabled(&cpu_has_bear)) + set_memory_nx(0, 1); + set_memory_nx(PAGE_SIZE, 1); pr_info("Write protected kernel read-only data: %luk\n", (unsigned long)(__end_rodata - _stext) >> 10); diff --git a/drivers/s390/char/Kconfig b/drivers/s390/char/Kconfig index 7d1749b0d378..80c4e5101c97 100644 --- a/drivers/s390/char/Kconfig +++ b/drivers/s390/char/Kconfig @@ -5,17 +5,10 @@ comment "S/390 character device drivers" config TN3270 def_tristate y prompt "Support for locally attached 3270 terminals" - depends on CCW + depends on CCW && TTY help Include support for IBM 3270 terminals. -config TN3270_TTY - def_tristate y - prompt "Support for tty input/output on 3270 terminals" - depends on TN3270 && TTY - help - Include support for using an IBM 3270 terminal as a Linux tty. - config TN3270_FS def_tristate m prompt "Support for fullscreen applications on 3270 terminals" @@ -26,7 +19,7 @@ config TN3270_FS config TN3270_CONSOLE def_bool y prompt "Support for console on 3270 terminal" - depends on TN3270=y && TN3270_TTY=y + depends on TN3270=y help Include support for using an IBM 3270 terminal as a Linux system console. Available only if 3270 support is compiled in statically. diff --git a/drivers/s390/char/Makefile b/drivers/s390/char/Makefile index ce32270082f5..b0f6b3201636 100644 --- a/drivers/s390/char/Makefile +++ b/drivers/s390/char/Makefile @@ -21,9 +21,7 @@ obj-y += ctrlchar.o keyboard.o defkeymap.o sclp.o sclp_rw.o sclp_quiesce.o \ sclp_cmd.o sclp_config.o sclp_cpi_sys.o sclp_ocf.o sclp_ctl.o \ sclp_early.o sclp_early_core.o sclp_sd.o -obj-$(CONFIG_TN3270) += raw3270.o -obj-$(CONFIG_TN3270_CONSOLE) += con3270.o -obj-$(CONFIG_TN3270_TTY) += tty3270.o +obj-$(CONFIG_TN3270) += raw3270.o con3270.o obj-$(CONFIG_TN3270_FS) += fs3270.o obj-$(CONFIG_TN3215) += con3215.o diff --git a/drivers/s390/char/con3270.c b/drivers/s390/char/con3270.c index 10f6a37fb153..d9983550062d 100644 --- a/drivers/s390/char/con3270.c +++ b/drivers/s390/char/con3270.c @@ -1,518 +1,2075 @@ // SPDX-License-Identifier: GPL-2.0 /* - * IBM/3270 Driver - console view. + * IBM/3270 Driver - tty functions. * - * Author(s): - * Original 3270 Code for 2.4 written by Richard Hitt (UTS Global) - * Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com> - * Copyright IBM Corp. 2003, 2009 + * Author(s): + * Original 3270 Code for 2.4 written by Richard Hitt (UTS Global) + * Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com> + * -- Copyright IBM Corp. 2003 */ #include <linux/module.h> -#include <linux/console.h> +#include <linux/types.h> +#include <linux/kdev_t.h> +#include <linux/tty.h> +#include <linux/vt_kern.h> #include <linux/init.h> +#include <linux/console.h> #include <linux/interrupt.h> -#include <linux/list.h> +#include <linux/workqueue.h> #include <linux/panic_notifier.h> -#include <linux/types.h> -#include <linux/slab.h> -#include <linux/err.h> #include <linux/reboot.h> +#include <linux/slab.h> +#include <linux/memblock.h> +#include <linux/compat.h> #include <asm/ccwdev.h> #include <asm/cio.h> -#include <asm/cpcmd.h> #include <asm/ebcdic.h> +#include <asm/cpcmd.h> +#include <linux/uaccess.h> #include "raw3270.h" -#include "tty3270.h" -#include "ctrlchar.h" +#include "keyboard.h" + +#define TTY3270_CHAR_BUF_SIZE 256 +#define TTY3270_OUTPUT_BUFFER_SIZE 4096 +#define TTY3270_SCREEN_PAGES 8 /* has to be power-of-two */ +#define TTY3270_RECALL_SIZE 16 /* has to be power-of-two */ +#define TTY3270_STATUS_AREA_SIZE 40 + +static struct tty_driver *tty3270_driver; +static int tty3270_max_index; +static struct raw3270_fn tty3270_fn; + +#define TTY3270_HIGHLIGHT_BLINK 1 +#define TTY3270_HIGHLIGHT_REVERSE 2 +#define TTY3270_HIGHLIGHT_UNDERSCORE 4 + +struct tty3270_attribute { + unsigned char alternate_charset:1; /* Graphics charset */ + unsigned char highlight:3; /* Blink/reverse/underscore */ + unsigned char f_color:4; /* Foreground color */ + unsigned char b_color:4; /* Background color */ +}; -#define CON3270_OUTPUT_BUFFER_SIZE 1024 -#define CON3270_STRING_PAGES 4 +struct tty3270_cell { + unsigned char character; + struct tty3270_attribute attributes; +}; -static struct raw3270_fn con3270_fn; +struct tty3270_line { + struct tty3270_cell *cells; + int len; + int dirty; +}; -static bool auto_update = true; -module_param(auto_update, bool, 0); +static const unsigned char sfq_read_partition[] = { + 0x00, 0x07, 0x01, 0xff, 0x03, 0x00, 0x81 +}; + +#define ESCAPE_NPAR 8 /* - * Main 3270 console view data structure. + * The main tty view data structure. + * FIXME: + * 1) describe line orientation & lines list concept against screen + * 2) describe conversion of screen to lines + * 3) describe line format. */ -struct con3270 { +struct tty3270 { struct raw3270_view view; - struct list_head freemem; /* list of free memory for strings. */ + struct tty_port port; /* Output stuff. */ - struct list_head lines; /* list of lines. */ - struct list_head update; /* list of lines to update. */ - int line_nr; /* line number for next update. */ - int nr_lines; /* # lines in list. */ + unsigned char wcc; /* Write control character. */ int nr_up; /* # lines up in history. */ unsigned long update_flags; /* Update indication bits. */ - struct string *cline; /* current output line. */ - struct string *status; /* last line of display. */ - struct raw3270_request *write; /* single write request. */ - struct timer_list timer; + struct raw3270_request *write; /* Single write request. */ + struct timer_list timer; /* Output delay timer. */ + char *converted_line; /* RAW 3270 data stream */ + unsigned int line_view_start; /* Start of visible area */ + unsigned int line_write_start; /* current write position */ + unsigned int oops_line; /* line counter used when print oops */ + + /* Current tty screen. */ + unsigned int cx, cy; /* Current output position. */ + struct tty3270_attribute attributes; + struct tty3270_attribute saved_attributes; + int allocated_lines; + struct tty3270_line *screen; /* Input stuff. */ - struct string *input; /* input string for read request. */ - struct raw3270_request *read; /* single read request. */ - struct raw3270_request *kreset; /* single keyboard reset request. */ - struct tasklet_struct readlet; /* tasklet to issue read request. */ + char *prompt; /* Output string for input area. */ + char *input; /* Input string for read request. */ + struct raw3270_request *read; /* Single read request. */ + struct raw3270_request *kreset; /* Single keyboard reset request. */ + struct raw3270_request *readpartreq; + unsigned char inattr; /* Visible/invisible input. */ + int throttle, attn; /* tty throttle/unthrottle. */ + struct tasklet_struct readlet; /* Tasklet to issue read request. */ + struct tasklet_struct hanglet; /* Tasklet to hang up the tty. */ + struct kbd_data *kbd; /* key_maps stuff. */ + + /* Escape sequence parsing. */ + int esc_state, esc_ques, esc_npar; + int esc_par[ESCAPE_NPAR]; + unsigned int saved_cx, saved_cy; + + /* Command recalling. */ + char **rcl_lines; /* Array of recallable lines */ + int rcl_write_index; /* Write index of recallable items */ + int rcl_read_index; /* Read index of recallable items */ + + /* Character array for put_char/flush_chars. */ + unsigned int char_count; + char char_buf[TTY3270_CHAR_BUF_SIZE]; }; -static struct con3270 *condev; - -/* con3270->update_flags. See con3270_update for details. */ -#define CON_UPDATE_ERASE 1 /* Use EWRITEA instead of WRITE. */ -#define CON_UPDATE_LIST 2 /* Update lines in tty3270->update. */ -#define CON_UPDATE_STATUS 4 /* Update status line. */ -#define CON_UPDATE_ALL 8 /* Recreate screen. */ +/* tty3270->update_flags. See tty3270_update for details. */ +#define TTY_UPDATE_INPUT 0x1 /* Update input line. */ +#define TTY_UPDATE_STATUS 0x2 /* Update status line. */ +#define TTY_UPDATE_LINES 0x4 /* Update visible screen lines */ +#define TTY_UPDATE_ALL 0x7 /* Recreate screen. */ -static void con3270_update(struct timer_list *); +#define TTY3270_INPUT_AREA_ROWS 2 /* * Setup timeout for a device. On timeout trigger an update. */ -static void con3270_set_timer(struct con3270 *cp, int expires) +static void tty3270_set_timer(struct tty3270 *tp, int expires) { - if (expires == 0) - del_timer(&cp->timer); - else - mod_timer(&cp->timer, jiffies + expires); + mod_timer(&tp->timer, jiffies + expires); } -/* - * The status line is the last line of the screen. It shows the string - * "console view" in the lower left corner and "Running"/"More..."/"Holding" - * in the lower right corner of the screen. - */ -static void -con3270_update_status(struct con3270 *cp) +static int tty3270_tty_rows(struct tty3270 *tp) { - char *str; + return tp->view.rows - TTY3270_INPUT_AREA_ROWS; +} - str = (cp->nr_up != 0) ? "History" : "Running"; - memcpy(cp->status->string + 24, str, 7); - codepage_convert(cp->view.ascebc, cp->status->string + 24, 7); - cp->update_flags |= CON_UPDATE_STATUS; +static char *tty3270_add_ba(struct tty3270 *tp, char *cp, char order, int x, int y) +{ + *cp++ = order; + raw3270_buffer_address(tp->view.dev, cp, x, y); + return cp + 2; } -static void -con3270_create_status(struct con3270 *cp) +static char *tty3270_add_ra(struct tty3270 *tp, char *cp, int x, int y, char c) +{ + cp = tty3270_add_ba(tp, cp, TO_RA, x, y); + *cp++ = c; + return cp; +} + +static char *tty3270_add_sa(struct tty3270 *tp, char *cp, char attr, char value) +{ + *cp++ = TO_SA; + *cp++ = attr; + *cp++ = value; + return cp; +} + +static char *tty3270_add_ge(struct tty3270 *tp, char *cp, char c) +{ + *cp++ = TO_GE; + *cp++ = c; + return cp; +} + +static char *tty3270_add_sf(struct tty3270 *tp, char *cp, char type) +{ + *cp++ = TO_SF; + *cp++ = type; + return cp; +} + +static int tty3270_line_increment(struct tty3270 *tp, unsigned int line, unsigned int incr) +{ + return (line + incr) & (tp->allocated_lines - 1); +} + +static struct tty3270_line *tty3270_get_write_line(struct tty3270 *tp, unsigned int num) { - static const unsigned char blueprint[] = - { TO_SBA, 0, 0, TO_SF,TF_LOG,TO_SA,TAT_COLOR, TAC_GREEN, - 'c','o','n','s','o','l','e',' ','v','i','e','w', - TO_RA,0,0,0,'R','u','n','n','i','n','g',TO_SF,TF_LOG }; + return tp->screen + tty3270_line_increment(tp, tp->line_write_start, num); +} + +static struct tty3270_line *tty3270_get_view_line(struct tty3270 *tp, unsigned int num) +{ + return tp->screen + tty3270_line_increment(tp, tp->line_view_start, num - tp->nr_up); +} - cp->status = alloc_string(&cp->freemem, sizeof(blueprint)); - /* Copy blueprint to status line */ - memcpy(cp->status->string, blueprint, sizeof(blueprint)); - /* Set TO_RA addresses. */ - raw3270_buffer_address(cp->view.dev, cp->status->string + 1, - cp->view.cols * (cp->view.rows - 1)); - raw3270_buffer_address(cp->view.dev, cp->status->string + 21, - cp->view.cols * cp->view.rows - 8); - /* Convert strings to ebcdic. */ - codepage_convert(cp->view.ascebc, cp->status->string + 8, 12); - codepage_convert(cp->view.ascebc, cp->status->string + 24, 7); +static int tty3270_input_size(int cols) +{ + return cols * 2 - 11; +} + +static void tty3270_update_prompt(struct tty3270 *tp, char *input) +{ + strcpy(tp->prompt, input); + tp->update_flags |= TTY_UPDATE_INPUT; + tty3270_set_timer(tp, 1); } /* - * Set output offsets to 3270 datastream fragment of a console string. + * The input line are the two last lines of the screen. */ -static void -con3270_update_string(struct con3270 *cp, struct string *s, int nr) +static int tty3270_add_prompt(struct tty3270 *tp) { - if (s->len < 4) { - /* This indicates a bug, but printing a warning would - * cause a deadlock. */ - return; + int count = 0; + char *cp; + + cp = tp->converted_line; + cp = tty3270_add_ba(tp, cp, TO_SBA, 0, -2); + *cp++ = tp->view.ascebc['>']; + + if (*tp->prompt) { + cp = tty3270_add_sf(tp, cp, TF_INMDT); + count = min_t(int, strlen(tp->prompt), + tp->view.cols * 2 - TTY3270_STATUS_AREA_SIZE - 2); + memcpy(cp, tp->prompt, count); + cp += count; + } else { + cp = tty3270_add_sf(tp, cp, tp->inattr); } - if (s->string[s->len - 4] != TO_RA) - return; - raw3270_buffer_address(cp->view.dev, s->string + s->len - 3, - cp->view.cols * (nr + 1)); + *cp++ = TO_IC; + /* Clear to end of input line. */ + if (count < tp->view.cols * 2 - 11) + cp = tty3270_add_ra(tp, cp, -TTY3270_STATUS_AREA_SIZE, -1, 0); + return cp - tp->converted_line; +} + +static char *tty3270_ebcdic_convert(struct tty3270 *tp, char *d, char *s) +{ + while (*s) + *d++ = tp->view.ascebc[(int)*s++]; + return d; } /* - * Rebuild update list to print all lines. + * The status line is the last line of the screen. It shows the string + * "Running"/"History X" in the lower right corner of the screen. */ -static void -con3270_rebuild_update(struct con3270 *cp) +static int tty3270_add_status(struct tty3270 *tp) { - struct string *s, *n; - int nr; + char *cp = tp->converted_line; + int len; + + cp = tty3270_add_ba(tp, cp, TO_SBA, -TTY3270_STATUS_AREA_SIZE, -1); + cp = tty3270_add_sf(tp, cp, TF_LOG); + cp = tty3270_add_sa(tp, cp, TAT_FGCOLOR, TAC_GREEN); + cp = tty3270_ebcdic_convert(tp, cp, " 7"); + cp = tty3270_add_sa(tp, cp, TAT_EXTHI, TAX_REVER); + cp = tty3270_ebcdic_convert(tp, cp, "PrevPg"); + cp = tty3270_add_sa(tp, cp, TAT_EXTHI, TAX_RESET); + cp = tty3270_ebcdic_convert(tp, cp, " 8"); + cp = tty3270_add_sa(tp, cp, TAT_EXTHI, TAX_REVER); + cp = tty3270_ebcdic_convert(tp, cp, "NextPg"); + cp = tty3270_add_sa(tp, cp, TAT_EXTHI, TAX_RESET); + cp = tty3270_ebcdic_convert(tp, cp, " 12"); + cp = tty3270_add_sa(tp, cp, TAT_EXTHI, TAX_REVER); + cp = tty3270_ebcdic_convert(tp, cp, "Recall"); + cp = tty3270_add_sa(tp, cp, TAT_EXTHI, TAX_RESET); + cp = tty3270_ebcdic_convert(tp, cp, " "); + if (tp->nr_up) { + len = sprintf(cp, "History %d", -tp->nr_up); + codepage_convert(tp->view.ascebc, cp, len); + cp += len; + } else { + cp = tty3270_ebcdic_convert(tp, cp, oops_in_progress ? "Crashed" : "Running"); + } + cp = tty3270_add_sf(tp, cp, TF_LOG); + cp = tty3270_add_sa(tp, cp, TAT_FGCOLOR, TAC_RESET); + return cp - (char *)tp->converted_line; +} - /* - * Throw away update list and create a new one, - * containing all lines that will fit on the screen. - */ - list_for_each_entry_safe(s, n, &cp->update, update) - list_del_init(&s->update); - nr = cp->view.rows - 2 + cp->nr_up; - list_for_each_entry_reverse(s, &cp->lines, list) { - if (nr < cp->view.rows - 1) - list_add(&s->update, &cp->update); - if (--nr < 0) - break; +static void tty3270_blank_screen(struct tty3270 *tp) +{ + struct tty3270_line *line; + int i; + + for (i = 0; i < tty3270_tty_rows(tp); i++) { + line = tty3270_get_write_line(tp, i); + line->len = 0; + line->dirty = 1; } - cp->line_nr = 0; - cp->update_flags |= CON_UPDATE_LIST; + tp->nr_up = 0; } /* - * Alloc string for size bytes. Free strings from history if necessary. + * Write request completion callback. */ -static struct string * -con3270_alloc_string(struct con3270 *cp, size_t size) +static void tty3270_write_callback(struct raw3270_request *rq, void *data) { - struct string *s, *n; + struct tty3270 *tp = container_of(rq->view, struct tty3270, view); - s = alloc_string(&cp->freemem, size); - if (s) - return s; - list_for_each_entry_safe(s, n, &cp->lines, list) { - list_del(&s->list); - if (!list_empty(&s->update)) - list_del(&s->update); - cp->nr_lines--; - if (free_string(&cp->freemem, s) >= size) - break; + if (rq->rc != 0) { + /* Write wasn't successful. Refresh all. */ + tp->update_flags = TTY_UPDATE_ALL; + tty3270_set_timer(tp, 1); } - s = alloc_string(&cp->freemem, size); - BUG_ON(!s); - if (cp->nr_up != 0 && cp->nr_up + cp->view.rows > cp->nr_lines) { - cp->nr_up = cp->nr_lines - cp->view.rows + 1; - con3270_rebuild_update(cp); - con3270_update_status(cp); + raw3270_request_reset(rq); + xchg(&tp->write, rq); +} + +static int tty3270_required_length(struct tty3270 *tp, struct tty3270_line *line) +{ + unsigned char f_color, b_color, highlight; + struct tty3270_cell *cell; + int i, flen = 3; /* Prefix (TO_SBA). */ + + flen += line->len; + highlight = 0; + f_color = TAC_RESET; + b_color = TAC_RESET; + + for (i = 0, cell = line->cells; i < line->len; i++, cell++) { + if (cell->attributes.highlight != highlight) { + flen += 3; /* TO_SA to switch highlight. */ + highlight = cell->attributes.highlight; + } + if (cell->attributes.f_color != f_color) { + flen += 3; /* TO_SA to switch color. */ + f_color = cell->attributes.f_color; + } + if (cell->attributes.b_color != b_color) { + flen += 3; /* TO_SA to switch color. */ + b_color = cell->attributes.b_color; + } + if (cell->attributes.alternate_charset) + flen += 1; /* TO_GE to switch to graphics extensions */ } - return s; + if (highlight) + flen += 3; /* TO_SA to reset hightlight. */ + if (f_color != TAC_RESET) + flen += 3; /* TO_SA to reset color. */ + if (b_color != TAC_RESET) + flen += 3; /* TO_SA to reset color. */ + if (line->len < tp->view.cols) + flen += 4; /* Postfix (TO_RA). */ + + return flen; +} + +static char *tty3270_add_reset_attributes(struct tty3270 *tp, struct tty3270_line *line, + char *cp, struct tty3270_attribute *attr, int lineno) +{ + if (attr->highlight) + cp = tty3270_add_sa(tp, cp, TAT_EXTHI, TAX_RESET); + if (attr->f_color != TAC_RESET) + cp = tty3270_add_sa(tp, cp, TAT_FGCOLOR, TAX_RESET); + if (attr->b_color != TAC_RESET) + cp = tty3270_add_sa(tp, cp, TAT_BGCOLOR, TAX_RESET); + if (line->len < tp->view.cols) + cp = tty3270_add_ra(tp, cp, 0, lineno + 1, 0); + return cp; +} + +static char tty3270_graphics_translate(struct tty3270 *tp, char ch) +{ + switch (ch) { + case 'q': /* - */ + return 0xa2; + case 'x': /* '|' */ + return 0x85; + case 'l': /* |- */ + return 0xc5; + case 't': /* |_ */ + return 0xc6; + case 'u': /* _| */ + return 0xd6; + case 'k': /* -| */ + return 0xd5; + case 'j': + return 0xd4; + case 'm': + return 0xc4; + case 'n': /* + */ + return 0xd3; + case 'v': + return 0xc7; + case 'w': + return 0xd7; + default: + return ch; + } +} + +static char *tty3270_add_attributes(struct tty3270 *tp, struct tty3270_line *line, + struct tty3270_attribute *attr, char *cp, int lineno) +{ + const unsigned char colors[16] = { + [0] = TAC_DEFAULT, + [1] = TAC_RED, + [2] = TAC_GREEN, + [3] = TAC_YELLOW, + [4] = TAC_BLUE, + [5] = TAC_PINK, + [6] = TAC_TURQ, + [7] = TAC_WHITE, + [9] = TAC_DEFAULT + }; + + const unsigned char highlights[8] = { + [TTY3270_HIGHLIGHT_BLINK] = TAX_BLINK, + [TTY3270_HIGHLIGHT_REVERSE] = TAX_REVER, + [TTY3270_HIGHLIGHT_UNDERSCORE] = TAX_UNDER, + }; + + struct tty3270_cell *cell; + int c, i; + + cp = tty3270_add_ba(tp, cp, TO_SBA, 0, lineno); + + for (i = 0, cell = line->cells; i < line->len; i++, cell++) { + if (cell->attributes.highlight != attr->highlight) { + attr->highlight = cell->attributes.highlight; + cp = tty3270_add_sa(tp, cp, TAT_EXTHI, highlights[attr->highlight]); + } + if (cell->attributes.f_color != attr->f_color) { + attr->f_color = cell->attributes.f_color; + cp = tty3270_add_sa(tp, cp, TAT_FGCOLOR, colors[attr->f_color]); + } + if (cell->attributes.b_color != attr->b_color) { + attr->b_color = cell->attributes.b_color; + cp = tty3270_add_sa(tp, cp, TAT_BGCOLOR, colors[attr->b_color]); + } + c = cell->character; + if (cell->attributes.alternate_charset) + cp = tty3270_add_ge(tp, cp, tty3270_graphics_translate(tp, c)); + else + *cp++ = tp->view.ascebc[c]; + } + return cp; +} + +static void tty3270_reset_attributes(struct tty3270_attribute *attr) +{ + attr->highlight = TAX_RESET; + attr->f_color = TAC_RESET; + attr->b_color = TAC_RESET; } /* - * Write completion callback. + * Convert a tty3270_line to a 3270 data fragment usable for output. */ -static void -con3270_write_callback(struct raw3270_request *rq, void *data) +static unsigned int tty3270_convert_line(struct tty3270 *tp, struct tty3270_line *line, int lineno) { - raw3270_request_reset(rq); - xchg(&((struct con3270 *) rq->view)->write, rq); + struct tty3270_attribute attr; + int flen; + char *cp; + + /* Determine how long the fragment will be. */ + flen = tty3270_required_length(tp, line); + if (flen > PAGE_SIZE) + return 0; + /* Write 3270 data fragment. */ + tty3270_reset_attributes(&attr); + cp = tty3270_add_attributes(tp, line, &attr, tp->converted_line, lineno); + cp = tty3270_add_reset_attributes(tp, line, cp, &attr, lineno); + return cp - (char *)tp->converted_line; +} + +static void tty3270_update_lines_visible(struct tty3270 *tp, struct raw3270_request *rq) +{ + struct tty3270_line *line; + int len, i; + + for (i = 0; i < tty3270_tty_rows(tp); i++) { + line = tty3270_get_view_line(tp, i); + if (!line->dirty) + continue; + len = tty3270_convert_line(tp, line, i); + if (raw3270_request_add_data(rq, tp->converted_line, len)) + break; + line->dirty = 0; + } + if (i == tty3270_tty_rows(tp)) { + for (i = 0; i < tp->allocated_lines; i++) + tp->screen[i].dirty = 0; + tp->update_flags &= ~TTY_UPDATE_LINES; + } +} + +static void tty3270_update_lines_all(struct tty3270 *tp, struct raw3270_request *rq) +{ + struct tty3270_line *line; + char buf[4]; + int len, i; + + for (i = 0; i < tp->allocated_lines; i++) { + line = tty3270_get_write_line(tp, i + tp->cy + 1); + if (!line->dirty) + continue; + len = tty3270_convert_line(tp, line, tp->oops_line); + if (raw3270_request_add_data(rq, tp->converted_line, len)) + break; + line->dirty = 0; + if (++tp->oops_line >= tty3270_tty_rows(tp)) + tp->oops_line = 0; + } + + if (i == tp->allocated_lines) { + if (tp->oops_line < tty3270_tty_rows(tp)) { + tty3270_add_ra(tp, buf, 0, tty3270_tty_rows(tp), 0); + if (raw3270_request_add_data(rq, buf, sizeof(buf))) + return; + } + tp->update_flags &= ~TTY_UPDATE_LINES; + } } /* - * Update console display. + * Update 3270 display. */ -static void -con3270_update(struct timer_list *t) +static void tty3270_update(struct timer_list *t) { - struct con3270 *cp = from_timer(cp, t, timer); + struct tty3270 *tp = from_timer(tp, t, timer); struct raw3270_request *wrq; - char wcc, prolog[6]; - unsigned long flags; - unsigned long updated; - struct string *s, *n; - int rc; + u8 cmd = TC_WRITE; + int rc, len; - if (!auto_update && !raw3270_view_active(&cp->view)) - return; - if (cp->view.dev) - raw3270_activate_view(&cp->view); - - wrq = xchg(&cp->write, 0); + wrq = xchg(&tp->write, 0); if (!wrq) { - con3270_set_timer(cp, 1); + tty3270_set_timer(tp, 1); return; } - spin_lock_irqsave(&cp->view.lock, flags); - updated = 0; - if (cp->update_flags & CON_UPDATE_ALL) { - con3270_rebuild_update(cp); - con3270_update_status(cp); - cp->update_flags = CON_UPDATE_ERASE | CON_UPDATE_LIST | - CON_UPDATE_STATUS; - } - if (cp->update_flags & CON_UPDATE_ERASE) { - /* Use erase write alternate to initialize display. */ - raw3270_request_set_cmd(wrq, TC_EWRITEA); - updated |= CON_UPDATE_ERASE; - } else - raw3270_request_set_cmd(wrq, TC_WRITE); + spin_lock_irq(&tp->view.lock); + if (tp->update_flags == TTY_UPDATE_ALL) + cmd = TC_EWRITEA; - wcc = TW_NONE; - raw3270_request_add_data(wrq, &wcc, 1); + raw3270_request_set_cmd(wrq, cmd); + raw3270_request_add_data(wrq, &tp->wcc, 1); + tp->wcc = TW_NONE; /* * Update status line. */ - if (cp->update_flags & CON_UPDATE_STATUS) - if (raw3270_request_add_data(wrq, cp->status->string, - cp->status->len) == 0) - updated |= CON_UPDATE_STATUS; - - if (cp->update_flags & CON_UPDATE_LIST) { - prolog[0] = TO_SBA; - prolog[3] = TO_SA; - prolog[4] = TAT_COLOR; - prolog[5] = TAC_TURQ; - raw3270_buffer_address(cp->view.dev, prolog + 1, - cp->view.cols * cp->line_nr); - raw3270_request_add_data(wrq, prolog, 6); - /* Write strings in the update list to the screen. */ - list_for_each_entry_safe(s, n, &cp->update, update) { - if (s != cp->cline) - con3270_update_string(cp, s, cp->line_nr); - if (raw3270_request_add_data(wrq, s->string, - s->len) != 0) - break; - list_del_init(&s->update); - if (s != cp->cline) - cp->line_nr++; - } - if (list_empty(&cp->update)) - updated |= CON_UPDATE_LIST; + if (tp->update_flags & TTY_UPDATE_STATUS) { + len = tty3270_add_status(tp); + if (raw3270_request_add_data(wrq, tp->converted_line, len) == 0) + tp->update_flags &= ~TTY_UPDATE_STATUS; + } + + /* + * Write input line. + */ + if (tp->update_flags & TTY_UPDATE_INPUT) { + len = tty3270_add_prompt(tp); + if (raw3270_request_add_data(wrq, tp->converted_line, len) == 0) + tp->update_flags &= ~TTY_UPDATE_INPUT; + } + + if (tp->update_flags & TTY_UPDATE_LINES) { + if (oops_in_progress) + tty3270_update_lines_all(tp, wrq); + else + tty3270_update_lines_visible(tp, wrq); } - wrq->callback = con3270_write_callback; - rc = raw3270_start(&cp->view, wrq); + + wrq->callback = tty3270_write_callback; + rc = raw3270_start(&tp->view, wrq); if (rc == 0) { - cp->update_flags &= ~updated; - if (cp->update_flags) - con3270_set_timer(cp, 1); + if (tp->update_flags) + tty3270_set_timer(tp, 1); } else { raw3270_request_reset(wrq); - xchg(&cp->write, wrq); + xchg(&tp->write, wrq); } - spin_unlock_irqrestore(&cp->view.lock, flags); + spin_unlock_irq(&tp->view.lock); } /* - * Read tasklet. + * Command recalling. */ -static void -con3270_read_tasklet(unsigned long data) +static void tty3270_rcl_add(struct tty3270 *tp, char *input, int len) +{ + char *p; + + if (len <= 0) + return; + p = tp->rcl_lines[tp->rcl_write_index++]; + tp->rcl_write_index &= TTY3270_RECALL_SIZE - 1; + memcpy(p, input, len); + p[len] = '\0'; + tp->rcl_read_index = tp->rcl_write_index; +} + +static void tty3270_rcl_backward(struct kbd_data *kbd) +{ + struct tty3270 *tp = container_of(kbd->port, struct tty3270, port); + int i = 0; + + spin_lock_irq(&tp->view.lock); + if (tp->inattr == TF_INPUT) { + do { + tp->rcl_read_index--; + tp->rcl_read_index &= TTY3270_RECALL_SIZE - 1; + } while (!*tp->rcl_lines[tp->rcl_read_index] && + i++ < TTY3270_RECALL_SIZE - 1); + tty3270_update_prompt(tp, tp->rcl_lines[tp->rcl_read_index]); + } + spin_unlock_irq(&tp->view.lock); +} + +/* + * Deactivate tty view. + */ +static void tty3270_exit_tty(struct kbd_data *kbd) +{ + struct tty3270 *tp = container_of(kbd->port, struct tty3270, port); + + raw3270_deactivate_view(&tp->view); +} + +static void tty3270_redraw(struct tty3270 *tp) +{ + int i; + + for (i = 0; i < tty3270_tty_rows(tp); i++) + tty3270_get_view_line(tp, i)->dirty = 1; + tp->update_flags = TTY_UPDATE_ALL; + tty3270_set_timer(tp, 1); +} + +/* + * Scroll forward in history. + */ +static void tty3270_scroll_forward(struct kbd_data *kbd) +{ + struct tty3270 *tp = container_of(kbd->port, struct tty3270, port); + + spin_lock_irq(&tp->view.lock); + + if (tp->nr_up >= tty3270_tty_rows(tp)) + tp->nr_up -= tty3270_tty_rows(tp) / 2; + else + tp->nr_up = 0; + tty3270_redraw(tp); + spin_unlock_irq(&tp->view.lock); +} + +/* + * Scroll backward in history. + */ +static void tty3270_scroll_backward(struct kbd_data *kbd) +{ + struct tty3270 *tp = container_of(kbd->port, struct tty3270, port); + + spin_lock_irq(&tp->view.lock); + tp->nr_up += tty3270_tty_rows(tp) / 2; + if (tp->nr_up > tp->allocated_lines - tty3270_tty_rows(tp)) + tp->nr_up = tp->allocated_lines - tty3270_tty_rows(tp); + tty3270_redraw(tp); + spin_unlock_irq(&tp->view.lock); +} + +/* + * Pass input line to tty. + */ +static void tty3270_read_tasklet(unsigned long data) { + struct raw3270_request *rrq = (struct raw3270_request *)data; static char kreset_data = TW_KR; - struct raw3270_request *rrq; - struct con3270 *cp; - unsigned long flags; - int nr_up, deactivate; - - rrq = (struct raw3270_request *)data; - cp = (struct con3270 *) rrq->view; - spin_lock_irqsave(&cp->view.lock, flags); - nr_up = cp->nr_up; - deactivate = 0; - /* Check aid byte. */ - switch (cp->input->string[0]) { - case 0x7d: /* enter: jump to bottom. */ - nr_up = 0; - break; - case 0xf3: /* PF3: deactivate the console view. */ - deactivate = 1; + struct tty3270 *tp = container_of(rrq->view, struct tty3270, view); + char *input; + int len; + + spin_lock_irq(&tp->view.lock); + /* + * Two AID keys are special: For 0x7d (enter) the input line + * has to be emitted to the tty and for 0x6d the screen + * needs to be redrawn. + */ + input = NULL; + len = 0; + switch (tp->input[0]) { + case AID_ENTER: + /* Enter: write input to tty. */ + input = tp->input + 6; + len = tty3270_input_size(tp->view.cols) - 6 - rrq->rescnt; + if (tp->inattr != TF_INPUTN) + tty3270_rcl_add(tp, input, len); + if (tp->nr_up > 0) + tp->nr_up = 0; + /* Clear input area. */ + tty3270_update_prompt(tp, ""); + tty3270_set_timer(tp, 1); break; - case 0x6d: /* clear: start from scratch. */ - cp->update_flags = CON_UPDATE_ALL; - con3270_set_timer(cp, 1); + case AID_CLEAR: + /* Display has been cleared. Redraw. */ + tp->update_flags = TTY_UPDATE_ALL; + tty3270_set_timer(tp, 1); + if (!list_empty(&tp->readpartreq->list)) + break; + raw3270_start_request(&tp->view, tp->readpartreq, TC_WRITESF, + (char *)sfq_read_partition, sizeof(sfq_read_partition)); break; - case 0xf7: /* PF7: do a page up in the console log. */ - nr_up += cp->view.rows - 2; - if (nr_up + cp->view.rows - 1 > cp->nr_lines) { - nr_up = cp->nr_lines - cp->view.rows + 1; - if (nr_up < 0) - nr_up = 0; - } + case AID_READ_PARTITION: + raw3270_read_modified_cb(tp->readpartreq, tp->input); break; - case 0xf8: /* PF8: do a page down in the console log. */ - nr_up -= cp->view.rows - 2; - if (nr_up < 0) - nr_up = 0; + default: break; } - if (nr_up != cp->nr_up) { - cp->nr_up = nr_up; - con3270_rebuild_update(cp); - con3270_update_status(cp); - con3270_set_timer(cp, 1); - } - spin_unlock_irqrestore(&cp->view.lock, flags); + spin_unlock_irq(&tp->view.lock); /* Start keyboard reset command. */ - raw3270_request_reset(cp->kreset); - raw3270_request_set_cmd(cp->kreset, TC_WRITE); - raw3270_request_add_data(cp->kreset, &kreset_data, 1); - raw3270_start(&cp->view, cp->kreset); + raw3270_start_request(&tp->view, tp->kreset, TC_WRITE, &kreset_data, 1); - if (deactivate) - raw3270_deactivate_view(&cp->view); + while (len-- > 0) + kbd_keycode(tp->kbd, *input++); + /* Emit keycode for AID byte. */ + kbd_keycode(tp->kbd, 256 + tp->input[0]); raw3270_request_reset(rrq); - xchg(&cp->read, rrq); - raw3270_put_view(&cp->view); + xchg(&tp->read, rrq); + raw3270_put_view(&tp->view); } /* * Read request completion callback. */ -static void -con3270_read_callback(struct raw3270_request *rq, void *data) +static void tty3270_read_callback(struct raw3270_request *rq, void *data) { + struct tty3270 *tp = container_of(rq->view, struct tty3270, view); + raw3270_get_view(rq->view); /* Schedule tasklet to pass input to tty. */ - tasklet_schedule(&((struct con3270 *) rq->view)->readlet); + tasklet_schedule(&tp->readlet); } /* - * Issue a read request. Called only from interrupt function. + * Issue a read request. Call with device lock. */ -static void -con3270_issue_read(struct con3270 *cp) +static void tty3270_issue_read(struct tty3270 *tp, int lock) { struct raw3270_request *rrq; int rc; - rrq = xchg(&cp->read, 0); + rrq = xchg(&tp->read, 0); if (!rrq) /* Read already scheduled. */ return; - rrq->callback = con3270_read_callback; - rrq->callback_data = cp; + rrq->callback = tty3270_read_callback; + rrq->callback_data = tp; raw3270_request_set_cmd(rrq, TC_READMOD); - raw3270_request_set_data(rrq, cp->input->string, cp->input->len); + raw3270_request_set_data(rrq, tp->input, tty3270_input_size(tp->view.cols)); /* Issue the read modified request. */ - rc = raw3270_start_irq(&cp->view, rrq); - if (rc) + if (lock) + rc = raw3270_start(&tp->view, rrq); + else + rc = raw3270_start_irq(&tp->view, rrq); + if (rc) { raw3270_request_reset(rrq); + xchg(&tp->read, rrq); + } } /* - * Switch to the console view. + * Hang up the tty */ -static int -con3270_activate(struct raw3270_view *view) +static void tty3270_hangup_tasklet(unsigned long data) { - struct con3270 *cp; + struct tty3270 *tp = (struct tty3270 *)data; + + tty_port_tty_hangup(&tp->port, true); + raw3270_put_view(&tp->view); +} - cp = (struct con3270 *) view; - cp->update_flags = CON_UPDATE_ALL; - con3270_set_timer(cp, 1); +/* + * Switch to the tty view. + */ +static int tty3270_activate(struct raw3270_view *view) +{ + struct tty3270 *tp = container_of(view, struct tty3270, view); + + tp->update_flags = TTY_UPDATE_ALL; + tty3270_set_timer(tp, 1); return 0; } -static void -con3270_deactivate(struct raw3270_view *view) +static void tty3270_deactivate(struct raw3270_view *view) { - struct con3270 *cp; + struct tty3270 *tp = container_of(view, struct tty3270, view); - cp = (struct con3270 *) view; - del_timer(&cp->timer); + del_timer(&tp->timer); } -static void -con3270_irq(struct con3270 *cp, struct raw3270_request *rq, struct irb *irb) +static void tty3270_irq(struct tty3270 *tp, struct raw3270_request *rq, struct irb *irb) { /* Handle ATTN. Schedule tasklet to read aid. */ - if (irb->scsw.cmd.dstat & DEV_STAT_ATTENTION) - con3270_issue_read(cp); + if (irb->scsw.cmd.dstat & DEV_STAT_ATTENTION) { + if (!tp->throttle) + tty3270_issue_read(tp, 0); + else + tp->attn = 1; + } if (rq) { - if (irb->scsw.cmd.dstat & DEV_STAT_UNIT_CHECK) + if (irb->scsw.cmd.dstat & DEV_STAT_UNIT_CHECK) { rq->rc = -EIO; - else + raw3270_get_view(&tp->view); + tasklet_schedule(&tp->hanglet); + } else { /* Normal end. Copy residual count. */ rq->rescnt = irb->scsw.cmd.count; + } } else if (irb->scsw.cmd.dstat & DEV_STAT_DEV_END) { /* Interrupt without an outstanding request -> update all */ - cp->update_flags = CON_UPDATE_ALL; - con3270_set_timer(cp, 1); + tp->update_flags = TTY_UPDATE_ALL; + tty3270_set_timer(tp, 1); + } +} + +/* + * Allocate tty3270 structure. + */ +static struct tty3270 *tty3270_alloc_view(void) +{ + struct tty3270 *tp; + + tp = kzalloc(sizeof(*tp), GFP_KERNEL); + if (!tp) + goto out_err; + + tp->write = raw3270_request_alloc(TTY3270_OUTPUT_BUFFER_SIZE); + if (IS_ERR(tp->write)) + goto out_tp; + tp->read = raw3270_request_alloc(0); + if (IS_ERR(tp->read)) + goto out_write; + tp->kreset = raw3270_request_alloc(1); + if (IS_ERR(tp->kreset)) + goto out_read; + tp->readpartreq = raw3270_request_alloc(sizeof(sfq_read_partition)); + if (IS_ERR(tp->readpartreq)) + goto out_reset; + tp->kbd = kbd_alloc(); + if (!tp->kbd) + goto out_readpartreq; + + tty_port_init(&tp->port); + timer_setup(&tp->timer, tty3270_update, 0); + tasklet_init(&tp->readlet, tty3270_read_tasklet, + (unsigned long)tp->read); + tasklet_init(&tp->hanglet, tty3270_hangup_tasklet, + (unsigned long)tp); + return tp; + +out_readpartreq: + raw3270_request_free(tp->readpartreq); +out_reset: + raw3270_request_free(tp->kreset); +out_read: + raw3270_request_free(tp->read); +out_write: + raw3270_request_free(tp->write); +out_tp: + kfree(tp); +out_err: + return ERR_PTR(-ENOMEM); +} + +/* + * Free tty3270 structure. + */ +static void tty3270_free_view(struct tty3270 *tp) +{ + kbd_free(tp->kbd); + raw3270_request_free(tp->kreset); + raw3270_request_free(tp->read); + raw3270_request_free(tp->write); + free_page((unsigned long)tp->converted_line); + tty_port_destroy(&tp->port); + kfree(tp); +} + +/* + * Allocate tty3270 screen. + */ +static struct tty3270_line *tty3270_alloc_screen(struct tty3270 *tp, unsigned int rows, + unsigned int cols, int *allocated_out) +{ + struct tty3270_line *screen; + int allocated, lines; + + allocated = __roundup_pow_of_two(rows) * TTY3270_SCREEN_PAGES; + screen = kcalloc(allocated, sizeof(struct tty3270_line), GFP_KERNEL); + if (!screen) + goto out_err; + for (lines = 0; lines < allocated; lines++) { + screen[lines].cells = kcalloc(cols, sizeof(struct tty3270_cell), GFP_KERNEL); + if (!screen[lines].cells) + goto out_screen; + } + *allocated_out = allocated; + return screen; +out_screen: + while (lines--) + kfree(screen[lines].cells); + kfree(screen); +out_err: + return ERR_PTR(-ENOMEM); +} + +static char **tty3270_alloc_recall(int cols) +{ + char **lines; + int i; + + lines = kmalloc_array(TTY3270_RECALL_SIZE, sizeof(char *), GFP_KERNEL); + if (!lines) + return NULL; + for (i = 0; i < TTY3270_RECALL_SIZE; i++) { + lines[i] = kcalloc(1, tty3270_input_size(cols) + 1, GFP_KERNEL); + if (!lines[i]) + break; } + + if (i == TTY3270_RECALL_SIZE) + return lines; + + while (i--) + kfree(lines[i]); + kfree(lines); + return NULL; } -/* Console view to a 3270 device. */ -static struct raw3270_fn con3270_fn = { - .activate = con3270_activate, - .deactivate = con3270_deactivate, - .intv = (void *) con3270_irq +static void tty3270_free_recall(char **lines) +{ + int i; + + for (i = 0; i < TTY3270_RECALL_SIZE; i++) + kfree(lines[i]); + kfree(lines); +} + +/* + * Free tty3270 screen. + */ +static void tty3270_free_screen(struct tty3270_line *screen, int old_lines) +{ + int lines; + + for (lines = 0; lines < old_lines; lines++) + kfree(screen[lines].cells); + kfree(screen); +} + +/* + * Resize tty3270 screen + */ +static void tty3270_resize(struct raw3270_view *view, + int new_model, int new_rows, int new_cols, + int old_model, int old_rows, int old_cols) +{ + struct tty3270 *tp = container_of(view, struct tty3270, view); + struct tty3270_line *screen, *oscreen; + char **old_rcl_lines, **new_rcl_lines; + char *old_prompt, *new_prompt; + char *old_input, *new_input; + struct tty_struct *tty; + struct winsize ws; + int new_allocated, old_allocated = tp->allocated_lines; + + if (old_model == new_model && + old_cols == new_cols && + old_rows == new_rows) { + spin_lock_irq(&tp->view.lock); + tty3270_redraw(tp); + spin_unlock_irq(&tp->view.lock); + return; + } + + new_input = kzalloc(tty3270_input_size(new_cols), GFP_KERNEL | GFP_DMA); + if (!new_input) + return; + new_prompt = kzalloc(tty3270_input_size(new_cols), GFP_KERNEL); + if (!new_prompt) + goto out_input; + screen = tty3270_alloc_screen(tp, new_rows, new_cols, &new_allocated); + if (IS_ERR(screen)) + goto out_prompt; + new_rcl_lines = tty3270_alloc_recall(new_cols); + if (!new_rcl_lines) + goto out_screen; + + /* Switch to new output size */ + spin_lock_irq(&tp->view.lock); + tty3270_blank_screen(tp); + oscreen = tp->screen; + tp->screen = screen; + tp->allocated_lines = new_allocated; + tp->view.rows = new_rows; + tp->view.cols = new_cols; + tp->view.model = new_model; + tp->update_flags = TTY_UPDATE_ALL; + old_input = tp->input; + old_prompt = tp->prompt; + old_rcl_lines = tp->rcl_lines; + tp->input = new_input; + tp->prompt = new_prompt; + tp->rcl_lines = new_rcl_lines; + tp->rcl_read_index = 0; + tp->rcl_write_index = 0; + spin_unlock_irq(&tp->view.lock); + tty3270_free_screen(oscreen, old_allocated); + kfree(old_input); + kfree(old_prompt); + tty3270_free_recall(old_rcl_lines); + tty3270_set_timer(tp, 1); + /* Informat tty layer about new size */ + tty = tty_port_tty_get(&tp->port); + if (!tty) + return; + ws.ws_row = tty3270_tty_rows(tp); + ws.ws_col = tp->view.cols; + tty_do_resize(tty, &ws); + tty_kref_put(tty); + return; +out_screen: + tty3270_free_screen(screen, new_rows); +out_prompt: + kfree(new_prompt); +out_input: + kfree(new_input); +} + +/* + * Unlink tty3270 data structure from tty. + */ +static void tty3270_release(struct raw3270_view *view) +{ + struct tty3270 *tp = container_of(view, struct tty3270, view); + struct tty_struct *tty = tty_port_tty_get(&tp->port); + + if (tty) { + tty->driver_data = NULL; + tty_port_tty_set(&tp->port, NULL); + tty_hangup(tty); + raw3270_put_view(&tp->view); + tty_kref_put(tty); + } +} + +/* + * Free tty3270 data structure + */ +static void tty3270_free(struct raw3270_view *view) +{ + struct tty3270 *tp = container_of(view, struct tty3270, view); + + del_timer_sync(&tp->timer); + tty3270_free_screen(tp->screen, tp->allocated_lines); + free_page((unsigned long)tp->converted_line); + kfree(tp->input); + kfree(tp->prompt); + tty3270_free_view(tp); +} + +/* + * Delayed freeing of tty3270 views. + */ +static void tty3270_del_views(void) +{ + int i; + + for (i = RAW3270_FIRSTMINOR; i <= tty3270_max_index; i++) { + struct raw3270_view *view = raw3270_find_view(&tty3270_fn, i); + + if (!IS_ERR(view)) + raw3270_del_view(view); + } +} + +static struct raw3270_fn tty3270_fn = { + .activate = tty3270_activate, + .deactivate = tty3270_deactivate, + .intv = (void *)tty3270_irq, + .release = tty3270_release, + .free = tty3270_free, + .resize = tty3270_resize }; -static inline void -con3270_cline_add(struct con3270 *cp) +static int +tty3270_create_view(int index, struct tty3270 **newtp) +{ + struct tty3270 *tp; + int rc; + + if (tty3270_max_index < index + 1) + tty3270_max_index = index + 1; + + /* Allocate tty3270 structure on first open. */ + tp = tty3270_alloc_view(); + if (IS_ERR(tp)) + return PTR_ERR(tp); + + rc = raw3270_add_view(&tp->view, &tty3270_fn, + index + RAW3270_FIRSTMINOR, + RAW3270_VIEW_LOCK_IRQ); + if (rc) + goto out_free_view; + + tp->screen = tty3270_alloc_screen(tp, tp->view.rows, tp->view.cols, + &tp->allocated_lines); + if (IS_ERR(tp->screen)) { + rc = PTR_ERR(tp->screen); + goto out_put_view; + } + + tp->converted_line = (void *)__get_free_page(GFP_KERNEL); + if (!tp->converted_line) { + rc = -ENOMEM; + goto out_free_screen; + } + + tp->input = kzalloc(tty3270_input_size(tp->view.cols), GFP_KERNEL | GFP_DMA); + if (!tp->input) { + rc = -ENOMEM; + goto out_free_converted_line; + } + + tp->prompt = kzalloc(tty3270_input_size(tp->view.cols), GFP_KERNEL); + if (!tp->prompt) { + rc = -ENOMEM; + goto out_free_input; + } + + tp->rcl_lines = tty3270_alloc_recall(tp->view.cols); + if (!tp->rcl_lines) { + rc = -ENOMEM; + goto out_free_prompt; + } + + /* Create blank line for every line in the tty output area. */ + tty3270_blank_screen(tp); + + tp->kbd->port = &tp->port; + tp->kbd->fn_handler[KVAL(K_INCRCONSOLE)] = tty3270_exit_tty; + tp->kbd->fn_handler[KVAL(K_SCROLLBACK)] = tty3270_scroll_backward; + tp->kbd->fn_handler[KVAL(K_SCROLLFORW)] = tty3270_scroll_forward; + tp->kbd->fn_handler[KVAL(K_CONS)] = tty3270_rcl_backward; + kbd_ascebc(tp->kbd, tp->view.ascebc); + + raw3270_activate_view(&tp->view); + raw3270_put_view(&tp->view); + *newtp = tp; + return 0; + +out_free_prompt: + kfree(tp->prompt); +out_free_input: + kfree(tp->input); +out_free_converted_line: + free_page((unsigned long)tp->converted_line); +out_free_screen: + tty3270_free_screen(tp->screen, tp->view.rows); +out_put_view: + raw3270_put_view(&tp->view); + raw3270_del_view(&tp->view); +out_free_view: + tty3270_free_view(tp); + return rc; +} + +/* + * This routine is called whenever a 3270 tty is opened first time. + */ +static int +tty3270_install(struct tty_driver *driver, struct tty_struct *tty) +{ + struct raw3270_view *view; + struct tty3270 *tp; + int rc; + + /* Check if the tty3270 is already there. */ + view = raw3270_find_view(&tty3270_fn, tty->index + RAW3270_FIRSTMINOR); + if (IS_ERR(view)) { + rc = tty3270_create_view(tty->index, &tp); + if (rc) + return rc; + } else { + tp = container_of(view, struct tty3270, view); + tty->driver_data = tp; + tp->inattr = TF_INPUT; + } + + tty->winsize.ws_row = tty3270_tty_rows(tp); + tty->winsize.ws_col = tp->view.cols; + rc = tty_port_install(&tp->port, driver, tty); + if (rc) { + raw3270_put_view(&tp->view); + return rc; + } + tty->driver_data = tp; + return 0; +} + +/* + * This routine is called whenever a 3270 tty is opened. + */ +static int tty3270_open(struct tty_struct *tty, struct file *filp) +{ + struct tty3270 *tp = tty->driver_data; + struct tty_port *port = &tp->port; + + port->count++; + tty_port_tty_set(port, tty); + return 0; +} + +/* + * This routine is called when the 3270 tty is closed. We wait + * for the remaining request to be completed. Then we clean up. + */ +static void tty3270_close(struct tty_struct *tty, struct file *filp) { - if (!list_empty(&cp->cline->list)) - /* Already added. */ + struct tty3270 *tp = tty->driver_data; + + if (tty->count > 1) return; - list_add_tail(&cp->cline->list, &cp->lines); - cp->nr_lines++; - con3270_rebuild_update(cp); + if (tp) + tty_port_tty_set(&tp->port, NULL); +} + +static void tty3270_cleanup(struct tty_struct *tty) +{ + struct tty3270 *tp = tty->driver_data; + + if (tp) { + tty->driver_data = NULL; + raw3270_put_view(&tp->view); + } +} + +/* + * We always have room. + */ +static unsigned int tty3270_write_room(struct tty_struct *tty) +{ + return INT_MAX; } -static inline void -con3270_cline_insert(struct con3270 *cp, unsigned char c) +/* + * Insert character into the screen at the current position with the + * current color and highlight. This function does NOT do cursor movement. + */ +static void tty3270_put_character(struct tty3270 *tp, char ch) { - cp->cline->string[cp->cline->len++] = - cp->view.ascebc[(c < ' ') ? ' ' : c]; - if (list_empty(&cp->cline->update)) { - list_add_tail(&cp->cline->update, &cp->update); - cp->update_flags |= CON_UPDATE_LIST; + struct tty3270_line *line; + struct tty3270_cell *cell; + + line = tty3270_get_write_line(tp, tp->cy); + if (line->len <= tp->cx) { + while (line->len < tp->cx) { + cell = line->cells + line->len; + cell->character = ' '; + cell->attributes = tp->attributes; + line->len++; + } + line->len++; } + cell = line->cells + tp->cx; + cell->character = ch; + cell->attributes = tp->attributes; + line->dirty = 1; +} + +/* + * Do carriage return. + */ +static void tty3270_cr(struct tty3270 *tp) +{ + tp->cx = 0; } -static inline void -con3270_cline_end(struct con3270 *cp) +/* + * Do line feed. + */ +static void tty3270_lf(struct tty3270 *tp) { - struct string *s; - unsigned int size; + struct tty3270_line *line; + int i; - /* Copy cline. */ - size = (cp->cline->len < cp->view.cols - 5) ? - cp->cline->len + 4 : cp->view.cols; - s = con3270_alloc_string(cp, size); - memcpy(s->string, cp->cline->string, cp->cline->len); - if (cp->cline->len < cp->view.cols - 5) { - s->string[s->len - 4] = TO_RA; - s->string[s->len - 1] = 0; + if (tp->cy < tty3270_tty_rows(tp) - 1) { + tp->cy++; } else { - while (--size >= cp->cline->len) - s->string[size] = cp->view.ascebc[' ']; + tp->line_view_start = tty3270_line_increment(tp, tp->line_view_start, 1); + tp->line_write_start = tty3270_line_increment(tp, tp->line_write_start, 1); + for (i = 0; i < tty3270_tty_rows(tp); i++) + tty3270_get_view_line(tp, i)->dirty = 1; + } + + line = tty3270_get_write_line(tp, tp->cy); + line->len = 0; + line->dirty = 1; +} + +static void tty3270_ri(struct tty3270 *tp) +{ + if (tp->cy > 0) + tp->cy--; +} + +static void tty3270_reset_cell(struct tty3270 *tp, struct tty3270_cell *cell) +{ + cell->character = ' '; + tty3270_reset_attributes(&cell->attributes); +} + +/* + * Insert characters at current position. + */ +static void tty3270_insert_characters(struct tty3270 *tp, int n) +{ + struct tty3270_line *line; + int k; + + line = tty3270_get_write_line(tp, tp->cy); + while (line->len < tp->cx) + tty3270_reset_cell(tp, &line->cells[line->len++]); + if (n > tp->view.cols - tp->cx) + n = tp->view.cols - tp->cx; + k = min_t(int, line->len - tp->cx, tp->view.cols - tp->cx - n); + while (k--) + line->cells[tp->cx + n + k] = line->cells[tp->cx + k]; + line->len += n; + if (line->len > tp->view.cols) + line->len = tp->view.cols; + while (n-- > 0) { + line->cells[tp->cx + n].character = ' '; + line->cells[tp->cx + n].attributes = tp->attributes; + } +} + +/* + * Delete characters at current position. + */ +static void tty3270_delete_characters(struct tty3270 *tp, int n) +{ + struct tty3270_line *line; + int i; + + line = tty3270_get_write_line(tp, tp->cy); + if (line->len <= tp->cx) + return; + if (line->len - tp->cx <= n) { + line->len = tp->cx; + return; + } + for (i = tp->cx; i + n < line->len; i++) + line->cells[i] = line->cells[i + n]; + line->len -= n; +} + +/* + * Erase characters at current position. + */ +static void tty3270_erase_characters(struct tty3270 *tp, int n) +{ + struct tty3270_line *line; + struct tty3270_cell *cell; + + line = tty3270_get_write_line(tp, tp->cy); + while (line->len > tp->cx && n-- > 0) { + cell = line->cells + tp->cx++; + tty3270_reset_cell(tp, cell); + } + tp->cx += n; + tp->cx = min_t(int, tp->cx, tp->view.cols - 1); +} + +/* + * Erase line, 3 different cases: + * Esc [ 0 K Erase from current position to end of line inclusive + * Esc [ 1 K Erase from beginning of line to current position inclusive + * Esc [ 2 K Erase entire line (without moving cursor) + */ +static void tty3270_erase_line(struct tty3270 *tp, int mode) +{ + struct tty3270_line *line; + struct tty3270_cell *cell; + int i, start, end; + + line = tty3270_get_write_line(tp, tp->cy); + + switch (mode) { + case 0: + start = tp->cx; + end = tp->view.cols; + break; + case 1: + start = 0; + end = tp->cx; + break; + case 2: + start = 0; + end = tp->view.cols; + break; + default: + return; + } + + for (i = start; i < end; i++) { + cell = line->cells + i; + tty3270_reset_cell(tp, cell); + cell->attributes.b_color = tp->attributes.b_color; + } + + if (line->len <= end) + line->len = end; +} + +/* + * Erase display, 3 different cases: + * Esc [ 0 J Erase from current position to bottom of screen inclusive + * Esc [ 1 J Erase from top of screen to current position inclusive + * Esc [ 2 J Erase entire screen (without moving the cursor) + */ +static void tty3270_erase_display(struct tty3270 *tp, int mode) +{ + struct tty3270_line *line; + int i, start, end; + + switch (mode) { + case 0: + tty3270_erase_line(tp, 0); + start = tp->cy + 1; + end = tty3270_tty_rows(tp); + break; + case 1: + start = 0; + end = tp->cy; + tty3270_erase_line(tp, 1); + break; + case 2: + start = 0; + end = tty3270_tty_rows(tp); + break; + default: + return; + } + for (i = start; i < end; i++) { + line = tty3270_get_write_line(tp, i); + line->len = 0; + line->dirty = 1; + } +} + +/* + * Set attributes found in an escape sequence. + * Esc [ <attr> ; <attr> ; ... m + */ +static void tty3270_set_attributes(struct tty3270 *tp) +{ + int i, attr; + + for (i = 0; i <= tp->esc_npar; i++) { + attr = tp->esc_par[i]; + switch (attr) { + case 0: /* Reset */ + tty3270_reset_attributes(&tp->attributes); + break; + /* Highlight. */ + case 4: /* Start underlining. */ + tp->attributes.highlight = TTY3270_HIGHLIGHT_UNDERSCORE; + break; + case 5: /* Start blink. */ + tp->attributes.highlight = TTY3270_HIGHLIGHT_BLINK; + break; + case 7: /* Start reverse. */ + tp->attributes.highlight = TTY3270_HIGHLIGHT_REVERSE; + break; + case 24: /* End underlining */ + tp->attributes.highlight &= ~TTY3270_HIGHLIGHT_UNDERSCORE; + break; + case 25: /* End blink. */ + tp->attributes.highlight &= ~TTY3270_HIGHLIGHT_BLINK; + break; + case 27: /* End reverse. */ + tp->attributes.highlight &= ~TTY3270_HIGHLIGHT_REVERSE; + break; + /* Foreground color. */ + case 30: /* Black */ + case 31: /* Red */ + case 32: /* Green */ + case 33: /* Yellow */ + case 34: /* Blue */ + case 35: /* Magenta */ + case 36: /* Cyan */ + case 37: /* White */ + case 39: /* Black */ + tp->attributes.f_color = attr - 30; + break; + /* Background color. */ + case 40: /* Black */ + case 41: /* Red */ + case 42: /* Green */ + case 43: /* Yellow */ + case 44: /* Blue */ + case 45: /* Magenta */ + case 46: /* Cyan */ + case 47: /* White */ + case 49: /* Black */ + tp->attributes.b_color = attr - 40; + break; + } + } +} + +static inline int tty3270_getpar(struct tty3270 *tp, int ix) +{ + return (tp->esc_par[ix] > 0) ? tp->esc_par[ix] : 1; +} + +static void tty3270_goto_xy(struct tty3270 *tp, int cx, int cy) +{ + struct tty3270_line *line; + struct tty3270_cell *cell; + int max_cx = max(0, cx); + int max_cy = max(0, cy); + + tp->cx = min_t(int, tp->view.cols - 1, max_cx); + line = tty3270_get_write_line(tp, tp->cy); + while (line->len < tp->cx) { + cell = line->cells + line->len; + cell->character = ' '; + cell->attributes = tp->attributes; + line->len++; + } + tp->cy = min_t(int, tty3270_tty_rows(tp) - 1, max_cy); +} + +/* + * Process escape sequences. Known sequences: + * Esc 7 Save Cursor Position + * Esc 8 Restore Cursor Position + * Esc [ Pn ; Pn ; .. m Set attributes + * Esc [ Pn ; Pn H Cursor Position + * Esc [ Pn ; Pn f Cursor Position + * Esc [ Pn A Cursor Up + * Esc [ Pn B Cursor Down + * Esc [ Pn C Cursor Forward + * Esc [ Pn D Cursor Backward + * Esc [ Pn G Cursor Horizontal Absolute + * Esc [ Pn X Erase Characters + * Esc [ Ps J Erase in Display + * Esc [ Ps K Erase in Line + * // FIXME: add all the new ones. + * + * Pn is a numeric parameter, a string of zero or more decimal digits. + * Ps is a selective parameter. + */ +static void tty3270_escape_sequence(struct tty3270 *tp, char ch) +{ + enum { ES_NORMAL, ES_ESC, ES_SQUARE, ES_PAREN, ES_GETPARS }; + + if (tp->esc_state == ES_NORMAL) { + if (ch == 0x1b) + /* Starting new escape sequence. */ + tp->esc_state = ES_ESC; + return; + } + if (tp->esc_state == ES_ESC) { + tp->esc_state = ES_NORMAL; + switch (ch) { + case '[': + tp->esc_state = ES_SQUARE; + break; + case '(': + tp->esc_state = ES_PAREN; + break; + case 'E': + tty3270_cr(tp); + tty3270_lf(tp); + break; + case 'M': + tty3270_ri(tp); + break; + case 'D': + tty3270_lf(tp); + break; + case 'Z': /* Respond ID. */ + kbd_puts_queue(&tp->port, "\033[?6c"); + break; + case '7': /* Save cursor position. */ + tp->saved_cx = tp->cx; + tp->saved_cy = tp->cy; + tp->saved_attributes = tp->attributes; + break; + case '8': /* Restore cursor position. */ + tty3270_goto_xy(tp, tp->saved_cx, tp->saved_cy); + tp->attributes = tp->saved_attributes; + break; + case 'c': /* Reset terminal. */ + tp->cx = 0; + tp->cy = 0; + tp->saved_cx = 0; + tp->saved_cy = 0; + tty3270_reset_attributes(&tp->attributes); + tty3270_reset_attributes(&tp->saved_attributes); + tty3270_erase_display(tp, 2); + break; + } + return; + } + + switch (tp->esc_state) { + case ES_PAREN: + tp->esc_state = ES_NORMAL; + switch (ch) { + case 'B': + tp->attributes.alternate_charset = 0; + break; + case '0': + tp->attributes.alternate_charset = 1; + break; + } + return; + case ES_SQUARE: + tp->esc_state = ES_GETPARS; + memset(tp->esc_par, 0, sizeof(tp->esc_par)); + tp->esc_npar = 0; + tp->esc_ques = (ch == '?'); + if (tp->esc_ques) + return; + fallthrough; + case ES_GETPARS: + if (ch == ';' && tp->esc_npar < ESCAPE_NPAR - 1) { + tp->esc_npar++; + return; + } + if (ch >= '0' && ch <= '9') { + tp->esc_par[tp->esc_npar] *= 10; + tp->esc_par[tp->esc_npar] += ch - '0'; + return; + } + break; + default: + break; + } + tp->esc_state = ES_NORMAL; + if (ch == 'n' && !tp->esc_ques) { + if (tp->esc_par[0] == 5) /* Status report. */ + kbd_puts_queue(&tp->port, "\033[0n"); + else if (tp->esc_par[0] == 6) { /* Cursor report. */ + char buf[40]; + + sprintf(buf, "\033[%d;%dR", tp->cy + 1, tp->cx + 1); + kbd_puts_queue(&tp->port, buf); + } + return; + } + if (tp->esc_ques) + return; + switch (ch) { + case 'm': + tty3270_set_attributes(tp); + break; + case 'H': /* Set cursor position. */ + case 'f': + tty3270_goto_xy(tp, tty3270_getpar(tp, 1) - 1, + tty3270_getpar(tp, 0) - 1); + break; + case 'd': /* Set y position. */ + tty3270_goto_xy(tp, tp->cx, tty3270_getpar(tp, 0) - 1); + break; + case 'A': /* Cursor up. */ + case 'F': + tty3270_goto_xy(tp, tp->cx, tp->cy - tty3270_getpar(tp, 0)); + break; + case 'B': /* Cursor down. */ + case 'e': + case 'E': + tty3270_goto_xy(tp, tp->cx, tp->cy + tty3270_getpar(tp, 0)); + break; + case 'C': /* Cursor forward. */ + case 'a': + tty3270_goto_xy(tp, tp->cx + tty3270_getpar(tp, 0), tp->cy); + break; + case 'D': /* Cursor backward. */ + tty3270_goto_xy(tp, tp->cx - tty3270_getpar(tp, 0), tp->cy); + break; + case 'G': /* Set x position. */ + case '`': + tty3270_goto_xy(tp, tty3270_getpar(tp, 0), tp->cy); + break; + case 'X': /* Erase Characters. */ + tty3270_erase_characters(tp, tty3270_getpar(tp, 0)); + break; + case 'J': /* Erase display. */ + tty3270_erase_display(tp, tp->esc_par[0]); + break; + case 'K': /* Erase line. */ + tty3270_erase_line(tp, tp->esc_par[0]); + break; + case 'P': /* Delete characters. */ + tty3270_delete_characters(tp, tty3270_getpar(tp, 0)); + break; + case '@': /* Insert characters. */ + tty3270_insert_characters(tp, tty3270_getpar(tp, 0)); + break; + case 's': /* Save cursor position. */ + tp->saved_cx = tp->cx; + tp->saved_cy = tp->cy; + tp->saved_attributes = tp->attributes; + break; + case 'u': /* Restore cursor position. */ + tty3270_goto_xy(tp, tp->saved_cx, tp->saved_cy); + tp->attributes = tp->saved_attributes; + break; + } +} + +/* + * String write routine for 3270 ttys + */ +static void tty3270_do_write(struct tty3270 *tp, struct tty_struct *tty, + const unsigned char *buf, int count) +{ + int i_msg, i; + + spin_lock_irq(&tp->view.lock); + for (i_msg = 0; !tty->flow.stopped && i_msg < count; i_msg++) { + if (tp->esc_state != 0) { + /* Continue escape sequence. */ + tty3270_escape_sequence(tp, buf[i_msg]); + continue; + } + + switch (buf[i_msg]) { + case 0x00: + break; + case 0x07: /* '\a' -- Alarm */ + tp->wcc |= TW_PLUSALARM; + break; + case 0x08: /* Backspace. */ + if (tp->cx > 0) { + tp->cx--; + tty3270_put_character(tp, ' '); + } + break; + case 0x09: /* '\t' -- Tabulate */ + for (i = tp->cx % 8; i < 8; i++) { + if (tp->cx >= tp->view.cols) { + tty3270_cr(tp); + tty3270_lf(tp); + break; + } + tty3270_put_character(tp, ' '); + tp->cx++; + } + break; + case 0x0a: /* '\n' -- New Line */ + tty3270_cr(tp); + tty3270_lf(tp); + break; + case 0x0c: /* '\f' -- Form Feed */ + tty3270_erase_display(tp, 2); + tp->cx = 0; + tp->cy = 0; + break; + case 0x0d: /* '\r' -- Carriage Return */ + tp->cx = 0; + break; + case 0x0e: + tp->attributes.alternate_charset = 1; + break; + case 0x0f: /* SuSE "exit alternate mode" */ + tp->attributes.alternate_charset = 0; + break; + case 0x1b: /* Start escape sequence. */ + tty3270_escape_sequence(tp, buf[i_msg]); + break; + default: /* Insert normal character. */ + if (tp->cx >= tp->view.cols) { + tty3270_cr(tp); + tty3270_lf(tp); + } + tty3270_put_character(tp, buf[i_msg]); + tp->cx++; + break; + } + } + /* Setup timer to update display after 1/10 second */ + tp->update_flags |= TTY_UPDATE_LINES; + if (!timer_pending(&tp->timer)) + tty3270_set_timer(tp, msecs_to_jiffies(100)); + + spin_unlock_irq(&tp->view.lock); +} + +/* + * String write routine for 3270 ttys + */ +static int tty3270_write(struct tty_struct *tty, + const unsigned char *buf, int count) +{ + struct tty3270 *tp; + + tp = tty->driver_data; + if (!tp) + return 0; + if (tp->char_count > 0) { + tty3270_do_write(tp, tty, tp->char_buf, tp->char_count); + tp->char_count = 0; + } + tty3270_do_write(tp, tty, buf, count); + return count; +} + +/* + * Put single characters to the ttys character buffer + */ +static int tty3270_put_char(struct tty_struct *tty, unsigned char ch) +{ + struct tty3270 *tp; + + tp = tty->driver_data; + if (!tp || tp->char_count >= TTY3270_CHAR_BUF_SIZE) + return 0; + tp->char_buf[tp->char_count++] = ch; + return 1; +} + +/* + * Flush all characters from the ttys characeter buffer put there + * by tty3270_put_char. + */ +static void tty3270_flush_chars(struct tty_struct *tty) +{ + struct tty3270 *tp; + + tp = tty->driver_data; + if (!tp) + return; + if (tp->char_count > 0) { + tty3270_do_write(tp, tty, tp->char_buf, tp->char_count); + tp->char_count = 0; } - /* Replace cline with allocated line s and reset cline. */ - list_add(&s->list, &cp->cline->list); - list_del_init(&cp->cline->list); - if (!list_empty(&cp->cline->update)) { - list_add(&s->update, &cp->cline->update); - list_del_init(&cp->cline->update); +} + +/* + * Check for visible/invisible input switches + */ +static void tty3270_set_termios(struct tty_struct *tty, const struct ktermios *old) +{ + struct tty3270 *tp; + int new; + + tp = tty->driver_data; + if (!tp) + return; + spin_lock_irq(&tp->view.lock); + if (L_ICANON(tty)) { + new = L_ECHO(tty) ? TF_INPUT : TF_INPUTN; + if (new != tp->inattr) { + tp->inattr = new; + tty3270_update_prompt(tp, ""); + tty3270_set_timer(tp, 1); + } } - cp->cline->len = 0; + spin_unlock_irq(&tp->view.lock); } /* - * Write a string to the 3270 console + * Disable reading from a 3270 tty */ +static void tty3270_throttle(struct tty_struct *tty) +{ + struct tty3270 *tp; + + tp = tty->driver_data; + if (!tp) + return; + tp->throttle = 1; +} + +/* + * Enable reading from a 3270 tty + */ +static void tty3270_unthrottle(struct tty_struct *tty) +{ + struct tty3270 *tp; + + tp = tty->driver_data; + if (!tp) + return; + tp->throttle = 0; + if (tp->attn) + tty3270_issue_read(tp, 1); +} + +/* + * Hang up the tty device. + */ +static void tty3270_hangup(struct tty_struct *tty) +{ + struct tty3270 *tp; + + tp = tty->driver_data; + if (!tp) + return; + spin_lock_irq(&tp->view.lock); + tp->cx = 0; + tp->cy = 0; + tp->saved_cx = 0; + tp->saved_cy = 0; + tty3270_reset_attributes(&tp->attributes); + tty3270_reset_attributes(&tp->saved_attributes); + tty3270_blank_screen(tp); + tp->update_flags = TTY_UPDATE_ALL; + spin_unlock_irq(&tp->view.lock); + tty3270_set_timer(tp, 1); +} + +static void tty3270_wait_until_sent(struct tty_struct *tty, int timeout) +{ +} + +static int tty3270_ioctl(struct tty_struct *tty, unsigned int cmd, + unsigned long arg) +{ + struct tty3270 *tp; + + tp = tty->driver_data; + if (!tp) + return -ENODEV; + if (tty_io_error(tty)) + return -EIO; + return kbd_ioctl(tp->kbd, cmd, arg); +} + +#ifdef CONFIG_COMPAT +static long tty3270_compat_ioctl(struct tty_struct *tty, + unsigned int cmd, unsigned long arg) +{ + struct tty3270 *tp; + + tp = tty->driver_data; + if (!tp) + return -ENODEV; + if (tty_io_error(tty)) + return -EIO; + return kbd_ioctl(tp->kbd, cmd, (unsigned long)compat_ptr(arg)); +} +#endif + +static const struct tty_operations tty3270_ops = { + .install = tty3270_install, + .cleanup = tty3270_cleanup, + .open = tty3270_open, + .close = tty3270_close, + .write = tty3270_write, + .put_char = tty3270_put_char, + .flush_chars = tty3270_flush_chars, + .write_room = tty3270_write_room, + .throttle = tty3270_throttle, + .unthrottle = tty3270_unthrottle, + .hangup = tty3270_hangup, + .wait_until_sent = tty3270_wait_until_sent, + .ioctl = tty3270_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = tty3270_compat_ioctl, +#endif + .set_termios = tty3270_set_termios +}; + +static void tty3270_create_cb(int minor) +{ + tty_register_device(tty3270_driver, minor - RAW3270_FIRSTMINOR, NULL); +} + +static void tty3270_destroy_cb(int minor) +{ + tty_unregister_device(tty3270_driver, minor - RAW3270_FIRSTMINOR); +} + +static struct raw3270_notifier tty3270_notifier = { + .create = tty3270_create_cb, + .destroy = tty3270_destroy_cb, +}; + +/* + * 3270 tty registration code called from tty_init(). + * Most kernel services (incl. kmalloc) are available at this poimt. + */ +static int __init tty3270_init(void) +{ + struct tty_driver *driver; + int ret; + + driver = tty_alloc_driver(RAW3270_MAXDEVS, + TTY_DRIVER_REAL_RAW | + TTY_DRIVER_DYNAMIC_DEV | + TTY_DRIVER_RESET_TERMIOS); + if (IS_ERR(driver)) + return PTR_ERR(driver); + + /* + * Initialize the tty_driver structure + * Entries in tty3270_driver that are NOT initialized: + * proc_entry, set_termios, flush_buffer, set_ldisc, write_proc + */ + driver->driver_name = "tty3270"; + driver->name = "3270/tty"; + driver->major = IBM_TTY3270_MAJOR; + driver->minor_start = RAW3270_FIRSTMINOR; + driver->name_base = RAW3270_FIRSTMINOR; + driver->type = TTY_DRIVER_TYPE_SYSTEM; + driver->subtype = SYSTEM_TYPE_TTY; + driver->init_termios = tty_std_termios; + tty_set_operations(driver, &tty3270_ops); + ret = tty_register_driver(driver); + if (ret) { + tty_driver_kref_put(driver); + return ret; + } + tty3270_driver = driver; + raw3270_register_notifier(&tty3270_notifier); + return 0; +} + +static void __exit tty3270_exit(void) +{ + struct tty_driver *driver; + + raw3270_unregister_notifier(&tty3270_notifier); + driver = tty3270_driver; + tty3270_driver = NULL; + tty_unregister_driver(driver); + tty_driver_kref_put(driver); + tty3270_del_views(); +} + +#if IS_ENABLED(CONFIG_TN3270_CONSOLE) + +static struct tty3270 *condev; + static void con3270_write(struct console *co, const char *str, unsigned int count) { - struct con3270 *cp; + struct tty3270 *tp = co->data; unsigned long flags; - unsigned char c; + char c; - cp = condev; - spin_lock_irqsave(&cp->view.lock, flags); - while (count-- > 0) { + spin_lock_irqsave(&tp->view.lock, flags); + while (count--) { c = *str++; - if (cp->cline->len == 0) - con3270_cline_add(cp); - if (c != '\n') - con3270_cline_insert(cp, c); - if (c == '\n' || cp->cline->len >= cp->view.cols) - con3270_cline_end(cp); + if (c == 0x0a) { + tty3270_cr(tp); + tty3270_lf(tp); + } else { + if (tp->cx >= tp->view.cols) { + tty3270_cr(tp); + tty3270_lf(tp); + } + tty3270_put_character(tp, c); + tp->cx++; + } } - /* Setup timer to output current console buffer after 1/10 second */ - cp->nr_up = 0; - if (cp->view.dev && !timer_pending(&cp->timer)) - con3270_set_timer(cp, HZ/10); - spin_unlock_irqrestore(&cp->view.lock,flags); + spin_unlock_irqrestore(&tp->view.lock, flags); } static struct tty_driver * @@ -522,14 +2079,11 @@ con3270_device(struct console *c, int *index) return tty3270_driver; } -/* - * Wait for end of write request. - */ static void -con3270_wait_write(struct con3270 *cp) +con3270_wait_write(struct tty3270 *tp) { - while (!cp->write) { - raw3270_wait_cons_dev(cp->view.dev); + while (!tp->write) { + raw3270_wait_cons_dev(tp->view.dev); barrier(); } } @@ -545,28 +2099,30 @@ con3270_wait_write(struct con3270 *cp) static int con3270_notify(struct notifier_block *self, unsigned long event, void *data) { - struct con3270 *cp; + struct tty3270 *tp; unsigned long flags; + int rc; - cp = condev; - if (!cp->view.dev) + tp = condev; + if (!tp->view.dev) return NOTIFY_DONE; - if (!raw3270_view_lock_unavailable(&cp->view)) - raw3270_activate_view(&cp->view); - if (!spin_trylock_irqsave(&cp->view.lock, flags)) + if (!raw3270_view_lock_unavailable(&tp->view)) { + rc = raw3270_activate_view(&tp->view); + if (rc) + return NOTIFY_DONE; + } + if (!spin_trylock_irqsave(&tp->view.lock, flags)) return NOTIFY_DONE; - con3270_wait_write(cp); - cp->nr_up = 0; - con3270_rebuild_update(cp); - con3270_update_status(cp); - while (cp->update_flags != 0) { - spin_unlock_irqrestore(&cp->view.lock, flags); - con3270_update(&cp->timer); - spin_lock_irqsave(&cp->view.lock, flags); - con3270_wait_write(cp); - } - spin_unlock_irqrestore(&cp->view.lock, flags); - + con3270_wait_write(tp); + tp->nr_up = 0; + tp->update_flags = TTY_UPDATE_ALL; + while (tp->update_flags != 0) { + spin_unlock_irqrestore(&tp->view.lock, flags); + tty3270_update(&tp->timer); + spin_lock_irqsave(&tp->view.lock, flags); + con3270_wait_write(tp); + } + spin_unlock_irqrestore(&tp->view.lock, flags); return NOTIFY_DONE; } @@ -580,9 +2136,6 @@ static struct notifier_block on_reboot_nb = { .priority = INT_MIN + 1, /* run the callback late */ }; -/* - * The console structure for the 3270 console - */ static struct console con3270 = { .name = "tty3270", .write = con3270_write, @@ -590,15 +2143,13 @@ static struct console con3270 = { .flags = CON_PRINTBUFFER, }; -/* - * 3270 console initialization code called from console_init(). - */ static int __init con3270_init(void) { + struct raw3270_view *view; struct raw3270 *rp; - void *cbuf; - int i; + struct tty3270 *tp; + int rc; /* Check if 3270 is to be the console */ if (!CONSOLE_IS_3270) @@ -614,38 +2165,28 @@ con3270_init(void) if (IS_ERR(rp)) return PTR_ERR(rp); - condev = kzalloc(sizeof(struct con3270), GFP_KERNEL | GFP_DMA); - if (!condev) - return -ENOMEM; - condev->view.dev = rp; - - condev->read = raw3270_request_alloc(0); - condev->read->callback = con3270_read_callback; - condev->read->callback_data = condev; - condev->write = raw3270_request_alloc(CON3270_OUTPUT_BUFFER_SIZE); - condev->kreset = raw3270_request_alloc(1); - - INIT_LIST_HEAD(&condev->lines); - INIT_LIST_HEAD(&condev->update); - timer_setup(&condev->timer, con3270_update, 0); - tasklet_init(&condev->readlet, con3270_read_tasklet, - (unsigned long) condev->read); - - raw3270_add_view(&condev->view, &con3270_fn, 1, RAW3270_VIEW_LOCK_IRQ); - - INIT_LIST_HEAD(&condev->freemem); - for (i = 0; i < CON3270_STRING_PAGES; i++) { - cbuf = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA); - add_string_memory(&condev->freemem, cbuf, PAGE_SIZE); - } - condev->cline = alloc_string(&condev->freemem, condev->view.cols); - condev->cline->len = 0; - con3270_create_status(condev); - condev->input = alloc_string(&condev->freemem, 80); + /* Check if the tty3270 is already there. */ + view = raw3270_find_view(&tty3270_fn, RAW3270_FIRSTMINOR); + if (IS_ERR(view)) { + rc = tty3270_create_view(0, &tp); + if (rc) + return rc; + } else { + tp = container_of(view, struct tty3270, view); + tp->inattr = TF_INPUT; + } + con3270.data = tp; + condev = tp; atomic_notifier_chain_register(&panic_notifier_list, &on_panic_nb); register_reboot_notifier(&on_reboot_nb); register_console(&con3270); return 0; } - console_initcall(con3270_init); +#endif + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_CHARDEV_MAJOR(IBM_TTY3270_MAJOR); + +module_init(tty3270_init); +module_exit(tty3270_exit); diff --git a/drivers/s390/char/diag_ftp.c b/drivers/s390/char/diag_ftp.c index 36bbd6b6e210..65c7f2d565d8 100644 --- a/drivers/s390/char/diag_ftp.c +++ b/drivers/s390/char/diag_ftp.c @@ -159,8 +159,8 @@ ssize_t diag_ftp_cmd(const struct hmcdrv_ftp_cmdspec *ftp, size_t *fsize) goto out; } - len = strlcpy(ldfpl->fident, ftp->fname, sizeof(ldfpl->fident)); - if (len >= HMCDRV_FTP_FIDENT_MAX) { + len = strscpy(ldfpl->fident, ftp->fname, sizeof(ldfpl->fident)); + if (len < 0) { len = -EINVAL; goto out_free; } diff --git a/drivers/s390/char/fs3270.c b/drivers/s390/char/fs3270.c index 4c4683d8784a..4f26b0a55620 100644 --- a/drivers/s390/char/fs3270.c +++ b/drivers/s390/char/fs3270.c @@ -19,6 +19,7 @@ #include <linux/slab.h> #include <linux/types.h> +#include <uapi/asm/fs3270.h> #include <asm/ccwdev.h> #include <asm/cio.h> #include <asm/ebcdic.h> @@ -44,14 +45,12 @@ struct fs3270 { static DEFINE_MUTEX(fs3270_mutex); -static void -fs3270_wake_up(struct raw3270_request *rq, void *data) +static void fs3270_wake_up(struct raw3270_request *rq, void *data) { - wake_up((wait_queue_head_t *) data); + wake_up((wait_queue_head_t *)data); } -static inline int -fs3270_working(struct fs3270 *fp) +static inline int fs3270_working(struct fs3270 *fp) { /* * The fullscreen view is in working order if the view @@ -60,13 +59,12 @@ fs3270_working(struct fs3270 *fp) return fp->active && raw3270_request_final(fp->init); } -static int -fs3270_do_io(struct raw3270_view *view, struct raw3270_request *rq) +static int fs3270_do_io(struct raw3270_view *view, struct raw3270_request *rq) { struct fs3270 *fp; int rc; - fp = (struct fs3270 *) view; + fp = (struct fs3270 *)view; rq->callback = fs3270_wake_up; rq->callback_data = &fp->wait; @@ -90,22 +88,20 @@ fs3270_do_io(struct raw3270_view *view, struct raw3270_request *rq) /* * Switch to the fullscreen view. */ -static void -fs3270_reset_callback(struct raw3270_request *rq, void *data) +static void fs3270_reset_callback(struct raw3270_request *rq, void *data) { struct fs3270 *fp; - fp = (struct fs3270 *) rq->view; + fp = (struct fs3270 *)rq->view; raw3270_request_reset(rq); wake_up(&fp->wait); } -static void -fs3270_restore_callback(struct raw3270_request *rq, void *data) +static void fs3270_restore_callback(struct raw3270_request *rq, void *data) { struct fs3270 *fp; - fp = (struct fs3270 *) rq->view; + fp = (struct fs3270 *)rq->view; if (rq->rc != 0 || rq->rescnt != 0) { if (fp->fs_pid) kill_pid(fp->fs_pid, SIGHUP, 1); @@ -115,29 +111,31 @@ fs3270_restore_callback(struct raw3270_request *rq, void *data) wake_up(&fp->wait); } -static int -fs3270_activate(struct raw3270_view *view) +static int fs3270_activate(struct raw3270_view *view) { struct fs3270 *fp; char *cp; int rc; - fp = (struct fs3270 *) view; + fp = (struct fs3270 *)view; /* If an old init command is still running just return. */ if (!raw3270_request_final(fp->init)) return 0; + raw3270_request_set_cmd(fp->init, TC_EWRITEA); + raw3270_request_set_idal(fp->init, fp->rdbuf); + fp->init->rescnt = 0; + cp = fp->rdbuf->data[0]; if (fp->rdbuf_size == 0) { /* No saved buffer. Just clear the screen. */ - raw3270_request_set_cmd(fp->init, TC_EWRITEA); + fp->init->ccw.count = 1; fp->init->callback = fs3270_reset_callback; + cp[0] = 0; } else { /* Restore fullscreen buffer saved by fs3270_deactivate. */ - raw3270_request_set_cmd(fp->init, TC_EWRITEA); - raw3270_request_set_idal(fp->init, fp->rdbuf); fp->init->ccw.count = fp->rdbuf_size; - cp = fp->rdbuf->data[0]; + fp->init->callback = fs3270_restore_callback; cp[0] = TW_KR; cp[1] = TO_SBA; cp[2] = cp[6]; @@ -146,10 +144,9 @@ fs3270_activate(struct raw3270_view *view) cp[5] = TO_SBA; cp[6] = 0x40; cp[7] = 0x40; - fp->init->rescnt = 0; - fp->init->callback = fs3270_restore_callback; } - rc = fp->init->rc = raw3270_start_locked(view, fp->init); + rc = raw3270_start_locked(view, fp->init); + fp->init->rc = rc; if (rc) fp->init->callback(fp->init, NULL); else @@ -160,12 +157,11 @@ fs3270_activate(struct raw3270_view *view) /* * Shutdown fullscreen view. */ -static void -fs3270_save_callback(struct raw3270_request *rq, void *data) +static void fs3270_save_callback(struct raw3270_request *rq, void *data) { struct fs3270 *fp; - fp = (struct fs3270 *) rq->view; + fp = (struct fs3270 *)rq->view; /* Correct idal buffer element 0 address. */ fp->rdbuf->data[0] -= 5; @@ -181,18 +177,18 @@ fs3270_save_callback(struct raw3270_request *rq, void *data) if (fp->fs_pid) kill_pid(fp->fs_pid, SIGHUP, 1); fp->rdbuf_size = 0; - } else + } else { fp->rdbuf_size = fp->rdbuf->size - rq->rescnt; + } raw3270_request_reset(rq); wake_up(&fp->wait); } -static void -fs3270_deactivate(struct raw3270_view *view) +static void fs3270_deactivate(struct raw3270_view *view) { struct fs3270 *fp; - fp = (struct fs3270 *) view; + fp = (struct fs3270 *)view; fp->active = 0; /* If an old init command is still running just return. */ @@ -218,8 +214,8 @@ fs3270_deactivate(struct raw3270_view *view) fp->init->callback(fp->init, NULL); } -static void -fs3270_irq(struct fs3270 *fp, struct raw3270_request *rq, struct irb *irb) +static void fs3270_irq(struct fs3270 *fp, struct raw3270_request *rq, + struct irb *irb) { /* Handle ATTN. Set indication and wake waiters for attention. */ if (irb->scsw.cmd.dstat & DEV_STAT_ATTENTION) { @@ -239,14 +235,14 @@ fs3270_irq(struct fs3270 *fp, struct raw3270_request *rq, struct irb *irb) /* * Process reads from fullscreen 3270. */ -static ssize_t -fs3270_read(struct file *filp, char __user *data, size_t count, loff_t *off) +static ssize_t fs3270_read(struct file *filp, char __user *data, + size_t count, loff_t *off) { struct fs3270 *fp; struct raw3270_request *rq; struct idal_buffer *ib; ssize_t rc; - + if (count == 0 || count > 65535) return -EINVAL; fp = filp->private_data; @@ -271,12 +267,12 @@ fs3270_read(struct file *filp, char __user *data, size_t count, loff_t *off) rc = -EFAULT; else rc = count; - } } raw3270_request_free(rq); - } else + } else { rc = PTR_ERR(rq); + } idal_buffer_free(ib); return rc; } @@ -284,8 +280,8 @@ fs3270_read(struct file *filp, char __user *data, size_t count, loff_t *off) /* * Process writes to fullscreen 3270. */ -static ssize_t -fs3270_write(struct file *filp, const char __user *data, size_t count, loff_t *off) +static ssize_t fs3270_write(struct file *filp, const char __user *data, + size_t count, loff_t *off) { struct fs3270 *fp; struct raw3270_request *rq; @@ -310,11 +306,13 @@ fs3270_write(struct file *filp, const char __user *data, size_t count, loff_t *o rc = fs3270_do_io(&fp->view, rq); if (rc == 0) rc = count - rq->rescnt; - } else + } else { rc = -EFAULT; + } raw3270_request_free(rq); - } else + } else { rc = PTR_ERR(rq); + } idal_buffer_free(ib); return rc; } @@ -322,8 +320,7 @@ fs3270_write(struct file *filp, const char __user *data, size_t count, loff_t *o /* * process ioctl commands for the tube driver */ -static long -fs3270_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +static long fs3270_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { char __user *argp; struct fs3270 *fp; @@ -370,12 +367,11 @@ fs3270_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) /* * Allocate fs3270 structure. */ -static struct fs3270 * -fs3270_alloc_view(void) +static struct fs3270 *fs3270_alloc_view(void) { struct fs3270 *fp; - fp = kzalloc(sizeof(struct fs3270),GFP_KERNEL); + fp = kzalloc(sizeof(*fp), GFP_KERNEL); if (!fp) return ERR_PTR(-ENOMEM); fp->init = raw3270_request_alloc(0); @@ -389,27 +385,25 @@ fs3270_alloc_view(void) /* * Free fs3270 structure. */ -static void -fs3270_free_view(struct raw3270_view *view) +static void fs3270_free_view(struct raw3270_view *view) { struct fs3270 *fp; - fp = (struct fs3270 *) view; + fp = (struct fs3270 *)view; if (fp->rdbuf) idal_buffer_free(fp->rdbuf); - raw3270_request_free(((struct fs3270 *) view)->init); + raw3270_request_free(((struct fs3270 *)view)->init); kfree(view); } /* * Unlink fs3270 data structure from filp. */ -static void -fs3270_release(struct raw3270_view *view) +static void fs3270_release(struct raw3270_view *view) { struct fs3270 *fp; - fp = (struct fs3270 *) view; + fp = (struct fs3270 *)view; if (fp->fs_pid) kill_pid(fp->fs_pid, SIGHUP, 1); } @@ -418,7 +412,7 @@ fs3270_release(struct raw3270_view *view) static struct raw3270_fn fs3270_fn = { .activate = fs3270_activate, .deactivate = fs3270_deactivate, - .intv = (void *) fs3270_irq, + .intv = (void *)fs3270_irq, .release = fs3270_release, .free = fs3270_free_view }; @@ -426,8 +420,7 @@ static struct raw3270_fn fs3270_fn = { /* * This routine is called whenever a 3270 fullscreen device is opened. */ -static int -fs3270_open(struct inode *inode, struct file *filp) +static int fs3270_open(struct inode *inode, struct file *filp) { struct fs3270 *fp; struct idal_buffer *ib; @@ -439,6 +432,7 @@ fs3270_open(struct inode *inode, struct file *filp) /* Check for minor 0 multiplexer. */ if (minor == 0) { struct tty_struct *tty = get_current_tty(); + if (!tty || tty->driver->major != IBM_TTY3270_MAJOR) { tty_kref_put(tty); return -ENODEV; @@ -448,7 +442,7 @@ fs3270_open(struct inode *inode, struct file *filp) } mutex_lock(&fs3270_mutex); /* Check if some other program is already using fullscreen mode. */ - fp = (struct fs3270 *) raw3270_find_view(&fs3270_fn, minor); + fp = (struct fs3270 *)raw3270_find_view(&fs3270_fn, minor); if (!IS_ERR(fp)) { raw3270_put_view(&fp->view); rc = -EBUSY; @@ -471,7 +465,7 @@ fs3270_open(struct inode *inode, struct file *filp) } /* Allocate idal-buffer. */ - ib = idal_buffer_alloc(2*fp->view.rows*fp->view.cols + 5, 0); + ib = idal_buffer_alloc(2 * fp->view.rows * fp->view.cols + 5, 0); if (IS_ERR(ib)) { raw3270_put_view(&fp->view); raw3270_del_view(&fp->view); @@ -497,8 +491,7 @@ out: * This routine is called when the 3270 tty is closed. We wait * for the remaining request to be completed. Then we clean up. */ -static int -fs3270_close(struct inode *inode, struct file *filp) +static int fs3270_close(struct inode *inode, struct file *filp) { struct fs3270 *fp; @@ -538,8 +531,7 @@ static void fs3270_destroy_cb(int minor) __unregister_chrdev(IBM_FS3270_MAJOR, minor, 1, "tub"); } -static struct raw3270_notifier fs3270_notifier = -{ +static struct raw3270_notifier fs3270_notifier = { .create = fs3270_create_cb, .destroy = fs3270_destroy_cb, }; @@ -547,8 +539,7 @@ static struct raw3270_notifier fs3270_notifier = /* * 3270 fullscreen driver initialization. */ -static int __init -fs3270_init(void) +static int __init fs3270_init(void) { int rc; @@ -561,8 +552,7 @@ fs3270_init(void) return 0; } -static void __exit -fs3270_exit(void) +static void __exit fs3270_exit(void) { raw3270_unregister_notifier(&fs3270_notifier); device_destroy(class3270, MKDEV(IBM_FS3270_MAJOR, 0)); diff --git a/drivers/s390/char/raw3270.c b/drivers/s390/char/raw3270.c index fb3f62ac8be4..09d7570d3b7d 100644 --- a/drivers/s390/char/raw3270.c +++ b/drivers/s390/char/raw3270.c @@ -30,6 +30,7 @@ #include <linux/mutex.h> struct class *class3270; +EXPORT_SYMBOL(class3270); /* The main 3270 data structure. */ struct raw3270 { @@ -37,7 +38,8 @@ struct raw3270 { struct ccw_device *cdev; int minor; - short model, rows, cols; + int model, rows, cols; + int old_model, old_rows, old_cols; unsigned int state; unsigned long flags; @@ -54,6 +56,7 @@ struct raw3270 { struct raw3270_request init_readpart; struct raw3270_request init_readmod; unsigned char init_data[256]; + struct work_struct resize_work; }; /* raw3270->state */ @@ -89,6 +92,7 @@ module_param(tubxcorrect, bool, 0); * Wait queue for device init/delete, view delete. */ DECLARE_WAIT_QUEUE_HEAD(raw3270_wait_queue); +EXPORT_SYMBOL(raw3270_wait_queue); static void __raw3270_disconnect(struct raw3270 *rp); @@ -111,9 +115,15 @@ static inline int raw3270_state_ready(struct raw3270 *rp) return rp->state == RAW3270_STATE_READY; } -void -raw3270_buffer_address(struct raw3270 *rp, char *cp, unsigned short addr) +void raw3270_buffer_address(struct raw3270 *rp, char *cp, int x, int y) { + int addr; + + if (x < 0) + x = max_t(int, 0, rp->view->cols + x); + if (y < 0) + y = max_t(int, 0, rp->view->rows + y); + addr = (y * rp->view->cols) + x; if (test_bit(RAW3270_FLAGS_14BITADDR, &rp->flags)) { cp[0] = (addr >> 8) & 0x3f; cp[1] = addr & 0xff; @@ -122,17 +132,17 @@ raw3270_buffer_address(struct raw3270 *rp, char *cp, unsigned short addr) cp[1] = raw3270_ebcgraf[addr & 0x3f]; } } +EXPORT_SYMBOL(raw3270_buffer_address); /* * Allocate a new 3270 ccw request */ -struct raw3270_request * -raw3270_request_alloc(size_t size) +struct raw3270_request *raw3270_request_alloc(size_t size) { struct raw3270_request *rq; /* Allocate request structure */ - rq = kzalloc(sizeof(struct raw3270_request), GFP_KERNEL | GFP_DMA); + rq = kzalloc(sizeof(*rq), GFP_KERNEL | GFP_DMA); if (!rq) return ERR_PTR(-ENOMEM); @@ -155,46 +165,48 @@ raw3270_request_alloc(size_t size) return rq; } +EXPORT_SYMBOL(raw3270_request_alloc); /* * Free 3270 ccw request */ -void -raw3270_request_free (struct raw3270_request *rq) +void raw3270_request_free(struct raw3270_request *rq) { kfree(rq->buffer); kfree(rq); } +EXPORT_SYMBOL(raw3270_request_free); /* * Reset request to initial state. */ -void -raw3270_request_reset(struct raw3270_request *rq) +int raw3270_request_reset(struct raw3270_request *rq) { - BUG_ON(!list_empty(&rq->list)); + if (WARN_ON_ONCE(!list_empty(&rq->list))) + return -EBUSY; rq->ccw.cmd_code = 0; rq->ccw.count = 0; rq->ccw.cda = __pa(rq->buffer); rq->ccw.flags = CCW_FLAG_SLI; rq->rescnt = 0; rq->rc = 0; + return 0; } +EXPORT_SYMBOL(raw3270_request_reset); /* * Set command code to ccw of a request. */ -void -raw3270_request_set_cmd(struct raw3270_request *rq, u8 cmd) +void raw3270_request_set_cmd(struct raw3270_request *rq, u8 cmd) { rq->ccw.cmd_code = cmd; } +EXPORT_SYMBOL(raw3270_request_set_cmd); /* * Add data fragment to output buffer. */ -int -raw3270_request_add_data(struct raw3270_request *rq, void *data, size_t size) +int raw3270_request_add_data(struct raw3270_request *rq, void *data, size_t size) { if (size + rq->ccw.count > rq->size) return -E2BIG; @@ -202,35 +214,35 @@ raw3270_request_add_data(struct raw3270_request *rq, void *data, size_t size) rq->ccw.count += size; return 0; } +EXPORT_SYMBOL(raw3270_request_add_data); /* * Set address/length pair to ccw of a request. */ -void -raw3270_request_set_data(struct raw3270_request *rq, void *data, size_t size) +void raw3270_request_set_data(struct raw3270_request *rq, void *data, size_t size) { rq->ccw.cda = __pa(data); rq->ccw.count = size; } +EXPORT_SYMBOL(raw3270_request_set_data); /* * Set idal buffer to ccw of a request. */ -void -raw3270_request_set_idal(struct raw3270_request *rq, struct idal_buffer *ib) +void raw3270_request_set_idal(struct raw3270_request *rq, struct idal_buffer *ib) { rq->ccw.cda = __pa(ib->data); rq->ccw.count = ib->size; rq->ccw.flags |= CCW_FLAG_IDA; } +EXPORT_SYMBOL(raw3270_request_set_idal); /* * Add the request to the request queue, try to start it if the * 3270 device is idle. Return without waiting for end of i/o. */ -static int -__raw3270_start(struct raw3270 *rp, struct raw3270_view *view, - struct raw3270_request *rq) +static int __raw3270_start(struct raw3270 *rp, struct raw3270_view *view, + struct raw3270_request *rq) { rq->view = view; raw3270_get_view(view); @@ -238,7 +250,7 @@ __raw3270_start(struct raw3270 *rp, struct raw3270_view *view, !test_bit(RAW3270_FLAGS_BUSY, &rp->flags)) { /* No other requests are on the queue. Start this one. */ rq->rc = ccw_device_start(rp->cdev, &rq->ccw, - (unsigned long) rq, 0, 0); + (unsigned long)rq, 0, 0); if (rq->rc) { raw3270_put_view(view); return rq->rc; @@ -248,16 +260,14 @@ __raw3270_start(struct raw3270 *rp, struct raw3270_view *view, return 0; } -int -raw3270_view_active(struct raw3270_view *view) +int raw3270_view_active(struct raw3270_view *view) { struct raw3270 *rp = view->dev; return rp && rp->view == view; } -int -raw3270_start(struct raw3270_view *view, struct raw3270_request *rq) +int raw3270_start(struct raw3270_view *view, struct raw3270_request *rq) { unsigned long flags; struct raw3270 *rp; @@ -274,9 +284,25 @@ raw3270_start(struct raw3270_view *view, struct raw3270_request *rq) spin_unlock_irqrestore(get_ccwdev_lock(view->dev->cdev), flags); return rc; } +EXPORT_SYMBOL(raw3270_start); -int -raw3270_start_locked(struct raw3270_view *view, struct raw3270_request *rq) +int raw3270_start_request(struct raw3270_view *view, struct raw3270_request *rq, + int cmd, void *data, size_t len) +{ + int rc; + + rc = raw3270_request_reset(rq); + if (rc) + return rc; + raw3270_request_set_cmd(rq, cmd); + rc = raw3270_request_add_data(rq, data, len); + if (rc) + return rc; + return raw3270_start(view, rq); +} +EXPORT_SYMBOL(raw3270_start_request); + +int raw3270_start_locked(struct raw3270_view *view, struct raw3270_request *rq) { struct raw3270 *rp; int rc; @@ -290,9 +316,9 @@ raw3270_start_locked(struct raw3270_view *view, struct raw3270_request *rq) rc = __raw3270_start(rp, view, rq); return rc; } +EXPORT_SYMBOL(raw3270_start_locked); -int -raw3270_start_irq(struct raw3270_view *view, struct raw3270_request *rq) +int raw3270_start_irq(struct raw3270_view *view, struct raw3270_request *rq) { struct raw3270 *rp; @@ -302,12 +328,12 @@ raw3270_start_irq(struct raw3270_view *view, struct raw3270_request *rq) list_add_tail(&rq->list, &rp->req_queue); return 0; } +EXPORT_SYMBOL(raw3270_start_irq); /* * 3270 interrupt routine, called from the ccw_device layer */ -static void -raw3270_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb) +static void raw3270_irq(struct ccw_device *cdev, unsigned long intparm, struct irb *irb) { struct raw3270 *rp; struct raw3270_view *view; @@ -316,7 +342,7 @@ raw3270_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb) rp = dev_get_drvdata(&cdev->dev); if (!rp) return; - rq = (struct raw3270_request *) intparm; + rq = (struct raw3270_request *)intparm; view = rq ? rq->view : rp->view; if (!IS_ERR(irb)) { @@ -357,9 +383,9 @@ raw3270_irq (struct ccw_device *cdev, unsigned long intparm, struct irb *irb) * started successful. */ while (!list_empty(&rp->req_queue)) { - rq = list_entry(rp->req_queue.next,struct raw3270_request,list); + rq = list_entry(rp->req_queue.next, struct raw3270_request, list); rq->rc = ccw_device_start(rp->cdev, &rq->ccw, - (unsigned long) rq, 0, 0); + (unsigned long)rq, 0, 0); if (rq->rc == 0) break; /* Start failed. Remove request and do callback. */ @@ -399,7 +425,7 @@ struct raw3270_ua { /* Query Reply structure for Usable Area */ char ymin; char xmax; char ymax; - } __attribute__ ((packed)) uab; + } __packed uab; struct { /* Alternate Usable Area Self-Defining Parameter */ char l; /* Length of this Self-Defining Parm */ char sdpid; /* 0x02 if Alternate Usable Area */ @@ -412,17 +438,27 @@ struct raw3270_ua { /* Query Reply structure for Usable Area */ int auayr; char awauai; char ahauai; - } __attribute__ ((packed)) aua; -} __attribute__ ((packed)); + } __packed aua; +} __packed; -static void -raw3270_size_device_vm(struct raw3270 *rp) +static void raw3270_size_device_vm(struct raw3270 *rp) { int rc, model; struct ccw_dev_id dev_id; struct diag210 diag_data; + struct diag8c diag8c_data; ccw_device_get_id(rp->cdev, &dev_id); + rc = diag8c(&diag8c_data, &dev_id); + if (!rc) { + rp->model = 2; + rp->rows = diag8c_data.height; + rp->cols = diag8c_data.width; + if (diag8c_data.flags & 1) + set_bit(RAW3270_FLAGS_14BITADDR, &rp->flags); + return; + } + diag_data.vrdcdvno = dev_id.devno; diag_data.vrdclen = sizeof(struct diag210); rc = diag210(&diag_data); @@ -454,16 +490,14 @@ raw3270_size_device_vm(struct raw3270 *rp) } } -static void -raw3270_size_device(struct raw3270 *rp) +static void raw3270_size_device(struct raw3270 *rp, char *init_data) { struct raw3270_ua *uap; /* Got a Query Reply */ - uap = (struct raw3270_ua *) (rp->init_data + 1); + uap = (struct raw3270_ua *)(init_data + 1); /* Paranoia check. */ - if (rp->init_readmod.rc || rp->init_data[0] != 0x88 || - uap->uab.qcode != 0x81) { + if (init_data[0] != 0x88 || uap->uab.qcode != 0x81) { /* Couldn't detect size. Use default model 2. */ rp->model = 2; rp->rows = 24; @@ -494,17 +528,20 @@ raw3270_size_device(struct raw3270 *rp) rp->model = 5; } -static void -raw3270_size_device_done(struct raw3270 *rp) +static void raw3270_resize_work(struct work_struct *work) { + struct raw3270 *rp = container_of(work, struct raw3270, resize_work); struct raw3270_view *view; - rp->view = NULL; - rp->state = RAW3270_STATE_READY; /* Notify views about new size */ - list_for_each_entry(view, &rp->view_list, list) + list_for_each_entry(view, &rp->view_list, list) { if (view->fn->resize) - view->fn->resize(view, rp->model, rp->rows, rp->cols); + view->fn->resize(view, rp->model, rp->rows, rp->cols, + rp->old_model, rp->old_rows, rp->old_cols); + } + rp->old_cols = rp->cols; + rp->old_rows = rp->rows; + rp->old_model = rp->model; /* Setup processing done, now activate a view */ list_for_each_entry(view, &rp->view_list, list) { rp->view = view; @@ -514,17 +551,23 @@ raw3270_size_device_done(struct raw3270 *rp) } } -static void -raw3270_read_modified_cb(struct raw3270_request *rq, void *data) +static void raw3270_size_device_done(struct raw3270 *rp) +{ + rp->view = NULL; + rp->state = RAW3270_STATE_READY; + schedule_work(&rp->resize_work); +} + +void raw3270_read_modified_cb(struct raw3270_request *rq, void *data) { struct raw3270 *rp = rq->view->dev; - raw3270_size_device(rp); + raw3270_size_device(rp, data); raw3270_size_device_done(rp); } +EXPORT_SYMBOL(raw3270_read_modified_cb); -static void -raw3270_read_modified(struct raw3270 *rp) +static void raw3270_read_modified(struct raw3270 *rp) { if (rp->state != RAW3270_STATE_W4ATTN) return; @@ -534,17 +577,18 @@ raw3270_read_modified(struct raw3270 *rp) rp->init_readmod.ccw.cmd_code = TC_READMOD; rp->init_readmod.ccw.flags = CCW_FLAG_SLI; rp->init_readmod.ccw.count = sizeof(rp->init_data); - rp->init_readmod.ccw.cda = (__u32) __pa(rp->init_data); + rp->init_readmod.ccw.cda = (__u32)__pa(rp->init_data); rp->init_readmod.callback = raw3270_read_modified_cb; + rp->init_readmod.callback_data = rp->init_data; rp->state = RAW3270_STATE_READMOD; raw3270_start_irq(&rp->init_view, &rp->init_readmod); } -static void -raw3270_writesf_readpart(struct raw3270 *rp) +static void raw3270_writesf_readpart(struct raw3270 *rp) { - static const unsigned char wbuf[] = - { 0x00, 0x07, 0x01, 0xff, 0x03, 0x00, 0x81 }; + static const unsigned char wbuf[] = { + 0x00, 0x07, 0x01, 0xff, 0x03, 0x00, 0x81 + }; /* Store 'read partition' data stream to init_data */ memset(&rp->init_readpart, 0, sizeof(rp->init_readpart)); @@ -553,7 +597,7 @@ raw3270_writesf_readpart(struct raw3270 *rp) rp->init_readpart.ccw.cmd_code = TC_WRITESF; rp->init_readpart.ccw.flags = CCW_FLAG_SLI; rp->init_readpart.ccw.count = sizeof(wbuf); - rp->init_readpart.ccw.cda = (__u32) __pa(&rp->init_data); + rp->init_readpart.ccw.cda = (__u32)__pa(&rp->init_data); rp->state = RAW3270_STATE_W4ATTN; raw3270_start_irq(&rp->init_view, &rp->init_readpart); } @@ -561,8 +605,7 @@ raw3270_writesf_readpart(struct raw3270 *rp) /* * Device reset */ -static void -raw3270_reset_device_cb(struct raw3270_request *rq, void *data) +static void raw3270_reset_device_cb(struct raw3270_request *rq, void *data) { struct raw3270 *rp = rq->view->dev; @@ -574,13 +617,13 @@ raw3270_reset_device_cb(struct raw3270_request *rq, void *data) } else if (MACHINE_IS_VM) { raw3270_size_device_vm(rp); raw3270_size_device_done(rp); - } else + } else { raw3270_writesf_readpart(rp); + } memset(&rp->init_reset, 0, sizeof(rp->init_reset)); } -static int -__raw3270_reset_device(struct raw3270 *rp) +static int __raw3270_reset_device(struct raw3270 *rp) { int rc; @@ -592,7 +635,7 @@ __raw3270_reset_device(struct raw3270 *rp) rp->init_reset.ccw.cmd_code = TC_EWRITEA; rp->init_reset.ccw.flags = CCW_FLAG_SLI; rp->init_reset.ccw.count = 1; - rp->init_reset.ccw.cda = (__u32) __pa(rp->init_data); + rp->init_reset.ccw.cda = (__u32)__pa(rp->init_data); rp->init_reset.callback = raw3270_reset_device_cb; rc = __raw3270_start(rp, &rp->init_view, &rp->init_reset); if (rc == 0 && rp->state == RAW3270_STATE_INIT) @@ -600,8 +643,7 @@ __raw3270_reset_device(struct raw3270 *rp) return rc; } -static int -raw3270_reset_device(struct raw3270 *rp) +static int raw3270_reset_device(struct raw3270 *rp) { unsigned long flags; int rc; @@ -612,8 +654,7 @@ raw3270_reset_device(struct raw3270 *rp) return rc; } -int -raw3270_reset(struct raw3270_view *view) +int raw3270_reset(struct raw3270_view *view) { struct raw3270 *rp; int rc; @@ -627,9 +668,9 @@ raw3270_reset(struct raw3270_view *view) rc = raw3270_reset_device(view->dev); return rc; } +EXPORT_SYMBOL(raw3270_reset); -static void -__raw3270_disconnect(struct raw3270 *rp) +static void __raw3270_disconnect(struct raw3270 *rp) { struct raw3270_request *rq; struct raw3270_view *view; @@ -638,7 +679,7 @@ __raw3270_disconnect(struct raw3270 *rp) rp->view = &rp->init_view; /* Cancel all queued requests */ while (!list_empty(&rp->req_queue)) { - rq = list_entry(rp->req_queue.next,struct raw3270_request,list); + rq = list_entry(rp->req_queue.next, struct raw3270_request, list); view = rq->view; rq->rc = -EACCES; list_del_init(&rq->list); @@ -650,9 +691,8 @@ __raw3270_disconnect(struct raw3270 *rp) __raw3270_reset_device(rp); } -static void -raw3270_init_irq(struct raw3270_view *view, struct raw3270_request *rq, - struct irb *irb) +static void raw3270_init_irq(struct raw3270_view *view, struct raw3270_request *rq, + struct irb *irb) { struct raw3270 *rp; @@ -678,8 +718,8 @@ static struct raw3270_fn raw3270_init_fn = { /* * Setup new 3270 device. */ -static int -raw3270_setup_device(struct ccw_device *cdev, struct raw3270 *rp, char *ascebc) +static int raw3270_setup_device(struct ccw_device *cdev, struct raw3270 *rp, + char *ascebc) { struct list_head *l; struct raw3270 *tmp; @@ -699,6 +739,8 @@ raw3270_setup_device(struct ccw_device *cdev, struct raw3270 *rp, char *ascebc) /* Set defaults. */ rp->rows = 24; rp->cols = 80; + rp->old_rows = rp->rows; + rp->old_cols = rp->cols; INIT_LIST_HEAD(&rp->req_queue); INIT_LIST_HEAD(&rp->view_list); @@ -706,6 +748,7 @@ raw3270_setup_device(struct ccw_device *cdev, struct raw3270 *rp, char *ascebc) rp->init_view.dev = rp; rp->init_view.fn = &raw3270_init_fn; rp->view = &rp->init_view; + INIT_WORK(&rp->resize_work, raw3270_resize_work); /* * Add device to list and find the smallest unused minor @@ -764,7 +807,7 @@ struct raw3270 __init *raw3270_setup_console(void) if (IS_ERR(cdev)) return ERR_CAST(cdev); - rp = kzalloc(sizeof(struct raw3270), GFP_KERNEL | GFP_DMA); + rp = kzalloc(sizeof(*rp), GFP_KERNEL | GFP_DMA); ascebc = kzalloc(256, GFP_KERNEL); rc = raw3270_setup_device(cdev, rp, ascebc); if (rc) @@ -789,8 +832,7 @@ struct raw3270 __init *raw3270_setup_console(void) return rp; } -void -raw3270_wait_cons_dev(struct raw3270 *rp) +void raw3270_wait_cons_dev(struct raw3270 *rp) { unsigned long flags; @@ -804,14 +846,13 @@ raw3270_wait_cons_dev(struct raw3270 *rp) /* * Create a 3270 device structure. */ -static struct raw3270 * -raw3270_create_device(struct ccw_device *cdev) +static struct raw3270 *raw3270_create_device(struct ccw_device *cdev) { struct raw3270 *rp; char *ascebc; int rc; - rp = kzalloc(sizeof(struct raw3270), GFP_KERNEL | GFP_DMA); + rp = kzalloc(sizeof(*rp), GFP_KERNEL | GFP_DMA); if (!rp) return ERR_PTR(-ENOMEM); ascebc = kmalloc(256, GFP_KERNEL); @@ -845,14 +886,57 @@ int raw3270_view_lock_unavailable(struct raw3270_view *view) return 0; } +static int raw3270_assign_activate_view(struct raw3270 *rp, struct raw3270_view *view) +{ + rp->view = view; + return view->fn->activate(view); +} + +static int __raw3270_activate_view(struct raw3270 *rp, struct raw3270_view *view) +{ + struct raw3270_view *oldview = NULL, *nv; + int rc; + + if (rp->view == view) + return 0; + + if (!raw3270_state_ready(rp)) + return -EBUSY; + + if (rp->view && rp->view->fn->deactivate) { + oldview = rp->view; + oldview->fn->deactivate(oldview); + } + + rc = raw3270_assign_activate_view(rp, view); + if (!rc) + return 0; + + /* Didn't work. Try to reactivate the old view. */ + if (oldview) { + rc = raw3270_assign_activate_view(rp, oldview); + if (!rc) + return 0; + } + + /* Didn't work as well. Try any other view. */ + list_for_each_entry(nv, &rp->view_list, list) { + if (nv == view || nv == oldview) + continue; + rc = raw3270_assign_activate_view(rp, nv); + if (!rc) + break; + rp->view = NULL; + } + return rc; +} + /* * Activate a view. */ -int -raw3270_activate_view(struct raw3270_view *view) +int raw3270_activate_view(struct raw3270_view *view) { struct raw3270 *rp; - struct raw3270_view *oldview, *nv; unsigned long flags; int rc; @@ -860,42 +944,16 @@ raw3270_activate_view(struct raw3270_view *view) if (!rp) return -ENODEV; spin_lock_irqsave(get_ccwdev_lock(rp->cdev), flags); - if (rp->view == view) - rc = 0; - else if (!raw3270_state_ready(rp)) - rc = -EBUSY; - else { - oldview = NULL; - if (rp->view && rp->view->fn->deactivate) { - oldview = rp->view; - oldview->fn->deactivate(oldview); - } - rp->view = view; - rc = view->fn->activate(view); - if (rc) { - /* Didn't work. Try to reactivate the old view. */ - rp->view = oldview; - if (!oldview || oldview->fn->activate(oldview) != 0) { - /* Didn't work as well. Try any other view. */ - list_for_each_entry(nv, &rp->view_list, list) - if (nv != view && nv != oldview) { - rp->view = nv; - if (nv->fn->activate(nv) == 0) - break; - rp->view = NULL; - } - } - } - } + rc = __raw3270_activate_view(rp, view); spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags); return rc; } +EXPORT_SYMBOL(raw3270_activate_view); /* * Deactivate current view. */ -void -raw3270_deactivate_view(struct raw3270_view *view) +void raw3270_deactivate_view(struct raw3270_view *view) { unsigned long flags; struct raw3270 *rp; @@ -922,12 +980,13 @@ raw3270_deactivate_view(struct raw3270_view *view) } spin_unlock_irqrestore(get_ccwdev_lock(rp->cdev), flags); } +EXPORT_SYMBOL(raw3270_deactivate_view); /* * Add view to device with minor "minor". */ -int -raw3270_add_view(struct raw3270_view *view, struct raw3270_fn *fn, int minor, int subclass) +int raw3270_add_view(struct raw3270_view *view, struct raw3270_fn *fn, + int minor, int subclass) { unsigned long flags; struct raw3270 *rp; @@ -958,12 +1017,12 @@ raw3270_add_view(struct raw3270_view *view, struct raw3270_fn *fn, int minor, in mutex_unlock(&raw3270_mutex); return rc; } +EXPORT_SYMBOL(raw3270_add_view); /* * Find specific view of device with minor "minor". */ -struct raw3270_view * -raw3270_find_view(struct raw3270_fn *fn, int minor) +struct raw3270_view *raw3270_find_view(struct raw3270_fn *fn, int minor) { struct raw3270 *rp; struct raw3270_view *view, *tmp; @@ -988,12 +1047,12 @@ raw3270_find_view(struct raw3270_fn *fn, int minor) mutex_unlock(&raw3270_mutex); return view; } +EXPORT_SYMBOL(raw3270_find_view); /* * Remove view from device and free view structure via call to view->fn->free. */ -void -raw3270_del_view(struct raw3270_view *view) +void raw3270_del_view(struct raw3270_view *view) { unsigned long flags; struct raw3270 *rp; @@ -1022,12 +1081,12 @@ raw3270_del_view(struct raw3270_view *view) if (view->fn->free) view->fn->free(view); } +EXPORT_SYMBOL(raw3270_del_view); /* * Remove a 3270 device structure. */ -static void -raw3270_delete_device(struct raw3270 *rp) +static void raw3270_delete_device(struct raw3270 *rp) { struct ccw_device *cdev; @@ -1050,8 +1109,7 @@ raw3270_delete_device(struct raw3270 *rp) kfree(rp); } -static int -raw3270_probe (struct ccw_device *cdev) +static int raw3270_probe(struct ccw_device *cdev) { return 0; } @@ -1059,31 +1117,32 @@ raw3270_probe (struct ccw_device *cdev) /* * Additional attributes for a 3270 device */ -static ssize_t -raw3270_model_show(struct device *dev, struct device_attribute *attr, char *buf) +static ssize_t model_show(struct device *dev, struct device_attribute *attr, + char *buf) { return sysfs_emit(buf, "%i\n", ((struct raw3270 *)dev_get_drvdata(dev))->model); } -static DEVICE_ATTR(model, 0444, raw3270_model_show, NULL); +static DEVICE_ATTR_RO(model); -static ssize_t -raw3270_rows_show(struct device *dev, struct device_attribute *attr, char *buf) +static ssize_t rows_show(struct device *dev, struct device_attribute *attr, + char *buf) { return sysfs_emit(buf, "%i\n", ((struct raw3270 *)dev_get_drvdata(dev))->rows); } -static DEVICE_ATTR(rows, 0444, raw3270_rows_show, NULL); +static DEVICE_ATTR_RO(rows); static ssize_t -raw3270_columns_show(struct device *dev, struct device_attribute *attr, char *buf) +columns_show(struct device *dev, struct device_attribute *attr, + char *buf) { return sysfs_emit(buf, "%i\n", ((struct raw3270 *)dev_get_drvdata(dev))->cols); } -static DEVICE_ATTR(columns, 0444, raw3270_columns_show, NULL); +static DEVICE_ATTR_RO(columns); -static struct attribute * raw3270_attrs[] = { +static struct attribute *raw3270_attrs[] = { &dev_attr_model.attr, &dev_attr_rows.attr, &dev_attr_columns.attr, @@ -1115,6 +1174,7 @@ int raw3270_register_notifier(struct raw3270_notifier *notifier) mutex_unlock(&raw3270_mutex); return 0; } +EXPORT_SYMBOL(raw3270_register_notifier); void raw3270_unregister_notifier(struct raw3270_notifier *notifier) { @@ -1126,12 +1186,12 @@ void raw3270_unregister_notifier(struct raw3270_notifier *notifier) list_del(¬ifier->list); mutex_unlock(&raw3270_mutex); } +EXPORT_SYMBOL(raw3270_unregister_notifier); /* * Set 3270 device online. */ -static int -raw3270_set_online (struct ccw_device *cdev) +static int raw3270_set_online(struct ccw_device *cdev) { struct raw3270_notifier *np; struct raw3270 *rp; @@ -1158,8 +1218,7 @@ failure: /* * Remove 3270 device structure. */ -static void -raw3270_remove (struct ccw_device *cdev) +static void raw3270_remove(struct ccw_device *cdev) { unsigned long flags; struct raw3270 *rp; @@ -1173,7 +1232,7 @@ raw3270_remove (struct ccw_device *cdev) * devices even if they haven't been varied online. * Thus, rp may validly be NULL here. */ - if (rp == NULL) + if (!rp) return; sysfs_remove_group(&cdev->dev.kobj, &raw3270_attr_group); @@ -1209,8 +1268,7 @@ raw3270_remove (struct ccw_device *cdev) /* * Set 3270 device offline. */ -static int -raw3270_set_offline (struct ccw_device *cdev) +static int raw3270_set_offline(struct ccw_device *cdev) { struct raw3270 *rp; @@ -1249,8 +1307,7 @@ static struct ccw_driver raw3270_ccw_driver = { .int_class = IRQIO_C70, }; -static int -raw3270_init(void) +static int raw3270_init(void) { struct raw3270 *rp; int rc; @@ -1272,8 +1329,7 @@ raw3270_init(void) return rc; } -static void -raw3270_exit(void) +static void raw3270_exit(void) { ccw_driver_unregister(&raw3270_ccw_driver); class_destroy(class3270); @@ -1283,25 +1339,3 @@ MODULE_LICENSE("GPL"); module_init(raw3270_init); module_exit(raw3270_exit); - -EXPORT_SYMBOL(class3270); -EXPORT_SYMBOL(raw3270_request_alloc); -EXPORT_SYMBOL(raw3270_request_free); -EXPORT_SYMBOL(raw3270_request_reset); -EXPORT_SYMBOL(raw3270_request_set_cmd); -EXPORT_SYMBOL(raw3270_request_add_data); -EXPORT_SYMBOL(raw3270_request_set_data); -EXPORT_SYMBOL(raw3270_request_set_idal); -EXPORT_SYMBOL(raw3270_buffer_address); -EXPORT_SYMBOL(raw3270_add_view); -EXPORT_SYMBOL(raw3270_del_view); -EXPORT_SYMBOL(raw3270_find_view); -EXPORT_SYMBOL(raw3270_activate_view); -EXPORT_SYMBOL(raw3270_deactivate_view); -EXPORT_SYMBOL(raw3270_start); -EXPORT_SYMBOL(raw3270_start_locked); -EXPORT_SYMBOL(raw3270_start_irq); -EXPORT_SYMBOL(raw3270_reset); -EXPORT_SYMBOL(raw3270_register_notifier); -EXPORT_SYMBOL(raw3270_unregister_notifier); -EXPORT_SYMBOL(raw3270_wait_queue); diff --git a/drivers/s390/char/raw3270.h b/drivers/s390/char/raw3270.h index 4cb6b5ee44ca..b1beecc7a0a9 100644 --- a/drivers/s390/char/raw3270.h +++ b/drivers/s390/char/raw3270.h @@ -8,88 +8,10 @@ * Copyright IBM Corp. 2003, 2009 */ +#include <uapi/asm/raw3270.h> #include <asm/idals.h> #include <asm/ioctl.h> -/* ioctls for fullscreen 3270 */ -#define TUBICMD _IO('3', 3) /* set ccw command for fs reads. */ -#define TUBOCMD _IO('3', 4) /* set ccw command for fs writes. */ -#define TUBGETI _IO('3', 7) /* get ccw command for fs reads. */ -#define TUBGETO _IO('3', 8) /* get ccw command for fs writes. */ -#define TUBSETMOD _IO('3',12) /* FIXME: what does it do ?*/ -#define TUBGETMOD _IO('3',13) /* FIXME: what does it do ?*/ - -/* Local Channel Commands */ -#define TC_WRITE 0x01 /* Write */ -#define TC_RDBUF 0x02 /* Read Buffer */ -#define TC_EWRITE 0x05 /* Erase write */ -#define TC_READMOD 0x06 /* Read modified */ -#define TC_EWRITEA 0x0d /* Erase write alternate */ -#define TC_WRITESF 0x11 /* Write structured field */ - -/* Buffer Control Orders */ -#define TO_SF 0x1d /* Start field */ -#define TO_SBA 0x11 /* Set buffer address */ -#define TO_IC 0x13 /* Insert cursor */ -#define TO_PT 0x05 /* Program tab */ -#define TO_RA 0x3c /* Repeat to address */ -#define TO_SFE 0x29 /* Start field extended */ -#define TO_EUA 0x12 /* Erase unprotected to address */ -#define TO_MF 0x2c /* Modify field */ -#define TO_SA 0x28 /* Set attribute */ - -/* Field Attribute Bytes */ -#define TF_INPUT 0x40 /* Visible input */ -#define TF_INPUTN 0x4c /* Invisible input */ -#define TF_INMDT 0xc1 /* Visible, Set-MDT */ -#define TF_LOG 0x60 - -/* Character Attribute Bytes */ -#define TAT_RESET 0x00 -#define TAT_FIELD 0xc0 -#define TAT_EXTHI 0x41 -#define TAT_COLOR 0x42 -#define TAT_CHARS 0x43 -#define TAT_TRANS 0x46 - -/* Extended-Highlighting Bytes */ -#define TAX_RESET 0x00 -#define TAX_BLINK 0xf1 -#define TAX_REVER 0xf2 -#define TAX_UNDER 0xf4 - -/* Reset value */ -#define TAR_RESET 0x00 - -/* Color values */ -#define TAC_RESET 0x00 -#define TAC_BLUE 0xf1 -#define TAC_RED 0xf2 -#define TAC_PINK 0xf3 -#define TAC_GREEN 0xf4 -#define TAC_TURQ 0xf5 -#define TAC_YELLOW 0xf6 -#define TAC_WHITE 0xf7 -#define TAC_DEFAULT 0x00 - -/* Write Control Characters */ -#define TW_NONE 0x40 /* No particular action */ -#define TW_KR 0xc2 /* Keyboard restore */ -#define TW_PLUSALARM 0x04 /* Add this bit for alarm */ - -#define RAW3270_FIRSTMINOR 1 /* First minor number */ -#define RAW3270_MAXDEVS 255 /* Max number of 3270 devices */ - -/* For TUBGETMOD and TUBSETMOD. Should include. */ -struct raw3270_iocb { - short model; - short line_cnt; - short col_cnt; - short pf_cnt; - short re_cnt; - short map; -}; - struct raw3270; struct raw3270_view; extern struct class *class3270; @@ -105,17 +27,17 @@ struct raw3270_request { int rc; /* return code for this request. */ /* Callback for delivering final status. */ - void (*callback)(struct raw3270_request *, void *); + void (*callback)(struct raw3270_request *rq, void *data); void *callback_data; }; struct raw3270_request *raw3270_request_alloc(size_t size); -void raw3270_request_free(struct raw3270_request *); -void raw3270_request_reset(struct raw3270_request *); -void raw3270_request_set_cmd(struct raw3270_request *, u8 cmd); -int raw3270_request_add_data(struct raw3270_request *, void *, size_t); -void raw3270_request_set_data(struct raw3270_request *, void *, size_t); -void raw3270_request_set_idal(struct raw3270_request *, struct idal_buffer *); +void raw3270_request_free(struct raw3270_request *rq); +int raw3270_request_reset(struct raw3270_request *rq); +void raw3270_request_set_cmd(struct raw3270_request *rq, u8 cmd); +int raw3270_request_add_data(struct raw3270_request *rq, void *data, size_t size); +void raw3270_request_set_data(struct raw3270_request *rq, void *data, size_t size); +void raw3270_request_set_idal(struct raw3270_request *rq, struct idal_buffer *ib); static inline int raw3270_request_final(struct raw3270_request *rq) @@ -123,19 +45,21 @@ raw3270_request_final(struct raw3270_request *rq) return list_empty(&rq->list); } -void raw3270_buffer_address(struct raw3270 *, char *, unsigned short); +void raw3270_buffer_address(struct raw3270 *, char *, int, int); /* * Functions of a 3270 view. */ struct raw3270_fn { - int (*activate)(struct raw3270_view *); - void (*deactivate)(struct raw3270_view *); - void (*intv)(struct raw3270_view *, - struct raw3270_request *, struct irb *); - void (*release)(struct raw3270_view *); - void (*free)(struct raw3270_view *); - void (*resize)(struct raw3270_view *, int, int, int); + int (*activate)(struct raw3270_view *rq); + void (*deactivate)(struct raw3270_view *rq); + void (*intv)(struct raw3270_view *view, + struct raw3270_request *rq, struct irb *ib); + void (*release)(struct raw3270_view *view); + void (*free)(struct raw3270_view *view); + void (*resize)(struct raw3270_view *view, + int new_model, int new_cols, int new_rows, + int old_model, int old_cols, int old_rows); }; /* @@ -148,7 +72,7 @@ struct raw3270_fn { */ struct raw3270_view { struct list_head list; - spinlock_t lock; + spinlock_t lock; /* protects members of view */ #define RAW3270_VIEW_LOCK_IRQ 0 #define RAW3270_VIEW_LOCK_BH 1 atomic_t ref_count; @@ -159,18 +83,21 @@ struct raw3270_view { unsigned char *ascebc; /* ascii -> ebcdic table */ }; -int raw3270_add_view(struct raw3270_view *, struct raw3270_fn *, int, int); +int raw3270_add_view(struct raw3270_view *view, struct raw3270_fn *fn, int minor, int subclass); int raw3270_view_lock_unavailable(struct raw3270_view *view); -int raw3270_activate_view(struct raw3270_view *); -void raw3270_del_view(struct raw3270_view *); -void raw3270_deactivate_view(struct raw3270_view *); -struct raw3270_view *raw3270_find_view(struct raw3270_fn *, int); -int raw3270_start(struct raw3270_view *, struct raw3270_request *); -int raw3270_start_locked(struct raw3270_view *, struct raw3270_request *); -int raw3270_start_irq(struct raw3270_view *, struct raw3270_request *); -int raw3270_reset(struct raw3270_view *); -struct raw3270_view *raw3270_view(struct raw3270_view *); -int raw3270_view_active(struct raw3270_view *); +int raw3270_activate_view(struct raw3270_view *view); +void raw3270_del_view(struct raw3270_view *view); +void raw3270_deactivate_view(struct raw3270_view *view); +struct raw3270_view *raw3270_find_view(struct raw3270_fn *fn, int minor); +int raw3270_start(struct raw3270_view *view, struct raw3270_request *rq); +int raw3270_start_locked(struct raw3270_view *view, struct raw3270_request *rq); +int raw3270_start_irq(struct raw3270_view *view, struct raw3270_request *rq); +int raw3270_reset(struct raw3270_view *view); +struct raw3270_view *raw3270_view(struct raw3270_view *view); +int raw3270_view_active(struct raw3270_view *view); +int raw3270_start_request(struct raw3270_view *view, struct raw3270_request *rq, + int cmd, void *data, size_t len); +void raw3270_read_modified_cb(struct raw3270_request *rq, void *data); /* Reference count inliner for view structures. */ static inline void @@ -189,7 +116,7 @@ raw3270_put_view(struct raw3270_view *view) } struct raw3270 *raw3270_setup_console(void); -void raw3270_wait_cons_dev(struct raw3270 *); +void raw3270_wait_cons_dev(struct raw3270 *rp); /* Notifier for device addition/removal */ struct raw3270_notifier { @@ -198,87 +125,5 @@ struct raw3270_notifier { void (*destroy)(int minor); }; -int raw3270_register_notifier(struct raw3270_notifier *); -void raw3270_unregister_notifier(struct raw3270_notifier *); - -/* - * Little memory allocator for string objects. - */ -struct string -{ - struct list_head list; - struct list_head update; - unsigned long size; - unsigned long len; - char string[]; -} __attribute__ ((aligned(8))); - -static inline struct string * -alloc_string(struct list_head *free_list, unsigned long len) -{ - struct string *cs, *tmp; - unsigned long size; - - size = (len + 7L) & -8L; - list_for_each_entry(cs, free_list, list) { - if (cs->size < size) - continue; - if (cs->size > size + sizeof(struct string)) { - char *endaddr = (char *) (cs + 1) + cs->size; - tmp = (struct string *) (endaddr - size) - 1; - tmp->size = size; - cs->size -= size + sizeof(struct string); - cs = tmp; - } else - list_del(&cs->list); - cs->len = len; - INIT_LIST_HEAD(&cs->list); - INIT_LIST_HEAD(&cs->update); - return cs; - } - return NULL; -} - -static inline unsigned long -free_string(struct list_head *free_list, struct string *cs) -{ - struct string *tmp; - struct list_head *p, *left; - - /* Find out the left neighbour in free memory list. */ - left = free_list; - list_for_each(p, free_list) { - if (list_entry(p, struct string, list) > cs) - break; - left = p; - } - /* Try to merge with right neighbour = next element from left. */ - if (left->next != free_list) { - tmp = list_entry(left->next, struct string, list); - if ((char *) (cs + 1) + cs->size == (char *) tmp) { - list_del(&tmp->list); - cs->size += tmp->size + sizeof(struct string); - } - } - /* Try to merge with left neighbour. */ - if (left != free_list) { - tmp = list_entry(left, struct string, list); - if ((char *) (tmp + 1) + tmp->size == (char *) cs) { - tmp->size += cs->size + sizeof(struct string); - return tmp->size; - } - } - __list_add(&cs->list, left, left->next); - return cs->size; -} - -static inline void -add_string_memory(struct list_head *free_list, void *mem, unsigned long size) -{ - struct string *cs; - - cs = (struct string *) mem; - cs->size = size - sizeof(struct string); - free_string(free_list, cs); -} - +int raw3270_register_notifier(struct raw3270_notifier *notifier); +void raw3270_unregister_notifier(struct raw3270_notifier *notifier); diff --git a/drivers/s390/char/sclp_early.c b/drivers/s390/char/sclp_early.c index c1c70a161c0e..f480d6c7fd39 100644 --- a/drivers/s390/char/sclp_early.c +++ b/drivers/s390/char/sclp_early.c @@ -163,7 +163,7 @@ static void __init sclp_early_console_detect(struct init_sccb *sccb) sclp.has_linemode = 1; } -void __init sclp_early_adjust_va(void) +void __init __no_sanitize_address sclp_early_adjust_va(void) { sclp_early_sccb = __va((unsigned long)sclp_early_sccb); } diff --git a/drivers/s390/char/sclp_ftp.c b/drivers/s390/char/sclp_ftp.c index ec5a0e2b9255..d27e2cbfbccb 100644 --- a/drivers/s390/char/sclp_ftp.c +++ b/drivers/s390/char/sclp_ftp.c @@ -90,7 +90,7 @@ static int sclp_ftp_et7(const struct hmcdrv_ftp_cmdspec *ftp) struct completion completion; struct sclp_diag_sccb *sccb; struct sclp_req *req; - size_t len; + ssize_t len; int rc; req = kzalloc(sizeof(*req), GFP_KERNEL); @@ -117,9 +117,9 @@ static int sclp_ftp_et7(const struct hmcdrv_ftp_cmdspec *ftp) sccb->evbuf.mdd.ftp.length = ftp->len; sccb->evbuf.mdd.ftp.bufaddr = virt_to_phys(ftp->buf); - len = strlcpy(sccb->evbuf.mdd.ftp.fident, ftp->fname, + len = strscpy(sccb->evbuf.mdd.ftp.fident, ftp->fname, HMCDRV_FTP_FIDENT_MAX); - if (len >= HMCDRV_FTP_FIDENT_MAX) { + if (len < 0) { rc = -EINVAL; goto out_free; } diff --git a/drivers/s390/char/tty3270.c b/drivers/s390/char/tty3270.c deleted file mode 100644 index 26e3995ac062..000000000000 --- a/drivers/s390/char/tty3270.c +++ /dev/null @@ -1,1963 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * IBM/3270 Driver - tty functions. - * - * Author(s): - * Original 3270 Code for 2.4 written by Richard Hitt (UTS Global) - * Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com> - * -- Copyright IBM Corp. 2003 - */ - -#include <linux/module.h> -#include <linux/types.h> -#include <linux/kdev_t.h> -#include <linux/tty.h> -#include <linux/vt_kern.h> -#include <linux/init.h> -#include <linux/console.h> -#include <linux/interrupt.h> -#include <linux/workqueue.h> - -#include <linux/slab.h> -#include <linux/memblock.h> -#include <linux/compat.h> - -#include <asm/ccwdev.h> -#include <asm/cio.h> -#include <asm/ebcdic.h> -#include <linux/uaccess.h> - -#include "raw3270.h" -#include "tty3270.h" -#include "keyboard.h" - -#define TTY3270_CHAR_BUF_SIZE 256 -#define TTY3270_OUTPUT_BUFFER_SIZE 1024 -#define TTY3270_STRING_PAGES 5 - -struct tty_driver *tty3270_driver; -static int tty3270_max_index; - -static struct raw3270_fn tty3270_fn; - -struct tty3270_cell { - unsigned char character; - unsigned char highlight; - unsigned char f_color; -}; - -struct tty3270_line { - struct tty3270_cell *cells; - int len; -}; - -#define ESCAPE_NPAR 8 - -/* - * The main tty view data structure. - * FIXME: - * 1) describe line orientation & lines list concept against screen - * 2) describe conversion of screen to lines - * 3) describe line format. - */ -struct tty3270 { - struct raw3270_view view; - struct tty_port port; - void **freemem_pages; /* Array of pages used for freemem. */ - struct list_head freemem; /* List of free memory for strings. */ - - /* Output stuff. */ - struct list_head lines; /* List of lines. */ - struct list_head update; /* List of lines to update. */ - unsigned char wcc; /* Write control character. */ - int nr_lines; /* # lines in list. */ - int nr_up; /* # lines up in history. */ - unsigned long update_flags; /* Update indication bits. */ - struct string *status; /* Lower right of display. */ - struct raw3270_request *write; /* Single write request. */ - struct timer_list timer; /* Output delay timer. */ - - /* Current tty screen. */ - unsigned int cx, cy; /* Current output position. */ - unsigned int highlight; /* Blink/reverse/underscore */ - unsigned int f_color; /* Foreground color */ - struct tty3270_line *screen; - unsigned int n_model, n_cols, n_rows; /* New model & size */ - struct work_struct resize_work; - - /* Input stuff. */ - struct string *prompt; /* Output string for input area. */ - struct string *input; /* Input string for read request. */ - struct raw3270_request *read; /* Single read request. */ - struct raw3270_request *kreset; /* Single keyboard reset request. */ - unsigned char inattr; /* Visible/invisible input. */ - int throttle, attn; /* tty throttle/unthrottle. */ - struct tasklet_struct readlet; /* Tasklet to issue read request. */ - struct tasklet_struct hanglet; /* Tasklet to hang up the tty. */ - struct kbd_data *kbd; /* key_maps stuff. */ - - /* Escape sequence parsing. */ - int esc_state, esc_ques, esc_npar; - int esc_par[ESCAPE_NPAR]; - unsigned int saved_cx, saved_cy; - unsigned int saved_highlight, saved_f_color; - - /* Command recalling. */ - struct list_head rcl_lines; /* List of recallable lines. */ - struct list_head *rcl_walk; /* Point in rcl_lines list. */ - int rcl_nr, rcl_max; /* Number/max number of rcl_lines. */ - - /* Character array for put_char/flush_chars. */ - unsigned int char_count; - char char_buf[TTY3270_CHAR_BUF_SIZE]; -}; - -/* tty3270->update_flags. See tty3270_update for details. */ -#define TTY_UPDATE_ERASE 1 /* Use EWRITEA instead of WRITE. */ -#define TTY_UPDATE_LIST 2 /* Update lines in tty3270->update. */ -#define TTY_UPDATE_INPUT 4 /* Update input line. */ -#define TTY_UPDATE_STATUS 8 /* Update status line. */ -#define TTY_UPDATE_ALL 16 /* Recreate screen. */ - -static void tty3270_update(struct timer_list *); -static void tty3270_resize_work(struct work_struct *work); - -/* - * Setup timeout for a device. On timeout trigger an update. - */ -static void tty3270_set_timer(struct tty3270 *tp, int expires) -{ - mod_timer(&tp->timer, jiffies + expires); -} - -/* - * The input line are the two last lines of the screen. - */ -static void -tty3270_update_prompt(struct tty3270 *tp, char *input, int count) -{ - struct string *line; - unsigned int off; - - line = tp->prompt; - if (count != 0) - line->string[5] = TF_INMDT; - else - line->string[5] = tp->inattr; - if (count > tp->view.cols * 2 - 11) - count = tp->view.cols * 2 - 11; - memcpy(line->string + 6, input, count); - line->string[6 + count] = TO_IC; - /* Clear to end of input line. */ - if (count < tp->view.cols * 2 - 11) { - line->string[7 + count] = TO_RA; - line->string[10 + count] = 0; - off = tp->view.cols * tp->view.rows - 9; - raw3270_buffer_address(tp->view.dev, line->string+count+8, off); - line->len = 11 + count; - } else - line->len = 7 + count; - tp->update_flags |= TTY_UPDATE_INPUT; -} - -static void -tty3270_create_prompt(struct tty3270 *tp) -{ - static const unsigned char blueprint[] = - { TO_SBA, 0, 0, 0x6e, TO_SF, TF_INPUT, - /* empty input string */ - TO_IC, TO_RA, 0, 0, 0 }; - struct string *line; - unsigned int offset; - - line = alloc_string(&tp->freemem, - sizeof(blueprint) + tp->view.cols * 2 - 9); - tp->prompt = line; - tp->inattr = TF_INPUT; - /* Copy blueprint to status line */ - memcpy(line->string, blueprint, sizeof(blueprint)); - line->len = sizeof(blueprint); - /* Set output offsets. */ - offset = tp->view.cols * (tp->view.rows - 2); - raw3270_buffer_address(tp->view.dev, line->string + 1, offset); - offset = tp->view.cols * tp->view.rows - 9; - raw3270_buffer_address(tp->view.dev, line->string + 8, offset); - - /* Allocate input string for reading. */ - tp->input = alloc_string(&tp->freemem, tp->view.cols * 2 - 9 + 6); -} - -/* - * The status line is the last line of the screen. It shows the string - * "Running"/"Holding" in the lower right corner of the screen. - */ -static void -tty3270_update_status(struct tty3270 * tp) -{ - char *str; - - str = (tp->nr_up != 0) ? "History" : "Running"; - memcpy(tp->status->string + 8, str, 7); - codepage_convert(tp->view.ascebc, tp->status->string + 8, 7); - tp->update_flags |= TTY_UPDATE_STATUS; -} - -static void -tty3270_create_status(struct tty3270 * tp) -{ - static const unsigned char blueprint[] = - { TO_SBA, 0, 0, TO_SF, TF_LOG, TO_SA, TAT_COLOR, TAC_GREEN, - 0, 0, 0, 0, 0, 0, 0, TO_SF, TF_LOG, TO_SA, TAT_COLOR, - TAC_RESET }; - struct string *line; - unsigned int offset; - - line = alloc_string(&tp->freemem,sizeof(blueprint)); - tp->status = line; - /* Copy blueprint to status line */ - memcpy(line->string, blueprint, sizeof(blueprint)); - /* Set address to start of status string (= last 9 characters). */ - offset = tp->view.cols * tp->view.rows - 9; - raw3270_buffer_address(tp->view.dev, line->string + 1, offset); -} - -/* - * Set output offsets to 3270 datastream fragment of a tty string. - * (TO_SBA offset at the start and TO_RA offset at the end of the string) - */ -static void -tty3270_update_string(struct tty3270 *tp, struct string *line, int nr) -{ - unsigned char *cp; - - raw3270_buffer_address(tp->view.dev, line->string + 1, - tp->view.cols * nr); - cp = line->string + line->len - 4; - if (*cp == TO_RA) - raw3270_buffer_address(tp->view.dev, cp + 1, - tp->view.cols * (nr + 1)); -} - -/* - * Rebuild update list to print all lines. - */ -static void -tty3270_rebuild_update(struct tty3270 *tp) -{ - struct string *s, *n; - int line, nr_up; - - /* - * Throw away update list and create a new one, - * containing all lines that will fit on the screen. - */ - list_for_each_entry_safe(s, n, &tp->update, update) - list_del_init(&s->update); - line = tp->view.rows - 3; - nr_up = tp->nr_up; - list_for_each_entry_reverse(s, &tp->lines, list) { - if (nr_up > 0) { - nr_up--; - continue; - } - tty3270_update_string(tp, s, line); - list_add(&s->update, &tp->update); - if (--line < 0) - break; - } - tp->update_flags |= TTY_UPDATE_LIST; -} - -/* - * Alloc string for size bytes. If there is not enough room in - * freemem, free strings until there is room. - */ -static struct string * -tty3270_alloc_string(struct tty3270 *tp, size_t size) -{ - struct string *s, *n; - - s = alloc_string(&tp->freemem, size); - if (s) - return s; - list_for_each_entry_safe(s, n, &tp->lines, list) { - BUG_ON(tp->nr_lines <= tp->view.rows - 2); - list_del(&s->list); - if (!list_empty(&s->update)) - list_del(&s->update); - tp->nr_lines--; - if (free_string(&tp->freemem, s) >= size) - break; - } - s = alloc_string(&tp->freemem, size); - BUG_ON(!s); - if (tp->nr_up != 0 && - tp->nr_up + tp->view.rows - 2 >= tp->nr_lines) { - tp->nr_up = tp->nr_lines - tp->view.rows + 2; - tty3270_rebuild_update(tp); - tty3270_update_status(tp); - } - return s; -} - -/* - * Add an empty line to the list. - */ -static void -tty3270_blank_line(struct tty3270 *tp) -{ - static const unsigned char blueprint[] = - { TO_SBA, 0, 0, TO_SA, TAT_EXTHI, TAX_RESET, - TO_SA, TAT_COLOR, TAC_RESET, TO_RA, 0, 0, 0 }; - struct string *s; - - s = tty3270_alloc_string(tp, sizeof(blueprint)); - memcpy(s->string, blueprint, sizeof(blueprint)); - s->len = sizeof(blueprint); - list_add_tail(&s->list, &tp->lines); - tp->nr_lines++; - if (tp->nr_up != 0) - tp->nr_up++; -} - -/* - * Create a blank screen and remove all lines from the history. - */ -static void -tty3270_blank_screen(struct tty3270 *tp) -{ - struct string *s, *n; - int i; - - for (i = 0; i < tp->view.rows - 2; i++) - tp->screen[i].len = 0; - tp->nr_up = 0; - list_for_each_entry_safe(s, n, &tp->lines, list) { - list_del(&s->list); - if (!list_empty(&s->update)) - list_del(&s->update); - tp->nr_lines--; - free_string(&tp->freemem, s); - } -} - -/* - * Write request completion callback. - */ -static void -tty3270_write_callback(struct raw3270_request *rq, void *data) -{ - struct tty3270 *tp = container_of(rq->view, struct tty3270, view); - - if (rq->rc != 0) { - /* Write wasn't successful. Refresh all. */ - tp->update_flags = TTY_UPDATE_ALL; - tty3270_set_timer(tp, 1); - } - raw3270_request_reset(rq); - xchg(&tp->write, rq); -} - -/* - * Update 3270 display. - */ -static void -tty3270_update(struct timer_list *t) -{ - struct tty3270 *tp = from_timer(tp, t, timer); - static char invalid_sba[2] = { 0xff, 0xff }; - struct raw3270_request *wrq; - unsigned long updated; - struct string *s, *n; - char *sba, *str; - int rc, len; - - wrq = xchg(&tp->write, 0); - if (!wrq) { - tty3270_set_timer(tp, 1); - return; - } - - spin_lock(&tp->view.lock); - updated = 0; - if (tp->update_flags & TTY_UPDATE_ALL) { - tty3270_rebuild_update(tp); - tty3270_update_status(tp); - tp->update_flags = TTY_UPDATE_ERASE | TTY_UPDATE_LIST | - TTY_UPDATE_INPUT | TTY_UPDATE_STATUS; - } - if (tp->update_flags & TTY_UPDATE_ERASE) { - /* Use erase write alternate to erase display. */ - raw3270_request_set_cmd(wrq, TC_EWRITEA); - updated |= TTY_UPDATE_ERASE; - } else - raw3270_request_set_cmd(wrq, TC_WRITE); - - raw3270_request_add_data(wrq, &tp->wcc, 1); - tp->wcc = TW_NONE; - - /* - * Update status line. - */ - if (tp->update_flags & TTY_UPDATE_STATUS) - if (raw3270_request_add_data(wrq, tp->status->string, - tp->status->len) == 0) - updated |= TTY_UPDATE_STATUS; - - /* - * Write input line. - */ - if (tp->update_flags & TTY_UPDATE_INPUT) - if (raw3270_request_add_data(wrq, tp->prompt->string, - tp->prompt->len) == 0) - updated |= TTY_UPDATE_INPUT; - - sba = invalid_sba; - - if (tp->update_flags & TTY_UPDATE_LIST) { - /* Write strings in the update list to the screen. */ - list_for_each_entry_safe(s, n, &tp->update, update) { - str = s->string; - len = s->len; - /* - * Skip TO_SBA at the start of the string if the - * last output position matches the start address - * of this line. - */ - if (s->string[1] == sba[0] && s->string[2] == sba[1]) { - str += 3; - len -= 3; - } - if (raw3270_request_add_data(wrq, str, len) != 0) - break; - list_del_init(&s->update); - if (s->string[s->len - 4] == TO_RA) - sba = s->string + s->len - 3; - else - sba = invalid_sba; - } - if (list_empty(&tp->update)) - updated |= TTY_UPDATE_LIST; - } - wrq->callback = tty3270_write_callback; - rc = raw3270_start(&tp->view, wrq); - if (rc == 0) { - tp->update_flags &= ~updated; - if (tp->update_flags) - tty3270_set_timer(tp, 1); - } else { - raw3270_request_reset(wrq); - xchg(&tp->write, wrq); - } - spin_unlock(&tp->view.lock); -} - -/* - * Command recalling. - */ -static void -tty3270_rcl_add(struct tty3270 *tp, char *input, int len) -{ - struct string *s; - - tp->rcl_walk = NULL; - if (len <= 0) - return; - if (tp->rcl_nr >= tp->rcl_max) { - s = list_entry(tp->rcl_lines.next, struct string, list); - list_del(&s->list); - free_string(&tp->freemem, s); - tp->rcl_nr--; - } - s = tty3270_alloc_string(tp, len); - memcpy(s->string, input, len); - list_add_tail(&s->list, &tp->rcl_lines); - tp->rcl_nr++; -} - -static void -tty3270_rcl_backward(struct kbd_data *kbd) -{ - struct tty3270 *tp = container_of(kbd->port, struct tty3270, port); - struct string *s; - - spin_lock_bh(&tp->view.lock); - if (tp->inattr == TF_INPUT) { - if (tp->rcl_walk && tp->rcl_walk->prev != &tp->rcl_lines) - tp->rcl_walk = tp->rcl_walk->prev; - else if (!list_empty(&tp->rcl_lines)) - tp->rcl_walk = tp->rcl_lines.prev; - s = tp->rcl_walk ? - list_entry(tp->rcl_walk, struct string, list) : NULL; - if (tp->rcl_walk) { - s = list_entry(tp->rcl_walk, struct string, list); - tty3270_update_prompt(tp, s->string, s->len); - } else - tty3270_update_prompt(tp, NULL, 0); - tty3270_set_timer(tp, 1); - } - spin_unlock_bh(&tp->view.lock); -} - -/* - * Deactivate tty view. - */ -static void -tty3270_exit_tty(struct kbd_data *kbd) -{ - struct tty3270 *tp = container_of(kbd->port, struct tty3270, port); - - raw3270_deactivate_view(&tp->view); -} - -/* - * Scroll forward in history. - */ -static void -tty3270_scroll_forward(struct kbd_data *kbd) -{ - struct tty3270 *tp = container_of(kbd->port, struct tty3270, port); - int nr_up; - - spin_lock_bh(&tp->view.lock); - nr_up = tp->nr_up - tp->view.rows + 2; - if (nr_up < 0) - nr_up = 0; - if (nr_up != tp->nr_up) { - tp->nr_up = nr_up; - tty3270_rebuild_update(tp); - tty3270_update_status(tp); - tty3270_set_timer(tp, 1); - } - spin_unlock_bh(&tp->view.lock); -} - -/* - * Scroll backward in history. - */ -static void -tty3270_scroll_backward(struct kbd_data *kbd) -{ - struct tty3270 *tp = container_of(kbd->port, struct tty3270, port); - int nr_up; - - spin_lock_bh(&tp->view.lock); - nr_up = tp->nr_up + tp->view.rows - 2; - if (nr_up + tp->view.rows - 2 > tp->nr_lines) - nr_up = tp->nr_lines - tp->view.rows + 2; - if (nr_up != tp->nr_up) { - tp->nr_up = nr_up; - tty3270_rebuild_update(tp); - tty3270_update_status(tp); - tty3270_set_timer(tp, 1); - } - spin_unlock_bh(&tp->view.lock); -} - -/* - * Pass input line to tty. - */ -static void -tty3270_read_tasklet(unsigned long data) -{ - struct raw3270_request *rrq = (struct raw3270_request *)data; - static char kreset_data = TW_KR; - struct tty3270 *tp = container_of(rrq->view, struct tty3270, view); - char *input; - int len; - - spin_lock_bh(&tp->view.lock); - /* - * Two AID keys are special: For 0x7d (enter) the input line - * has to be emitted to the tty and for 0x6d the screen - * needs to be redrawn. - */ - input = NULL; - len = 0; - if (tp->input->string[0] == 0x7d) { - /* Enter: write input to tty. */ - input = tp->input->string + 6; - len = tp->input->len - 6 - rrq->rescnt; - if (tp->inattr != TF_INPUTN) - tty3270_rcl_add(tp, input, len); - if (tp->nr_up > 0) { - tp->nr_up = 0; - tty3270_rebuild_update(tp); - tty3270_update_status(tp); - } - /* Clear input area. */ - tty3270_update_prompt(tp, NULL, 0); - tty3270_set_timer(tp, 1); - } else if (tp->input->string[0] == 0x6d) { - /* Display has been cleared. Redraw. */ - tp->update_flags = TTY_UPDATE_ALL; - tty3270_set_timer(tp, 1); - } - spin_unlock_bh(&tp->view.lock); - - /* Start keyboard reset command. */ - raw3270_request_reset(tp->kreset); - raw3270_request_set_cmd(tp->kreset, TC_WRITE); - raw3270_request_add_data(tp->kreset, &kreset_data, 1); - raw3270_start(&tp->view, tp->kreset); - - while (len-- > 0) - kbd_keycode(tp->kbd, *input++); - /* Emit keycode for AID byte. */ - kbd_keycode(tp->kbd, 256 + tp->input->string[0]); - - raw3270_request_reset(rrq); - xchg(&tp->read, rrq); - raw3270_put_view(&tp->view); -} - -/* - * Read request completion callback. - */ -static void -tty3270_read_callback(struct raw3270_request *rq, void *data) -{ - struct tty3270 *tp = container_of(rq->view, struct tty3270, view); - raw3270_get_view(rq->view); - /* Schedule tasklet to pass input to tty. */ - tasklet_schedule(&tp->readlet); -} - -/* - * Issue a read request. Call with device lock. - */ -static void -tty3270_issue_read(struct tty3270 *tp, int lock) -{ - struct raw3270_request *rrq; - int rc; - - rrq = xchg(&tp->read, 0); - if (!rrq) - /* Read already scheduled. */ - return; - rrq->callback = tty3270_read_callback; - rrq->callback_data = tp; - raw3270_request_set_cmd(rrq, TC_READMOD); - raw3270_request_set_data(rrq, tp->input->string, tp->input->len); - /* Issue the read modified request. */ - if (lock) { - rc = raw3270_start(&tp->view, rrq); - } else - rc = raw3270_start_irq(&tp->view, rrq); - if (rc) { - raw3270_request_reset(rrq); - xchg(&tp->read, rrq); - } -} - -/* - * Hang up the tty - */ -static void -tty3270_hangup_tasklet(unsigned long data) -{ - struct tty3270 *tp = (struct tty3270 *)data; - tty_port_tty_hangup(&tp->port, true); - raw3270_put_view(&tp->view); -} - -/* - * Switch to the tty view. - */ -static int -tty3270_activate(struct raw3270_view *view) -{ - struct tty3270 *tp = container_of(view, struct tty3270, view); - - tp->update_flags = TTY_UPDATE_ALL; - tty3270_set_timer(tp, 1); - return 0; -} - -static void -tty3270_deactivate(struct raw3270_view *view) -{ - struct tty3270 *tp = container_of(view, struct tty3270, view); - - del_timer(&tp->timer); -} - -static void -tty3270_irq(struct tty3270 *tp, struct raw3270_request *rq, struct irb *irb) -{ - /* Handle ATTN. Schedule tasklet to read aid. */ - if (irb->scsw.cmd.dstat & DEV_STAT_ATTENTION) { - if (!tp->throttle) - tty3270_issue_read(tp, 0); - else - tp->attn = 1; - } - - if (rq) { - if (irb->scsw.cmd.dstat & DEV_STAT_UNIT_CHECK) { - rq->rc = -EIO; - raw3270_get_view(&tp->view); - tasklet_schedule(&tp->hanglet); - } else { - /* Normal end. Copy residual count. */ - rq->rescnt = irb->scsw.cmd.count; - } - } else if (irb->scsw.cmd.dstat & DEV_STAT_DEV_END) { - /* Interrupt without an outstanding request -> update all */ - tp->update_flags = TTY_UPDATE_ALL; - tty3270_set_timer(tp, 1); - } -} - -/* - * Allocate tty3270 structure. - */ -static struct tty3270 * -tty3270_alloc_view(void) -{ - struct tty3270 *tp; - int pages; - - tp = kzalloc(sizeof(struct tty3270), GFP_KERNEL); - if (!tp) - goto out_err; - tp->freemem_pages = - kmalloc_array(TTY3270_STRING_PAGES, sizeof(void *), - GFP_KERNEL); - if (!tp->freemem_pages) - goto out_tp; - INIT_LIST_HEAD(&tp->freemem); - INIT_LIST_HEAD(&tp->lines); - INIT_LIST_HEAD(&tp->update); - INIT_LIST_HEAD(&tp->rcl_lines); - tp->rcl_max = 20; - - for (pages = 0; pages < TTY3270_STRING_PAGES; pages++) { - tp->freemem_pages[pages] = (void *) - __get_free_pages(GFP_KERNEL|GFP_DMA, 0); - if (!tp->freemem_pages[pages]) - goto out_pages; - add_string_memory(&tp->freemem, - tp->freemem_pages[pages], PAGE_SIZE); - } - tp->write = raw3270_request_alloc(TTY3270_OUTPUT_BUFFER_SIZE); - if (IS_ERR(tp->write)) - goto out_pages; - tp->read = raw3270_request_alloc(0); - if (IS_ERR(tp->read)) - goto out_write; - tp->kreset = raw3270_request_alloc(1); - if (IS_ERR(tp->kreset)) - goto out_read; - tp->kbd = kbd_alloc(); - if (!tp->kbd) - goto out_reset; - - tty_port_init(&tp->port); - timer_setup(&tp->timer, tty3270_update, 0); - tasklet_init(&tp->readlet, tty3270_read_tasklet, - (unsigned long) tp->read); - tasklet_init(&tp->hanglet, tty3270_hangup_tasklet, - (unsigned long) tp); - INIT_WORK(&tp->resize_work, tty3270_resize_work); - - return tp; - -out_reset: - raw3270_request_free(tp->kreset); -out_read: - raw3270_request_free(tp->read); -out_write: - raw3270_request_free(tp->write); -out_pages: - while (pages--) - free_pages((unsigned long) tp->freemem_pages[pages], 0); - kfree(tp->freemem_pages); - tty_port_destroy(&tp->port); -out_tp: - kfree(tp); -out_err: - return ERR_PTR(-ENOMEM); -} - -/* - * Free tty3270 structure. - */ -static void -tty3270_free_view(struct tty3270 *tp) -{ - int pages; - - kbd_free(tp->kbd); - raw3270_request_free(tp->kreset); - raw3270_request_free(tp->read); - raw3270_request_free(tp->write); - for (pages = 0; pages < TTY3270_STRING_PAGES; pages++) - free_pages((unsigned long) tp->freemem_pages[pages], 0); - kfree(tp->freemem_pages); - tty_port_destroy(&tp->port); - kfree(tp); -} - -/* - * Allocate tty3270 screen. - */ -static struct tty3270_line * -tty3270_alloc_screen(unsigned int rows, unsigned int cols) -{ - struct tty3270_line *screen; - unsigned long size; - int lines; - - size = sizeof(struct tty3270_line) * (rows - 2); - screen = kzalloc(size, GFP_KERNEL); - if (!screen) - goto out_err; - for (lines = 0; lines < rows - 2; lines++) { - size = sizeof(struct tty3270_cell) * cols; - screen[lines].cells = kzalloc(size, GFP_KERNEL); - if (!screen[lines].cells) - goto out_screen; - } - return screen; -out_screen: - while (lines--) - kfree(screen[lines].cells); - kfree(screen); -out_err: - return ERR_PTR(-ENOMEM); -} - -/* - * Free tty3270 screen. - */ -static void -tty3270_free_screen(struct tty3270_line *screen, unsigned int rows) -{ - int lines; - - for (lines = 0; lines < rows - 2; lines++) - kfree(screen[lines].cells); - kfree(screen); -} - -/* - * Resize tty3270 screen - */ -static void tty3270_resize_work(struct work_struct *work) -{ - struct tty3270 *tp = container_of(work, struct tty3270, resize_work); - struct tty3270_line *screen, *oscreen; - struct tty_struct *tty; - unsigned int orows; - struct winsize ws; - - screen = tty3270_alloc_screen(tp->n_rows, tp->n_cols); - if (IS_ERR(screen)) - return; - /* Switch to new output size */ - spin_lock_bh(&tp->view.lock); - tty3270_blank_screen(tp); - oscreen = tp->screen; - orows = tp->view.rows; - tp->view.model = tp->n_model; - tp->view.rows = tp->n_rows; - tp->view.cols = tp->n_cols; - tp->screen = screen; - free_string(&tp->freemem, tp->prompt); - free_string(&tp->freemem, tp->status); - tty3270_create_prompt(tp); - tty3270_create_status(tp); - while (tp->nr_lines < tp->view.rows - 2) - tty3270_blank_line(tp); - tp->update_flags = TTY_UPDATE_ALL; - spin_unlock_bh(&tp->view.lock); - tty3270_free_screen(oscreen, orows); - tty3270_set_timer(tp, 1); - /* Informat tty layer about new size */ - tty = tty_port_tty_get(&tp->port); - if (!tty) - return; - ws.ws_row = tp->view.rows - 2; - ws.ws_col = tp->view.cols; - tty_do_resize(tty, &ws); - tty_kref_put(tty); -} - -static void -tty3270_resize(struct raw3270_view *view, int model, int rows, int cols) -{ - struct tty3270 *tp = container_of(view, struct tty3270, view); - - if (tp->n_model == model && tp->n_rows == rows && tp->n_cols == cols) - return; - tp->n_model = model; - tp->n_rows = rows; - tp->n_cols = cols; - schedule_work(&tp->resize_work); -} - -/* - * Unlink tty3270 data structure from tty. - */ -static void -tty3270_release(struct raw3270_view *view) -{ - struct tty3270 *tp = container_of(view, struct tty3270, view); - struct tty_struct *tty = tty_port_tty_get(&tp->port); - - if (tty) { - tty->driver_data = NULL; - tty_port_tty_set(&tp->port, NULL); - tty_hangup(tty); - raw3270_put_view(&tp->view); - tty_kref_put(tty); - } -} - -/* - * Free tty3270 data structure - */ -static void -tty3270_free(struct raw3270_view *view) -{ - struct tty3270 *tp = container_of(view, struct tty3270, view); - - del_timer_sync(&tp->timer); - tty3270_free_screen(tp->screen, tp->view.rows); - tty3270_free_view(tp); -} - -/* - * Delayed freeing of tty3270 views. - */ -static void -tty3270_del_views(void) -{ - int i; - - for (i = RAW3270_FIRSTMINOR; i <= tty3270_max_index; i++) { - struct raw3270_view *view = raw3270_find_view(&tty3270_fn, i); - if (!IS_ERR(view)) - raw3270_del_view(view); - } -} - -static struct raw3270_fn tty3270_fn = { - .activate = tty3270_activate, - .deactivate = tty3270_deactivate, - .intv = (void *) tty3270_irq, - .release = tty3270_release, - .free = tty3270_free, - .resize = tty3270_resize -}; - -/* - * This routine is called whenever a 3270 tty is opened first time. - */ -static int tty3270_install(struct tty_driver *driver, struct tty_struct *tty) -{ - struct raw3270_view *view; - struct tty3270 *tp; - int i, rc; - - /* Check if the tty3270 is already there. */ - view = raw3270_find_view(&tty3270_fn, tty->index + RAW3270_FIRSTMINOR); - if (!IS_ERR(view)) { - tp = container_of(view, struct tty3270, view); - tty->driver_data = tp; - tty->winsize.ws_row = tp->view.rows - 2; - tty->winsize.ws_col = tp->view.cols; - tp->inattr = TF_INPUT; - goto port_install; - } - if (tty3270_max_index < tty->index + 1) - tty3270_max_index = tty->index + 1; - - /* Allocate tty3270 structure on first open. */ - tp = tty3270_alloc_view(); - if (IS_ERR(tp)) - return PTR_ERR(tp); - - rc = raw3270_add_view(&tp->view, &tty3270_fn, - tty->index + RAW3270_FIRSTMINOR, - RAW3270_VIEW_LOCK_BH); - if (rc) { - tty3270_free_view(tp); - return rc; - } - - tp->screen = tty3270_alloc_screen(tp->view.rows, tp->view.cols); - if (IS_ERR(tp->screen)) { - rc = PTR_ERR(tp->screen); - raw3270_put_view(&tp->view); - raw3270_del_view(&tp->view); - tty3270_free_view(tp); - return rc; - } - - tty->winsize.ws_row = tp->view.rows - 2; - tty->winsize.ws_col = tp->view.cols; - - tty3270_create_prompt(tp); - tty3270_create_status(tp); - tty3270_update_status(tp); - - /* Create blank line for every line in the tty output area. */ - for (i = 0; i < tp->view.rows - 2; i++) - tty3270_blank_line(tp); - - tp->kbd->port = &tp->port; - tp->kbd->fn_handler[KVAL(K_INCRCONSOLE)] = tty3270_exit_tty; - tp->kbd->fn_handler[KVAL(K_SCROLLBACK)] = tty3270_scroll_backward; - tp->kbd->fn_handler[KVAL(K_SCROLLFORW)] = tty3270_scroll_forward; - tp->kbd->fn_handler[KVAL(K_CONS)] = tty3270_rcl_backward; - kbd_ascebc(tp->kbd, tp->view.ascebc); - - raw3270_activate_view(&tp->view); - -port_install: - rc = tty_port_install(&tp->port, driver, tty); - if (rc) { - raw3270_put_view(&tp->view); - return rc; - } - - tty->driver_data = tp; - - return 0; -} - -/* - * This routine is called whenever a 3270 tty is opened. - */ -static int -tty3270_open(struct tty_struct *tty, struct file *filp) -{ - struct tty3270 *tp = tty->driver_data; - struct tty_port *port = &tp->port; - - port->count++; - tty_port_tty_set(port, tty); - return 0; -} - -/* - * This routine is called when the 3270 tty is closed. We wait - * for the remaining request to be completed. Then we clean up. - */ -static void -tty3270_close(struct tty_struct *tty, struct file * filp) -{ - struct tty3270 *tp = tty->driver_data; - - if (tty->count > 1) - return; - if (tp) - tty_port_tty_set(&tp->port, NULL); -} - -static void tty3270_cleanup(struct tty_struct *tty) -{ - struct tty3270 *tp = tty->driver_data; - - if (tp) { - tty->driver_data = NULL; - raw3270_put_view(&tp->view); - } -} - -/* - * We always have room. - */ -static unsigned int -tty3270_write_room(struct tty_struct *tty) -{ - return INT_MAX; -} - -/* - * Insert character into the screen at the current position with the - * current color and highlight. This function does NOT do cursor movement. - */ -static void tty3270_put_character(struct tty3270 *tp, char ch) -{ - struct tty3270_line *line; - struct tty3270_cell *cell; - - line = tp->screen + tp->cy; - if (line->len <= tp->cx) { - while (line->len < tp->cx) { - cell = line->cells + line->len; - cell->character = tp->view.ascebc[' ']; - cell->highlight = tp->highlight; - cell->f_color = tp->f_color; - line->len++; - } - line->len++; - } - cell = line->cells + tp->cx; - cell->character = tp->view.ascebc[(unsigned int) ch]; - cell->highlight = tp->highlight; - cell->f_color = tp->f_color; -} - -/* - * Convert a tty3270_line to a 3270 data fragment usable for output. - */ -static void -tty3270_convert_line(struct tty3270 *tp, int line_nr) -{ - struct tty3270_line *line; - struct tty3270_cell *cell; - struct string *s, *n; - unsigned char highlight; - unsigned char f_color; - char *cp; - int flen, i; - - /* Determine how long the fragment will be. */ - flen = 3; /* Prefix (TO_SBA). */ - line = tp->screen + line_nr; - flen += line->len; - highlight = TAX_RESET; - f_color = TAC_RESET; - for (i = 0, cell = line->cells; i < line->len; i++, cell++) { - if (cell->highlight != highlight) { - flen += 3; /* TO_SA to switch highlight. */ - highlight = cell->highlight; - } - if (cell->f_color != f_color) { - flen += 3; /* TO_SA to switch color. */ - f_color = cell->f_color; - } - } - if (highlight != TAX_RESET) - flen += 3; /* TO_SA to reset hightlight. */ - if (f_color != TAC_RESET) - flen += 3; /* TO_SA to reset color. */ - if (line->len < tp->view.cols) - flen += 4; /* Postfix (TO_RA). */ - - /* Find the line in the list. */ - i = tp->view.rows - 2 - line_nr; - list_for_each_entry_reverse(s, &tp->lines, list) - if (--i <= 0) - break; - /* - * Check if the line needs to get reallocated. - */ - if (s->len != flen) { - /* Reallocate string. */ - n = tty3270_alloc_string(tp, flen); - list_add(&n->list, &s->list); - list_del_init(&s->list); - if (!list_empty(&s->update)) - list_del_init(&s->update); - free_string(&tp->freemem, s); - s = n; - } - - /* Write 3270 data fragment. */ - cp = s->string; - *cp++ = TO_SBA; - *cp++ = 0; - *cp++ = 0; - - highlight = TAX_RESET; - f_color = TAC_RESET; - for (i = 0, cell = line->cells; i < line->len; i++, cell++) { - if (cell->highlight != highlight) { - *cp++ = TO_SA; - *cp++ = TAT_EXTHI; - *cp++ = cell->highlight; - highlight = cell->highlight; - } - if (cell->f_color != f_color) { - *cp++ = TO_SA; - *cp++ = TAT_COLOR; - *cp++ = cell->f_color; - f_color = cell->f_color; - } - *cp++ = cell->character; - } - if (highlight != TAX_RESET) { - *cp++ = TO_SA; - *cp++ = TAT_EXTHI; - *cp++ = TAX_RESET; - } - if (f_color != TAC_RESET) { - *cp++ = TO_SA; - *cp++ = TAT_COLOR; - *cp++ = TAC_RESET; - } - if (line->len < tp->view.cols) { - *cp++ = TO_RA; - *cp++ = 0; - *cp++ = 0; - *cp++ = 0; - } - - if (tp->nr_up + line_nr < tp->view.rows - 2) { - /* Line is currently visible on screen. */ - tty3270_update_string(tp, s, line_nr); - /* Add line to update list. */ - if (list_empty(&s->update)) { - list_add_tail(&s->update, &tp->update); - tp->update_flags |= TTY_UPDATE_LIST; - } - } -} - -/* - * Do carriage return. - */ -static void -tty3270_cr(struct tty3270 *tp) -{ - tp->cx = 0; -} - -/* - * Do line feed. - */ -static void -tty3270_lf(struct tty3270 *tp) -{ - struct tty3270_line temp; - int i; - - tty3270_convert_line(tp, tp->cy); - if (tp->cy < tp->view.rows - 3) { - tp->cy++; - return; - } - /* Last line just filled up. Add new, blank line. */ - tty3270_blank_line(tp); - temp = tp->screen[0]; - temp.len = 0; - for (i = 0; i < tp->view.rows - 3; i++) - tp->screen[i] = tp->screen[i+1]; - tp->screen[tp->view.rows - 3] = temp; - tty3270_rebuild_update(tp); -} - -static void -tty3270_ri(struct tty3270 *tp) -{ - if (tp->cy > 0) { - tty3270_convert_line(tp, tp->cy); - tp->cy--; - } -} - -/* - * Insert characters at current position. - */ -static void -tty3270_insert_characters(struct tty3270 *tp, int n) -{ - struct tty3270_line *line; - int k; - - line = tp->screen + tp->cy; - while (line->len < tp->cx) { - line->cells[line->len].character = tp->view.ascebc[' ']; - line->cells[line->len].highlight = TAX_RESET; - line->cells[line->len].f_color = TAC_RESET; - line->len++; - } - if (n > tp->view.cols - tp->cx) - n = tp->view.cols - tp->cx; - k = min_t(int, line->len - tp->cx, tp->view.cols - tp->cx - n); - while (k--) - line->cells[tp->cx + n + k] = line->cells[tp->cx + k]; - line->len += n; - if (line->len > tp->view.cols) - line->len = tp->view.cols; - while (n-- > 0) { - line->cells[tp->cx + n].character = tp->view.ascebc[' ']; - line->cells[tp->cx + n].highlight = tp->highlight; - line->cells[tp->cx + n].f_color = tp->f_color; - } -} - -/* - * Delete characters at current position. - */ -static void -tty3270_delete_characters(struct tty3270 *tp, int n) -{ - struct tty3270_line *line; - int i; - - line = tp->screen + tp->cy; - if (line->len <= tp->cx) - return; - if (line->len - tp->cx <= n) { - line->len = tp->cx; - return; - } - for (i = tp->cx; i + n < line->len; i++) - line->cells[i] = line->cells[i + n]; - line->len -= n; -} - -/* - * Erase characters at current position. - */ -static void -tty3270_erase_characters(struct tty3270 *tp, int n) -{ - struct tty3270_line *line; - struct tty3270_cell *cell; - - line = tp->screen + tp->cy; - while (line->len > tp->cx && n-- > 0) { - cell = line->cells + tp->cx++; - cell->character = ' '; - cell->highlight = TAX_RESET; - cell->f_color = TAC_RESET; - } - tp->cx += n; - tp->cx = min_t(int, tp->cx, tp->view.cols - 1); -} - -/* - * Erase line, 3 different cases: - * Esc [ 0 K Erase from current position to end of line inclusive - * Esc [ 1 K Erase from beginning of line to current position inclusive - * Esc [ 2 K Erase entire line (without moving cursor) - */ -static void -tty3270_erase_line(struct tty3270 *tp, int mode) -{ - struct tty3270_line *line; - struct tty3270_cell *cell; - int i; - - line = tp->screen + tp->cy; - if (mode == 0) - line->len = tp->cx; - else if (mode == 1) { - for (i = 0; i < tp->cx; i++) { - cell = line->cells + i; - cell->character = ' '; - cell->highlight = TAX_RESET; - cell->f_color = TAC_RESET; - } - if (line->len <= tp->cx) - line->len = tp->cx + 1; - } else if (mode == 2) - line->len = 0; - tty3270_convert_line(tp, tp->cy); -} - -/* - * Erase display, 3 different cases: - * Esc [ 0 J Erase from current position to bottom of screen inclusive - * Esc [ 1 J Erase from top of screen to current position inclusive - * Esc [ 2 J Erase entire screen (without moving the cursor) - */ -static void -tty3270_erase_display(struct tty3270 *tp, int mode) -{ - int i; - - if (mode == 0) { - tty3270_erase_line(tp, 0); - for (i = tp->cy + 1; i < tp->view.rows - 2; i++) { - tp->screen[i].len = 0; - tty3270_convert_line(tp, i); - } - } else if (mode == 1) { - for (i = 0; i < tp->cy; i++) { - tp->screen[i].len = 0; - tty3270_convert_line(tp, i); - } - tty3270_erase_line(tp, 1); - } else if (mode == 2) { - for (i = 0; i < tp->view.rows - 2; i++) { - tp->screen[i].len = 0; - tty3270_convert_line(tp, i); - } - } - tty3270_rebuild_update(tp); -} - -/* - * Set attributes found in an escape sequence. - * Esc [ <attr> ; <attr> ; ... m - */ -static void -tty3270_set_attributes(struct tty3270 *tp) -{ - static unsigned char f_colors[] = { - TAC_DEFAULT, TAC_RED, TAC_GREEN, TAC_YELLOW, TAC_BLUE, - TAC_PINK, TAC_TURQ, TAC_WHITE, 0, TAC_DEFAULT - }; - int i, attr; - - for (i = 0; i <= tp->esc_npar; i++) { - attr = tp->esc_par[i]; - switch (attr) { - case 0: /* Reset */ - tp->highlight = TAX_RESET; - tp->f_color = TAC_RESET; - break; - /* Highlight. */ - case 4: /* Start underlining. */ - tp->highlight = TAX_UNDER; - break; - case 5: /* Start blink. */ - tp->highlight = TAX_BLINK; - break; - case 7: /* Start reverse. */ - tp->highlight = TAX_REVER; - break; - case 24: /* End underlining */ - if (tp->highlight == TAX_UNDER) - tp->highlight = TAX_RESET; - break; - case 25: /* End blink. */ - if (tp->highlight == TAX_BLINK) - tp->highlight = TAX_RESET; - break; - case 27: /* End reverse. */ - if (tp->highlight == TAX_REVER) - tp->highlight = TAX_RESET; - break; - /* Foreground color. */ - case 30: /* Black */ - case 31: /* Red */ - case 32: /* Green */ - case 33: /* Yellow */ - case 34: /* Blue */ - case 35: /* Magenta */ - case 36: /* Cyan */ - case 37: /* White */ - case 39: /* Black */ - tp->f_color = f_colors[attr - 30]; - break; - } - } -} - -static inline int -tty3270_getpar(struct tty3270 *tp, int ix) -{ - return (tp->esc_par[ix] > 0) ? tp->esc_par[ix] : 1; -} - -static void -tty3270_goto_xy(struct tty3270 *tp, int cx, int cy) -{ - int max_cx = max(0, cx); - int max_cy = max(0, cy); - - tp->cx = min_t(int, tp->view.cols - 1, max_cx); - cy = min_t(int, tp->view.rows - 3, max_cy); - if (cy != tp->cy) { - tty3270_convert_line(tp, tp->cy); - tp->cy = cy; - } -} - -/* - * Process escape sequences. Known sequences: - * Esc 7 Save Cursor Position - * Esc 8 Restore Cursor Position - * Esc [ Pn ; Pn ; .. m Set attributes - * Esc [ Pn ; Pn H Cursor Position - * Esc [ Pn ; Pn f Cursor Position - * Esc [ Pn A Cursor Up - * Esc [ Pn B Cursor Down - * Esc [ Pn C Cursor Forward - * Esc [ Pn D Cursor Backward - * Esc [ Pn G Cursor Horizontal Absolute - * Esc [ Pn X Erase Characters - * Esc [ Ps J Erase in Display - * Esc [ Ps K Erase in Line - * // FIXME: add all the new ones. - * - * Pn is a numeric parameter, a string of zero or more decimal digits. - * Ps is a selective parameter. - */ -static void -tty3270_escape_sequence(struct tty3270 *tp, char ch) -{ - enum { ESnormal, ESesc, ESsquare, ESgetpars }; - - if (tp->esc_state == ESnormal) { - if (ch == 0x1b) - /* Starting new escape sequence. */ - tp->esc_state = ESesc; - return; - } - if (tp->esc_state == ESesc) { - tp->esc_state = ESnormal; - switch (ch) { - case '[': - tp->esc_state = ESsquare; - break; - case 'E': - tty3270_cr(tp); - tty3270_lf(tp); - break; - case 'M': - tty3270_ri(tp); - break; - case 'D': - tty3270_lf(tp); - break; - case 'Z': /* Respond ID. */ - kbd_puts_queue(&tp->port, "\033[?6c"); - break; - case '7': /* Save cursor position. */ - tp->saved_cx = tp->cx; - tp->saved_cy = tp->cy; - tp->saved_highlight = tp->highlight; - tp->saved_f_color = tp->f_color; - break; - case '8': /* Restore cursor position. */ - tty3270_convert_line(tp, tp->cy); - tty3270_goto_xy(tp, tp->saved_cx, tp->saved_cy); - tp->highlight = tp->saved_highlight; - tp->f_color = tp->saved_f_color; - break; - case 'c': /* Reset terminal. */ - tp->cx = tp->saved_cx = 0; - tp->cy = tp->saved_cy = 0; - tp->highlight = tp->saved_highlight = TAX_RESET; - tp->f_color = tp->saved_f_color = TAC_RESET; - tty3270_erase_display(tp, 2); - break; - } - return; - } - if (tp->esc_state == ESsquare) { - tp->esc_state = ESgetpars; - memset(tp->esc_par, 0, sizeof(tp->esc_par)); - tp->esc_npar = 0; - tp->esc_ques = (ch == '?'); - if (tp->esc_ques) - return; - } - if (tp->esc_state == ESgetpars) { - if (ch == ';' && tp->esc_npar < ESCAPE_NPAR - 1) { - tp->esc_npar++; - return; - } - if (ch >= '0' && ch <= '9') { - tp->esc_par[tp->esc_npar] *= 10; - tp->esc_par[tp->esc_npar] += ch - '0'; - return; - } - } - tp->esc_state = ESnormal; - if (ch == 'n' && !tp->esc_ques) { - if (tp->esc_par[0] == 5) /* Status report. */ - kbd_puts_queue(&tp->port, "\033[0n"); - else if (tp->esc_par[0] == 6) { /* Cursor report. */ - char buf[40]; - sprintf(buf, "\033[%d;%dR", tp->cy + 1, tp->cx + 1); - kbd_puts_queue(&tp->port, buf); - } - return; - } - if (tp->esc_ques) - return; - switch (ch) { - case 'm': - tty3270_set_attributes(tp); - break; - case 'H': /* Set cursor position. */ - case 'f': - tty3270_goto_xy(tp, tty3270_getpar(tp, 1) - 1, - tty3270_getpar(tp, 0) - 1); - break; - case 'd': /* Set y position. */ - tty3270_goto_xy(tp, tp->cx, tty3270_getpar(tp, 0) - 1); - break; - case 'A': /* Cursor up. */ - case 'F': - tty3270_goto_xy(tp, tp->cx, tp->cy - tty3270_getpar(tp, 0)); - break; - case 'B': /* Cursor down. */ - case 'e': - case 'E': - tty3270_goto_xy(tp, tp->cx, tp->cy + tty3270_getpar(tp, 0)); - break; - case 'C': /* Cursor forward. */ - case 'a': - tty3270_goto_xy(tp, tp->cx + tty3270_getpar(tp, 0), tp->cy); - break; - case 'D': /* Cursor backward. */ - tty3270_goto_xy(tp, tp->cx - tty3270_getpar(tp, 0), tp->cy); - break; - case 'G': /* Set x position. */ - case '`': - tty3270_goto_xy(tp, tty3270_getpar(tp, 0), tp->cy); - break; - case 'X': /* Erase Characters. */ - tty3270_erase_characters(tp, tty3270_getpar(tp, 0)); - break; - case 'J': /* Erase display. */ - tty3270_erase_display(tp, tp->esc_par[0]); - break; - case 'K': /* Erase line. */ - tty3270_erase_line(tp, tp->esc_par[0]); - break; - case 'P': /* Delete characters. */ - tty3270_delete_characters(tp, tty3270_getpar(tp, 0)); - break; - case '@': /* Insert characters. */ - tty3270_insert_characters(tp, tty3270_getpar(tp, 0)); - break; - case 's': /* Save cursor position. */ - tp->saved_cx = tp->cx; - tp->saved_cy = tp->cy; - tp->saved_highlight = tp->highlight; - tp->saved_f_color = tp->f_color; - break; - case 'u': /* Restore cursor position. */ - tty3270_convert_line(tp, tp->cy); - tty3270_goto_xy(tp, tp->saved_cx, tp->saved_cy); - tp->highlight = tp->saved_highlight; - tp->f_color = tp->saved_f_color; - break; - } -} - -/* - * String write routine for 3270 ttys - */ -static void -tty3270_do_write(struct tty3270 *tp, struct tty_struct *tty, - const unsigned char *buf, int count) -{ - int i_msg, i; - - spin_lock_bh(&tp->view.lock); - for (i_msg = 0; !tty->flow.stopped && i_msg < count; i_msg++) { - if (tp->esc_state != 0) { - /* Continue escape sequence. */ - tty3270_escape_sequence(tp, buf[i_msg]); - continue; - } - - switch (buf[i_msg]) { - case 0x07: /* '\a' -- Alarm */ - tp->wcc |= TW_PLUSALARM; - break; - case 0x08: /* Backspace. */ - if (tp->cx > 0) { - tp->cx--; - tty3270_put_character(tp, ' '); - } - break; - case 0x09: /* '\t' -- Tabulate */ - for (i = tp->cx % 8; i < 8; i++) { - if (tp->cx >= tp->view.cols) { - tty3270_cr(tp); - tty3270_lf(tp); - break; - } - tty3270_put_character(tp, ' '); - tp->cx++; - } - break; - case 0x0a: /* '\n' -- New Line */ - tty3270_cr(tp); - tty3270_lf(tp); - break; - case 0x0c: /* '\f' -- Form Feed */ - tty3270_erase_display(tp, 2); - tp->cx = tp->cy = 0; - break; - case 0x0d: /* '\r' -- Carriage Return */ - tp->cx = 0; - break; - case 0x0f: /* SuSE "exit alternate mode" */ - break; - case 0x1b: /* Start escape sequence. */ - tty3270_escape_sequence(tp, buf[i_msg]); - break; - default: /* Insert normal character. */ - if (tp->cx >= tp->view.cols) { - tty3270_cr(tp); - tty3270_lf(tp); - } - tty3270_put_character(tp, buf[i_msg]); - tp->cx++; - break; - } - } - /* Convert current line to 3270 data fragment. */ - tty3270_convert_line(tp, tp->cy); - - /* Setup timer to update display after 1/10 second */ - if (!timer_pending(&tp->timer)) - tty3270_set_timer(tp, HZ/10); - - spin_unlock_bh(&tp->view.lock); -} - -/* - * String write routine for 3270 ttys - */ -static int -tty3270_write(struct tty_struct * tty, - const unsigned char *buf, int count) -{ - struct tty3270 *tp; - - tp = tty->driver_data; - if (!tp) - return 0; - if (tp->char_count > 0) { - tty3270_do_write(tp, tty, tp->char_buf, tp->char_count); - tp->char_count = 0; - } - tty3270_do_write(tp, tty, buf, count); - return count; -} - -/* - * Put single characters to the ttys character buffer - */ -static int tty3270_put_char(struct tty_struct *tty, unsigned char ch) -{ - struct tty3270 *tp; - - tp = tty->driver_data; - if (!tp || tp->char_count >= TTY3270_CHAR_BUF_SIZE) - return 0; - tp->char_buf[tp->char_count++] = ch; - return 1; -} - -/* - * Flush all characters from the ttys characeter buffer put there - * by tty3270_put_char. - */ -static void -tty3270_flush_chars(struct tty_struct *tty) -{ - struct tty3270 *tp; - - tp = tty->driver_data; - if (!tp) - return; - if (tp->char_count > 0) { - tty3270_do_write(tp, tty, tp->char_buf, tp->char_count); - tp->char_count = 0; - } -} - -/* - * Check for visible/invisible input switches - */ -static void -tty3270_set_termios(struct tty_struct *tty, const struct ktermios *old) -{ - struct tty3270 *tp; - int new; - - tp = tty->driver_data; - if (!tp) - return; - spin_lock_bh(&tp->view.lock); - if (L_ICANON(tty)) { - new = L_ECHO(tty) ? TF_INPUT: TF_INPUTN; - if (new != tp->inattr) { - tp->inattr = new; - tty3270_update_prompt(tp, NULL, 0); - tty3270_set_timer(tp, 1); - } - } - spin_unlock_bh(&tp->view.lock); -} - -/* - * Disable reading from a 3270 tty - */ -static void -tty3270_throttle(struct tty_struct * tty) -{ - struct tty3270 *tp; - - tp = tty->driver_data; - if (!tp) - return; - tp->throttle = 1; -} - -/* - * Enable reading from a 3270 tty - */ -static void -tty3270_unthrottle(struct tty_struct * tty) -{ - struct tty3270 *tp; - - tp = tty->driver_data; - if (!tp) - return; - tp->throttle = 0; - if (tp->attn) - tty3270_issue_read(tp, 1); -} - -/* - * Hang up the tty device. - */ -static void -tty3270_hangup(struct tty_struct *tty) -{ - struct tty3270 *tp; - - tp = tty->driver_data; - if (!tp) - return; - spin_lock_bh(&tp->view.lock); - tp->cx = tp->saved_cx = 0; - tp->cy = tp->saved_cy = 0; - tp->highlight = tp->saved_highlight = TAX_RESET; - tp->f_color = tp->saved_f_color = TAC_RESET; - tty3270_blank_screen(tp); - while (tp->nr_lines < tp->view.rows - 2) - tty3270_blank_line(tp); - tp->update_flags = TTY_UPDATE_ALL; - spin_unlock_bh(&tp->view.lock); - tty3270_set_timer(tp, 1); -} - -static void -tty3270_wait_until_sent(struct tty_struct *tty, int timeout) -{ -} - -static int tty3270_ioctl(struct tty_struct *tty, unsigned int cmd, - unsigned long arg) -{ - struct tty3270 *tp; - - tp = tty->driver_data; - if (!tp) - return -ENODEV; - if (tty_io_error(tty)) - return -EIO; - return kbd_ioctl(tp->kbd, cmd, arg); -} - -#ifdef CONFIG_COMPAT -static long tty3270_compat_ioctl(struct tty_struct *tty, - unsigned int cmd, unsigned long arg) -{ - struct tty3270 *tp; - - tp = tty->driver_data; - if (!tp) - return -ENODEV; - if (tty_io_error(tty)) - return -EIO; - return kbd_ioctl(tp->kbd, cmd, (unsigned long)compat_ptr(arg)); -} -#endif - -static const struct tty_operations tty3270_ops = { - .install = tty3270_install, - .cleanup = tty3270_cleanup, - .open = tty3270_open, - .close = tty3270_close, - .write = tty3270_write, - .put_char = tty3270_put_char, - .flush_chars = tty3270_flush_chars, - .write_room = tty3270_write_room, - .throttle = tty3270_throttle, - .unthrottle = tty3270_unthrottle, - .hangup = tty3270_hangup, - .wait_until_sent = tty3270_wait_until_sent, - .ioctl = tty3270_ioctl, -#ifdef CONFIG_COMPAT - .compat_ioctl = tty3270_compat_ioctl, -#endif - .set_termios = tty3270_set_termios -}; - -static void tty3270_create_cb(int minor) -{ - tty_register_device(tty3270_driver, minor - RAW3270_FIRSTMINOR, NULL); -} - -static void tty3270_destroy_cb(int minor) -{ - tty_unregister_device(tty3270_driver, minor - RAW3270_FIRSTMINOR); -} - -static struct raw3270_notifier tty3270_notifier = -{ - .create = tty3270_create_cb, - .destroy = tty3270_destroy_cb, -}; - -/* - * 3270 tty registration code called from tty_init(). - * Most kernel services (incl. kmalloc) are available at this poimt. - */ -static int __init tty3270_init(void) -{ - struct tty_driver *driver; - int ret; - - driver = tty_alloc_driver(RAW3270_MAXDEVS, - TTY_DRIVER_REAL_RAW | - TTY_DRIVER_DYNAMIC_DEV | - TTY_DRIVER_RESET_TERMIOS); - if (IS_ERR(driver)) - return PTR_ERR(driver); - - /* - * Initialize the tty_driver structure - * Entries in tty3270_driver that are NOT initialized: - * proc_entry, set_termios, flush_buffer, set_ldisc, write_proc - */ - driver->driver_name = "tty3270"; - driver->name = "3270/tty"; - driver->major = IBM_TTY3270_MAJOR; - driver->minor_start = RAW3270_FIRSTMINOR; - driver->name_base = RAW3270_FIRSTMINOR; - driver->type = TTY_DRIVER_TYPE_SYSTEM; - driver->subtype = SYSTEM_TYPE_TTY; - driver->init_termios = tty_std_termios; - tty_set_operations(driver, &tty3270_ops); - ret = tty_register_driver(driver); - if (ret) { - tty_driver_kref_put(driver); - return ret; - } - tty3270_driver = driver; - raw3270_register_notifier(&tty3270_notifier); - return 0; -} - -static void __exit -tty3270_exit(void) -{ - struct tty_driver *driver; - - raw3270_unregister_notifier(&tty3270_notifier); - driver = tty3270_driver; - tty3270_driver = NULL; - tty_unregister_driver(driver); - tty_driver_kref_put(driver); - tty3270_del_views(); -} - -MODULE_LICENSE("GPL"); -MODULE_ALIAS_CHARDEV_MAJOR(IBM_TTY3270_MAJOR); - -module_init(tty3270_init); -module_exit(tty3270_exit); diff --git a/drivers/s390/char/tty3270.h b/drivers/s390/char/tty3270.h deleted file mode 100644 index 52ceed6f8408..000000000000 --- a/drivers/s390/char/tty3270.h +++ /dev/null @@ -1,15 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Copyright IBM Corp. 2007 - * - */ - -#ifndef __DRIVERS_S390_CHAR_TTY3270_H -#define __DRIVERS_S390_CHAR_TTY3270_H - -#include <linux/tty.h> -#include <linux/tty_driver.h> - -extern struct tty_driver *tty3270_driver; - -#endif /* __DRIVERS_S390_CHAR_TTY3270_H */ diff --git a/drivers/s390/cio/css.c b/drivers/s390/cio/css.c index c7db95398500..dfbb998db86f 100644 --- a/drivers/s390/cio/css.c +++ b/drivers/s390/cio/css.c @@ -740,12 +740,21 @@ void css_schedule_eval_all(void) spin_unlock_irqrestore(&slow_subchannel_lock, flags); } -static int __unset_registered(struct device *dev, void *data) +static int __unset_validpath(struct device *dev, void *data) { struct idset *set = data; struct subchannel *sch = to_subchannel(dev); + struct pmcw *pmcw = &sch->schib.pmcw; + + /* Here we want to make sure that we are considering only those subchannels + * which do not have an operational device attached to it. This can be found + * with the help of PAM and POM values of pmcw. OPM provides the information + * about any path which is currently vary-off, so that we should not consider. + */ + if (sch->st == SUBCHANNEL_TYPE_IO && + (sch->opm & pmcw->pam & pmcw->pom)) + idset_sch_del(set, sch->schid); - idset_sch_del(set, sch->schid); return 0; } @@ -774,8 +783,8 @@ void css_schedule_eval_cond(enum css_eval_cond cond, unsigned long delay) } idset_fill(set); switch (cond) { - case CSS_EVAL_UNREG: - bus_for_each_dev(&css_bus_type, NULL, set, __unset_registered); + case CSS_EVAL_NO_PATH: + bus_for_each_dev(&css_bus_type, NULL, set, __unset_validpath); break; case CSS_EVAL_NOT_ONLINE: bus_for_each_dev(&css_bus_type, NULL, set, __unset_online); @@ -798,11 +807,11 @@ void css_wait_for_slow_path(void) flush_workqueue(cio_work_q); } -/* Schedule reprobing of all unregistered subchannels. */ +/* Schedule reprobing of all subchannels with no valid operational path. */ void css_schedule_reprobe(void) { /* Schedule with a delay to allow merging of subsequent calls. */ - css_schedule_eval_cond(CSS_EVAL_UNREG, 1 * HZ); + css_schedule_eval_cond(CSS_EVAL_NO_PATH, 1 * HZ); } EXPORT_SYMBOL_GPL(css_schedule_reprobe); diff --git a/drivers/s390/cio/css.h b/drivers/s390/cio/css.h index ede0b905bc6f..ea5550554297 100644 --- a/drivers/s390/cio/css.h +++ b/drivers/s390/cio/css.h @@ -38,7 +38,7 @@ * Conditions used to specify which subchannels need evaluation */ enum css_eval_cond { - CSS_EVAL_UNREG, /* unregistered subchannels */ + CSS_EVAL_NO_PATH, /* Subchannels with no operational paths */ CSS_EVAL_NOT_ONLINE /* sch without an online-device */ }; diff --git a/drivers/s390/cio/device.c b/drivers/s390/cio/device.c index 9e0cf44ff9d4..5418e60dbfc3 100644 --- a/drivers/s390/cio/device.c +++ b/drivers/s390/cio/device.c @@ -244,10 +244,13 @@ int ccw_device_is_orphan(struct ccw_device *cdev) static void ccw_device_unregister(struct ccw_device *cdev) { + mutex_lock(&cdev->reg_mutex); if (device_is_registered(&cdev->dev)) { /* Undo device_add(). */ device_del(&cdev->dev); } + mutex_unlock(&cdev->reg_mutex); + if (cdev->private->flags.initialized) { cdev->private->flags.initialized = 0; /* Release reference from device_initialize(). */ @@ -653,11 +656,13 @@ static void ccw_device_do_unbind_bind(struct ccw_device *cdev) { int ret; + mutex_lock(&cdev->reg_mutex); if (device_is_registered(&cdev->dev)) { device_release_driver(&cdev->dev); ret = device_attach(&cdev->dev); WARN_ON(ret == -ENODEV); } + mutex_unlock(&cdev->reg_mutex); } static void @@ -740,6 +745,7 @@ static int io_subchannel_initialize_dev(struct subchannel *sch, INIT_LIST_HEAD(&priv->cmb_list); init_waitqueue_head(&priv->wait_q); timer_setup(&priv->timer, ccw_device_timeout, 0); + mutex_init(&cdev->reg_mutex); atomic_set(&priv->onoff, 0); cdev->ccwlock = sch->lock; @@ -825,6 +831,7 @@ static void io_subchannel_register(struct ccw_device *cdev) * be registered). We need to reprobe since we may now have sense id * information. */ + mutex_lock(&cdev->reg_mutex); if (device_is_registered(&cdev->dev)) { if (!cdev->drv) { ret = device_reprobe(&cdev->dev); @@ -847,12 +854,14 @@ static void io_subchannel_register(struct ccw_device *cdev) spin_lock_irqsave(sch->lock, flags); sch_set_cdev(sch, NULL); spin_unlock_irqrestore(sch->lock, flags); + mutex_unlock(&cdev->reg_mutex); /* Release initial device reference. */ put_device(&cdev->dev); goto out_err; } out: cdev->private->flags.recog_done = 1; + mutex_unlock(&cdev->reg_mutex); wake_up(&cdev->private->wait_q); out_err: if (adjust_init_count && atomic_dec_and_test(&ccw_device_init_count)) diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c index c0a09fa8991a..1c31e81ca8de 100644 --- a/drivers/s390/cio/vfio_ccw_cp.c +++ b/drivers/s390/cio/vfio_ccw_cp.c @@ -42,8 +42,7 @@ struct ccwchain { /* * page_array_alloc() - alloc memory for page array * @pa: page_array on which to perform the operation - * @iova: target guest physical address - * @len: number of bytes that should be pinned from @iova + * @len: number of pages that should be pinned from @iova * * Attempt to allocate memory for page array. * @@ -56,31 +55,24 @@ struct ccwchain { * -EINVAL if pa->pa_nr is not initially zero, or pa->pa_iova is not NULL * -ENOMEM if alloc failed */ -static int page_array_alloc(struct page_array *pa, u64 iova, unsigned int len) +static int page_array_alloc(struct page_array *pa, unsigned int len) { - int i; - if (pa->pa_nr || pa->pa_iova) return -EINVAL; - pa->pa_nr = ((iova & ~PAGE_MASK) + len + (PAGE_SIZE - 1)) >> PAGE_SHIFT; - if (!pa->pa_nr) + if (len == 0) return -EINVAL; - pa->pa_iova = kcalloc(pa->pa_nr, - sizeof(*pa->pa_iova) + sizeof(*pa->pa_page), - GFP_KERNEL); - if (unlikely(!pa->pa_iova)) { - pa->pa_nr = 0; + pa->pa_nr = len; + + pa->pa_iova = kcalloc(len, sizeof(*pa->pa_iova), GFP_KERNEL); + if (!pa->pa_iova) return -ENOMEM; - } - pa->pa_page = (struct page **)&pa->pa_iova[pa->pa_nr]; - pa->pa_iova[0] = iova; - pa->pa_page[0] = NULL; - for (i = 1; i < pa->pa_nr; i++) { - pa->pa_iova[i] = pa->pa_iova[i - 1] + PAGE_SIZE; - pa->pa_page[i] = NULL; + pa->pa_page = kcalloc(len, sizeof(*pa->pa_page), GFP_KERNEL); + if (!pa->pa_page) { + kfree(pa->pa_iova); + return -ENOMEM; } return 0; @@ -91,12 +83,13 @@ static int page_array_alloc(struct page_array *pa, u64 iova, unsigned int len) * @pa: page_array on which to perform the operation * @vdev: the vfio device to perform the operation * @pa_nr: number of user pages to unpin + * @unaligned: were pages unaligned on the pin request * * Only unpin if any pages were pinned to begin with, i.e. pa_nr > 0, * otherwise only clear pa->pa_nr */ static void page_array_unpin(struct page_array *pa, - struct vfio_device *vdev, int pa_nr) + struct vfio_device *vdev, int pa_nr, bool unaligned) { int unpinned = 0, npage = 1; @@ -105,7 +98,8 @@ static void page_array_unpin(struct page_array *pa, dma_addr_t *last = &first[npage]; if (unpinned + npage < pa_nr && - *first + npage * PAGE_SIZE == *last) { + *first + npage * PAGE_SIZE == *last && + !unaligned) { npage++; continue; } @@ -121,13 +115,20 @@ static void page_array_unpin(struct page_array *pa, /* * page_array_pin() - Pin user pages in memory * @pa: page_array on which to perform the operation - * @mdev: the mediated device to perform pin operations + * @vdev: the vfio device to perform pin operations + * @unaligned: are pages aligned to 4K boundary? * * Returns number of pages pinned upon success. * If the pin request partially succeeds, or fails completely, * all pages are left unpinned and a negative error value is returned. + * + * Requests to pin "aligned" pages can be coalesced into a single + * vfio_pin_pages request for the sake of efficiency, based on the + * expectation of 4K page requests. Unaligned requests are probably + * dealing with 2K "pages", and cannot be coalesced without + * reworking this logic to incorporate that math. */ -static int page_array_pin(struct page_array *pa, struct vfio_device *vdev) +static int page_array_pin(struct page_array *pa, struct vfio_device *vdev, bool unaligned) { int pinned = 0, npage = 1; int ret = 0; @@ -137,7 +138,8 @@ static int page_array_pin(struct page_array *pa, struct vfio_device *vdev) dma_addr_t *last = &first[npage]; if (pinned + npage < pa->pa_nr && - *first + npage * PAGE_SIZE == *last) { + *first + npage * PAGE_SIZE == *last && + !unaligned) { npage++; continue; } @@ -159,14 +161,15 @@ static int page_array_pin(struct page_array *pa, struct vfio_device *vdev) return ret; err_out: - page_array_unpin(pa, vdev, pinned); + page_array_unpin(pa, vdev, pinned, unaligned); return ret; } /* Unpin the pages before releasing the memory. */ -static void page_array_unpin_free(struct page_array *pa, struct vfio_device *vdev) +static void page_array_unpin_free(struct page_array *pa, struct vfio_device *vdev, bool unaligned) { - page_array_unpin(pa, vdev, pa->pa_nr); + page_array_unpin(pa, vdev, pa->pa_nr, unaligned); + kfree(pa->pa_page); kfree(pa->pa_iova); } @@ -199,11 +202,12 @@ static inline void page_array_idal_create_words(struct page_array *pa, * idaw. */ - for (i = 0; i < pa->pa_nr; i++) + for (i = 0; i < pa->pa_nr; i++) { idaws[i] = page_to_phys(pa->pa_page[i]); - /* Adjust the first IDAW, since it may not start on a page boundary */ - idaws[0] += pa->pa_iova[0] & (PAGE_SIZE - 1); + /* Incorporate any offset from each starting address */ + idaws[i] += pa->pa_iova[i] & (PAGE_SIZE - 1); + } } static void convert_ccw0_to_ccw1(struct ccw1 *source, unsigned long len) @@ -228,50 +232,7 @@ static void convert_ccw0_to_ccw1(struct ccw1 *source, unsigned long len) } } -/* - * Within the domain (@mdev), copy @n bytes from a guest physical - * address (@iova) to a host physical address (@to). - */ -static long copy_from_iova(struct vfio_device *vdev, void *to, u64 iova, - unsigned long n) -{ - struct page_array pa = {0}; - int i, ret; - unsigned long l, m; - - ret = page_array_alloc(&pa, iova, n); - if (ret < 0) - return ret; - - ret = page_array_pin(&pa, vdev); - if (ret < 0) { - page_array_unpin_free(&pa, vdev); - return ret; - } - - l = n; - for (i = 0; i < pa.pa_nr; i++) { - void *from = kmap_local_page(pa.pa_page[i]); - - m = PAGE_SIZE; - if (i == 0) { - from += iova & (PAGE_SIZE - 1); - m -= iova & (PAGE_SIZE - 1); - } - - m = min(l, m); - memcpy(to + (n - l), from, m); - kunmap_local(from); - - l -= m; - if (l == 0) - break; - } - - page_array_unpin_free(&pa, vdev); - - return l; -} +#define idal_is_2k(_cp) (!(_cp)->orb.cmd.c64 || (_cp)->orb.cmd.i2k) /* * Helpers to operate ccwchain. @@ -356,40 +317,41 @@ static inline int is_tic_within_range(struct ccw1 *ccw, u32 head, int len) static struct ccwchain *ccwchain_alloc(struct channel_program *cp, int len) { struct ccwchain *chain; - void *data; - size_t size; - - /* Make ccw address aligned to 8. */ - size = ((sizeof(*chain) + 7L) & -8L) + - sizeof(*chain->ch_ccw) * len + - sizeof(*chain->ch_pa) * len; - chain = kzalloc(size, GFP_DMA | GFP_KERNEL); + + chain = kzalloc(sizeof(*chain), GFP_KERNEL); if (!chain) return NULL; - data = (u8 *)chain + ((sizeof(*chain) + 7L) & -8L); - chain->ch_ccw = (struct ccw1 *)data; + chain->ch_ccw = kcalloc(len, sizeof(*chain->ch_ccw), GFP_DMA | GFP_KERNEL); + if (!chain->ch_ccw) + goto out_err; - data = (u8 *)(chain->ch_ccw) + sizeof(*chain->ch_ccw) * len; - chain->ch_pa = (struct page_array *)data; - - chain->ch_len = len; + chain->ch_pa = kcalloc(len, sizeof(*chain->ch_pa), GFP_KERNEL); + if (!chain->ch_pa) + goto out_err; list_add_tail(&chain->next, &cp->ccwchain_list); return chain; + +out_err: + kfree(chain->ch_ccw); + kfree(chain); + return NULL; } static void ccwchain_free(struct ccwchain *chain) { list_del(&chain->next); + kfree(chain->ch_pa); + kfree(chain->ch_ccw); kfree(chain); } /* Free resource for a ccw that allocated memory for its cda. */ static void ccwchain_cda_free(struct ccwchain *chain, int idx) { - struct ccw1 *ccw = chain->ch_ccw + idx; + struct ccw1 *ccw = &chain->ch_ccw[idx]; if (ccw_is_tic(ccw)) return; @@ -419,14 +381,6 @@ static int ccwchain_calc_length(u64 iova, struct channel_program *cp) cnt++; /* - * As we don't want to fail direct addressing even if the - * orb specified one of the unsupported formats, we defer - * checking for IDAWs in unsupported formats to here. - */ - if ((!cp->orb.cmd.c64 || cp->orb.cmd.i2k) && ccw_is_idal(ccw)) - return -EOPNOTSUPP; - - /* * We want to keep counting if the current CCW has the * command-chaining flag enabled, or if it is a TIC CCW * that loops back into the current chain. The latter @@ -471,10 +425,9 @@ static int ccwchain_handle_ccw(u32 cda, struct channel_program *cp) int len, ret; /* Copy 2K (the most we support today) of possible CCWs */ - len = copy_from_iova(vdev, cp->guest_cp, cda, - CCWCHAIN_LEN_MAX * sizeof(struct ccw1)); - if (len) - return len; + ret = vfio_dma_rw(vdev, cda, cp->guest_cp, CCWCHAIN_LEN_MAX * sizeof(struct ccw1), false); + if (ret) + return ret; /* Convert any Format-0 CCWs to Format-1 */ if (!cp->orb.cmd.fmt) @@ -489,6 +442,8 @@ static int ccwchain_handle_ccw(u32 cda, struct channel_program *cp) chain = ccwchain_alloc(cp, len); if (!chain) return -ENOMEM; + + chain->ch_len = len; chain->ch_iova = cda; /* Copy the actual CCWs into the new chain */ @@ -510,7 +465,7 @@ static int ccwchain_loop_tic(struct ccwchain *chain, struct channel_program *cp) int i, ret; for (i = 0; i < chain->ch_len; i++) { - tic = chain->ch_ccw + i; + tic = &chain->ch_ccw[i]; if (!ccw_is_tic(tic)) continue; @@ -528,11 +483,9 @@ static int ccwchain_loop_tic(struct ccwchain *chain, struct channel_program *cp) return 0; } -static int ccwchain_fetch_tic(struct ccwchain *chain, - int idx, +static int ccwchain_fetch_tic(struct ccw1 *ccw, struct channel_program *cp) { - struct ccw1 *ccw = chain->ch_ccw + idx; struct ccwchain *iter; u32 ccw_head; @@ -548,43 +501,124 @@ static int ccwchain_fetch_tic(struct ccwchain *chain, return -EFAULT; } -static int ccwchain_fetch_direct(struct ccwchain *chain, - int idx, - struct channel_program *cp) +static unsigned long *get_guest_idal(struct ccw1 *ccw, + struct channel_program *cp, + int idaw_nr) { struct vfio_device *vdev = &container_of(cp, struct vfio_ccw_private, cp)->vdev; - struct ccw1 *ccw; - struct page_array *pa; - u64 iova; unsigned long *idaws; + unsigned int *idaws_f1; + int idal_len = idaw_nr * sizeof(*idaws); + int idaw_size = idal_is_2k(cp) ? PAGE_SIZE / 2 : PAGE_SIZE; + int idaw_mask = ~(idaw_size - 1); + int i, ret; + + idaws = kcalloc(idaw_nr, sizeof(*idaws), GFP_DMA | GFP_KERNEL); + if (!idaws) + return ERR_PTR(-ENOMEM); + + if (ccw_is_idal(ccw)) { + /* Copy IDAL from guest */ + ret = vfio_dma_rw(vdev, ccw->cda, idaws, idal_len, false); + if (ret) { + kfree(idaws); + return ERR_PTR(ret); + } + } else { + /* Fabricate an IDAL based off CCW data address */ + if (cp->orb.cmd.c64) { + idaws[0] = ccw->cda; + for (i = 1; i < idaw_nr; i++) + idaws[i] = (idaws[i - 1] + idaw_size) & idaw_mask; + } else { + idaws_f1 = (unsigned int *)idaws; + idaws_f1[0] = ccw->cda; + for (i = 1; i < idaw_nr; i++) + idaws_f1[i] = (idaws_f1[i - 1] + idaw_size) & idaw_mask; + } + } + + return idaws; +} + +/* + * ccw_count_idaws() - Calculate the number of IDAWs needed to transfer + * a specified amount of data + * + * @ccw: The Channel Command Word being translated + * @cp: Channel Program being processed + * + * The ORB is examined, since it specifies what IDAWs could actually be + * used by any CCW in the channel program, regardless of whether or not + * the CCW actually does. An ORB that does not specify Format-2-IDAW + * Control could still contain a CCW with an IDAL, which would be + * Format-1 and thus only move 2K with each IDAW. Thus all CCWs within + * the channel program must follow the same size requirements. + */ +static int ccw_count_idaws(struct ccw1 *ccw, + struct channel_program *cp) +{ + struct vfio_device *vdev = + &container_of(cp, struct vfio_ccw_private, cp)->vdev; + u64 iova; + int size = cp->orb.cmd.c64 ? sizeof(u64) : sizeof(u32); int ret; int bytes = 1; - int idaw_nr, idal_len; - int i; - - ccw = chain->ch_ccw + idx; if (ccw->count) bytes = ccw->count; - /* Calculate size of IDAL */ if (ccw_is_idal(ccw)) { - /* Read first IDAW to see if it's 4K-aligned or not. */ - /* All subsequent IDAws will be 4K-aligned. */ - ret = copy_from_iova(vdev, &iova, ccw->cda, sizeof(iova)); + /* Read first IDAW to check its starting address. */ + /* All subsequent IDAWs will be 2K- or 4K-aligned. */ + ret = vfio_dma_rw(vdev, ccw->cda, &iova, size, false); if (ret) return ret; + + /* + * Format-1 IDAWs only occupy the first 32 bits, + * and bit 0 is always off. + */ + if (!cp->orb.cmd.c64) + iova = iova >> 32; } else { iova = ccw->cda; } - idaw_nr = idal_nr_words((void *)iova, bytes); - idal_len = idaw_nr * sizeof(*idaws); + + /* Format-1 IDAWs operate on 2K each */ + if (!cp->orb.cmd.c64) + return idal_2k_nr_words((void *)iova, bytes); + + /* Using the 2K variant of Format-2 IDAWs? */ + if (cp->orb.cmd.i2k) + return idal_2k_nr_words((void *)iova, bytes); + + /* The 'usual' case is 4K Format-2 IDAWs */ + return idal_nr_words((void *)iova, bytes); +} + +static int ccwchain_fetch_ccw(struct ccw1 *ccw, + struct page_array *pa, + struct channel_program *cp) +{ + struct vfio_device *vdev = + &container_of(cp, struct vfio_ccw_private, cp)->vdev; + unsigned long *idaws; + unsigned int *idaws_f1; + int ret; + int idaw_nr; + int i; + + /* Calculate size of IDAL */ + idaw_nr = ccw_count_idaws(ccw, cp); + if (idaw_nr < 0) + return idaw_nr; /* Allocate an IDAL from host storage */ - idaws = kcalloc(idaw_nr, sizeof(*idaws), GFP_DMA | GFP_KERNEL); - if (!idaws) { - ret = -ENOMEM; + idaws = get_guest_idal(ccw, cp, idaw_nr); + if (IS_ERR(idaws)) { + ret = PTR_ERR(idaws); goto out_init; } @@ -594,33 +628,24 @@ static int ccwchain_fetch_direct(struct ccwchain *chain, * required for the data transfer, since we only only support * 4K IDAWs today. */ - pa = chain->ch_pa + idx; - ret = page_array_alloc(pa, iova, bytes); + ret = page_array_alloc(pa, idaw_nr); if (ret < 0) goto out_free_idaws; - if (ccw_is_idal(ccw)) { - /* Copy guest IDAL into host IDAL */ - ret = copy_from_iova(vdev, idaws, ccw->cda, idal_len); - if (ret) - goto out_unpin; - - /* - * Copy guest IDAWs into page_array, in case the memory they - * occupy is not contiguous. - */ - for (i = 0; i < idaw_nr; i++) + /* + * Copy guest IDAWs into page_array, in case the memory they + * occupy is not contiguous. + */ + idaws_f1 = (unsigned int *)idaws; + for (i = 0; i < idaw_nr; i++) { + if (cp->orb.cmd.c64) pa->pa_iova[i] = idaws[i]; - } else { - /* - * No action is required here; the iova addresses in page_array - * were initialized sequentially in page_array_alloc() beginning - * with the contents of ccw->cda. - */ + else + pa->pa_iova[i] = idaws_f1[i]; } if (ccw_does_data_transfer(ccw)) { - ret = page_array_pin(pa, vdev); + ret = page_array_pin(pa, vdev, idal_is_2k(cp)); if (ret < 0) goto out_unpin; } else { @@ -636,7 +661,7 @@ static int ccwchain_fetch_direct(struct ccwchain *chain, return 0; out_unpin: - page_array_unpin_free(pa, vdev); + page_array_unpin_free(pa, vdev, idal_is_2k(cp)); out_free_idaws: kfree(idaws); out_init: @@ -650,22 +675,20 @@ out_init: * and to get rid of the cda 2G limitiaion of ccw1, we'll translate * direct ccws to idal ccws. */ -static int ccwchain_fetch_one(struct ccwchain *chain, - int idx, +static int ccwchain_fetch_one(struct ccw1 *ccw, + struct page_array *pa, struct channel_program *cp) -{ - struct ccw1 *ccw = chain->ch_ccw + idx; +{ if (ccw_is_tic(ccw)) - return ccwchain_fetch_tic(chain, idx, cp); + return ccwchain_fetch_tic(ccw, cp); - return ccwchain_fetch_direct(chain, idx, cp); + return ccwchain_fetch_ccw(ccw, pa, cp); } /** * cp_init() - allocate ccwchains for a channel program. * @cp: channel_program on which to perform the operation - * @mdev: the mediated device to perform pin/unpin operations * @orb: control block for the channel program from the guest * * This creates one or more ccwchain(s), and copies the raw data of @@ -708,15 +731,9 @@ int cp_init(struct channel_program *cp, union orb *orb) /* Build a ccwchain for the first CCW segment */ ret = ccwchain_handle_ccw(orb->cmd.cpa, cp); - if (!ret) { + if (!ret) cp->initialized = true; - /* It is safe to force: if it was not set but idals used - * ccwchain_calc_length would have returned an error. - */ - cp->orb.cmd.c64 = 1; - } - return ret; } @@ -742,7 +759,7 @@ void cp_free(struct channel_program *cp) cp->initialized = false; list_for_each_entry_safe(chain, temp, &cp->ccwchain_list, next) { for (i = 0; i < chain->ch_len; i++) { - page_array_unpin_free(chain->ch_pa + i, vdev); + page_array_unpin_free(&chain->ch_pa[i], vdev, idal_is_2k(cp)); ccwchain_cda_free(chain, i); } ccwchain_free(chain); @@ -789,6 +806,8 @@ void cp_free(struct channel_program *cp) int cp_prefetch(struct channel_program *cp) { struct ccwchain *chain; + struct ccw1 *ccw; + struct page_array *pa; int len, idx, ret; /* this is an error in the caller */ @@ -798,7 +817,10 @@ int cp_prefetch(struct channel_program *cp) list_for_each_entry(chain, &cp->ccwchain_list, next) { len = chain->ch_len; for (idx = 0; idx < len; idx++) { - ret = ccwchain_fetch_one(chain, idx, cp); + ccw = &chain->ch_ccw[idx]; + pa = &chain->ch_pa[idx]; + + ret = ccwchain_fetch_one(ccw, pa, cp); if (ret) goto out_err; } @@ -817,14 +839,13 @@ out_err: /** * cp_get_orb() - get the orb of the channel program * @cp: channel_program on which to perform the operation - * @intparm: new intparm for the returned orb - * @lpm: candidate value of the logical-path mask for the returned orb + * @sch: subchannel the operation will be performed against * * This function returns the address of the updated orb of the channel * program. Channel I/O device drivers could use this orb to issue a * ssch. */ -union orb *cp_get_orb(struct channel_program *cp, u32 intparm, u8 lpm) +union orb *cp_get_orb(struct channel_program *cp, struct subchannel *sch) { union orb *orb; struct ccwchain *chain; @@ -836,12 +857,20 @@ union orb *cp_get_orb(struct channel_program *cp, u32 intparm, u8 lpm) orb = &cp->orb; - orb->cmd.intparm = intparm; + orb->cmd.intparm = (u32)virt_to_phys(sch); orb->cmd.fmt = 1; - orb->cmd.key = PAGE_DEFAULT_KEY >> 4; + + /* + * Everything built by vfio-ccw is a Format-2 IDAL. + * If the input was a Format-1 IDAL, indicate that + * 2K Format-2 IDAWs were created here. + */ + if (!orb->cmd.c64) + orb->cmd.i2k = 1; + orb->cmd.c64 = 1; if (orb->cmd.lpm == 0) - orb->cmd.lpm = lpm; + orb->cmd.lpm = sch->lpm; chain = list_first_entry(&cp->ccwchain_list, struct ccwchain, next); cpa = chain->ch_ccw; @@ -919,7 +948,7 @@ bool cp_iova_pinned(struct channel_program *cp, u64 iova, u64 length) list_for_each_entry(chain, &cp->ccwchain_list, next) { for (i = 0; i < chain->ch_len; i++) - if (page_array_iova_pinned(chain->ch_pa + i, iova, length)) + if (page_array_iova_pinned(&chain->ch_pa[i], iova, length)) return true; } diff --git a/drivers/s390/cio/vfio_ccw_cp.h b/drivers/s390/cio/vfio_ccw_cp.h index 54d26e242533..fc31eb699807 100644 --- a/drivers/s390/cio/vfio_ccw_cp.h +++ b/drivers/s390/cio/vfio_ccw_cp.h @@ -27,7 +27,6 @@ * struct channel_program - manage information for channel program * @ccwchain_list: list head of ccwchains * @orb: orb for the currently processed ssch request - * @mdev: the mediated device to perform page pinning/unpinning * @initialized: whether this instance is actually initialized * * @ccwchain_list is the head of a ccwchain list, that contents the @@ -44,7 +43,7 @@ struct channel_program { int cp_init(struct channel_program *cp, union orb *orb); void cp_free(struct channel_program *cp); int cp_prefetch(struct channel_program *cp); -union orb *cp_get_orb(struct channel_program *cp, u32 intparm, u8 lpm); +union orb *cp_get_orb(struct channel_program *cp, struct subchannel *sch); void cp_update_scsw(struct channel_program *cp, union scsw *scsw); bool cp_iova_pinned(struct channel_program *cp, u64 iova, u64 length); diff --git a/drivers/s390/cio/vfio_ccw_drv.c b/drivers/s390/cio/vfio_ccw_drv.c index 54aba7cceb33..ff538a086fc7 100644 --- a/drivers/s390/cio/vfio_ccw_drv.c +++ b/drivers/s390/cio/vfio_ccw_drv.c @@ -225,7 +225,7 @@ static void vfio_ccw_sch_shutdown(struct subchannel *sch) struct vfio_ccw_parent *parent = dev_get_drvdata(&sch->dev); struct vfio_ccw_private *private = dev_get_drvdata(&parent->dev); - if (WARN_ON(!private)) + if (!private) return; vfio_ccw_fsm_event(private, VFIO_CCW_EVENT_CLOSE); diff --git a/drivers/s390/cio/vfio_ccw_fsm.c b/drivers/s390/cio/vfio_ccw_fsm.c index 2784a4e4d2be..757b73141246 100644 --- a/drivers/s390/cio/vfio_ccw_fsm.c +++ b/drivers/s390/cio/vfio_ccw_fsm.c @@ -27,7 +27,7 @@ static int fsm_io_helper(struct vfio_ccw_private *private) spin_lock_irqsave(sch->lock, flags); - orb = cp_get_orb(&private->cp, (u32)virt_to_phys(sch), sch->lpm); + orb = cp_get_orb(&private->cp, sch); if (!orb) { ret = -EIO; goto out; diff --git a/drivers/s390/crypto/vfio_ap_ops.c b/drivers/s390/crypto/vfio_ap_ops.c index 9c01957e56b3..28a36e016ea9 100644 --- a/drivers/s390/crypto/vfio_ap_ops.c +++ b/drivers/s390/crypto/vfio_ap_ops.c @@ -30,10 +30,13 @@ #define AP_QUEUE_UNASSIGNED "unassigned" #define AP_QUEUE_IN_USE "in use" +#define MAX_RESET_CHECK_WAIT 200 /* Sleep max 200ms for reset check */ +#define AP_RESET_INTERVAL 20 /* Reset sleep interval (20ms) */ + static int vfio_ap_mdev_reset_queues(struct ap_queue_table *qtable); static struct vfio_ap_queue *vfio_ap_find_queue(int apqn); static const struct vfio_device_ops vfio_ap_matrix_dev_ops; -static int vfio_ap_mdev_reset_queue(struct vfio_ap_queue *q, unsigned int retry); +static int vfio_ap_mdev_reset_queue(struct vfio_ap_queue *q); /** * get_update_locks_for_kvm: Acquire the locks required to dynamically update a @@ -349,6 +352,8 @@ static int vfio_ap_validate_nib(struct kvm_vcpu *vcpu, dma_addr_t *nib) { *nib = vcpu->run->s.regs.gprs[2]; + if (!*nib) + return -EINVAL; if (kvm_is_error_hva(gfn_to_hva(vcpu->kvm, *nib >> PAGE_SHIFT))) return -EINVAL; @@ -1598,12 +1603,56 @@ static struct vfio_ap_queue *vfio_ap_find_queue(int apqn) return q; } -static int vfio_ap_mdev_reset_queue(struct vfio_ap_queue *q, - unsigned int retry) +static int apq_status_check(int apqn, struct ap_queue_status *status) +{ + switch (status->response_code) { + case AP_RESPONSE_NORMAL: + case AP_RESPONSE_RESET_IN_PROGRESS: + if (status->queue_empty && !status->irq_enabled) + return 0; + return -EBUSY; + case AP_RESPONSE_DECONFIGURED: + /* + * If the AP queue is deconfigured, any subsequent AP command + * targeting the queue will fail with the same response code. On the + * other hand, when an AP adapter is deconfigured, the associated + * queues are reset, so let's return a value indicating the reset + * for which we're waiting completed successfully. + */ + return 0; + default: + WARN(true, + "failed to verify reset of queue %02x.%04x: TAPQ rc=%u\n", + AP_QID_CARD(apqn), AP_QID_QUEUE(apqn), + status->response_code); + return -EIO; + } +} + +static int apq_reset_check(struct vfio_ap_queue *q) +{ + int ret; + int iters = MAX_RESET_CHECK_WAIT / AP_RESET_INTERVAL; + struct ap_queue_status status; + + for (; iters > 0; iters--) { + msleep(AP_RESET_INTERVAL); + status = ap_tapq(q->apqn, NULL); + ret = apq_status_check(q->apqn, &status); + if (ret != -EBUSY) + return ret; + } + WARN_ONCE(iters <= 0, + "timeout verifying reset of queue %02x.%04x (%u, %u, %u)", + AP_QID_CARD(q->apqn), AP_QID_QUEUE(q->apqn), + status.queue_empty, status.irq_enabled, status.response_code); + return ret; +} + +static int vfio_ap_mdev_reset_queue(struct vfio_ap_queue *q) { struct ap_queue_status status; int ret; - int retry2 = 2; if (!q) return 0; @@ -1613,25 +1662,29 @@ retry_zapq: switch (status.response_code) { case AP_RESPONSE_NORMAL: ret = 0; + /* if the reset has not completed, wait for it to take effect */ + if (!status.queue_empty || status.irq_enabled) + ret = apq_reset_check(q); break; case AP_RESPONSE_RESET_IN_PROGRESS: - if (retry--) { - msleep(20); - goto retry_zapq; - } - ret = -EBUSY; - break; - case AP_RESPONSE_Q_NOT_AVAIL: + /* + * There is a reset issued by another process in progress. Let's wait + * for that to complete. Since we have no idea whether it was a RAPQ or + * ZAPQ, then if it completes successfully, let's issue the ZAPQ. + */ + ret = apq_reset_check(q); + if (ret) + break; + goto retry_zapq; case AP_RESPONSE_DECONFIGURED: - case AP_RESPONSE_CHECKSTOPPED: - WARN_ONCE(status.irq_enabled, - "PQAP/ZAPQ for %02x.%04x failed with rc=%u while IRQ enabled", - AP_QID_CARD(q->apqn), AP_QID_QUEUE(q->apqn), - status.response_code); - ret = -EBUSY; - goto free_resources; + /* + * When an AP adapter is deconfigured, the associated + * queues are reset, so let's return a value indicating the reset + * completed successfully. + */ + ret = 0; + break; default: - /* things are really broken, give up */ WARN(true, "PQAP/ZAPQ for %02x.%04x failed with invalid rc=%u\n", AP_QID_CARD(q->apqn), AP_QID_QUEUE(q->apqn), @@ -1639,17 +1692,6 @@ retry_zapq: return -EIO; } - /* wait for the reset to take effect */ - while (retry2--) { - if (status.queue_empty && !status.irq_enabled) - break; - msleep(20); - status = ap_tapq(q->apqn, NULL); - } - WARN_ONCE(retry2 <= 0, "unable to verify reset of queue %02x.%04x", - AP_QID_CARD(q->apqn), AP_QID_QUEUE(q->apqn)); - -free_resources: vfio_ap_free_aqic_resources(q); return ret; @@ -1661,7 +1703,7 @@ static int vfio_ap_mdev_reset_queues(struct ap_queue_table *qtable) struct vfio_ap_queue *q; hash_for_each(qtable->queues, loop_cursor, q, mdev_qnode) { - ret = vfio_ap_mdev_reset_queue(q, 1); + ret = vfio_ap_mdev_reset_queue(q); /* * Regardless whether a queue turns out to be busy, or * is not operational, we need to continue resetting @@ -1857,8 +1899,10 @@ int vfio_ap_mdev_probe_queue(struct ap_device *apdev) return ret; q = kzalloc(sizeof(*q), GFP_KERNEL); - if (!q) - return -ENOMEM; + if (!q) { + ret = -ENOMEM; + goto err_remove_group; + } q->apqn = to_ap_queue(&apdev->device)->qid; q->saved_isc = VFIO_AP_ISC_INVALID; @@ -1876,6 +1920,10 @@ int vfio_ap_mdev_probe_queue(struct ap_device *apdev) release_update_locks_for_mdev(matrix_mdev); return 0; + +err_remove_group: + sysfs_remove_group(&apdev->device.kobj, &vfio_queue_attr_group); + return ret; } void vfio_ap_mdev_remove_queue(struct ap_device *apdev) @@ -1906,7 +1954,7 @@ void vfio_ap_mdev_remove_queue(struct ap_device *apdev) } } - vfio_ap_mdev_reset_queue(q, 1); + vfio_ap_mdev_reset_queue(q); dev_set_drvdata(&apdev->device, NULL); kfree(q); release_update_locks_for_mdev(matrix_mdev); diff --git a/drivers/s390/crypto/zcrypt_api.c b/drivers/s390/crypto/zcrypt_api.c index 4bf36e53fe3e..6fe05bb82c77 100644 --- a/drivers/s390/crypto/zcrypt_api.c +++ b/drivers/s390/crypto/zcrypt_api.c @@ -347,8 +347,7 @@ static ssize_t zcdn_create_store(struct class *class, int rc; char name[ZCDN_MAX_NAME]; - strncpy(name, skip_spaces(buf), sizeof(name)); - name[sizeof(name) - 1] = '\0'; + strscpy(name, skip_spaces(buf), sizeof(name)); rc = zcdn_create(strim(name)); @@ -365,8 +364,7 @@ static ssize_t zcdn_destroy_store(struct class *class, int rc; char name[ZCDN_MAX_NAME]; - strncpy(name, skip_spaces(buf), sizeof(name)); - name[sizeof(name) - 1] = '\0'; + strscpy(name, skip_spaces(buf), sizeof(name)); rc = zcdn_destroy(strim(name)); diff --git a/drivers/watchdog/diag288_wdt.c b/drivers/watchdog/diag288_wdt.c index 6ca5d9515d85..4631d0a3866a 100644 --- a/drivers/watchdog/diag288_wdt.c +++ b/drivers/watchdog/diag288_wdt.c @@ -27,7 +27,6 @@ #include <linux/moduleparam.h> #include <linux/slab.h> #include <linux/watchdog.h> -#include <linux/suspend.h> #include <asm/ebcdic.h> #include <asm/diag.h> #include <linux/io.h> @@ -70,77 +69,58 @@ MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default = C MODULE_ALIAS("vmwatchdog"); -static int __diag288(unsigned int func, unsigned int timeout, - unsigned long action, unsigned int len) +static char *cmd_buf; + +static int diag288(unsigned int func, unsigned int timeout, + unsigned long action, unsigned int len) { - register unsigned long __func asm("2") = func; - register unsigned long __timeout asm("3") = timeout; - register unsigned long __action asm("4") = action; - register unsigned long __len asm("5") = len; + union register_pair r1 = { .even = func, .odd = timeout, }; + union register_pair r3 = { .even = action, .odd = len, }; int err; + diag_stat_inc(DIAG_STAT_X288); + err = -EINVAL; asm volatile( - " diag %1, %3, 0x288\n" - "0: la %0, 0\n" + " diag %[r1],%[r3],0x288\n" + "0: la %[err],0\n" "1:\n" EX_TABLE(0b, 1b) - : "+d" (err) : "d"(__func), "d"(__timeout), - "d"(__action), "d"(__len) : "1", "cc", "memory"); + : [err] "+d" (err) + : [r1] "d" (r1.pair), [r3] "d" (r3.pair) + : "cc", "memory"); return err; } -static int __diag288_vm(unsigned int func, unsigned int timeout, - char *cmd, size_t len) +static int diag288_str(unsigned int func, unsigned int timeout, char *cmd) { - diag_stat_inc(DIAG_STAT_X288); - return __diag288(func, timeout, virt_to_phys(cmd), len); -} + ssize_t len; -static int __diag288_lpar(unsigned int func, unsigned int timeout, - unsigned long action) -{ - diag_stat_inc(DIAG_STAT_X288); - return __diag288(func, timeout, action, 0); -} - -static unsigned long wdt_status; + len = strscpy(cmd_buf, cmd, MAX_CMDLEN); + if (len < 0) + return len; + ASCEBC(cmd_buf, MAX_CMDLEN); + EBC_TOUPPER(cmd_buf, MAX_CMDLEN); -#define DIAG_WDOG_BUSY 0 + return diag288(func, timeout, virt_to_phys(cmd_buf), len); +} static int wdt_start(struct watchdog_device *dev) { - char *ebc_cmd; - size_t len; int ret; unsigned int func; - if (test_and_set_bit(DIAG_WDOG_BUSY, &wdt_status)) - return -EBUSY; - if (MACHINE_IS_VM) { - ebc_cmd = kmalloc(MAX_CMDLEN, GFP_KERNEL); - if (!ebc_cmd) { - clear_bit(DIAG_WDOG_BUSY, &wdt_status); - return -ENOMEM; - } - len = strlcpy(ebc_cmd, wdt_cmd, MAX_CMDLEN); - ASCEBC(ebc_cmd, MAX_CMDLEN); - EBC_TOUPPER(ebc_cmd, MAX_CMDLEN); - func = conceal_on ? (WDT_FUNC_INIT | WDT_FUNC_CONCEAL) : WDT_FUNC_INIT; - ret = __diag288_vm(func, dev->timeout, ebc_cmd, len); + ret = diag288_str(func, dev->timeout, wdt_cmd); WARN_ON(ret != 0); - kfree(ebc_cmd); } else { - ret = __diag288_lpar(WDT_FUNC_INIT, - dev->timeout, LPARWDT_RESTART); + ret = diag288(WDT_FUNC_INIT, dev->timeout, LPARWDT_RESTART, 0); } if (ret) { pr_err("The watchdog cannot be activated\n"); - clear_bit(DIAG_WDOG_BUSY, &wdt_status); return ret; } return 0; @@ -148,31 +128,15 @@ static int wdt_start(struct watchdog_device *dev) static int wdt_stop(struct watchdog_device *dev) { - int ret; - - diag_stat_inc(DIAG_STAT_X288); - ret = __diag288(WDT_FUNC_CANCEL, 0, 0, 0); - - clear_bit(DIAG_WDOG_BUSY, &wdt_status); - - return ret; + return diag288(WDT_FUNC_CANCEL, 0, 0, 0); } static int wdt_ping(struct watchdog_device *dev) { - char *ebc_cmd; - size_t len; int ret; unsigned int func; if (MACHINE_IS_VM) { - ebc_cmd = kmalloc(MAX_CMDLEN, GFP_KERNEL); - if (!ebc_cmd) - return -ENOMEM; - len = strlcpy(ebc_cmd, wdt_cmd, MAX_CMDLEN); - ASCEBC(ebc_cmd, MAX_CMDLEN); - EBC_TOUPPER(ebc_cmd, MAX_CMDLEN); - /* * It seems to be ok to z/VM to use the init function to * retrigger the watchdog. On LPAR WDT_FUNC_CHANGE must @@ -181,11 +145,10 @@ static int wdt_ping(struct watchdog_device *dev) func = conceal_on ? (WDT_FUNC_INIT | WDT_FUNC_CONCEAL) : WDT_FUNC_INIT; - ret = __diag288_vm(func, dev->timeout, ebc_cmd, len); + ret = diag288_str(func, dev->timeout, wdt_cmd); WARN_ON(ret != 0); - kfree(ebc_cmd); } else { - ret = __diag288_lpar(WDT_FUNC_CHANGE, dev->timeout, 0); + ret = diag288(WDT_FUNC_CHANGE, dev->timeout, 0, 0); } if (ret) @@ -223,96 +186,45 @@ static struct watchdog_device wdt_dev = { .max_timeout = MAX_INTERVAL, }; -/* - * It makes no sense to go into suspend while the watchdog is running. - * Depending on the memory size, the watchdog might trigger, while we - * are still saving the memory. - */ -static int wdt_suspend(void) -{ - if (test_and_set_bit(DIAG_WDOG_BUSY, &wdt_status)) { - pr_err("Linux cannot be suspended while the watchdog is in use\n"); - return notifier_from_errno(-EBUSY); - } - return NOTIFY_DONE; -} - -static int wdt_resume(void) -{ - clear_bit(DIAG_WDOG_BUSY, &wdt_status); - return NOTIFY_DONE; -} - -static int wdt_power_event(struct notifier_block *this, unsigned long event, - void *ptr) -{ - switch (event) { - case PM_POST_HIBERNATION: - case PM_POST_SUSPEND: - return wdt_resume(); - case PM_HIBERNATION_PREPARE: - case PM_SUSPEND_PREPARE: - return wdt_suspend(); - default: - return NOTIFY_DONE; - } -} - -static struct notifier_block wdt_power_notifier = { - .notifier_call = wdt_power_event, -}; - static int __init diag288_init(void) { int ret; - char ebc_begin[] = { - 194, 197, 199, 201, 213 - }; - char *ebc_cmd; watchdog_set_nowayout(&wdt_dev, nowayout_info); if (MACHINE_IS_VM) { - ebc_cmd = kmalloc(sizeof(ebc_begin), GFP_KERNEL); - if (!ebc_cmd) { + cmd_buf = kmalloc(MAX_CMDLEN, GFP_KERNEL); + if (!cmd_buf) { pr_err("The watchdog cannot be initialized\n"); return -ENOMEM; } - memcpy(ebc_cmd, ebc_begin, sizeof(ebc_begin)); - ret = __diag288_vm(WDT_FUNC_INIT, 15, - ebc_cmd, sizeof(ebc_begin)); - kfree(ebc_cmd); + + ret = diag288_str(WDT_FUNC_INIT, MIN_INTERVAL, "BEGIN"); if (ret != 0) { pr_err("The watchdog cannot be initialized\n"); + kfree(cmd_buf); return -EINVAL; } } else { - if (__diag288_lpar(WDT_FUNC_INIT, 30, LPARWDT_RESTART)) { + if (diag288(WDT_FUNC_INIT, WDT_DEFAULT_TIMEOUT, + LPARWDT_RESTART, 0)) { pr_err("The watchdog cannot be initialized\n"); return -EINVAL; } } - if (__diag288_lpar(WDT_FUNC_CANCEL, 0, 0)) { + if (diag288(WDT_FUNC_CANCEL, 0, 0, 0)) { pr_err("The watchdog cannot be deactivated\n"); return -EINVAL; } - ret = register_pm_notifier(&wdt_power_notifier); - if (ret) - return ret; - - ret = watchdog_register_device(&wdt_dev); - if (ret) - unregister_pm_notifier(&wdt_power_notifier); - - return ret; + return watchdog_register_device(&wdt_dev); } static void __exit diag288_exit(void) { watchdog_unregister_device(&wdt_dev); - unregister_pm_notifier(&wdt_power_notifier); + kfree(cmd_buf); } module_init(diag288_init); |