| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Channel report handling code |
| * |
| * Copyright IBM Corp. 2000, 2009 |
| * Author(s): Ingo Adlung <adlung@de.ibm.com>, |
| * Martin Schwidefsky <schwidefsky@de.ibm.com>, |
| * Cornelia Huck <cornelia.huck@de.ibm.com>, |
| */ |
| |
| #include <linux/mutex.h> |
| #include <linux/kthread.h> |
| #include <linux/init.h> |
| #include <linux/wait.h> |
| #include <asm/ctlreg.h> |
| #include <asm/crw.h> |
| #include "ioasm.h" |
| |
| static DEFINE_MUTEX(crw_handler_mutex); |
| static crw_handler_t crw_handlers[NR_RSCS]; |
| static atomic_t crw_nr_req = ATOMIC_INIT(0); |
| static DECLARE_WAIT_QUEUE_HEAD(crw_handler_wait_q); |
| |
| /** |
| * crw_register_handler() - register a channel report word handler |
| * @rsc: reporting source code to handle |
| * @handler: handler to be registered |
| * |
| * Returns %0 on success and a negative error value otherwise. |
| */ |
| int crw_register_handler(int rsc, crw_handler_t handler) |
| { |
| int rc = 0; |
| |
| if ((rsc < 0) || (rsc >= NR_RSCS)) |
| return -EINVAL; |
| mutex_lock(&crw_handler_mutex); |
| if (crw_handlers[rsc]) |
| rc = -EBUSY; |
| else |
| crw_handlers[rsc] = handler; |
| mutex_unlock(&crw_handler_mutex); |
| return rc; |
| } |
| |
| /** |
| * crw_unregister_handler() - unregister a channel report word handler |
| * @rsc: reporting source code to handle |
| */ |
| void crw_unregister_handler(int rsc) |
| { |
| if ((rsc < 0) || (rsc >= NR_RSCS)) |
| return; |
| mutex_lock(&crw_handler_mutex); |
| crw_handlers[rsc] = NULL; |
| mutex_unlock(&crw_handler_mutex); |
| } |
| |
| /* |
| * Retrieve CRWs and call function to handle event. |
| */ |
| static int crw_collect_info(void *unused) |
| { |
| struct crw crw[2]; |
| int ccode, signal; |
| unsigned int chain; |
| |
| repeat: |
| signal = wait_event_interruptible(crw_handler_wait_q, |
| atomic_read(&crw_nr_req) > 0); |
| if (unlikely(signal)) |
| atomic_inc(&crw_nr_req); |
| chain = 0; |
| while (1) { |
| crw_handler_t handler; |
| |
| if (unlikely(chain > 1)) { |
| struct crw tmp_crw; |
| |
| printk(KERN_WARNING"%s: Code does not support more " |
| "than two chained crws; please report to " |
| "linux390@de.ibm.com!\n", __func__); |
| ccode = stcrw(&tmp_crw); |
| printk(KERN_WARNING"%s: crw reports slct=%d, oflw=%d, " |
| "chn=%d, rsc=%X, anc=%d, erc=%X, rsid=%X\n", |
| __func__, tmp_crw.slct, tmp_crw.oflw, |
| tmp_crw.chn, tmp_crw.rsc, tmp_crw.anc, |
| tmp_crw.erc, tmp_crw.rsid); |
| printk(KERN_WARNING"%s: This was crw number %x in the " |
| "chain\n", __func__, chain); |
| if (ccode != 0) |
| break; |
| chain = tmp_crw.chn ? chain + 1 : 0; |
| continue; |
| } |
| ccode = stcrw(&crw[chain]); |
| if (ccode != 0) |
| break; |
| printk(KERN_DEBUG "crw_info : CRW reports slct=%d, oflw=%d, " |
| "chn=%d, rsc=%X, anc=%d, erc=%X, rsid=%X\n", |
| crw[chain].slct, crw[chain].oflw, crw[chain].chn, |
| crw[chain].rsc, crw[chain].anc, crw[chain].erc, |
| crw[chain].rsid); |
| /* Check for overflows. */ |
| if (crw[chain].oflw) { |
| int i; |
| |
| pr_debug("%s: crw overflow detected!\n", __func__); |
| mutex_lock(&crw_handler_mutex); |
| for (i = 0; i < NR_RSCS; i++) { |
| if (crw_handlers[i]) |
| crw_handlers[i](NULL, NULL, 1); |
| } |
| mutex_unlock(&crw_handler_mutex); |
| chain = 0; |
| continue; |
| } |
| if (crw[0].chn && !chain) { |
| chain++; |
| continue; |
| } |
| mutex_lock(&crw_handler_mutex); |
| handler = crw_handlers[crw[chain].rsc]; |
| if (handler) |
| handler(&crw[0], chain ? &crw[1] : NULL, 0); |
| mutex_unlock(&crw_handler_mutex); |
| /* chain is always 0 or 1 here. */ |
| chain = crw[chain].chn ? chain + 1 : 0; |
| } |
| if (atomic_dec_and_test(&crw_nr_req)) |
| wake_up(&crw_handler_wait_q); |
| goto repeat; |
| return 0; |
| } |
| |
| void crw_handle_channel_report(void) |
| { |
| atomic_inc(&crw_nr_req); |
| wake_up(&crw_handler_wait_q); |
| } |
| |
| void crw_wait_for_channel_report(void) |
| { |
| crw_handle_channel_report(); |
| wait_event(crw_handler_wait_q, atomic_read(&crw_nr_req) == 0); |
| } |
| |
| /* |
| * Machine checks for the channel subsystem must be enabled |
| * after the channel subsystem is initialized |
| */ |
| static int __init crw_machine_check_init(void) |
| { |
| struct task_struct *task; |
| |
| task = kthread_run(crw_collect_info, NULL, "kmcheck"); |
| if (IS_ERR(task)) |
| return PTR_ERR(task); |
| system_ctl_set_bit(14, CR14_CHANNEL_REPORT_SUBMASK_BIT); |
| return 0; |
| } |
| device_initcall(crw_machine_check_init); |