18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-1.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * zcore module to export memory content and register sets for creating system 48c2ecf20Sopenharmony_ci * dumps on SCSI/NVMe disks (zfcp/nvme dump). 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * For more information please refer to Documentation/s390/zfcpdump.rst 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Copyright IBM Corp. 2003, 2008 98c2ecf20Sopenharmony_ci * Author(s): Michael Holzheu 108c2ecf20Sopenharmony_ci */ 118c2ecf20Sopenharmony_ci 128c2ecf20Sopenharmony_ci#define KMSG_COMPONENT "zdump" 138c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include <linux/init.h> 168c2ecf20Sopenharmony_ci#include <linux/slab.h> 178c2ecf20Sopenharmony_ci#include <linux/debugfs.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#include <asm/asm-offsets.h> 208c2ecf20Sopenharmony_ci#include <asm/ipl.h> 218c2ecf20Sopenharmony_ci#include <asm/sclp.h> 228c2ecf20Sopenharmony_ci#include <asm/setup.h> 238c2ecf20Sopenharmony_ci#include <linux/uaccess.h> 248c2ecf20Sopenharmony_ci#include <asm/debug.h> 258c2ecf20Sopenharmony_ci#include <asm/processor.h> 268c2ecf20Sopenharmony_ci#include <asm/irqflags.h> 278c2ecf20Sopenharmony_ci#include <asm/checksum.h> 288c2ecf20Sopenharmony_ci#include <asm/os_info.h> 298c2ecf20Sopenharmony_ci#include <asm/switch_to.h> 308c2ecf20Sopenharmony_ci#include "sclp.h" 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_ci#define TRACE(x...) debug_sprintf_event(zcore_dbf, 1, x) 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cienum arch_id { 358c2ecf20Sopenharmony_ci ARCH_S390 = 0, 368c2ecf20Sopenharmony_ci ARCH_S390X = 1, 378c2ecf20Sopenharmony_ci}; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistruct ipib_info { 408c2ecf20Sopenharmony_ci unsigned long ipib; 418c2ecf20Sopenharmony_ci u32 checksum; 428c2ecf20Sopenharmony_ci} __attribute__((packed)); 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistatic struct debug_info *zcore_dbf; 458c2ecf20Sopenharmony_cistatic int hsa_available; 468c2ecf20Sopenharmony_cistatic struct dentry *zcore_dir; 478c2ecf20Sopenharmony_cistatic struct dentry *zcore_reipl_file; 488c2ecf20Sopenharmony_cistatic struct dentry *zcore_hsa_file; 498c2ecf20Sopenharmony_cistatic struct ipl_parameter_block *zcore_ipl_block; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(hsa_buf_mutex); 528c2ecf20Sopenharmony_cistatic char hsa_buf[PAGE_SIZE] __aligned(PAGE_SIZE); 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci/* 558c2ecf20Sopenharmony_ci * Copy memory from HSA to user memory (not reentrant): 568c2ecf20Sopenharmony_ci * 578c2ecf20Sopenharmony_ci * @dest: User buffer where memory should be copied to 588c2ecf20Sopenharmony_ci * @src: Start address within HSA where data should be copied 598c2ecf20Sopenharmony_ci * @count: Size of buffer, which should be copied 608c2ecf20Sopenharmony_ci */ 618c2ecf20Sopenharmony_ciint memcpy_hsa_user(void __user *dest, unsigned long src, size_t count) 628c2ecf20Sopenharmony_ci{ 638c2ecf20Sopenharmony_ci unsigned long offset, bytes; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci if (!hsa_available) 668c2ecf20Sopenharmony_ci return -ENODATA; 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_ci mutex_lock(&hsa_buf_mutex); 698c2ecf20Sopenharmony_ci while (count) { 708c2ecf20Sopenharmony_ci if (sclp_sdias_copy(hsa_buf, src / PAGE_SIZE + 2, 1)) { 718c2ecf20Sopenharmony_ci TRACE("sclp_sdias_copy() failed\n"); 728c2ecf20Sopenharmony_ci mutex_unlock(&hsa_buf_mutex); 738c2ecf20Sopenharmony_ci return -EIO; 748c2ecf20Sopenharmony_ci } 758c2ecf20Sopenharmony_ci offset = src % PAGE_SIZE; 768c2ecf20Sopenharmony_ci bytes = min(PAGE_SIZE - offset, count); 778c2ecf20Sopenharmony_ci if (copy_to_user(dest, hsa_buf + offset, bytes)) { 788c2ecf20Sopenharmony_ci mutex_unlock(&hsa_buf_mutex); 798c2ecf20Sopenharmony_ci return -EFAULT; 808c2ecf20Sopenharmony_ci } 818c2ecf20Sopenharmony_ci src += bytes; 828c2ecf20Sopenharmony_ci dest += bytes; 838c2ecf20Sopenharmony_ci count -= bytes; 848c2ecf20Sopenharmony_ci } 858c2ecf20Sopenharmony_ci mutex_unlock(&hsa_buf_mutex); 868c2ecf20Sopenharmony_ci return 0; 878c2ecf20Sopenharmony_ci} 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci/* 908c2ecf20Sopenharmony_ci * Copy memory from HSA to kernel memory (not reentrant): 918c2ecf20Sopenharmony_ci * 928c2ecf20Sopenharmony_ci * @dest: Kernel or user buffer where memory should be copied to 938c2ecf20Sopenharmony_ci * @src: Start address within HSA where data should be copied 948c2ecf20Sopenharmony_ci * @count: Size of buffer, which should be copied 958c2ecf20Sopenharmony_ci */ 968c2ecf20Sopenharmony_ciint memcpy_hsa_kernel(void *dest, unsigned long src, size_t count) 978c2ecf20Sopenharmony_ci{ 988c2ecf20Sopenharmony_ci unsigned long offset, bytes; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci if (!hsa_available) 1018c2ecf20Sopenharmony_ci return -ENODATA; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci mutex_lock(&hsa_buf_mutex); 1048c2ecf20Sopenharmony_ci while (count) { 1058c2ecf20Sopenharmony_ci if (sclp_sdias_copy(hsa_buf, src / PAGE_SIZE + 2, 1)) { 1068c2ecf20Sopenharmony_ci TRACE("sclp_sdias_copy() failed\n"); 1078c2ecf20Sopenharmony_ci mutex_unlock(&hsa_buf_mutex); 1088c2ecf20Sopenharmony_ci return -EIO; 1098c2ecf20Sopenharmony_ci } 1108c2ecf20Sopenharmony_ci offset = src % PAGE_SIZE; 1118c2ecf20Sopenharmony_ci bytes = min(PAGE_SIZE - offset, count); 1128c2ecf20Sopenharmony_ci memcpy(dest, hsa_buf + offset, bytes); 1138c2ecf20Sopenharmony_ci src += bytes; 1148c2ecf20Sopenharmony_ci dest += bytes; 1158c2ecf20Sopenharmony_ci count -= bytes; 1168c2ecf20Sopenharmony_ci } 1178c2ecf20Sopenharmony_ci mutex_unlock(&hsa_buf_mutex); 1188c2ecf20Sopenharmony_ci return 0; 1198c2ecf20Sopenharmony_ci} 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_cistatic int __init init_cpu_info(void) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci struct save_area *sa; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci /* get info for boot cpu from lowcore, stored in the HSA */ 1268c2ecf20Sopenharmony_ci sa = save_area_boot_cpu(); 1278c2ecf20Sopenharmony_ci if (!sa) 1288c2ecf20Sopenharmony_ci return -ENOMEM; 1298c2ecf20Sopenharmony_ci if (memcpy_hsa_kernel(hsa_buf, __LC_FPREGS_SAVE_AREA, 512) < 0) { 1308c2ecf20Sopenharmony_ci TRACE("could not copy from HSA\n"); 1318c2ecf20Sopenharmony_ci return -EIO; 1328c2ecf20Sopenharmony_ci } 1338c2ecf20Sopenharmony_ci save_area_add_regs(sa, hsa_buf); /* vx registers are saved in smp.c */ 1348c2ecf20Sopenharmony_ci return 0; 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci/* 1388c2ecf20Sopenharmony_ci * Release the HSA 1398c2ecf20Sopenharmony_ci */ 1408c2ecf20Sopenharmony_cistatic void release_hsa(void) 1418c2ecf20Sopenharmony_ci{ 1428c2ecf20Sopenharmony_ci diag308(DIAG308_REL_HSA, NULL); 1438c2ecf20Sopenharmony_ci hsa_available = 0; 1448c2ecf20Sopenharmony_ci} 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_cistatic ssize_t zcore_reipl_write(struct file *filp, const char __user *buf, 1478c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 1488c2ecf20Sopenharmony_ci{ 1498c2ecf20Sopenharmony_ci if (zcore_ipl_block) { 1508c2ecf20Sopenharmony_ci diag308(DIAG308_SET, zcore_ipl_block); 1518c2ecf20Sopenharmony_ci diag308(DIAG308_LOAD_CLEAR, NULL); 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci return count; 1548c2ecf20Sopenharmony_ci} 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_cistatic int zcore_reipl_open(struct inode *inode, struct file *filp) 1578c2ecf20Sopenharmony_ci{ 1588c2ecf20Sopenharmony_ci return stream_open(inode, filp); 1598c2ecf20Sopenharmony_ci} 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_cistatic int zcore_reipl_release(struct inode *inode, struct file *filp) 1628c2ecf20Sopenharmony_ci{ 1638c2ecf20Sopenharmony_ci return 0; 1648c2ecf20Sopenharmony_ci} 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_cistatic const struct file_operations zcore_reipl_fops = { 1678c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1688c2ecf20Sopenharmony_ci .write = zcore_reipl_write, 1698c2ecf20Sopenharmony_ci .open = zcore_reipl_open, 1708c2ecf20Sopenharmony_ci .release = zcore_reipl_release, 1718c2ecf20Sopenharmony_ci .llseek = no_llseek, 1728c2ecf20Sopenharmony_ci}; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_cistatic ssize_t zcore_hsa_read(struct file *filp, char __user *buf, 1758c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 1768c2ecf20Sopenharmony_ci{ 1778c2ecf20Sopenharmony_ci static char str[18]; 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci if (hsa_available) 1808c2ecf20Sopenharmony_ci snprintf(str, sizeof(str), "%lx\n", sclp.hsa_size); 1818c2ecf20Sopenharmony_ci else 1828c2ecf20Sopenharmony_ci snprintf(str, sizeof(str), "0\n"); 1838c2ecf20Sopenharmony_ci return simple_read_from_buffer(buf, count, ppos, str, strlen(str)); 1848c2ecf20Sopenharmony_ci} 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_cistatic ssize_t zcore_hsa_write(struct file *filp, const char __user *buf, 1878c2ecf20Sopenharmony_ci size_t count, loff_t *ppos) 1888c2ecf20Sopenharmony_ci{ 1898c2ecf20Sopenharmony_ci char value; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci if (*ppos != 0) 1928c2ecf20Sopenharmony_ci return -EPIPE; 1938c2ecf20Sopenharmony_ci if (copy_from_user(&value, buf, 1)) 1948c2ecf20Sopenharmony_ci return -EFAULT; 1958c2ecf20Sopenharmony_ci if (value != '0') 1968c2ecf20Sopenharmony_ci return -EINVAL; 1978c2ecf20Sopenharmony_ci release_hsa(); 1988c2ecf20Sopenharmony_ci return count; 1998c2ecf20Sopenharmony_ci} 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_cistatic const struct file_operations zcore_hsa_fops = { 2028c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2038c2ecf20Sopenharmony_ci .write = zcore_hsa_write, 2048c2ecf20Sopenharmony_ci .read = zcore_hsa_read, 2058c2ecf20Sopenharmony_ci .open = nonseekable_open, 2068c2ecf20Sopenharmony_ci .llseek = no_llseek, 2078c2ecf20Sopenharmony_ci}; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_cistatic int __init check_sdias(void) 2108c2ecf20Sopenharmony_ci{ 2118c2ecf20Sopenharmony_ci if (!sclp.hsa_size) { 2128c2ecf20Sopenharmony_ci TRACE("Could not determine HSA size\n"); 2138c2ecf20Sopenharmony_ci return -ENODEV; 2148c2ecf20Sopenharmony_ci } 2158c2ecf20Sopenharmony_ci return 0; 2168c2ecf20Sopenharmony_ci} 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci/* 2198c2ecf20Sopenharmony_ci * Provide IPL parameter information block from either HSA or memory 2208c2ecf20Sopenharmony_ci * for future reipl 2218c2ecf20Sopenharmony_ci */ 2228c2ecf20Sopenharmony_cistatic int __init zcore_reipl_init(void) 2238c2ecf20Sopenharmony_ci{ 2248c2ecf20Sopenharmony_ci struct ipib_info ipib_info; 2258c2ecf20Sopenharmony_ci int rc; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci rc = memcpy_hsa_kernel(&ipib_info, __LC_DUMP_REIPL, sizeof(ipib_info)); 2288c2ecf20Sopenharmony_ci if (rc) 2298c2ecf20Sopenharmony_ci return rc; 2308c2ecf20Sopenharmony_ci if (ipib_info.ipib == 0) 2318c2ecf20Sopenharmony_ci return 0; 2328c2ecf20Sopenharmony_ci zcore_ipl_block = (void *) __get_free_page(GFP_KERNEL); 2338c2ecf20Sopenharmony_ci if (!zcore_ipl_block) 2348c2ecf20Sopenharmony_ci return -ENOMEM; 2358c2ecf20Sopenharmony_ci if (ipib_info.ipib < sclp.hsa_size) 2368c2ecf20Sopenharmony_ci rc = memcpy_hsa_kernel(zcore_ipl_block, ipib_info.ipib, 2378c2ecf20Sopenharmony_ci PAGE_SIZE); 2388c2ecf20Sopenharmony_ci else 2398c2ecf20Sopenharmony_ci rc = memcpy_real(zcore_ipl_block, (void *) ipib_info.ipib, 2408c2ecf20Sopenharmony_ci PAGE_SIZE); 2418c2ecf20Sopenharmony_ci if (rc || (__force u32)csum_partial(zcore_ipl_block, zcore_ipl_block->hdr.len, 0) != 2428c2ecf20Sopenharmony_ci ipib_info.checksum) { 2438c2ecf20Sopenharmony_ci TRACE("Checksum does not match\n"); 2448c2ecf20Sopenharmony_ci free_page((unsigned long) zcore_ipl_block); 2458c2ecf20Sopenharmony_ci zcore_ipl_block = NULL; 2468c2ecf20Sopenharmony_ci } 2478c2ecf20Sopenharmony_ci return 0; 2488c2ecf20Sopenharmony_ci} 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_cistatic int __init zcore_init(void) 2518c2ecf20Sopenharmony_ci{ 2528c2ecf20Sopenharmony_ci unsigned char arch; 2538c2ecf20Sopenharmony_ci int rc; 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci if (!is_ipl_type_dump()) 2568c2ecf20Sopenharmony_ci return -ENODATA; 2578c2ecf20Sopenharmony_ci if (OLDMEM_BASE) 2588c2ecf20Sopenharmony_ci return -ENODATA; 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci zcore_dbf = debug_register("zcore", 4, 1, 4 * sizeof(long)); 2618c2ecf20Sopenharmony_ci debug_register_view(zcore_dbf, &debug_sprintf_view); 2628c2ecf20Sopenharmony_ci debug_set_level(zcore_dbf, 6); 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci if (ipl_info.type == IPL_TYPE_FCP_DUMP) { 2658c2ecf20Sopenharmony_ci TRACE("type: fcp\n"); 2668c2ecf20Sopenharmony_ci TRACE("devno: %x\n", ipl_info.data.fcp.dev_id.devno); 2678c2ecf20Sopenharmony_ci TRACE("wwpn: %llx\n", (unsigned long long) ipl_info.data.fcp.wwpn); 2688c2ecf20Sopenharmony_ci TRACE("lun: %llx\n", (unsigned long long) ipl_info.data.fcp.lun); 2698c2ecf20Sopenharmony_ci } else if (ipl_info.type == IPL_TYPE_NVME_DUMP) { 2708c2ecf20Sopenharmony_ci TRACE("type: nvme\n"); 2718c2ecf20Sopenharmony_ci TRACE("fid: %x\n", ipl_info.data.nvme.fid); 2728c2ecf20Sopenharmony_ci TRACE("nsid: %x\n", ipl_info.data.nvme.nsid); 2738c2ecf20Sopenharmony_ci } 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci rc = sclp_sdias_init(); 2768c2ecf20Sopenharmony_ci if (rc) 2778c2ecf20Sopenharmony_ci goto fail; 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci rc = check_sdias(); 2808c2ecf20Sopenharmony_ci if (rc) 2818c2ecf20Sopenharmony_ci goto fail; 2828c2ecf20Sopenharmony_ci hsa_available = 1; 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_ci rc = memcpy_hsa_kernel(&arch, __LC_AR_MODE_ID, 1); 2858c2ecf20Sopenharmony_ci if (rc) 2868c2ecf20Sopenharmony_ci goto fail; 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci if (arch == ARCH_S390) { 2898c2ecf20Sopenharmony_ci pr_alert("The 64-bit dump tool cannot be used for a " 2908c2ecf20Sopenharmony_ci "32-bit system\n"); 2918c2ecf20Sopenharmony_ci rc = -EINVAL; 2928c2ecf20Sopenharmony_ci goto fail; 2938c2ecf20Sopenharmony_ci } 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci pr_alert("The dump process started for a 64-bit operating system\n"); 2968c2ecf20Sopenharmony_ci rc = init_cpu_info(); 2978c2ecf20Sopenharmony_ci if (rc) 2988c2ecf20Sopenharmony_ci goto fail; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci rc = zcore_reipl_init(); 3018c2ecf20Sopenharmony_ci if (rc) 3028c2ecf20Sopenharmony_ci goto fail; 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci zcore_dir = debugfs_create_dir("zcore" , NULL); 3058c2ecf20Sopenharmony_ci if (!zcore_dir) { 3068c2ecf20Sopenharmony_ci rc = -ENOMEM; 3078c2ecf20Sopenharmony_ci goto fail; 3088c2ecf20Sopenharmony_ci } 3098c2ecf20Sopenharmony_ci zcore_reipl_file = debugfs_create_file("reipl", S_IRUSR, zcore_dir, 3108c2ecf20Sopenharmony_ci NULL, &zcore_reipl_fops); 3118c2ecf20Sopenharmony_ci if (!zcore_reipl_file) { 3128c2ecf20Sopenharmony_ci rc = -ENOMEM; 3138c2ecf20Sopenharmony_ci goto fail_dir; 3148c2ecf20Sopenharmony_ci } 3158c2ecf20Sopenharmony_ci zcore_hsa_file = debugfs_create_file("hsa", S_IRUSR|S_IWUSR, zcore_dir, 3168c2ecf20Sopenharmony_ci NULL, &zcore_hsa_fops); 3178c2ecf20Sopenharmony_ci if (!zcore_hsa_file) { 3188c2ecf20Sopenharmony_ci rc = -ENOMEM; 3198c2ecf20Sopenharmony_ci goto fail_reipl_file; 3208c2ecf20Sopenharmony_ci } 3218c2ecf20Sopenharmony_ci return 0; 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_cifail_reipl_file: 3248c2ecf20Sopenharmony_ci debugfs_remove(zcore_reipl_file); 3258c2ecf20Sopenharmony_cifail_dir: 3268c2ecf20Sopenharmony_ci debugfs_remove(zcore_dir); 3278c2ecf20Sopenharmony_cifail: 3288c2ecf20Sopenharmony_ci diag308(DIAG308_REL_HSA, NULL); 3298c2ecf20Sopenharmony_ci return rc; 3308c2ecf20Sopenharmony_ci} 3318c2ecf20Sopenharmony_cisubsys_initcall(zcore_init); 332