/* * File...........: linux/drivers/s390/block/dasd_fba.c * Author(s)......: Holger Smolinski * Bugreports.to..: * (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000 * fixed partition handling and HDIO_GETGEO */ #include #include #include #include #include #include /* HDIO_GETGEO */ #include #include #include #include #include #include #include #include "dasd_int.h" #include "dasd_fba.h" #include "dasd_3370_erp.h" #include "dasd_9336_erp.h" #ifdef PRINTK_HEADER #undef PRINTK_HEADER #endif /* PRINTK_HEADER */ #define PRINTK_HEADER DASD_NAME"(fba):" #define DASD_FBA_CCW_WRITE 0x41 #define DASD_FBA_CCW_READ 0x42 #define DASD_FBA_CCW_LOCATE 0x43 #define DASD_FBA_CCW_DEFINE_EXTENT 0x63 dasd_discipline_t dasd_fba_discipline; typedef struct dasd_fba_private_t { dasd_fba_characteristics_t rdc_data; } dasd_fba_private_t; #ifdef CONFIG_DASD_DYNAMIC static devreg_t dasd_fba_known_devices[] = { { ci: { hc: {ctype:0x6310, dtype:0x9336}}, flag:(DEVREG_MATCH_CU_TYPE | DEVREG_MATCH_DEV_TYPE | DEVREG_TYPE_DEVCHARS), oper_func:dasd_oper_handler }, { ci: { hc: {ctype:0x3880, dtype:0x3370}}, flag:(DEVREG_MATCH_CU_TYPE | DEVREG_MATCH_DEV_TYPE | DEVREG_TYPE_DEVCHARS), oper_func:dasd_oper_handler } }; #endif static inline int define_extent (ccw1_t * ccw, DE_fba_data_t * DE_data, int rw, int blksize, int beg, int nr, ccw_req_t* cqr, dasd_device_t* device) { int rc=0; memset (DE_data, 0, sizeof (DE_fba_data_t)); ccw->cmd_code = DASD_FBA_CCW_DEFINE_EXTENT; ccw->count = 16; if ((rc=dasd_set_normalized_cda (ccw, __pa (DE_data), cqr, device))) return rc; if (rw == WRITE) (DE_data->mask).perm = 0x0; else if (rw == READ) (DE_data->mask).perm = 0x1; else DE_data->mask.perm = 0x2; DE_data->blk_size = blksize; DE_data->ext_loc = beg; DE_data->ext_end = nr - 1; return rc; } static inline void locate_record (ccw1_t * ccw, LO_fba_data_t * LO_data, int rw, int block_nr, int block_ct, ccw_req_t* cqr, dasd_device_t* device) { memset (LO_data, 0, sizeof (LO_fba_data_t)); ccw->cmd_code = DASD_FBA_CCW_LOCATE; ccw->count = 8; dasd_set_normalized_cda (ccw, __pa (LO_data), cqr, device); if (rw == WRITE) LO_data->operation.cmd = 0x5; else if (rw == READ) LO_data->operation.cmd = 0x6; else LO_data->operation.cmd = 0x8; LO_data->blk_nr = block_nr; LO_data->blk_ct = block_ct; } static int dasd_fba_id_check (s390_dev_info_t * info) { if (info->sid_data.cu_type == 0x3880) if (info->sid_data.dev_type == 0x3370) return 0; if (info->sid_data.cu_type == 0x6310) if (info->sid_data.dev_type == 0x9336) return 0; return -ENODEV; } static int dasd_fba_check_characteristics (struct dasd_device_t *device) { int rc = -ENODEV; void *rdc_data; dasd_fba_private_t *private; if (device == NULL) { printk (KERN_WARNING PRINTK_HEADER "Null device pointer passed to characteristics checker\n"); return -ENODEV; } device->private = kmalloc (sizeof (dasd_fba_private_t), GFP_KERNEL); if (device->private == NULL) { printk (KERN_WARNING PRINTK_HEADER "memory allocation failed for private data\n"); rc = -ENOMEM; goto fail; } private = (dasd_fba_private_t *) device->private; rdc_data = (void *) &(private->rdc_data); rc = read_dev_chars (device->devinfo.irq, &rdc_data, 32); if (rc) { printk (KERN_WARNING PRINTK_HEADER "Read device characteristics returned error %d\n", rc); goto fail; } printk (KERN_INFO PRINTK_HEADER "%04X on sch %d: %04X/%02X(CU:%04X/%02X) %dMB at(%d B/blk)\n", device->devinfo.devno, device->devinfo.irq, device->devinfo.sid_data.dev_type, device->devinfo.sid_data.dev_model, device->devinfo.sid_data.cu_type, device->devinfo.sid_data.cu_model, (private->rdc_data.blk_bdsa * (private->rdc_data.blk_size >> 9)) >> 11, private->rdc_data.blk_size); goto out; fail: if ( rc ) { kfree(device->private); device->private = NULL; } out: return rc; } static int dasd_fba_do_analysis (struct dasd_device_t *device) { int rc = 0; int sb; dasd_fba_private_t *private = (dasd_fba_private_t *) device->private; int bs = private->rdc_data.blk_size; memset (&(device->sizes), 0, sizeof (dasd_sizes_t)); switch (bs) { case 512: case 1024: case 2048: case 4096: device->sizes.bp_block = bs; break; default: printk (KERN_INFO PRINTK_HEADER "/dev/%s (%04X): unknown blocksize %d\n", device->name, device->devinfo.devno, bs); return -EMEDIUMTYPE; } device->sizes.s2b_shift = 0; /* bits to shift 512 to get a block */ for (sb = 512; sb < bs; sb = sb << 1) device->sizes.s2b_shift++; device->sizes.blocks = (private->rdc_data.blk_bdsa); device->sizes.pt_block = 1; return rc; } static int dasd_fba_fill_geometry (struct dasd_device_t *device, struct hd_geometry *geo) { int rc = 0; unsigned long sectors = device->sizes.blocks << device->sizes.s2b_shift; unsigned long tracks = sectors >> 6; unsigned long cyls = tracks >> 4; switch (device->sizes.bp_block) { case 512: case 1024: case 2048: case 4096: break; default: return -EINVAL; } geo->cylinders = cyls; geo->heads = 16; geo->sectors = 128 >> device->sizes.s2b_shift; return rc; } static dasd_era_t dasd_fba_examine_error (ccw_req_t * cqr, devstat_t * stat) { dasd_device_t *device = (dasd_device_t *) cqr->device; if (stat->cstat == 0x00 && stat->dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END)) return dasd_era_none; switch (device->devinfo.sid_data.dev_model) { case 0x3370: return dasd_3370_erp_examine (cqr, stat); case 0x9336: return dasd_9336_erp_examine (cqr, stat); default: return dasd_era_recover; } } static dasd_erp_action_fn_t dasd_fba_erp_action (ccw_req_t * cqr) { return dasd_default_erp_action; } static dasd_erp_postaction_fn_t dasd_fba_erp_postaction (ccw_req_t * cqr) { if (cqr->function == dasd_default_erp_action) return dasd_default_erp_postaction; printk (KERN_WARNING PRINTK_HEADER "unknown ERP action %p\n", cqr->function); return NULL; } static ccw_req_t * dasd_fba_build_cp_from_req (dasd_device_t * device, struct request *req) { ccw_req_t *rw_cp = NULL; int rw_cmd; int bhct, i = 0; long size; ccw1_t *ccw; DE_fba_data_t *DE_data; LO_fba_data_t *LO_data; struct buffer_head *bh; dasd_fba_private_t *private = (dasd_fba_private_t *) device->private; int byt_per_blk = device->sizes.bp_block; if (req->cmd == READ) { rw_cmd = DASD_FBA_CCW_READ; } else if (req->cmd == WRITE) { rw_cmd = DASD_FBA_CCW_WRITE; } else { PRINT_ERR ("Unknown command %d\n", req->cmd); return NULL; } /* Build the request */ /* count hs to prevent errors, when bh smaller than block */ bh = req -> bh; bhct = 0; while ( bh != NULL ) { if (bh->b_size < byt_per_blk) { BUG(); } bhct += bh->b_size >> (device->sizes.s2b_shift+9); bh = bh->b_reqnext; } if (private->rdc_data.mode.bits.data_chain) { rw_cp = dasd_alloc_request (dasd_fba_discipline.name, 2 + bhct, sizeof (DE_fba_data_t) + sizeof (LO_fba_data_t), device); } else { rw_cp = dasd_alloc_request (dasd_fba_discipline.name, 1 + 2 * bhct, sizeof (DE_fba_data_t) + bhct * sizeof (LO_fba_data_t), device); } if (!rw_cp) { return NULL; } DE_data = rw_cp->data; LO_data = rw_cp->data + sizeof (DE_fba_data_t); ccw = rw_cp->cpaddr; if (define_extent (ccw, DE_data, req->cmd, byt_per_blk, req->sector, req->nr_sectors, rw_cp, device)) { goto clear_rw_cp; } ccw->flags |= CCW_FLAG_CC; ccw ++; locate_record (ccw, LO_data, req->cmd, 0, private->rdc_data.mode.bits.data_chain ? bhct : 1, rw_cp, device); if (ccw->cda == 0) { goto clear_rw_cp; } ccw->flags |= CCW_FLAG_CC; bh = req -> bh; i = 0; while ( bh != NULL ) { for (size = 0; size < bh->b_size; size += byt_per_blk) { ccw ++; ccw->cmd_code = rw_cmd; ccw->count = byt_per_blk; if (dasd_set_normalized_cda (ccw,__pa (bh->b_data + size), rw_cp, device)) { goto clear_rw_cp; } if (private->rdc_data.mode.bits.data_chain) { ccw->flags |= CCW_FLAG_DC; } else { ccw->flags |= CCW_FLAG_CC; } } bh = bh->b_reqnext; if ( bh != NULL && !(private->rdc_data.mode.bits.data_chain)) { ccw++; i++; LO_data++; locate_record (ccw, LO_data, req->cmd, i, 1, rw_cp, device); if (ccw->cda == 0) { goto clear_rw_cp; } ccw->flags |= CCW_FLAG_CC; } } ccw->flags &= ~(CCW_FLAG_DC | CCW_FLAG_CC); rw_cp->device = device; rw_cp->expires = 5 * TOD_MIN; /* 5 minutes */ rw_cp->req = req; check_then_set (&rw_cp->status, CQR_STATUS_EMPTY, CQR_STATUS_FILLED); goto out; clear_rw_cp: dasd_free_request (rw_cp, device); rw_cp = NULL; out: return rw_cp; } static int dasd_fba_fill_info (dasd_device_t * device, dasd_information_t * info) { int rc = 0; info->label_block = 1; info->FBA_layout = 1; info->characteristics_size = sizeof (dasd_fba_characteristics_t); memcpy (info->characteristics, &((dasd_fba_private_t *) device->private)->rdc_data, sizeof (dasd_fba_characteristics_t)); info->confdata_size = 0; return rc; } static char * dasd_fba_dump_sense (struct dasd_device_t *device, ccw_req_t * req) { char *page = (char *) get_free_page (GFP_KERNEL); int len; if (page == NULL) { return NULL; } len = sprintf (page, KERN_WARNING PRINTK_HEADER "device %04X on irq %d: I/O status report:\n", device->devinfo.devno, device->devinfo.irq); return page; } dasd_discipline_t dasd_fba_discipline = { owner: THIS_MODULE, name:"FBA ", ebcname:"FBA ", max_blocks:((PAGE_SIZE >> 1) / sizeof (ccw1_t) - 1), id_check:dasd_fba_id_check, check_characteristics:dasd_fba_check_characteristics, do_analysis:dasd_fba_do_analysis, fill_geometry:dasd_fba_fill_geometry, start_IO:dasd_start_IO, term_IO:dasd_term_IO, examine_error:dasd_fba_examine_error, erp_action:dasd_fba_erp_action, erp_postaction:dasd_fba_erp_postaction, build_cp_from_req:dasd_fba_build_cp_from_req, dump_sense:dasd_fba_dump_sense, int_handler:dasd_int_handler, fill_info:dasd_fba_fill_info, }; int dasd_fba_init (void) { int rc = 0; printk (KERN_INFO PRINTK_HEADER "%s discipline initializing\n", dasd_fba_discipline.name); ASCEBC (dasd_fba_discipline.ebcname, 4); dasd_discipline_add (&dasd_fba_discipline); #ifdef CONFIG_DASD_DYNAMIC { int i; for (i = 0; i < sizeof (dasd_fba_known_devices) / sizeof (devreg_t); i++) { printk (KERN_INFO PRINTK_HEADER "We are interested in: Dev %04X/%02X @ CU %04X/%02x\n", dasd_fba_known_devices[i].ci.hc.dtype, dasd_fba_known_devices[i].ci.hc.dmode, dasd_fba_known_devices[i].ci.hc.ctype, dasd_fba_known_devices[i].ci.hc.cmode); s390_device_register (&dasd_fba_known_devices[i]); } } #endif /* CONFIG_DASD_DYNAMIC */ return rc; } void dasd_fba_cleanup( void ) { printk ( KERN_INFO PRINTK_HEADER "%s discipline cleaning up\n", dasd_fba_discipline.name); #ifdef CONFIG_DASD_DYNAMIC { int i; for ( i=0; i