blob: e1851f00be568f07e17fbd6c45070bdc9d01de93 [file] [log] [blame]
/* 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 <linux/moduleparam.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/hdreg.h>
#include <linux/kdev_t.h>
#include <linux/blkdev.h>
#include <asm/system.h>
#include <linux/uaccess.h>
#include <linux/scatterlist.h>
#include <linux/time.h>
#include <linux/signal.h>
#include <linux/delay.h>
#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");
/*[]*/