/* cyanblkdev_block.c - West Bridge Linux Block Driver source file ## =========================== ## Copyright (C) 2010 Cypress Semiconductor ## ## This program is free software; you can redistribute it and/or ## modify it under the terms of the GNU General Public License ## as published by the Free Software Foundation; either version 2 ## of the License, or (at your option) any later version. ## ## This program is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with this program; if not, write to the Free Software ## Foundation, Inc., 51 Franklin Street, Fifth Floor ## Boston, MA 02110-1301, USA. ## =========================== */ /* * Linux block driver implementation for Cypress West Bridge. * Based on the mmc block driver implementation by Andrew Christian * for the linux 2.6.26 kernel. * mmc_block.c, 5/28/2002 */ /* * Block driver for media (i.e., flash cards) * * Copyright 2002 Hewlett-Packard Company * * Use consistent with the GNU GPL is permitted, * provided that this copyright notice is * preserved in its entirety in all copies and derived works. * * HEWLETT-PACKARD COMPANY MAKES NO WARRANTIES, EXPRESSED OR IMPLIED, * AS TO THE USEFULNESS OR CORRECTNESS OF THIS CODE OR ITS * FITNESS FOR ANY PARTICULAR PURPOSE. * * Many thanks to Alessandro Rubini and Jonathan Corbet! * * Author: Andrew Christian * 28 May 2002 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cyasblkdev_queue.h" #define CYASBLKDEV_SHIFT 0 /* Only a single partition. */ #define CYASBLKDEV_MAX_REQ_LEN (256) #define CYASBLKDEV_NUM_MINORS (256 >> CYASBLKDEV_SHIFT) #define CY_AS_TEST_NUM_BLOCKS (64) #define CYASBLKDEV_MINOR_0 1 #define CYASBLKDEV_MINOR_1 2 #define CYASBLKDEV_MINOR_2 3 static int major; module_param(major, int, 0444); MODULE_PARM_DESC(major, "specify the major device number for cyasblkdev block driver"); /* parameters passed from the user space */ static int vfat_search; module_param(vfat_search, bool, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(vfat_search, "dynamically find the location of the first sector"); static int private_partition_bus = -1; module_param(private_partition_bus, int, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(private_partition_bus, "bus number for private partition"); static int private_partition_size = -1; module_param(private_partition_size, int, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(private_partition_size, "size of the private partition"); /* * There is one cyasblkdev_blk_data per slot. */ struct cyasblkdev_blk_data { spinlock_t lock; int media_count[2]; const struct block_device_operations *blkops; unsigned int usage; unsigned int suspended; /* handle to the west bridge device this handle, typdefed as *void */ cy_as_device_handle dev_handle; /* our custom structure, in addition to request queue, * adds lock & semaphore items*/ struct cyasblkdev_queue queue; /* 16 entries is enough given max request size * 16 * 4K (64 K per request)*/ struct scatterlist sg[16]; /* non-zero enables printk of executed reqests */ unsigned int dbgprn_flags; /*gen_disk for private, system disk */ struct gendisk *system_disk; cy_as_media_type system_disk_type; cy_bool system_disk_read_only; cy_bool system_disk_bus_num; /* sector size for the medium */ unsigned int system_disk_blk_size; unsigned int system_disk_first_sector; unsigned int system_disk_unit_no; /*gen_disk for bus 0 */ struct gendisk *user_disk_0; cy_as_media_type user_disk_0_type; cy_bool user_disk_0_read_only; cy_bool user_disk_0_bus_num; /* sector size for the medium */ unsigned int user_disk_0_blk_size; unsigned int user_disk_0_first_sector; unsigned int user_disk_0_unit_no; /*gen_disk for bus 1 */ struct gendisk *user_disk_1; cy_as_media_type user_disk_1_type; cy_bool user_disk_1_read_only; cy_bool user_disk_1_bus_num; /* sector size for the medium */ unsigned int user_disk_1_blk_size; unsigned int user_disk_1_first_sector; unsigned int user_disk_1_unit_no; }; /* pointer to west bridge block data device superstructure */ static struct cyasblkdev_blk_data *gl_bd; static DEFINE_SEMAPHORE(open_lock); /* local forwardd declarationss */ static cy_as_device_handle *cyas_dev_handle; static void cyasblkdev_blk_deinit(struct cyasblkdev_blk_data *bd); /*change debug print options */ #define DBGPRN_RD_RQ (1 < 0) #define DBGPRN_WR_RQ (1 < 1) #define DBGPRN_RQ_END (1 < 2) int blkdev_ctl_dbgprn( int prn_flags ) { int cur_options = gl_bd->dbgprn_flags; DBGPRN_FUNC_NAME; /* set new debug print options */ gl_bd->dbgprn_flags = prn_flags; /* return previous */ return cur_options; } EXPORT_SYMBOL(blkdev_ctl_dbgprn); static struct cyasblkdev_blk_data *cyasblkdev_blk_get( struct gendisk *disk ) { struct cyasblkdev_blk_data *bd; DBGPRN_FUNC_NAME; down(&open_lock); bd = disk->private_data; if (bd && (bd->usage == 0)) bd = NULL; if (bd) { bd->usage++; #ifndef NBDEBUG cy_as_hal_print_message( "cyasblkdev_blk_get: usage = %d\n", bd->usage); #endif } up(&open_lock); return bd; } static void cyasblkdev_blk_put( struct cyasblkdev_blk_data *bd ) { DBGPRN_FUNC_NAME; down(&open_lock); if (bd) { bd->usage--; #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( " cyasblkdev_blk_put , bd->usage= %d\n", bd->usage); #endif } else { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "cyasblkdev: blk_put(bd) on bd = NULL!: usage = %d\n", bd->usage); #endif up(&open_lock); return; } if (bd->usage == 0) { put_disk(bd->user_disk_0); put_disk(bd->user_disk_1); put_disk(bd->system_disk); cyasblkdev_cleanup_queue(&bd->queue); if (CY_AS_ERROR_SUCCESS != cy_as_storage_release(bd->dev_handle, 0, 0, 0, 0)) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "cyasblkdev: cannot release bus 0\n"); #endif } if (CY_AS_ERROR_SUCCESS != cy_as_storage_release(bd->dev_handle, 1, 0, 0, 0)) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "cyasblkdev: cannot release bus 1\n"); #endif } if (CY_AS_ERROR_SUCCESS != cy_as_storage_stop(bd->dev_handle, 0, 0)) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "cyasblkdev: cannot stop storage stack\n"); #endif } #ifdef __CY_ASTORIA_SCM_KERNEL_HAL__ /* If the SCM Kernel HAL is being used, disable the use * of scatter/gather lists at the end of block driver usage. */ cy_as_hal_disable_scatter_list(cyasdevice_gethaltag()); #endif /*ptr to global struct cyasblkdev_blk_data */ gl_bd = NULL; kfree(bd); } #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "cyasblkdev (blk_put): usage = %d\n", bd->usage); #endif up(&open_lock); } static int cyasblkdev_blk_open( struct block_device *bdev, fmode_t mode ) { struct cyasblkdev_blk_data *bd = cyasblkdev_blk_get(bdev->bd_disk); int ret = -ENXIO; DBGPRN_FUNC_NAME; if (bd) { if (bd->usage == 2) check_disk_change(bdev); ret = 0; if (bdev->bd_disk == bd->user_disk_0) { if ((mode & FMODE_WRITE) && bd->user_disk_0_read_only) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "device marked as readonly " "and write requested\n"); #endif cyasblkdev_blk_put(bd); ret = -EROFS; } } else if (bdev->bd_disk == bd->user_disk_1) { if ((mode & FMODE_WRITE) && bd->user_disk_1_read_only) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "device marked as readonly " "and write requested\n"); #endif cyasblkdev_blk_put(bd); ret = -EROFS; } } else if (bdev->bd_disk == bd->system_disk) { if ((mode & FMODE_WRITE) && bd->system_disk_read_only) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "device marked as readonly " "and write requested\n"); #endif cyasblkdev_blk_put(bd); ret = -EROFS; } } } return ret; } static int cyasblkdev_blk_release( struct gendisk *disk, fmode_t mode ) { struct cyasblkdev_blk_data *bd = disk->private_data; DBGPRN_FUNC_NAME; cyasblkdev_blk_put(bd); return 0; } static int cyasblkdev_blk_ioctl( struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg ) { DBGPRN_FUNC_NAME; if (cmd == HDIO_GETGEO) { /*for now we only process geometry IOCTL*/ struct hd_geometry geo; memset(&geo, 0, sizeof(struct hd_geometry)); geo.cylinders = get_capacity(bdev->bd_disk) / (4 * 16); geo.heads = 4; geo.sectors = 16; geo.start = get_start_sect(bdev); /* copy to user space */ return copy_to_user((void __user *)arg, &geo, sizeof(geo)) ? -EFAULT : 0; } return -ENOTTY; } /* Media_changed block_device opp * this one is called by kernel to confirm if the media really changed * as we indicated by issuing check_disk_change() call */ int cyasblkdev_media_changed(struct gendisk *gd) { struct cyasblkdev_blk_data *bd; #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("cyasblkdev_media_changed() is called\n"); #endif if (gd) bd = gd->private_data; else { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "cyasblkdev_media_changed() is called, " "but gd is null\n"); #endif } /* return media change state "1" yes, 0 no */ return 0; } /* this one called by kernel to give us a chence * to prep the new media before it starts to rescaning * of the newlly inserted SD media */ int cyasblkdev_revalidate_disk(struct gendisk *gd) { /*int (*revalidate_disk) (struct gendisk *); */ #ifndef WESTBRIDGE_NDEBUG if (gd) cy_as_hal_print_message( "cyasblkdev_revalidate_disk() is called, " "(gl_bd->usage:%d)\n", gl_bd->usage); #endif /* 0 means ok, kern can go ahead with partition rescan */ return 0; } /*standard block device driver interface */ static struct block_device_operations cyasblkdev_bdops = { .open = cyasblkdev_blk_open, .release = cyasblkdev_blk_release, .ioctl = cyasblkdev_blk_ioctl, /* .getgeo = cyasblkdev_blk_getgeo, */ /* added to support media removal( real and simulated) media */ .media_changed = cyasblkdev_media_changed, /* added to support media removal( real and simulated) media */ .revalidate_disk = cyasblkdev_revalidate_disk, .owner = THIS_MODULE, }; /* west bridge block device prep request function */ static int cyasblkdev_blk_prep_rq( struct cyasblkdev_queue *bq, struct request *req ) { struct cyasblkdev_blk_data *bd = bq->data; int stat = BLKPREP_OK; DBGPRN_FUNC_NAME; /* If we have no device, we haven't finished initialising. */ if (!bd || !bd->dev_handle) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message(KERN_ERR "cyasblkdev %s: killing request - no device/host\n", req->rq_disk->disk_name); #endif stat = BLKPREP_KILL; } if (bd->suspended) { blk_plug_device(bd->queue.queue); stat = BLKPREP_DEFER; } /* Check for excessive requests.*/ if (blk_rq_pos(req) + blk_rq_sectors(req) > get_capacity(req->rq_disk)) { cy_as_hal_print_message("cyasblkdev: bad request address\n"); stat = BLKPREP_KILL; } return stat; } /*west bridge storage async api on_completed callback */ static void cyasblkdev_issuecallback( /* Handle to the device completing the storage operation */ cy_as_device_handle handle, /* The media type completing the operation */ cy_as_media_type type, /* The device completing the operation */ uint32_t device, /* The unit completing the operation */ uint32_t unit, /* The block number of the completed operation */ uint32_t block_number, /* The type of operation */ cy_as_oper_type op, /* The error status */ cy_as_return_status_t status ) { int retry_cnt = 0; DBGPRN_FUNC_NAME; if (status != CY_AS_ERROR_SUCCESS) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: async r/w: op:%d failed with error %d at address %d\n", __func__, op, status, block_number); #endif } #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s calling blk_end_request from issue_callback " "req=0x%x, status=0x%x, nr_sectors=0x%x\n", __func__, (unsigned int) gl_bd->queue.req, status, (unsigned int) blk_rq_sectors(gl_bd->queue.req)); #endif /* note: blk_end_request w/o __ prefix should * not require spinlocks on the queue*/ while (blk_end_request(gl_bd->queue.req, status, blk_rq_sectors(gl_bd->queue.req)*512)) { retry_cnt++; }; #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s blkdev_callback: ended rq on %d sectors, " "with err:%d, n:%d times\n", __func__, (int)blk_rq_sectors(gl_bd->queue.req), status, retry_cnt ); #endif spin_lock_irq(&gl_bd->lock); /*elevate next request, if there is one*/ if (!blk_queue_plugged(gl_bd->queue.queue)) { /* queue is not plugged */ gl_bd->queue.req = blk_fetch_request(gl_bd->queue.queue); #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s blkdev_callback: " "blk_fetch_request():%p\n", __func__, gl_bd->queue.req); #endif } if (gl_bd->queue.req) { spin_unlock_irq(&gl_bd->lock); #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s blkdev_callback: about to " "call issue_fn:%p\n", __func__, gl_bd->queue.req); #endif gl_bd->queue.issue_fn(&gl_bd->queue, gl_bd->queue.req); } else { spin_unlock_irq(&gl_bd->lock); } } /* issue astoria blkdev request (issue_fn) */ static int cyasblkdev_blk_issue_rq( struct cyasblkdev_queue *bq, struct request *req ) { struct cyasblkdev_blk_data *bd = bq->data; int index = 0; int ret = CY_AS_ERROR_SUCCESS; uint32_t req_sector = 0; uint32_t req_nr_sectors = 0; int bus_num = 0; int lcl_unit_no = 0; DBGPRN_FUNC_NAME; /* * will construct a scatterlist for the given request; * the return value is the number of actually used * entries in the resulting list. Then, this scatterlist * can be used for the actual DMA prep operation. */ spin_lock_irq(&bd->lock); index = blk_rq_map_sg(bq->queue, req, bd->sg); if (req->rq_disk == bd->user_disk_0) { bus_num = bd->user_disk_0_bus_num; req_sector = blk_rq_pos(req) + gl_bd->user_disk_0_first_sector; req_nr_sectors = blk_rq_sectors(req); lcl_unit_no = gl_bd->user_disk_0_unit_no; #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: request made to disk 0 " "for sector=%d, num_sectors=%d, unit_no=%d\n", __func__, req_sector, (int) blk_rq_sectors(req), lcl_unit_no); #endif } else if (req->rq_disk == bd->user_disk_1) { bus_num = bd->user_disk_1_bus_num; req_sector = blk_rq_pos(req) + gl_bd->user_disk_1_first_sector; /*SECT_NUM_TRANSLATE(blk_rq_sectors(req));*/ req_nr_sectors = blk_rq_sectors(req); lcl_unit_no = gl_bd->user_disk_1_unit_no; #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: request made to disk 1 for " "sector=%d, num_sectors=%d, unit_no=%d\n", __func__, req_sector, (int) blk_rq_sectors(req), lcl_unit_no); #endif } else if (req->rq_disk == bd->system_disk) { bus_num = bd->system_disk_bus_num; req_sector = blk_rq_pos(req) + gl_bd->system_disk_first_sector; req_nr_sectors = blk_rq_sectors(req); lcl_unit_no = gl_bd->system_disk_unit_no; #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: request made to system disk " "for sector=%d, num_sectors=%d, unit_no=%d\n", __func__, req_sector, (int) blk_rq_sectors(req), lcl_unit_no); #endif } #ifndef WESTBRIDGE_NDEBUG else { cy_as_hal_print_message( "%s: invalid disk used for request\n", __func__); } #endif spin_unlock_irq(&bd->lock); if (rq_data_dir(req) == READ) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: calling readasync() " "req_sector=0x%x, req_nr_sectors=0x%x, bd->sg:%x\n\n", __func__, req_sector, req_nr_sectors, (uint32_t)bd->sg); #endif ret = cy_as_storage_read_async(bd->dev_handle, bus_num, 0, lcl_unit_no, req_sector, bd->sg, req_nr_sectors, (cy_as_storage_callback)cyasblkdev_issuecallback); if (ret != CY_AS_ERROR_SUCCESS) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s:readasync() error %d at " "address %ld, unit no %d\n", __func__, ret, blk_rq_pos(req), lcl_unit_no); cy_as_hal_print_message("%s:ending i/o request " "on reg:%x\n", __func__, (uint32_t)req); #endif while (blk_end_request(req, (ret == CY_AS_ERROR_SUCCESS), req_nr_sectors*512)) ; bq->req = NULL; } } else { ret = cy_as_storage_write_async(bd->dev_handle, bus_num, 0, lcl_unit_no, req_sector, bd->sg, req_nr_sectors, (cy_as_storage_callback)cyasblkdev_issuecallback); if (ret != CY_AS_ERROR_SUCCESS) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: write failed with " "error %d at address %ld, unit no %d\n", __func__, ret, blk_rq_pos(req), lcl_unit_no); #endif /*end IO op on this request(does both * end_that_request_... _first & _last) */ while (blk_end_request(req, (ret == CY_AS_ERROR_SUCCESS), req_nr_sectors*512)) ; bq->req = NULL; } } return ret; } static unsigned long dev_use[CYASBLKDEV_NUM_MINORS / (8 * sizeof(unsigned long))]; /* storage event callback (note: called in astoria isr context) */ static void cyasblkdev_storage_callback( cy_as_device_handle dev_h, cy_as_bus_number_t bus, uint32_t device, cy_as_storage_event evtype, void *evdata ) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: bus:%d, device:%d, evtype:%d, " "evdata:%p\n ", __func__, bus, device, evtype, evdata); #endif switch (evtype) { case cy_as_storage_processor: break; case cy_as_storage_removed: break; case cy_as_storage_inserted: break; default: break; } } #define SECTORS_TO_SCAN 4096 uint32_t cyasblkdev_get_vfat_offset(int bus_num, int unit_no) { /* * for sd media, vfat partition boot record is not always * located at sector it greatly depends on the system and * software that was used to format the sd however, linux * fs layer always expects it at sector 0, this function * finds the offset and then uses it in all media r/w * operations */ int sect_no, stat; uint8_t *sect_buf; bool br_found = false; DBGPRN_FUNC_NAME; sect_buf = kmalloc(1024, GFP_KERNEL); /* since HAL layer always uses sg lists instead of the * buffer (for hw dmas) we need to initialize the sg list * for local buffer*/ sg_init_one(gl_bd->sg, sect_buf, 512); /* * Check MPR partition table 1st, then try to scan through * 1st 384 sectors until BR signature(intel JMP istruction * code and ,0x55AA) is found */ #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s scanning media for vfat partition...\n", __func__); #endif for (sect_no = 0; sect_no < SECTORS_TO_SCAN; sect_no++) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s before cyasstorageread " "gl_bd->sg addr=0x%x\n", __func__, (unsigned int) gl_bd->sg); #endif stat = cy_as_storage_read( /* Handle to the device of interest */ gl_bd->dev_handle, /* The bus to access */ bus_num, /* The device to access */ 0, /* The unit to access */ unit_no, /* absolute sector number */ sect_no, /* sg structure */ gl_bd->sg, /* The number of blocks to be read */ 1 ); /* try only sectors with boot signature */ if ((sect_buf[510] == 0x55) && (sect_buf[511] == 0xaa)) { /* vfat boot record may also be located at * sector 0, check it first */ if (sect_buf[0] == 0xEB) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s vfat partition found " "at sector:%d\n", __func__, sect_no); #endif br_found = true; break; } } if (stat != 0) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s sector scan error\n", __func__); #endif break; } } kfree(sect_buf); if (br_found) { return sect_no; } else { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s vfat partition is not found, using 0 offset\n", __func__); #endif return 0; } } cy_as_storage_query_device_data dev_data = {0}; static int cyasblkdev_add_disks(int bus_num, struct cyasblkdev_blk_data *bd, int total_media_count, int devidx) { int ret = 0; uint64_t disk_cap; int lcl_unit_no; cy_as_storage_query_unit_data unit_data = {0}; #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s:query device: " "type:%d, removable:%d, writable:%d, " "blksize %d, units:%d, locked:%d, " "erase_sz:%d\n", __func__, dev_data.desc_p.type, dev_data.desc_p.removable, dev_data.desc_p.writeable, dev_data.desc_p.block_size, dev_data.desc_p.number_units, dev_data.desc_p.locked, dev_data.desc_p.erase_unit_size ); #endif /* make sure that device is not locked */ if (dev_data.desc_p.locked) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: device is locked\n", __func__); #endif ret = cy_as_storage_release( bd->dev_handle, bus_num, 0, 0, 0); if (ret != CY_AS_ERROR_SUCCESS) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s cannot release" " storage\n", __func__); #endif goto out; } goto out; } unit_data.device = 0; unit_data.unit = 0; unit_data.bus = bus_num; ret = cy_as_storage_query_unit(bd->dev_handle, &unit_data, 0, 0); if (ret != CY_AS_ERROR_SUCCESS) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: cannot query " "%d device unit - reason code %d\n", __func__, bus_num, ret); #endif goto out; } if (private_partition_bus == bus_num) { if (private_partition_size > 0) { ret = cy_as_storage_create_p_partition( bd->dev_handle, bus_num, 0, private_partition_size, 0, 0); if ((ret != CY_AS_ERROR_SUCCESS) && (ret != CY_AS_ERROR_ALREADY_PARTITIONED)) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: cy_as_storage_" "create_p_partition after size > 0 check " "failed with error code %d\n", __func__, ret); #endif disk_cap = (uint64_t) (unit_data.desc_p.unit_size); lcl_unit_no = 0; } else if (ret == CY_AS_ERROR_ALREADY_PARTITIONED) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: cy_as_storage_create_p_partition " "indicates memory already partitioned\n", __func__); #endif /*check to see that partition * matches size */ if (unit_data.desc_p.unit_size != private_partition_size) { ret = cy_as_storage_remove_p_partition( bd->dev_handle, bus_num, 0, 0, 0); if (ret == CY_AS_ERROR_SUCCESS) { ret = cy_as_storage_create_p_partition( bd->dev_handle, bus_num, 0, private_partition_size, 0, 0); if (ret == CY_AS_ERROR_SUCCESS) { unit_data.bus = bus_num; unit_data.device = 0; unit_data.unit = 1; } else { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: cy_as_storage_create_p_partition " "after removal unexpectedly failed " "with error %d\n", __func__, ret); #endif /* need to requery bus * seeing as delete * successful and create * failed we have changed * the disk properties */ unit_data.bus = bus_num; unit_data.device = 0; unit_data.unit = 0; } ret = cy_as_storage_query_unit( bd->dev_handle, &unit_data, 0, 0); if (ret != CY_AS_ERROR_SUCCESS) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: cannot query %d " "device unit - reason code %d\n", __func__, bus_num, ret); #endif goto out; } else { disk_cap = (uint64_t) (unit_data.desc_p.unit_size); lcl_unit_no = unit_data.unit; } } else { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: cy_as_storage_remove_p_partition " "failed with error %d\n", __func__, ret); #endif unit_data.bus = bus_num; unit_data.device = 0; unit_data.unit = 1; ret = cy_as_storage_query_unit( bd->dev_handle, &unit_data, 0, 0); if (ret != CY_AS_ERROR_SUCCESS) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: cannot query %d " "device unit - reason " "code %d\n", __func__, bus_num, ret); #endif goto out; } disk_cap = (uint64_t) (unit_data.desc_p.unit_size); lcl_unit_no = unit_data.unit; } } else { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: partition " "exists and sizes equal\n", __func__); #endif /*partition already existed, * need to query second unit*/ unit_data.bus = bus_num; unit_data.device = 0; unit_data.unit = 1; ret = cy_as_storage_query_unit( bd->dev_handle, &unit_data, 0, 0); if (ret != CY_AS_ERROR_SUCCESS) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: cannot query %d " "device unit " "- reason code %d\n", __func__, bus_num, ret); #endif goto out; } else { disk_cap = (uint64_t) (unit_data.desc_p.unit_size); lcl_unit_no = unit_data.unit; } } } else { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: cy_as_storage_create_p_partition " "created successfully\n", __func__); #endif disk_cap = (uint64_t) (unit_data.desc_p.unit_size - private_partition_size); lcl_unit_no = 1; } } #ifndef WESTBRIDGE_NDEBUG else { cy_as_hal_print_message( "%s: invalid partition_size%d\n", __func__, private_partition_size); disk_cap = (uint64_t) (unit_data.desc_p.unit_size); lcl_unit_no = 0; } #endif } else { disk_cap = (uint64_t) (unit_data.desc_p.unit_size); lcl_unit_no = 0; } if ((bus_num == 0) || (total_media_count == 1)) { sprintf(bd->user_disk_0->disk_name, "cyasblkdevblk%d", devidx); #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: disk unit_sz:%lu blk_sz:%d, " "start_blk:%lu, capacity:%llu\n", __func__, (unsigned long) unit_data.desc_p.unit_size, unit_data.desc_p.block_size, (unsigned long) unit_data.desc_p.start_block, (uint64_t)disk_cap ); #endif #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: setting gendisk disk " "capacity to %d\n", __func__, (int) disk_cap); #endif /* initializing bd->queue */ #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: init bd->queue\n", __func__); #endif /* this will create a * queue kernel thread */ cyasblkdev_init_queue( &bd->queue, &bd->lock); bd->queue.prep_fn = cyasblkdev_blk_prep_rq; bd->queue.issue_fn = cyasblkdev_blk_issue_rq; bd->queue.data = bd; /*blk_size should always * be a multiple of 512, * set to the max to ensure * that all accesses aligned * to the greatest multiple, * can adjust request to * smaller block sizes * dynamically*/ bd->user_disk_0_read_only = !dev_data.desc_p.writeable; bd->user_disk_0_blk_size = dev_data.desc_p.block_size; bd->user_disk_0_type = dev_data.desc_p.type; bd->user_disk_0_bus_num = bus_num; bd->user_disk_0->major = major; bd->user_disk_0->first_minor = devidx << CYASBLKDEV_SHIFT; bd->user_disk_0->minors = 8; bd->user_disk_0->fops = &cyasblkdev_bdops; bd->user_disk_0->private_data = bd; bd->user_disk_0->queue = bd->queue.queue; bd->dbgprn_flags = DBGPRN_RD_RQ; bd->user_disk_0_unit_no = lcl_unit_no; blk_queue_logical_block_size(bd->queue.queue, bd->user_disk_0_blk_size); set_capacity(bd->user_disk_0, disk_cap); #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: returned from set_capacity %d\n", __func__, (int) disk_cap); #endif /* need to start search from * public partition beginning */ if (vfat_search) { bd->user_disk_0_first_sector = cyasblkdev_get_vfat_offset( bd->user_disk_0_bus_num, bd->user_disk_0_unit_no); } else { bd->user_disk_0_first_sector = 0; } #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: set user_disk_0_first " "sector to %d\n", __func__, bd->user_disk_0_first_sector); cy_as_hal_print_message( "%s: add_disk: disk->major=0x%x\n", __func__, bd->user_disk_0->major); cy_as_hal_print_message( "%s: add_disk: " "disk->first_minor=0x%x\n", __func__, bd->user_disk_0->first_minor); cy_as_hal_print_message( "%s: add_disk: " "disk->minors=0x%x\n", __func__, bd->user_disk_0->minors); cy_as_hal_print_message( "%s: add_disk: " "disk->disk_name=%s\n", __func__, bd->user_disk_0->disk_name); cy_as_hal_print_message( "%s: add_disk: " "disk->part_tbl=0x%x\n", __func__, (unsigned int) bd->user_disk_0->part_tbl); cy_as_hal_print_message( "%s: add_disk: " "disk->queue=0x%x\n", __func__, (unsigned int) bd->user_disk_0->queue); cy_as_hal_print_message( "%s: add_disk: " "disk->flags=0x%x\n", __func__, (unsigned int) bd->user_disk_0->flags); cy_as_hal_print_message( "%s: add_disk: " "disk->driverfs_dev=0x%x\n", __func__, (unsigned int) bd->user_disk_0->driverfs_dev); cy_as_hal_print_message( "%s: add_disk: " "disk->slave_dir=0x%x\n", __func__, (unsigned int) bd->user_disk_0->slave_dir); cy_as_hal_print_message( "%s: add_disk: " "disk->random=0x%x\n", __func__, (unsigned int) bd->user_disk_0->random); cy_as_hal_print_message( "%s: add_disk: " "disk->node_id=0x%x\n", __func__, (unsigned int) bd->user_disk_0->node_id); #endif add_disk(bd->user_disk_0); } else if ((bus_num == 1) && (total_media_count == 2)) { bd->user_disk_1_read_only = !dev_data.desc_p.writeable; bd->user_disk_1_blk_size = dev_data.desc_p.block_size; bd->user_disk_1_type = dev_data.desc_p.type; bd->user_disk_1_bus_num = bus_num; bd->user_disk_1->major = major; bd->user_disk_1->first_minor = (devidx + 1) << CYASBLKDEV_SHIFT; bd->user_disk_1->minors = 8; bd->user_disk_1->fops = &cyasblkdev_bdops; bd->user_disk_1->private_data = bd; bd->user_disk_1->queue = bd->queue.queue; bd->dbgprn_flags = DBGPRN_RD_RQ; bd->user_disk_1_unit_no = lcl_unit_no; sprintf(bd->user_disk_1->disk_name, "cyasblkdevblk%d", (devidx + 1)); #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: disk unit_sz:%lu " "blk_sz:%d, " "start_blk:%lu, " "capacity:%llu\n", __func__, (unsigned long) unit_data.desc_p.unit_size, unit_data.desc_p.block_size, (unsigned long) unit_data.desc_p.start_block, (uint64_t)disk_cap ); #endif /*blk_size should always be a * multiple of 512, set to the max * to ensure that all accesses * aligned to the greatest multiple, * can adjust request to smaller * block sizes dynamically*/ if (bd->user_disk_0_blk_size > bd->user_disk_1_blk_size) { blk_queue_logical_block_size(bd->queue.queue, bd->user_disk_0_blk_size); #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: set hard sect_sz:%d\n", __func__, bd->user_disk_0_blk_size); #endif } else { blk_queue_logical_block_size(bd->queue.queue, bd->user_disk_1_blk_size); #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: set hard sect_sz:%d\n", __func__, bd->user_disk_1_blk_size); #endif } set_capacity(bd->user_disk_1, disk_cap); if (vfat_search) { bd->user_disk_1_first_sector = cyasblkdev_get_vfat_offset( bd->user_disk_1_bus_num, bd->user_disk_1_unit_no); } else { bd->user_disk_1_first_sector = 0; } add_disk(bd->user_disk_1); } if (lcl_unit_no > 0) { if (bd->system_disk == NULL) { bd->system_disk = alloc_disk(8); if (bd->system_disk == NULL) { kfree(bd); bd = ERR_PTR(-ENOMEM); return bd; } disk_cap = (uint64_t) (private_partition_size); /* set properties of * system disk */ bd->system_disk_read_only = !dev_data.desc_p.writeable; bd->system_disk_blk_size = dev_data.desc_p.block_size; bd->system_disk_bus_num = bus_num; bd->system_disk->major = major; bd->system_disk->first_minor = (devidx + 2) << CYASBLKDEV_SHIFT; bd->system_disk->minors = 8; bd->system_disk->fops = &cyasblkdev_bdops; bd->system_disk->private_data = bd; bd->system_disk->queue = bd->queue.queue; /* don't search for vfat * with system disk */ bd->system_disk_first_sector = 0; sprintf( bd->system_disk->disk_name, "cyasblkdevblk%d", (devidx + 2)); set_capacity(bd->system_disk, disk_cap); add_disk(bd->system_disk); } #ifndef WESTBRIDGE_NDEBUG else { cy_as_hal_print_message( "%s: system disk already allocated %d\n", __func__, bus_num); } #endif } out: return ret; } static struct cyasblkdev_blk_data *cyasblkdev_blk_alloc(void) { struct cyasblkdev_blk_data *bd; int ret = 0; cy_as_return_status_t stat = -1; int bus_num = 0; int total_media_count = 0; int devidx = 0; DBGPRN_FUNC_NAME; total_media_count = 0; devidx = find_first_zero_bit(dev_use, CYASBLKDEV_NUM_MINORS); if (devidx >= CYASBLKDEV_NUM_MINORS) return ERR_PTR(-ENOSPC); __set_bit(devidx, dev_use); __set_bit(devidx + 1, dev_use); bd = kzalloc(sizeof(struct cyasblkdev_blk_data), GFP_KERNEL); if (bd) { gl_bd = bd; spin_lock_init(&bd->lock); bd->usage = 1; /* setup the block_dev_ops pointer*/ bd->blkops = &cyasblkdev_bdops; /* Get the device handle */ bd->dev_handle = cyasdevice_getdevhandle(); if (0 == bd->dev_handle) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: get device failed\n", __func__); #endif ret = ENODEV; goto out; } #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s west bridge device handle:%x\n", __func__, (uint32_t)bd->dev_handle); #endif /* start the storage api and get a handle to the * device we are interested in. */ /* Error code to use if the conditions are not satisfied. */ ret = ENOMEDIUM; stat = cy_as_misc_release_resource(bd->dev_handle, cy_as_bus_0); if ((stat != CY_AS_ERROR_SUCCESS) && (stat != CY_AS_ERROR_RESOURCE_NOT_OWNED)) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: cannot release " "resource bus 0 - reason code %d\n", __func__, stat); #endif } stat = cy_as_misc_release_resource(bd->dev_handle, cy_as_bus_1); if ((stat != CY_AS_ERROR_SUCCESS) && (stat != CY_AS_ERROR_RESOURCE_NOT_OWNED)) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: cannot release " "resource bus 0 - reason code %d\n", __func__, stat); #endif } /* start storage stack*/ stat = cy_as_storage_start(bd->dev_handle, 0, 0x101); if (stat != CY_AS_ERROR_SUCCESS) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: cannot start storage " "stack - reason code %d\n", __func__, stat); #endif goto out; } #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: storage started:%d ok\n", __func__, stat); #endif stat = cy_as_storage_register_callback(bd->dev_handle, cyasblkdev_storage_callback); if (stat != CY_AS_ERROR_SUCCESS) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: cannot register callback " "- reason code %d\n", __func__, stat); #endif goto out; } for (bus_num = 0; bus_num < 2; bus_num++) { stat = cy_as_storage_query_bus(bd->dev_handle, bus_num, &bd->media_count[bus_num], 0, 0); if (stat == CY_AS_ERROR_SUCCESS) { total_media_count = total_media_count + bd->media_count[bus_num]; } else { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: cannot query %d, " "reason code: %d\n", __func__, bus_num, stat); #endif goto out; } } if (total_media_count == 0) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: no storage media was found\n", __func__); #endif goto out; } else if (total_media_count >= 1) { if (bd->user_disk_0 == NULL) { bd->user_disk_0 = alloc_disk(8); if (bd->user_disk_0 == NULL) { kfree(bd); bd = ERR_PTR(-ENOMEM); return bd; } } #ifndef WESTBRIDGE_NDEBUG else { cy_as_hal_print_message("%s: no available " "gen_disk for disk 0, " "physically inconsistent\n", __func__); } #endif } if (total_media_count == 2) { if (bd->user_disk_1 == NULL) { bd->user_disk_1 = alloc_disk(8); if (bd->user_disk_1 == NULL) { kfree(bd); bd = ERR_PTR(-ENOMEM); return bd; } } #ifndef WESTBRIDGE_NDEBUG else { cy_as_hal_print_message("%s: no available " "gen_disk for media, " "physically inconsistent\n", __func__); } #endif } #ifndef WESTBRIDGE_NDEBUG else if (total_media_count > 2) { cy_as_hal_print_message("%s: count corrupted = 0x%d\n", __func__, total_media_count); } #endif #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("%s: %d device(s) found\n", __func__, total_media_count); #endif for (bus_num = 0; bus_num <= 1; bus_num++) { /*claim storage for cpu */ stat = cy_as_storage_claim(bd->dev_handle, bus_num, 0, 0, 0); if (stat != CY_AS_ERROR_SUCCESS) { cy_as_hal_print_message("%s: cannot claim " "%d bus - reason code %d\n", __func__, bus_num, stat); goto out; } dev_data.bus = bus_num; dev_data.device = 0; stat = cy_as_storage_query_device(bd->dev_handle, &dev_data, 0, 0); if (stat == CY_AS_ERROR_SUCCESS) { cyasblkdev_add_disks(bus_num, bd, total_media_count, devidx); } else if (stat == CY_AS_ERROR_NO_SUCH_DEVICE) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: no device on bus %d\n", __func__, bus_num); #endif } else { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: cannot query %d device " "- reason code %d\n", __func__, bus_num, stat); #endif goto out; } } /* end for (bus_num = 0; bus_num <= 1; bus_num++)*/ return bd; } out: #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s: bd failed to initialize\n", __func__); #endif kfree(bd); bd = ERR_PTR(-ret); return bd; } /*init west bridge block device */ static int cyasblkdev_blk_initialize(void) { struct cyasblkdev_blk_data *bd; int res; DBGPRN_FUNC_NAME; res = register_blkdev(major, "cyasblkdev"); if (res < 0) { #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message(KERN_WARNING "%s unable to get major %d for cyasblkdev media: %d\n", __func__, major, res); #endif return res; } if (major == 0) major = res; #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message( "%s cyasblkdev registered with major number: %d\n", __func__, major); #endif bd = cyasblkdev_blk_alloc(); if (IS_ERR(bd)) return PTR_ERR(bd); return 0; } /* start block device */ static int __init cyasblkdev_blk_init(void) { int res = -ENOMEM; DBGPRN_FUNC_NAME; /* get the cyasdev handle for future use*/ cyas_dev_handle = cyasdevice_getdevhandle(); if (cyasblkdev_blk_initialize() == 0) return 0; #ifndef WESTBRIDGE_NDEBUG cy_as_hal_print_message("cyasblkdev init error:%d\n", res); #endif return res; } static void cyasblkdev_blk_deinit(struct cyasblkdev_blk_data *bd) { DBGPRN_FUNC_NAME; if (bd) { int devidx; if (bd->user_disk_0 != NULL) { del_gendisk(bd->user_disk_0); devidx = bd->user_disk_0->first_minor >> CYASBLKDEV_SHIFT; __clear_bit(devidx, dev_use); } if (bd->user_disk_1 != NULL) { del_gendisk(bd->user_disk_1); devidx = bd->user_disk_1->first_minor >> CYASBLKDEV_SHIFT; __clear_bit(devidx, dev_use); } if (bd->system_disk != NULL) { del_gendisk(bd->system_disk); devidx = bd->system_disk->first_minor >> CYASBLKDEV_SHIFT; __clear_bit(devidx, dev_use); } cyasblkdev_blk_put(bd); } } /* block device exit */ static void __exit cyasblkdev_blk_exit(void) { DBGPRN_FUNC_NAME; cyasblkdev_blk_deinit(gl_bd); unregister_blkdev(major, "cyasblkdev"); } module_init(cyasblkdev_blk_init); module_exit(cyasblkdev_blk_exit); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("antioch (cyasblkdev) block device driver"); MODULE_AUTHOR("cypress semiconductor"); /*[]*/