162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Character device driver for extended error reporting. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright IBM Corp. 2005 662306a36Sopenharmony_ci * extended error reporting for DASD ECKD devices 762306a36Sopenharmony_ci * Author(s): Stefan Weinhuber <wein@de.ibm.com> 862306a36Sopenharmony_ci */ 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#define KMSG_COMPONENT "dasd-eckd" 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/init.h> 1362306a36Sopenharmony_ci#include <linux/fs.h> 1462306a36Sopenharmony_ci#include <linux/kernel.h> 1562306a36Sopenharmony_ci#include <linux/miscdevice.h> 1662306a36Sopenharmony_ci#include <linux/module.h> 1762306a36Sopenharmony_ci#include <linux/moduleparam.h> 1862306a36Sopenharmony_ci#include <linux/device.h> 1962306a36Sopenharmony_ci#include <linux/poll.h> 2062306a36Sopenharmony_ci#include <linux/mutex.h> 2162306a36Sopenharmony_ci#include <linux/err.h> 2262306a36Sopenharmony_ci#include <linux/slab.h> 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci#include <linux/uaccess.h> 2562306a36Sopenharmony_ci#include <linux/atomic.h> 2662306a36Sopenharmony_ci#include <asm/ebcdic.h> 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ci#include "dasd_int.h" 2962306a36Sopenharmony_ci#include "dasd_eckd.h" 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci#ifdef PRINTK_HEADER 3262306a36Sopenharmony_ci#undef PRINTK_HEADER 3362306a36Sopenharmony_ci#endif /* PRINTK_HEADER */ 3462306a36Sopenharmony_ci#define PRINTK_HEADER "dasd(eer):" 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci/* 3762306a36Sopenharmony_ci * SECTION: the internal buffer 3862306a36Sopenharmony_ci */ 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_ci/* 4162306a36Sopenharmony_ci * The internal buffer is meant to store obaque blobs of data, so it does 4262306a36Sopenharmony_ci * not know of higher level concepts like triggers. 4362306a36Sopenharmony_ci * It consists of a number of pages that are used as a ringbuffer. Each data 4462306a36Sopenharmony_ci * blob is stored in a simple record that consists of an integer, which 4562306a36Sopenharmony_ci * contains the size of the following data, and the data bytes themselfes. 4662306a36Sopenharmony_ci * 4762306a36Sopenharmony_ci * To allow for multiple independent readers we create one internal buffer 4862306a36Sopenharmony_ci * each time the device is opened and destroy the buffer when the file is 4962306a36Sopenharmony_ci * closed again. The number of pages used for this buffer is determined by 5062306a36Sopenharmony_ci * the module parmeter eer_pages. 5162306a36Sopenharmony_ci * 5262306a36Sopenharmony_ci * One record can be written to a buffer by using the functions 5362306a36Sopenharmony_ci * - dasd_eer_start_record (one time per record to write the size to the 5462306a36Sopenharmony_ci * buffer and reserve the space for the data) 5562306a36Sopenharmony_ci * - dasd_eer_write_buffer (one or more times per record to write the data) 5662306a36Sopenharmony_ci * The data can be written in several steps but you will have to compute 5762306a36Sopenharmony_ci * the total size up front for the invocation of dasd_eer_start_record. 5862306a36Sopenharmony_ci * If the ringbuffer is full, dasd_eer_start_record will remove the required 5962306a36Sopenharmony_ci * number of old records. 6062306a36Sopenharmony_ci * 6162306a36Sopenharmony_ci * A record is typically read in two steps, first read the integer that 6262306a36Sopenharmony_ci * specifies the size of the following data, then read the data. 6362306a36Sopenharmony_ci * Both can be done by 6462306a36Sopenharmony_ci * - dasd_eer_read_buffer 6562306a36Sopenharmony_ci * 6662306a36Sopenharmony_ci * For all mentioned functions you need to get the bufferlock first and keep 6762306a36Sopenharmony_ci * it until a complete record is written or read. 6862306a36Sopenharmony_ci * 6962306a36Sopenharmony_ci * All information necessary to keep track of an internal buffer is kept in 7062306a36Sopenharmony_ci * a struct eerbuffer. The buffer specific to a file pointer is strored in 7162306a36Sopenharmony_ci * the private_data field of that file. To be able to write data to all 7262306a36Sopenharmony_ci * existing buffers, each buffer is also added to the bufferlist. 7362306a36Sopenharmony_ci * If the user does not want to read a complete record in one go, we have to 7462306a36Sopenharmony_ci * keep track of the rest of the record. residual stores the number of bytes 7562306a36Sopenharmony_ci * that are still to deliver. If the rest of the record is invalidated between 7662306a36Sopenharmony_ci * two reads then residual will be set to -1 so that the next read will fail. 7762306a36Sopenharmony_ci * All entries in the eerbuffer structure are protected with the bufferlock. 7862306a36Sopenharmony_ci * To avoid races between writing to a buffer on the one side and creating 7962306a36Sopenharmony_ci * and destroying buffers on the other side, the bufferlock must also be used 8062306a36Sopenharmony_ci * to protect the bufferlist. 8162306a36Sopenharmony_ci */ 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic int eer_pages = 5; 8462306a36Sopenharmony_cimodule_param(eer_pages, int, S_IRUGO|S_IWUSR); 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistruct eerbuffer { 8762306a36Sopenharmony_ci struct list_head list; 8862306a36Sopenharmony_ci char **buffer; 8962306a36Sopenharmony_ci int buffersize; 9062306a36Sopenharmony_ci int buffer_page_count; 9162306a36Sopenharmony_ci int head; 9262306a36Sopenharmony_ci int tail; 9362306a36Sopenharmony_ci int residual; 9462306a36Sopenharmony_ci}; 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic LIST_HEAD(bufferlist); 9762306a36Sopenharmony_cistatic DEFINE_SPINLOCK(bufferlock); 9862306a36Sopenharmony_cistatic DECLARE_WAIT_QUEUE_HEAD(dasd_eer_read_wait_queue); 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci/* 10162306a36Sopenharmony_ci * How many free bytes are available on the buffer. 10262306a36Sopenharmony_ci * Needs to be called with bufferlock held. 10362306a36Sopenharmony_ci */ 10462306a36Sopenharmony_cistatic int dasd_eer_get_free_bytes(struct eerbuffer *eerb) 10562306a36Sopenharmony_ci{ 10662306a36Sopenharmony_ci if (eerb->head < eerb->tail) 10762306a36Sopenharmony_ci return eerb->tail - eerb->head - 1; 10862306a36Sopenharmony_ci return eerb->buffersize - eerb->head + eerb->tail -1; 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci/* 11262306a36Sopenharmony_ci * How many bytes of buffer space are used. 11362306a36Sopenharmony_ci * Needs to be called with bufferlock held. 11462306a36Sopenharmony_ci */ 11562306a36Sopenharmony_cistatic int dasd_eer_get_filled_bytes(struct eerbuffer *eerb) 11662306a36Sopenharmony_ci{ 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci if (eerb->head >= eerb->tail) 11962306a36Sopenharmony_ci return eerb->head - eerb->tail; 12062306a36Sopenharmony_ci return eerb->buffersize - eerb->tail + eerb->head; 12162306a36Sopenharmony_ci} 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci/* 12462306a36Sopenharmony_ci * The dasd_eer_write_buffer function just copies count bytes of data 12562306a36Sopenharmony_ci * to the buffer. Make sure to call dasd_eer_start_record first, to 12662306a36Sopenharmony_ci * make sure that enough free space is available. 12762306a36Sopenharmony_ci * Needs to be called with bufferlock held. 12862306a36Sopenharmony_ci */ 12962306a36Sopenharmony_cistatic void dasd_eer_write_buffer(struct eerbuffer *eerb, 13062306a36Sopenharmony_ci char *data, int count) 13162306a36Sopenharmony_ci{ 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci unsigned long headindex,localhead; 13462306a36Sopenharmony_ci unsigned long rest, len; 13562306a36Sopenharmony_ci char *nextdata; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci nextdata = data; 13862306a36Sopenharmony_ci rest = count; 13962306a36Sopenharmony_ci while (rest > 0) { 14062306a36Sopenharmony_ci headindex = eerb->head / PAGE_SIZE; 14162306a36Sopenharmony_ci localhead = eerb->head % PAGE_SIZE; 14262306a36Sopenharmony_ci len = min(rest, PAGE_SIZE - localhead); 14362306a36Sopenharmony_ci memcpy(eerb->buffer[headindex]+localhead, nextdata, len); 14462306a36Sopenharmony_ci nextdata += len; 14562306a36Sopenharmony_ci rest -= len; 14662306a36Sopenharmony_ci eerb->head += len; 14762306a36Sopenharmony_ci if (eerb->head == eerb->buffersize) 14862306a36Sopenharmony_ci eerb->head = 0; /* wrap around */ 14962306a36Sopenharmony_ci BUG_ON(eerb->head > eerb->buffersize); 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci/* 15462306a36Sopenharmony_ci * Needs to be called with bufferlock held. 15562306a36Sopenharmony_ci */ 15662306a36Sopenharmony_cistatic int dasd_eer_read_buffer(struct eerbuffer *eerb, char *data, int count) 15762306a36Sopenharmony_ci{ 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci unsigned long tailindex,localtail; 16062306a36Sopenharmony_ci unsigned long rest, len, finalcount; 16162306a36Sopenharmony_ci char *nextdata; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci finalcount = min(count, dasd_eer_get_filled_bytes(eerb)); 16462306a36Sopenharmony_ci nextdata = data; 16562306a36Sopenharmony_ci rest = finalcount; 16662306a36Sopenharmony_ci while (rest > 0) { 16762306a36Sopenharmony_ci tailindex = eerb->tail / PAGE_SIZE; 16862306a36Sopenharmony_ci localtail = eerb->tail % PAGE_SIZE; 16962306a36Sopenharmony_ci len = min(rest, PAGE_SIZE - localtail); 17062306a36Sopenharmony_ci memcpy(nextdata, eerb->buffer[tailindex] + localtail, len); 17162306a36Sopenharmony_ci nextdata += len; 17262306a36Sopenharmony_ci rest -= len; 17362306a36Sopenharmony_ci eerb->tail += len; 17462306a36Sopenharmony_ci if (eerb->tail == eerb->buffersize) 17562306a36Sopenharmony_ci eerb->tail = 0; /* wrap around */ 17662306a36Sopenharmony_ci BUG_ON(eerb->tail > eerb->buffersize); 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci return finalcount; 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci/* 18262306a36Sopenharmony_ci * Whenever you want to write a blob of data to the internal buffer you 18362306a36Sopenharmony_ci * have to start by using this function first. It will write the number 18462306a36Sopenharmony_ci * of bytes that will be written to the buffer. If necessary it will remove 18562306a36Sopenharmony_ci * old records to make room for the new one. 18662306a36Sopenharmony_ci * Needs to be called with bufferlock held. 18762306a36Sopenharmony_ci */ 18862306a36Sopenharmony_cistatic int dasd_eer_start_record(struct eerbuffer *eerb, int count) 18962306a36Sopenharmony_ci{ 19062306a36Sopenharmony_ci int tailcount; 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci if (count + sizeof(count) > eerb->buffersize) 19362306a36Sopenharmony_ci return -ENOMEM; 19462306a36Sopenharmony_ci while (dasd_eer_get_free_bytes(eerb) < count + sizeof(count)) { 19562306a36Sopenharmony_ci if (eerb->residual > 0) { 19662306a36Sopenharmony_ci eerb->tail += eerb->residual; 19762306a36Sopenharmony_ci if (eerb->tail >= eerb->buffersize) 19862306a36Sopenharmony_ci eerb->tail -= eerb->buffersize; 19962306a36Sopenharmony_ci eerb->residual = -1; 20062306a36Sopenharmony_ci } 20162306a36Sopenharmony_ci dasd_eer_read_buffer(eerb, (char *) &tailcount, 20262306a36Sopenharmony_ci sizeof(tailcount)); 20362306a36Sopenharmony_ci eerb->tail += tailcount; 20462306a36Sopenharmony_ci if (eerb->tail >= eerb->buffersize) 20562306a36Sopenharmony_ci eerb->tail -= eerb->buffersize; 20662306a36Sopenharmony_ci } 20762306a36Sopenharmony_ci dasd_eer_write_buffer(eerb, (char*) &count, sizeof(count)); 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci return 0; 21062306a36Sopenharmony_ci}; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci/* 21362306a36Sopenharmony_ci * Release pages that are not used anymore. 21462306a36Sopenharmony_ci */ 21562306a36Sopenharmony_cistatic void dasd_eer_free_buffer_pages(char **buf, int no_pages) 21662306a36Sopenharmony_ci{ 21762306a36Sopenharmony_ci int i; 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci for (i = 0; i < no_pages; i++) 22062306a36Sopenharmony_ci free_page((unsigned long) buf[i]); 22162306a36Sopenharmony_ci} 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci/* 22462306a36Sopenharmony_ci * Allocate a new set of memory pages. 22562306a36Sopenharmony_ci */ 22662306a36Sopenharmony_cistatic int dasd_eer_allocate_buffer_pages(char **buf, int no_pages) 22762306a36Sopenharmony_ci{ 22862306a36Sopenharmony_ci int i; 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci for (i = 0; i < no_pages; i++) { 23162306a36Sopenharmony_ci buf[i] = (char *) get_zeroed_page(GFP_KERNEL); 23262306a36Sopenharmony_ci if (!buf[i]) { 23362306a36Sopenharmony_ci dasd_eer_free_buffer_pages(buf, i); 23462306a36Sopenharmony_ci return -ENOMEM; 23562306a36Sopenharmony_ci } 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci return 0; 23862306a36Sopenharmony_ci} 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci/* 24162306a36Sopenharmony_ci * SECTION: The extended error reporting functionality 24262306a36Sopenharmony_ci */ 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci/* 24562306a36Sopenharmony_ci * When a DASD device driver wants to report an error, it calls the 24662306a36Sopenharmony_ci * function dasd_eer_write and gives the respective trigger ID as 24762306a36Sopenharmony_ci * parameter. Currently there are four kinds of triggers: 24862306a36Sopenharmony_ci * 24962306a36Sopenharmony_ci * DASD_EER_FATALERROR: all kinds of unrecoverable I/O problems 25062306a36Sopenharmony_ci * DASD_EER_PPRCSUSPEND: PPRC was suspended 25162306a36Sopenharmony_ci * DASD_EER_NOPATH: There is no path to the device left. 25262306a36Sopenharmony_ci * DASD_EER_STATECHANGE: The state of the device has changed. 25362306a36Sopenharmony_ci * 25462306a36Sopenharmony_ci * For the first three triggers all required information can be supplied by 25562306a36Sopenharmony_ci * the caller. For these triggers a record is written by the function 25662306a36Sopenharmony_ci * dasd_eer_write_standard_trigger. 25762306a36Sopenharmony_ci * 25862306a36Sopenharmony_ci * The DASD_EER_STATECHANGE trigger is special since a sense subsystem 25962306a36Sopenharmony_ci * status ccw need to be executed to gather the necessary sense data first. 26062306a36Sopenharmony_ci * The dasd_eer_snss function will queue the SNSS request and the request 26162306a36Sopenharmony_ci * callback will then call dasd_eer_write with the DASD_EER_STATCHANGE 26262306a36Sopenharmony_ci * trigger. 26362306a36Sopenharmony_ci * 26462306a36Sopenharmony_ci * To avoid memory allocations at runtime, the necessary memory is allocated 26562306a36Sopenharmony_ci * when the extended error reporting is enabled for a device (by 26662306a36Sopenharmony_ci * dasd_eer_probe). There is one sense subsystem status request for each 26762306a36Sopenharmony_ci * eer enabled DASD device. The presence of the cqr in device->eer_cqr 26862306a36Sopenharmony_ci * indicates that eer is enable for the device. The use of the snss request 26962306a36Sopenharmony_ci * is protected by the DASD_FLAG_EER_IN_USE bit. When this flag indicates 27062306a36Sopenharmony_ci * that the cqr is currently in use, dasd_eer_snss cannot start a second 27162306a36Sopenharmony_ci * request but sets the DASD_FLAG_EER_SNSS flag instead. The callback of 27262306a36Sopenharmony_ci * the SNSS request will check the bit and call dasd_eer_snss again. 27362306a36Sopenharmony_ci */ 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci#define SNSS_DATA_SIZE 44 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci#define DASD_EER_BUSID_SIZE 10 27862306a36Sopenharmony_cistruct dasd_eer_header { 27962306a36Sopenharmony_ci __u32 total_size; 28062306a36Sopenharmony_ci __u32 trigger; 28162306a36Sopenharmony_ci __u64 tv_sec; 28262306a36Sopenharmony_ci __u64 tv_usec; 28362306a36Sopenharmony_ci char busid[DASD_EER_BUSID_SIZE]; 28462306a36Sopenharmony_ci} __attribute__ ((packed)); 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci/* 28762306a36Sopenharmony_ci * The following function can be used for those triggers that have 28862306a36Sopenharmony_ci * all necessary data available when the function is called. 28962306a36Sopenharmony_ci * If the parameter cqr is not NULL, the chain of requests will be searched 29062306a36Sopenharmony_ci * for valid sense data, and all valid sense data sets will be added to 29162306a36Sopenharmony_ci * the triggers data. 29262306a36Sopenharmony_ci */ 29362306a36Sopenharmony_cistatic void dasd_eer_write_standard_trigger(struct dasd_device *device, 29462306a36Sopenharmony_ci struct dasd_ccw_req *cqr, 29562306a36Sopenharmony_ci int trigger) 29662306a36Sopenharmony_ci{ 29762306a36Sopenharmony_ci struct dasd_ccw_req *temp_cqr; 29862306a36Sopenharmony_ci int data_size; 29962306a36Sopenharmony_ci struct timespec64 ts; 30062306a36Sopenharmony_ci struct dasd_eer_header header; 30162306a36Sopenharmony_ci unsigned long flags; 30262306a36Sopenharmony_ci struct eerbuffer *eerb; 30362306a36Sopenharmony_ci char *sense; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci /* go through cqr chain and count the valid sense data sets */ 30662306a36Sopenharmony_ci data_size = 0; 30762306a36Sopenharmony_ci for (temp_cqr = cqr; temp_cqr; temp_cqr = temp_cqr->refers) 30862306a36Sopenharmony_ci if (dasd_get_sense(&temp_cqr->irb)) 30962306a36Sopenharmony_ci data_size += 32; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci header.total_size = sizeof(header) + data_size + 4; /* "EOR" */ 31262306a36Sopenharmony_ci header.trigger = trigger; 31362306a36Sopenharmony_ci ktime_get_real_ts64(&ts); 31462306a36Sopenharmony_ci header.tv_sec = ts.tv_sec; 31562306a36Sopenharmony_ci header.tv_usec = ts.tv_nsec / NSEC_PER_USEC; 31662306a36Sopenharmony_ci strscpy(header.busid, dev_name(&device->cdev->dev), 31762306a36Sopenharmony_ci DASD_EER_BUSID_SIZE); 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci spin_lock_irqsave(&bufferlock, flags); 32062306a36Sopenharmony_ci list_for_each_entry(eerb, &bufferlist, list) { 32162306a36Sopenharmony_ci dasd_eer_start_record(eerb, header.total_size); 32262306a36Sopenharmony_ci dasd_eer_write_buffer(eerb, (char *) &header, sizeof(header)); 32362306a36Sopenharmony_ci for (temp_cqr = cqr; temp_cqr; temp_cqr = temp_cqr->refers) { 32462306a36Sopenharmony_ci sense = dasd_get_sense(&temp_cqr->irb); 32562306a36Sopenharmony_ci if (sense) 32662306a36Sopenharmony_ci dasd_eer_write_buffer(eerb, sense, 32); 32762306a36Sopenharmony_ci } 32862306a36Sopenharmony_ci dasd_eer_write_buffer(eerb, "EOR", 4); 32962306a36Sopenharmony_ci } 33062306a36Sopenharmony_ci spin_unlock_irqrestore(&bufferlock, flags); 33162306a36Sopenharmony_ci wake_up_interruptible(&dasd_eer_read_wait_queue); 33262306a36Sopenharmony_ci} 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci/* 33562306a36Sopenharmony_ci * This function writes a DASD_EER_STATECHANGE trigger. 33662306a36Sopenharmony_ci */ 33762306a36Sopenharmony_cistatic void dasd_eer_write_snss_trigger(struct dasd_device *device, 33862306a36Sopenharmony_ci struct dasd_ccw_req *cqr, 33962306a36Sopenharmony_ci int trigger) 34062306a36Sopenharmony_ci{ 34162306a36Sopenharmony_ci int data_size; 34262306a36Sopenharmony_ci int snss_rc; 34362306a36Sopenharmony_ci struct timespec64 ts; 34462306a36Sopenharmony_ci struct dasd_eer_header header; 34562306a36Sopenharmony_ci unsigned long flags; 34662306a36Sopenharmony_ci struct eerbuffer *eerb; 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci snss_rc = (cqr->status == DASD_CQR_DONE) ? 0 : -EIO; 34962306a36Sopenharmony_ci if (snss_rc) 35062306a36Sopenharmony_ci data_size = 0; 35162306a36Sopenharmony_ci else 35262306a36Sopenharmony_ci data_size = SNSS_DATA_SIZE; 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci header.total_size = sizeof(header) + data_size + 4; /* "EOR" */ 35562306a36Sopenharmony_ci header.trigger = DASD_EER_STATECHANGE; 35662306a36Sopenharmony_ci ktime_get_real_ts64(&ts); 35762306a36Sopenharmony_ci header.tv_sec = ts.tv_sec; 35862306a36Sopenharmony_ci header.tv_usec = ts.tv_nsec / NSEC_PER_USEC; 35962306a36Sopenharmony_ci strscpy(header.busid, dev_name(&device->cdev->dev), 36062306a36Sopenharmony_ci DASD_EER_BUSID_SIZE); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci spin_lock_irqsave(&bufferlock, flags); 36362306a36Sopenharmony_ci list_for_each_entry(eerb, &bufferlist, list) { 36462306a36Sopenharmony_ci dasd_eer_start_record(eerb, header.total_size); 36562306a36Sopenharmony_ci dasd_eer_write_buffer(eerb, (char *) &header , sizeof(header)); 36662306a36Sopenharmony_ci if (!snss_rc) 36762306a36Sopenharmony_ci dasd_eer_write_buffer(eerb, cqr->data, SNSS_DATA_SIZE); 36862306a36Sopenharmony_ci dasd_eer_write_buffer(eerb, "EOR", 4); 36962306a36Sopenharmony_ci } 37062306a36Sopenharmony_ci spin_unlock_irqrestore(&bufferlock, flags); 37162306a36Sopenharmony_ci wake_up_interruptible(&dasd_eer_read_wait_queue); 37262306a36Sopenharmony_ci} 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci/* 37562306a36Sopenharmony_ci * This function is called for all triggers. It calls the appropriate 37662306a36Sopenharmony_ci * function that writes the actual trigger records. 37762306a36Sopenharmony_ci */ 37862306a36Sopenharmony_civoid dasd_eer_write(struct dasd_device *device, struct dasd_ccw_req *cqr, 37962306a36Sopenharmony_ci unsigned int id) 38062306a36Sopenharmony_ci{ 38162306a36Sopenharmony_ci if (!device->eer_cqr) 38262306a36Sopenharmony_ci return; 38362306a36Sopenharmony_ci switch (id) { 38462306a36Sopenharmony_ci case DASD_EER_FATALERROR: 38562306a36Sopenharmony_ci case DASD_EER_PPRCSUSPEND: 38662306a36Sopenharmony_ci dasd_eer_write_standard_trigger(device, cqr, id); 38762306a36Sopenharmony_ci break; 38862306a36Sopenharmony_ci case DASD_EER_NOPATH: 38962306a36Sopenharmony_ci case DASD_EER_NOSPC: 39062306a36Sopenharmony_ci case DASD_EER_AUTOQUIESCE: 39162306a36Sopenharmony_ci dasd_eer_write_standard_trigger(device, NULL, id); 39262306a36Sopenharmony_ci break; 39362306a36Sopenharmony_ci case DASD_EER_STATECHANGE: 39462306a36Sopenharmony_ci dasd_eer_write_snss_trigger(device, cqr, id); 39562306a36Sopenharmony_ci break; 39662306a36Sopenharmony_ci default: /* unknown trigger, so we write it without any sense data */ 39762306a36Sopenharmony_ci dasd_eer_write_standard_trigger(device, NULL, id); 39862306a36Sopenharmony_ci break; 39962306a36Sopenharmony_ci } 40062306a36Sopenharmony_ci} 40162306a36Sopenharmony_ciEXPORT_SYMBOL(dasd_eer_write); 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci/* 40462306a36Sopenharmony_ci * Start a sense subsystem status request. 40562306a36Sopenharmony_ci * Needs to be called with the device held. 40662306a36Sopenharmony_ci */ 40762306a36Sopenharmony_civoid dasd_eer_snss(struct dasd_device *device) 40862306a36Sopenharmony_ci{ 40962306a36Sopenharmony_ci struct dasd_ccw_req *cqr; 41062306a36Sopenharmony_ci 41162306a36Sopenharmony_ci cqr = device->eer_cqr; 41262306a36Sopenharmony_ci if (!cqr) /* Device not eer enabled. */ 41362306a36Sopenharmony_ci return; 41462306a36Sopenharmony_ci if (test_and_set_bit(DASD_FLAG_EER_IN_USE, &device->flags)) { 41562306a36Sopenharmony_ci /* Sense subsystem status request in use. */ 41662306a36Sopenharmony_ci set_bit(DASD_FLAG_EER_SNSS, &device->flags); 41762306a36Sopenharmony_ci return; 41862306a36Sopenharmony_ci } 41962306a36Sopenharmony_ci /* cdev is already locked, can't use dasd_add_request_head */ 42062306a36Sopenharmony_ci clear_bit(DASD_FLAG_EER_SNSS, &device->flags); 42162306a36Sopenharmony_ci cqr->status = DASD_CQR_QUEUED; 42262306a36Sopenharmony_ci list_add(&cqr->devlist, &device->ccw_queue); 42362306a36Sopenharmony_ci dasd_schedule_device_bh(device); 42462306a36Sopenharmony_ci} 42562306a36Sopenharmony_ci 42662306a36Sopenharmony_ci/* 42762306a36Sopenharmony_ci * Callback function for use with sense subsystem status request. 42862306a36Sopenharmony_ci */ 42962306a36Sopenharmony_cistatic void dasd_eer_snss_cb(struct dasd_ccw_req *cqr, void *data) 43062306a36Sopenharmony_ci{ 43162306a36Sopenharmony_ci struct dasd_device *device = cqr->startdev; 43262306a36Sopenharmony_ci unsigned long flags; 43362306a36Sopenharmony_ci 43462306a36Sopenharmony_ci dasd_eer_write(device, cqr, DASD_EER_STATECHANGE); 43562306a36Sopenharmony_ci spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags); 43662306a36Sopenharmony_ci if (device->eer_cqr == cqr) { 43762306a36Sopenharmony_ci clear_bit(DASD_FLAG_EER_IN_USE, &device->flags); 43862306a36Sopenharmony_ci if (test_bit(DASD_FLAG_EER_SNSS, &device->flags)) 43962306a36Sopenharmony_ci /* Another SNSS has been requested in the meantime. */ 44062306a36Sopenharmony_ci dasd_eer_snss(device); 44162306a36Sopenharmony_ci cqr = NULL; 44262306a36Sopenharmony_ci } 44362306a36Sopenharmony_ci spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); 44462306a36Sopenharmony_ci if (cqr) 44562306a36Sopenharmony_ci /* 44662306a36Sopenharmony_ci * Extended error recovery has been switched off while 44762306a36Sopenharmony_ci * the SNSS request was running. It could even have 44862306a36Sopenharmony_ci * been switched off and on again in which case there 44962306a36Sopenharmony_ci * is a new ccw in device->eer_cqr. Free the "old" 45062306a36Sopenharmony_ci * snss request now. 45162306a36Sopenharmony_ci */ 45262306a36Sopenharmony_ci dasd_sfree_request(cqr, device); 45362306a36Sopenharmony_ci} 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci/* 45662306a36Sopenharmony_ci * Enable error reporting on a given device. 45762306a36Sopenharmony_ci */ 45862306a36Sopenharmony_ciint dasd_eer_enable(struct dasd_device *device) 45962306a36Sopenharmony_ci{ 46062306a36Sopenharmony_ci struct dasd_ccw_req *cqr = NULL; 46162306a36Sopenharmony_ci unsigned long flags; 46262306a36Sopenharmony_ci struct ccw1 *ccw; 46362306a36Sopenharmony_ci int rc = 0; 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_ci spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags); 46662306a36Sopenharmony_ci if (device->eer_cqr) 46762306a36Sopenharmony_ci goto out; 46862306a36Sopenharmony_ci else if (!device->discipline || 46962306a36Sopenharmony_ci strcmp(device->discipline->name, "ECKD")) 47062306a36Sopenharmony_ci rc = -EMEDIUMTYPE; 47162306a36Sopenharmony_ci else if (test_bit(DASD_FLAG_OFFLINE, &device->flags)) 47262306a36Sopenharmony_ci rc = -EBUSY; 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci if (rc) 47562306a36Sopenharmony_ci goto out; 47662306a36Sopenharmony_ci 47762306a36Sopenharmony_ci cqr = dasd_smalloc_request(DASD_ECKD_MAGIC, 1 /* SNSS */, 47862306a36Sopenharmony_ci SNSS_DATA_SIZE, device, NULL); 47962306a36Sopenharmony_ci if (IS_ERR(cqr)) { 48062306a36Sopenharmony_ci rc = -ENOMEM; 48162306a36Sopenharmony_ci cqr = NULL; 48262306a36Sopenharmony_ci goto out; 48362306a36Sopenharmony_ci } 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci cqr->startdev = device; 48662306a36Sopenharmony_ci cqr->retries = 255; 48762306a36Sopenharmony_ci cqr->expires = 10 * HZ; 48862306a36Sopenharmony_ci clear_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags); 48962306a36Sopenharmony_ci set_bit(DASD_CQR_ALLOW_SLOCK, &cqr->flags); 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci ccw = cqr->cpaddr; 49262306a36Sopenharmony_ci ccw->cmd_code = DASD_ECKD_CCW_SNSS; 49362306a36Sopenharmony_ci ccw->count = SNSS_DATA_SIZE; 49462306a36Sopenharmony_ci ccw->flags = 0; 49562306a36Sopenharmony_ci ccw->cda = (__u32)virt_to_phys(cqr->data); 49662306a36Sopenharmony_ci 49762306a36Sopenharmony_ci cqr->buildclk = get_tod_clock(); 49862306a36Sopenharmony_ci cqr->status = DASD_CQR_FILLED; 49962306a36Sopenharmony_ci cqr->callback = dasd_eer_snss_cb; 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci if (!device->eer_cqr) { 50262306a36Sopenharmony_ci device->eer_cqr = cqr; 50362306a36Sopenharmony_ci cqr = NULL; 50462306a36Sopenharmony_ci } 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ciout: 50762306a36Sopenharmony_ci spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci if (cqr) 51062306a36Sopenharmony_ci dasd_sfree_request(cqr, device); 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_ci return rc; 51362306a36Sopenharmony_ci} 51462306a36Sopenharmony_ci 51562306a36Sopenharmony_ci/* 51662306a36Sopenharmony_ci * Disable error reporting on a given device. 51762306a36Sopenharmony_ci */ 51862306a36Sopenharmony_civoid dasd_eer_disable(struct dasd_device *device) 51962306a36Sopenharmony_ci{ 52062306a36Sopenharmony_ci struct dasd_ccw_req *cqr; 52162306a36Sopenharmony_ci unsigned long flags; 52262306a36Sopenharmony_ci int in_use; 52362306a36Sopenharmony_ci 52462306a36Sopenharmony_ci if (!device->eer_cqr) 52562306a36Sopenharmony_ci return; 52662306a36Sopenharmony_ci spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags); 52762306a36Sopenharmony_ci cqr = device->eer_cqr; 52862306a36Sopenharmony_ci device->eer_cqr = NULL; 52962306a36Sopenharmony_ci clear_bit(DASD_FLAG_EER_SNSS, &device->flags); 53062306a36Sopenharmony_ci in_use = test_and_clear_bit(DASD_FLAG_EER_IN_USE, &device->flags); 53162306a36Sopenharmony_ci spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags); 53262306a36Sopenharmony_ci if (cqr && !in_use) 53362306a36Sopenharmony_ci dasd_sfree_request(cqr, device); 53462306a36Sopenharmony_ci} 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci/* 53762306a36Sopenharmony_ci * SECTION: the device operations 53862306a36Sopenharmony_ci */ 53962306a36Sopenharmony_ci 54062306a36Sopenharmony_ci/* 54162306a36Sopenharmony_ci * On the one side we need a lock to access our internal buffer, on the 54262306a36Sopenharmony_ci * other side a copy_to_user can sleep. So we need to copy the data we have 54362306a36Sopenharmony_ci * to transfer in a readbuffer, which is protected by the readbuffer_mutex. 54462306a36Sopenharmony_ci */ 54562306a36Sopenharmony_cistatic char readbuffer[PAGE_SIZE]; 54662306a36Sopenharmony_cistatic DEFINE_MUTEX(readbuffer_mutex); 54762306a36Sopenharmony_ci 54862306a36Sopenharmony_cistatic int dasd_eer_open(struct inode *inp, struct file *filp) 54962306a36Sopenharmony_ci{ 55062306a36Sopenharmony_ci struct eerbuffer *eerb; 55162306a36Sopenharmony_ci unsigned long flags; 55262306a36Sopenharmony_ci 55362306a36Sopenharmony_ci eerb = kzalloc(sizeof(struct eerbuffer), GFP_KERNEL); 55462306a36Sopenharmony_ci if (!eerb) 55562306a36Sopenharmony_ci return -ENOMEM; 55662306a36Sopenharmony_ci eerb->buffer_page_count = eer_pages; 55762306a36Sopenharmony_ci if (eerb->buffer_page_count < 1 || 55862306a36Sopenharmony_ci eerb->buffer_page_count > INT_MAX / PAGE_SIZE) { 55962306a36Sopenharmony_ci kfree(eerb); 56062306a36Sopenharmony_ci DBF_EVENT(DBF_WARNING, "can't open device since module " 56162306a36Sopenharmony_ci "parameter eer_pages is smaller than 1 or" 56262306a36Sopenharmony_ci " bigger than %d", (int)(INT_MAX / PAGE_SIZE)); 56362306a36Sopenharmony_ci return -EINVAL; 56462306a36Sopenharmony_ci } 56562306a36Sopenharmony_ci eerb->buffersize = eerb->buffer_page_count * PAGE_SIZE; 56662306a36Sopenharmony_ci eerb->buffer = kmalloc_array(eerb->buffer_page_count, sizeof(char *), 56762306a36Sopenharmony_ci GFP_KERNEL); 56862306a36Sopenharmony_ci if (!eerb->buffer) { 56962306a36Sopenharmony_ci kfree(eerb); 57062306a36Sopenharmony_ci return -ENOMEM; 57162306a36Sopenharmony_ci } 57262306a36Sopenharmony_ci if (dasd_eer_allocate_buffer_pages(eerb->buffer, 57362306a36Sopenharmony_ci eerb->buffer_page_count)) { 57462306a36Sopenharmony_ci kfree(eerb->buffer); 57562306a36Sopenharmony_ci kfree(eerb); 57662306a36Sopenharmony_ci return -ENOMEM; 57762306a36Sopenharmony_ci } 57862306a36Sopenharmony_ci filp->private_data = eerb; 57962306a36Sopenharmony_ci spin_lock_irqsave(&bufferlock, flags); 58062306a36Sopenharmony_ci list_add(&eerb->list, &bufferlist); 58162306a36Sopenharmony_ci spin_unlock_irqrestore(&bufferlock, flags); 58262306a36Sopenharmony_ci 58362306a36Sopenharmony_ci return nonseekable_open(inp,filp); 58462306a36Sopenharmony_ci} 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_cistatic int dasd_eer_close(struct inode *inp, struct file *filp) 58762306a36Sopenharmony_ci{ 58862306a36Sopenharmony_ci struct eerbuffer *eerb; 58962306a36Sopenharmony_ci unsigned long flags; 59062306a36Sopenharmony_ci 59162306a36Sopenharmony_ci eerb = (struct eerbuffer *) filp->private_data; 59262306a36Sopenharmony_ci spin_lock_irqsave(&bufferlock, flags); 59362306a36Sopenharmony_ci list_del(&eerb->list); 59462306a36Sopenharmony_ci spin_unlock_irqrestore(&bufferlock, flags); 59562306a36Sopenharmony_ci dasd_eer_free_buffer_pages(eerb->buffer, eerb->buffer_page_count); 59662306a36Sopenharmony_ci kfree(eerb->buffer); 59762306a36Sopenharmony_ci kfree(eerb); 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ci return 0; 60062306a36Sopenharmony_ci} 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_cistatic ssize_t dasd_eer_read(struct file *filp, char __user *buf, 60362306a36Sopenharmony_ci size_t count, loff_t *ppos) 60462306a36Sopenharmony_ci{ 60562306a36Sopenharmony_ci int tc,rc; 60662306a36Sopenharmony_ci int tailcount,effective_count; 60762306a36Sopenharmony_ci unsigned long flags; 60862306a36Sopenharmony_ci struct eerbuffer *eerb; 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci eerb = (struct eerbuffer *) filp->private_data; 61162306a36Sopenharmony_ci if (mutex_lock_interruptible(&readbuffer_mutex)) 61262306a36Sopenharmony_ci return -ERESTARTSYS; 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci spin_lock_irqsave(&bufferlock, flags); 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci if (eerb->residual < 0) { /* the remainder of this record */ 61762306a36Sopenharmony_ci /* has been deleted */ 61862306a36Sopenharmony_ci eerb->residual = 0; 61962306a36Sopenharmony_ci spin_unlock_irqrestore(&bufferlock, flags); 62062306a36Sopenharmony_ci mutex_unlock(&readbuffer_mutex); 62162306a36Sopenharmony_ci return -EIO; 62262306a36Sopenharmony_ci } else if (eerb->residual > 0) { 62362306a36Sopenharmony_ci /* OK we still have a second half of a record to deliver */ 62462306a36Sopenharmony_ci effective_count = min(eerb->residual, (int) count); 62562306a36Sopenharmony_ci eerb->residual -= effective_count; 62662306a36Sopenharmony_ci } else { 62762306a36Sopenharmony_ci tc = 0; 62862306a36Sopenharmony_ci while (!tc) { 62962306a36Sopenharmony_ci tc = dasd_eer_read_buffer(eerb, (char *) &tailcount, 63062306a36Sopenharmony_ci sizeof(tailcount)); 63162306a36Sopenharmony_ci if (!tc) { 63262306a36Sopenharmony_ci /* no data available */ 63362306a36Sopenharmony_ci spin_unlock_irqrestore(&bufferlock, flags); 63462306a36Sopenharmony_ci mutex_unlock(&readbuffer_mutex); 63562306a36Sopenharmony_ci if (filp->f_flags & O_NONBLOCK) 63662306a36Sopenharmony_ci return -EAGAIN; 63762306a36Sopenharmony_ci rc = wait_event_interruptible( 63862306a36Sopenharmony_ci dasd_eer_read_wait_queue, 63962306a36Sopenharmony_ci eerb->head != eerb->tail); 64062306a36Sopenharmony_ci if (rc) 64162306a36Sopenharmony_ci return rc; 64262306a36Sopenharmony_ci if (mutex_lock_interruptible(&readbuffer_mutex)) 64362306a36Sopenharmony_ci return -ERESTARTSYS; 64462306a36Sopenharmony_ci spin_lock_irqsave(&bufferlock, flags); 64562306a36Sopenharmony_ci } 64662306a36Sopenharmony_ci } 64762306a36Sopenharmony_ci WARN_ON(tc != sizeof(tailcount)); 64862306a36Sopenharmony_ci effective_count = min(tailcount,(int)count); 64962306a36Sopenharmony_ci eerb->residual = tailcount - effective_count; 65062306a36Sopenharmony_ci } 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_ci tc = dasd_eer_read_buffer(eerb, readbuffer, effective_count); 65362306a36Sopenharmony_ci WARN_ON(tc != effective_count); 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci spin_unlock_irqrestore(&bufferlock, flags); 65662306a36Sopenharmony_ci 65762306a36Sopenharmony_ci if (copy_to_user(buf, readbuffer, effective_count)) { 65862306a36Sopenharmony_ci mutex_unlock(&readbuffer_mutex); 65962306a36Sopenharmony_ci return -EFAULT; 66062306a36Sopenharmony_ci } 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci mutex_unlock(&readbuffer_mutex); 66362306a36Sopenharmony_ci return effective_count; 66462306a36Sopenharmony_ci} 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_cistatic __poll_t dasd_eer_poll(struct file *filp, poll_table *ptable) 66762306a36Sopenharmony_ci{ 66862306a36Sopenharmony_ci __poll_t mask; 66962306a36Sopenharmony_ci unsigned long flags; 67062306a36Sopenharmony_ci struct eerbuffer *eerb; 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ci eerb = (struct eerbuffer *) filp->private_data; 67362306a36Sopenharmony_ci poll_wait(filp, &dasd_eer_read_wait_queue, ptable); 67462306a36Sopenharmony_ci spin_lock_irqsave(&bufferlock, flags); 67562306a36Sopenharmony_ci if (eerb->head != eerb->tail) 67662306a36Sopenharmony_ci mask = EPOLLIN | EPOLLRDNORM ; 67762306a36Sopenharmony_ci else 67862306a36Sopenharmony_ci mask = 0; 67962306a36Sopenharmony_ci spin_unlock_irqrestore(&bufferlock, flags); 68062306a36Sopenharmony_ci return mask; 68162306a36Sopenharmony_ci} 68262306a36Sopenharmony_ci 68362306a36Sopenharmony_cistatic const struct file_operations dasd_eer_fops = { 68462306a36Sopenharmony_ci .open = &dasd_eer_open, 68562306a36Sopenharmony_ci .release = &dasd_eer_close, 68662306a36Sopenharmony_ci .read = &dasd_eer_read, 68762306a36Sopenharmony_ci .poll = &dasd_eer_poll, 68862306a36Sopenharmony_ci .owner = THIS_MODULE, 68962306a36Sopenharmony_ci .llseek = noop_llseek, 69062306a36Sopenharmony_ci}; 69162306a36Sopenharmony_ci 69262306a36Sopenharmony_cistatic struct miscdevice *dasd_eer_dev = NULL; 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ciint __init dasd_eer_init(void) 69562306a36Sopenharmony_ci{ 69662306a36Sopenharmony_ci int rc; 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci dasd_eer_dev = kzalloc(sizeof(*dasd_eer_dev), GFP_KERNEL); 69962306a36Sopenharmony_ci if (!dasd_eer_dev) 70062306a36Sopenharmony_ci return -ENOMEM; 70162306a36Sopenharmony_ci 70262306a36Sopenharmony_ci dasd_eer_dev->minor = MISC_DYNAMIC_MINOR; 70362306a36Sopenharmony_ci dasd_eer_dev->name = "dasd_eer"; 70462306a36Sopenharmony_ci dasd_eer_dev->fops = &dasd_eer_fops; 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_ci rc = misc_register(dasd_eer_dev); 70762306a36Sopenharmony_ci if (rc) { 70862306a36Sopenharmony_ci kfree(dasd_eer_dev); 70962306a36Sopenharmony_ci dasd_eer_dev = NULL; 71062306a36Sopenharmony_ci DBF_EVENT(DBF_ERR, "%s", "dasd_eer_init could not " 71162306a36Sopenharmony_ci "register misc device"); 71262306a36Sopenharmony_ci return rc; 71362306a36Sopenharmony_ci } 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_ci return 0; 71662306a36Sopenharmony_ci} 71762306a36Sopenharmony_ci 71862306a36Sopenharmony_civoid dasd_eer_exit(void) 71962306a36Sopenharmony_ci{ 72062306a36Sopenharmony_ci if (dasd_eer_dev) { 72162306a36Sopenharmony_ci misc_deregister(dasd_eer_dev); 72262306a36Sopenharmony_ci kfree(dasd_eer_dev); 72362306a36Sopenharmony_ci dasd_eer_dev = NULL; 72462306a36Sopenharmony_ci } 72562306a36Sopenharmony_ci} 726