📄 tape_core.c
字号:
/* * drivers/s390/char/tape_core.c * basic function of the tape device driver * * S390 and zSeries version * Copyright (C) 2001,2005 IBM Deutschland Entwicklung GmbH, IBM Corporation * Author(s): Carsten Otte <cotte@de.ibm.com> * Michael Holzheu <holzheu@de.ibm.com> * Tuan Ngo-Anh <ngoanh@de.ibm.com> * Martin Schwidefsky <schwidefsky@de.ibm.com> * Stefan Bader <shbader@de.ibm.com> */#include <linux/config.h>#include <linux/module.h>#include <linux/init.h> // for kernel parameters#include <linux/kmod.h> // for requesting modules#include <linux/spinlock.h> // for locks#include <linux/vmalloc.h>#include <linux/list.h>#include <asm/types.h> // for variable types#define TAPE_DBF_AREA tape_core_dbf#include "tape.h"#include "tape_std.h"#define PRINTK_HEADER "TAPE_CORE: "static void __tape_do_irq (struct ccw_device *, unsigned long, struct irb *);static void tape_delayed_next_request(void * data);/* * One list to contain all tape devices of all disciplines, so * we can assign the devices to minor numbers of the same major * The list is protected by the rwlock */static struct list_head tape_device_list = LIST_HEAD_INIT(tape_device_list);static DEFINE_RWLOCK(tape_device_lock);/* * Pointer to debug area. */debug_info_t *TAPE_DBF_AREA = NULL;EXPORT_SYMBOL(TAPE_DBF_AREA);/* * Printable strings for tape enumerations. */const char *tape_state_verbose[TS_SIZE] ={ [TS_UNUSED] = "UNUSED", [TS_IN_USE] = "IN_USE", [TS_BLKUSE] = "BLKUSE", [TS_INIT] = "INIT ", [TS_NOT_OPER] = "NOT_OP"};const char *tape_op_verbose[TO_SIZE] ={ [TO_BLOCK] = "BLK", [TO_BSB] = "BSB", [TO_BSF] = "BSF", [TO_DSE] = "DSE", [TO_FSB] = "FSB", [TO_FSF] = "FSF", [TO_LBL] = "LBL", [TO_NOP] = "NOP", [TO_RBA] = "RBA", [TO_RBI] = "RBI", [TO_RFO] = "RFO", [TO_REW] = "REW", [TO_RUN] = "RUN", [TO_WRI] = "WRI", [TO_WTM] = "WTM", [TO_MSEN] = "MSN", [TO_LOAD] = "LOA", [TO_READ_CONFIG] = "RCF", [TO_READ_ATTMSG] = "RAT", [TO_DIS] = "DIS", [TO_ASSIGN] = "ASS", [TO_UNASSIGN] = "UAS"};static inline intbusid_to_int(char *bus_id){ int dec; int d; char * s; for(s = bus_id, d = 0; *s != '\0' && *s != '.'; s++) d = (d * 10) + (*s - '0'); dec = d; for(s++, d = 0; *s != '\0' && *s != '.'; s++) d = (d * 10) + (*s - '0'); dec = (dec << 8) + d; for(s++; *s != '\0'; s++) { if (*s >= '0' && *s <= '9') { d = *s - '0'; } else if (*s >= 'a' && *s <= 'f') { d = *s - 'a' + 10; } else { d = *s - 'A' + 10; } dec = (dec << 4) + d; } return dec;}/* * Some channel attached tape specific attributes. * * FIXME: In the future the first_minor and blocksize attribute should be * replaced by a link to the cdev tree. */static ssize_ttape_medium_state_show(struct device *dev, struct device_attribute *attr, char *buf){ struct tape_device *tdev; tdev = (struct tape_device *) dev->driver_data; return scnprintf(buf, PAGE_SIZE, "%i\n", tdev->medium_state);}staticDEVICE_ATTR(medium_state, 0444, tape_medium_state_show, NULL);static ssize_ttape_first_minor_show(struct device *dev, struct device_attribute *attr, char *buf){ struct tape_device *tdev; tdev = (struct tape_device *) dev->driver_data; return scnprintf(buf, PAGE_SIZE, "%i\n", tdev->first_minor);}staticDEVICE_ATTR(first_minor, 0444, tape_first_minor_show, NULL);static ssize_ttape_state_show(struct device *dev, struct device_attribute *attr, char *buf){ struct tape_device *tdev; tdev = (struct tape_device *) dev->driver_data; return scnprintf(buf, PAGE_SIZE, "%s\n", (tdev->first_minor < 0) ? "OFFLINE" : tape_state_verbose[tdev->tape_state]);}staticDEVICE_ATTR(state, 0444, tape_state_show, NULL);static ssize_ttape_operation_show(struct device *dev, struct device_attribute *attr, char *buf){ struct tape_device *tdev; ssize_t rc; tdev = (struct tape_device *) dev->driver_data; if (tdev->first_minor < 0) return scnprintf(buf, PAGE_SIZE, "N/A\n"); spin_lock_irq(get_ccwdev_lock(tdev->cdev)); if (list_empty(&tdev->req_queue)) rc = scnprintf(buf, PAGE_SIZE, "---\n"); else { struct tape_request *req; req = list_entry(tdev->req_queue.next, struct tape_request, list); rc = scnprintf(buf,PAGE_SIZE, "%s\n", tape_op_verbose[req->op]); } spin_unlock_irq(get_ccwdev_lock(tdev->cdev)); return rc;}staticDEVICE_ATTR(operation, 0444, tape_operation_show, NULL);static ssize_ttape_blocksize_show(struct device *dev, struct device_attribute *attr, char *buf){ struct tape_device *tdev; tdev = (struct tape_device *) dev->driver_data; return scnprintf(buf, PAGE_SIZE, "%i\n", tdev->char_data.block_size);}staticDEVICE_ATTR(blocksize, 0444, tape_blocksize_show, NULL);static struct attribute *tape_attrs[] = { &dev_attr_medium_state.attr, &dev_attr_first_minor.attr, &dev_attr_state.attr, &dev_attr_operation.attr, &dev_attr_blocksize.attr, NULL};static struct attribute_group tape_attr_group = { .attrs = tape_attrs,};/* * Tape state functions */voidtape_state_set(struct tape_device *device, enum tape_state newstate){ const char *str; if (device->tape_state == TS_NOT_OPER) { DBF_EVENT(3, "ts_set err: not oper\n"); return; } DBF_EVENT(4, "ts. dev: %x\n", device->first_minor); if (device->tape_state < TO_SIZE && device->tape_state >= 0) str = tape_state_verbose[device->tape_state]; else str = "UNKNOWN TS"; DBF_EVENT(4, "old ts: %s\n", str); if (device->tape_state < TO_SIZE && device->tape_state >=0 ) str = tape_state_verbose[device->tape_state]; else str = "UNKNOWN TS"; DBF_EVENT(4, "%s\n", str); DBF_EVENT(4, "new ts:\t\n"); if (newstate < TO_SIZE && newstate >= 0) str = tape_state_verbose[newstate]; else str = "UNKNOWN TS"; DBF_EVENT(4, "%s\n", str); device->tape_state = newstate; wake_up(&device->state_change_wq);}voidtape_med_state_set(struct tape_device *device, enum tape_medium_state newstate){ if (device->medium_state == newstate) return; switch(newstate){ case MS_UNLOADED: device->tape_generic_status |= GMT_DR_OPEN(~0); PRINT_INFO("(%s): Tape is unloaded\n", device->cdev->dev.bus_id); break; case MS_LOADED: device->tape_generic_status &= ~GMT_DR_OPEN(~0); PRINT_INFO("(%s): Tape has been mounted\n", device->cdev->dev.bus_id); break; default: // print nothing break; } device->medium_state = newstate; wake_up(&device->state_change_wq);}/* * Stop running ccw. Has to be called with the device lock held. */static inline int__tape_cancel_io(struct tape_device *device, struct tape_request *request){ int retries; int rc; /* Check if interrupt has already been processed */ if (request->callback == NULL) return 0; rc = 0; for (retries = 0; retries < 5; retries++) { rc = ccw_device_clear(device->cdev, (long) request); switch (rc) { case 0: request->status = TAPE_REQUEST_DONE; return 0; case -EBUSY: request->status = TAPE_REQUEST_CANCEL; schedule_work(&device->tape_dnr); return 0; case -ENODEV: DBF_EXCEPTION(2, "device gone, retry\n"); break; case -EIO: DBF_EXCEPTION(2, "I/O error, retry\n"); break; default: BUG(); } } return rc;}/* * Add device into the sorted list, giving it the first * available minor number. */static inttape_assign_minor(struct tape_device *device){ struct tape_device *tmp; int minor; minor = 0; write_lock(&tape_device_lock); list_for_each_entry(tmp, &tape_device_list, node) { if (minor < tmp->first_minor) break; minor += TAPE_MINORS_PER_DEV; } if (minor >= 256) { write_unlock(&tape_device_lock); return -ENODEV; } device->first_minor = minor; list_add_tail(&device->node, &tmp->node); write_unlock(&tape_device_lock); return 0;}/* remove device from the list */static voidtape_remove_minor(struct tape_device *device){ write_lock(&tape_device_lock); list_del_init(&device->node); device->first_minor = -1; write_unlock(&tape_device_lock);}/* * Set a device online. * * This function is called by the common I/O layer to move a device from the * detected but offline into the online state. * If we return an error (RC < 0) the device remains in the offline state. This * can happen if the device is assigned somewhere else, for example. */inttape_generic_online(struct tape_device *device, struct tape_discipline *discipline){ int rc; DBF_LH(6, "tape_enable_device(%p, %p)\n", device, discipline); if (device->tape_state != TS_INIT) { DBF_LH(3, "Tapestate not INIT (%d)\n", device->tape_state); return -EINVAL; } /* Let the discipline have a go at the device. */ device->discipline = discipline; if (!try_module_get(discipline->owner)) { PRINT_ERR("Cannot get module. Module gone.\n"); return -EINVAL; } rc = discipline->setup_device(device); if (rc) goto out; rc = tape_assign_minor(device); if (rc) goto out_discipline; rc = tapechar_setup_device(device); if (rc) goto out_minor; rc = tapeblock_setup_device(device); if (rc) goto out_char; tape_state_set(device, TS_UNUSED); DBF_LH(3, "(%08x): Drive set online\n", device->cdev_id); return 0;out_char: tapechar_cleanup_device(device);out_discipline: device->discipline->cleanup_device(device); device->discipline = NULL;out_minor: tape_remove_minor(device);out: module_put(discipline->owner); return rc;}static inline voidtape_cleanup_device(struct tape_device *device){ tapeblock_cleanup_device(device); tapechar_cleanup_device(device); device->discipline->cleanup_device(device); module_put(device->discipline->owner); tape_remove_minor(device); tape_med_state_set(device, MS_UNKNOWN);}/* * Set device offline. * * Called by the common I/O layer if the drive should set offline on user * request. We may prevent this by returning an error. * Manual offline is only allowed while the drive is not in use. */inttape_generic_offline(struct tape_device *device){ if (!device) { PRINT_ERR("tape_generic_offline: no such device\n"); return -ENODEV; } DBF_LH(3, "(%08x): tape_generic_offline(%p)\n", device->cdev_id, device); spin_lock_irq(get_ccwdev_lock(device->cdev)); switch (device->tape_state) { case TS_INIT: case TS_NOT_OPER: spin_unlock_irq(get_ccwdev_lock(device->cdev)); break; case TS_UNUSED: tape_state_set(device, TS_INIT); spin_unlock_irq(get_ccwdev_lock(device->cdev)); tape_cleanup_device(device);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -