📄 dasd.c
字号:
/* * File...........: linux/drivers/s390/block/dasd.c * Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com> * Horst Hummel <Horst.Hummel@de.ibm.com> * Carsten Otte <Cotte@de.ibm.com> * Martin Schwidefsky <schwidefsky@de.ibm.com> * Bugreports.to..: <Linux390@de.ibm.com> * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001 * * $Revision: 1.147 $ */#include <linux/config.h>#include <linux/kmod.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/ctype.h>#include <linux/major.h>#include <linux/slab.h>#include <linux/buffer_head.h>#include <asm/ccwdev.h>#include <asm/ebcdic.h>#include <asm/idals.h>#include <asm/todclk.h>/* This is ugly... */#define PRINTK_HEADER "dasd:"#include "dasd_int.h"/* * SECTION: Constant definitions to be used within this file */#define DASD_CHANQ_MAX_SIZE 4/* * SECTION: exported variables of dasd.c */debug_info_t *dasd_debug_area;struct dasd_discipline *dasd_diag_discipline_pointer;MODULE_AUTHOR("Holger Smolinski <Holger.Smolinski@de.ibm.com>");MODULE_DESCRIPTION("Linux on S/390 DASD device driver," " Copyright 2000 IBM Corporation");MODULE_SUPPORTED_DEVICE("dasd");MODULE_PARM(dasd, "1-" __MODULE_STRING(256) "s");MODULE_LICENSE("GPL");/* * SECTION: prototypes for static functions of dasd.c */static int dasd_alloc_queue(struct dasd_device * device);static void dasd_setup_queue(struct dasd_device * device);static void dasd_free_queue(struct dasd_device * device);static void dasd_flush_request_queue(struct dasd_device *);static void dasd_int_handler(struct ccw_device *, unsigned long, struct irb *);static void dasd_flush_ccw_queue(struct dasd_device *, int);static void dasd_tasklet(struct dasd_device *);static void do_kick_device(void *data);/* * SECTION: Operations on the device structure. */static wait_queue_head_t dasd_init_waitq;/* * Allocate memory for a new device structure. */struct dasd_device *dasd_alloc_device(void){ struct dasd_device *device; device = kmalloc(sizeof (struct dasd_device), GFP_ATOMIC); if (device == NULL) return ERR_PTR(-ENOMEM); memset(device, 0, sizeof (struct dasd_device)); /* open_count = 0 means device online but not in use */ atomic_set(&device->open_count, -1); /* Get two pages for normal block device operations. */ device->ccw_mem = (void *) __get_free_pages(GFP_ATOMIC | GFP_DMA, 1); if (device->ccw_mem == NULL) { kfree(device); return ERR_PTR(-ENOMEM); } /* Get one page for error recovery. */ device->erp_mem = (void *) get_zeroed_page(GFP_ATOMIC | GFP_DMA); if (device->erp_mem == NULL) { free_pages((unsigned long) device->ccw_mem, 1); kfree(device); return ERR_PTR(-ENOMEM); } dasd_init_chunklist(&device->ccw_chunks, device->ccw_mem, PAGE_SIZE*2); dasd_init_chunklist(&device->erp_chunks, device->erp_mem, PAGE_SIZE); spin_lock_init(&device->mem_lock); spin_lock_init(&device->request_queue_lock); atomic_set (&device->tasklet_scheduled, 0); tasklet_init(&device->tasklet, (void (*)(unsigned long)) dasd_tasklet, (unsigned long) device); INIT_LIST_HEAD(&device->ccw_queue); init_timer(&device->timer); INIT_WORK(&device->kick_work, do_kick_device, device); device->state = DASD_STATE_NEW; device->target = DASD_STATE_NEW; return device;}/* * Free memory of a device structure. */voiddasd_free_device(struct dasd_device *device){ if (device->private) kfree(device->private); free_page((unsigned long) device->erp_mem); free_pages((unsigned long) device->ccw_mem, 1); kfree(device);}/* * Make a new device known to the system. */static inline intdasd_state_new_to_known(struct dasd_device *device){ int rc; /* * As long as the device is not in state DASD_STATE_NEW we want to * keep the reference count > 0. */ dasd_get_device(device); rc = dasd_alloc_queue(device); if (rc) { dasd_put_device(device); return rc; } device->state = DASD_STATE_KNOWN; return 0;}/* * Let the system forget about a device. */static inline voiddasd_state_known_to_new(struct dasd_device * device){ /* Forget the discipline information. */ device->discipline = NULL; device->state = DASD_STATE_NEW; dasd_free_queue(device); /* Give up reference we took in dasd_state_new_to_known. */ dasd_put_device(device);}/* * Request the irq line for the device. */static inline intdasd_state_known_to_basic(struct dasd_device * device){ int rc; /* Allocate and register gendisk structure. */ rc = dasd_gendisk_alloc(device); if (rc) return rc; /* register 'device' debug area, used for all DBF_DEV_XXX calls */ device->debug_area = debug_register(device->cdev->dev.bus_id, 0, 2, 8 * sizeof (long)); debug_register_view(device->debug_area, &debug_sprintf_view); debug_set_level(device->debug_area, DBF_ERR); DBF_DEV_EVENT(DBF_EMERG, device, "%s", "debug area created"); device->state = DASD_STATE_BASIC; return 0;}/* * Release the irq line for the device. Terminate any running i/o. */static inline voiddasd_state_basic_to_known(struct dasd_device * device){ dasd_gendisk_free(device); dasd_flush_ccw_queue(device, 1); DBF_DEV_EVENT(DBF_EMERG, device, "%p debug area deleted", device); if (device->debug_area != NULL) { debug_unregister(device->debug_area); device->debug_area = NULL; } device->state = DASD_STATE_KNOWN;}/* * Do the initial analysis. The do_analysis function may return * -EAGAIN in which case the device keeps the state DASD_STATE_BASIC * until the discipline decides to continue the startup sequence * by calling the function dasd_change_state. The eckd disciplines * uses this to start a ccw that detects the format. The completion * interrupt for this detection ccw uses the kernel event daemon to * trigger the call to dasd_change_state. All this is done in the * discipline code, see dasd_eckd.c. * After the analysis ccw is done (do_analysis returned 0 or error) * the block device is setup. Either a fake disk is added to allow * formatting or a proper device request queue is created. */static inline intdasd_state_basic_to_ready(struct dasd_device * device){ int rc; rc = 0; if (device->discipline->do_analysis != NULL) rc = device->discipline->do_analysis(device); if (rc) return rc; dasd_setup_queue(device); device->state = DASD_STATE_READY; if (dasd_scan_partitions(device) != 0) device->state = DASD_STATE_BASIC; return 0;}/* * Remove device from block device layer. Destroy dirty buffers. * Forget format information. Check if the target level is basic * and if it is create fake disk for formatting. */static inline voiddasd_state_ready_to_basic(struct dasd_device * device){ dasd_flush_ccw_queue(device, 0); dasd_destroy_partitions(device); dasd_flush_request_queue(device); device->blocks = 0; device->bp_block = 0; device->s2b_shift = 0; device->state = DASD_STATE_BASIC;}/* * Make the device online and schedule the bottom half to start * the requeueing of requests from the linux request queue to the * ccw queue. */static inline intdasd_state_ready_to_online(struct dasd_device * device){ device->state = DASD_STATE_ONLINE; dasd_schedule_bh(device); return 0;}/* * Stop the requeueing of requests again. */static inline voiddasd_state_online_to_ready(struct dasd_device * device){ device->state = DASD_STATE_READY;}/* * Device startup state changes. */static inline intdasd_increase_state(struct dasd_device *device){ int rc; rc = 0; if (device->state == DASD_STATE_NEW && device->target >= DASD_STATE_KNOWN) rc = dasd_state_new_to_known(device); if (!rc && device->state == DASD_STATE_KNOWN && device->target >= DASD_STATE_BASIC) rc = dasd_state_known_to_basic(device); if (!rc && device->state == DASD_STATE_BASIC && device->target >= DASD_STATE_READY) rc = dasd_state_basic_to_ready(device); if (!rc && device->state == DASD_STATE_READY && device->target >= DASD_STATE_ONLINE) rc = dasd_state_ready_to_online(device); return rc;}/* * Device shutdown state changes. */static inline intdasd_decrease_state(struct dasd_device *device){ if (device->state == DASD_STATE_ONLINE && device->target <= DASD_STATE_READY) dasd_state_online_to_ready(device); if (device->state == DASD_STATE_READY && device->target <= DASD_STATE_BASIC) dasd_state_ready_to_basic(device); if (device->state == DASD_STATE_BASIC && device->target <= DASD_STATE_KNOWN) dasd_state_basic_to_known(device); if (device->state == DASD_STATE_KNOWN && device->target <= DASD_STATE_NEW) dasd_state_known_to_new(device); return 0;}/* * This is the main startup/shutdown routine. */static voiddasd_change_state(struct dasd_device *device){ int rc; if (device->state == device->target) /* Already where we want to go today... */ return; if (device->state < device->target) rc = dasd_increase_state(device); else rc = dasd_decrease_state(device); if (rc && rc != -EAGAIN) device->target = device->state; if (device->state == device->target) wake_up(&dasd_init_waitq);}/* * Kick starter for devices that did not complete the startup/shutdown * procedure or were sleeping because of a pending state. * dasd_kick_device will schedule a call do do_kick_device to the kernel * event daemon. */static voiddo_kick_device(void *data){ struct dasd_device *device; device = (struct dasd_device *) data; dasd_change_state(device); dasd_schedule_bh(device); dasd_put_device(device);}voiddasd_kick_device(struct dasd_device *device){ dasd_get_device(device); /* queue call to dasd_kick_device to the kernel event daemon. */ schedule_work(&device->kick_work);}/* * Set the target state for a device and starts the state change. */voiddasd_set_target_state(struct dasd_device *device, int target){ /* If we are in probeonly mode stop at DASD_STATE_READY. */ if (dasd_probeonly && target > DASD_STATE_READY) target = DASD_STATE_READY; if (device->target != target) { if (device->state == target) wake_up(&dasd_init_waitq); device->target = target; } if (device->state != device->target) dasd_change_state(device);}/* * Enable devices with device numbers in [from..to]. */static inline int_wait_for_device(struct dasd_device *device){ return (device->state == device->target);}voiddasd_enable_device(struct dasd_device *device){ dasd_set_target_state(device, DASD_STATE_ONLINE); if (device->state <= DASD_STATE_KNOWN) /* No discipline for device found. */ dasd_set_target_state(device, DASD_STATE_NEW); /* Now wait for the devices to come up. */ wait_event(dasd_init_waitq, _wait_for_device(device));}/* * SECTION: device operation (interrupt handler, start i/o, term i/o ...) */#ifdef CONFIG_DASD_PROFILEstruct dasd_profile_info_t dasd_global_profile;unsigned int dasd_profile_level = DASD_PROFILE_OFF;/* * Increments counter in global and local profiling structures. */#define dasd_profile_counter(value, counter, device) \{ \ int index; \ for (index = 0; index < 31 && value >> (2+index); index++); \ dasd_global_profile.counter[index]++; \ device->profile.counter[index]++; \}/* * Add profiling information for cqr before execution. */static inline voiddasd_profile_start(struct dasd_device *device, struct dasd_ccw_req * cqr, struct request *req){ struct list_head *l; unsigned int counter; if (dasd_profile_level != DASD_PROFILE_ON) return; /* count the length of the chanq for statistics */ counter = 0; list_for_each(l, &device->ccw_queue) if (++counter >= 31) break; dasd_global_profile.dasd_io_nr_req[counter]++; device->profile.dasd_io_nr_req[counter]++;}/* * Add profiling information for cqr after execution. */static inline voiddasd_profile_end(struct dasd_device *device, struct dasd_ccw_req * cqr, struct request *req){ long strtime, irqtime, endtime, tottime; /* in microseconds */ long tottimeps, sectors; if (dasd_profile_level != DASD_PROFILE_ON) return; sectors = req->nr_sectors; if (!cqr->buildclk || !cqr->startclk || !cqr->stopclk || !cqr->endclk || !sectors) return; strtime = ((cqr->startclk - cqr->buildclk) >> 12); irqtime = ((cqr->stopclk - cqr->startclk) >> 12); endtime = ((cqr->endclk - cqr->stopclk) >> 12); tottime = ((cqr->endclk - cqr->buildclk) >> 12); tottimeps = tottime / sectors; if (!dasd_global_profile.dasd_io_reqs) memset(&dasd_global_profile, 0, sizeof (struct dasd_profile_info_t)); dasd_global_profile.dasd_io_reqs++; dasd_global_profile.dasd_io_sects += sectors; if (!device->profile.dasd_io_reqs) memset(&device->profile, 0, sizeof (struct dasd_profile_info_t)); device->profile.dasd_io_reqs++; device->profile.dasd_io_sects += sectors; dasd_profile_counter(sectors, dasd_io_secs, device); dasd_profile_counter(tottime, dasd_io_times, device); dasd_profile_counter(tottimeps, dasd_io_timps, device); dasd_profile_counter(strtime, dasd_io_time1, device); dasd_profile_counter(irqtime, dasd_io_time2, device); dasd_profile_counter(irqtime / sectors, dasd_io_time2ps, device); dasd_profile_counter(endtime, dasd_io_time3, device);}#else#define dasd_profile_start(device, cqr, req) do {} while (0)#define dasd_profile_end(device, cqr, req) do {} while (0)#endif /* CONFIG_DASD_PROFILE *//* * Allocate memory for a channel program with 'cplength' channel * command words and 'datasize' additional space. There are two * variantes: 1) dasd_kmalloc_request uses kmalloc to get the needed * memory and 2) dasd_smalloc_request uses the static ccw memory * that gets allocated for each device. */struct dasd_ccw_req *dasd_kmalloc_request(char *magic, int cplength, int datasize,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -