| /* | |
| * Block driver for s390 storage class memory. | |
| * | |
| * Copyright IBM Corp. 2012 | |
| * Author(s): Sebastian Ott <sebott@linux.vnet.ibm.com> | |
| */ | |
| #define KMSG_COMPONENT "scm_block" | |
| #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt | |
| #include <linux/interrupt.h> | |
| #include <linux/spinlock.h> | |
| #include <linux/mempool.h> | |
| #include <linux/module.h> | |
| #include <linux/blkdev.h> | |
| #include <linux/blk-mq.h> | |
| #include <linux/genhd.h> | |
| #include <linux/slab.h> | |
| #include <linux/list.h> | |
| #include <asm/eadm.h> | |
| #include "scm_blk.h" | |
| debug_info_t *scm_debug; | |
| static int scm_major; | |
| static mempool_t *aidaw_pool; | |
| static DEFINE_SPINLOCK(list_lock); | |
| static LIST_HEAD(inactive_requests); | |
| static unsigned int nr_requests = 64; | |
| static unsigned int nr_requests_per_io = 8; | |
| static atomic_t nr_devices = ATOMIC_INIT(0); | |
| module_param(nr_requests, uint, S_IRUGO); | |
| MODULE_PARM_DESC(nr_requests, "Number of parallel requests."); | |
| module_param(nr_requests_per_io, uint, S_IRUGO); | |
| MODULE_PARM_DESC(nr_requests_per_io, "Number of requests per IO."); | |
| MODULE_DESCRIPTION("Block driver for s390 storage class memory."); | |
| MODULE_LICENSE("GPL"); | |
| MODULE_ALIAS("scm:scmdev*"); | |
| static void __scm_free_rq(struct scm_request *scmrq) | |
| { | |
| struct aob_rq_header *aobrq = to_aobrq(scmrq); | |
| free_page((unsigned long) scmrq->aob); | |
| kfree(scmrq->request); | |
| kfree(aobrq); | |
| } | |
| static void scm_free_rqs(void) | |
| { | |
| struct list_head *iter, *safe; | |
| struct scm_request *scmrq; | |
| spin_lock_irq(&list_lock); | |
| list_for_each_safe(iter, safe, &inactive_requests) { | |
| scmrq = list_entry(iter, struct scm_request, list); | |
| list_del(&scmrq->list); | |
| __scm_free_rq(scmrq); | |
| } | |
| spin_unlock_irq(&list_lock); | |
| mempool_destroy(aidaw_pool); | |
| } | |
| static int __scm_alloc_rq(void) | |
| { | |
| struct aob_rq_header *aobrq; | |
| struct scm_request *scmrq; | |
| aobrq = kzalloc(sizeof(*aobrq) + sizeof(*scmrq), GFP_KERNEL); | |
| if (!aobrq) | |
| return -ENOMEM; | |
| scmrq = (void *) aobrq->data; | |
| scmrq->aob = (void *) get_zeroed_page(GFP_DMA); | |
| if (!scmrq->aob) | |
| goto free; | |
| scmrq->request = kcalloc(nr_requests_per_io, sizeof(scmrq->request[0]), | |
| GFP_KERNEL); | |
| if (!scmrq->request) | |
| goto free; | |
| INIT_LIST_HEAD(&scmrq->list); | |
| spin_lock_irq(&list_lock); | |
| list_add(&scmrq->list, &inactive_requests); | |
| spin_unlock_irq(&list_lock); | |
| return 0; | |
| free: | |
| __scm_free_rq(scmrq); | |
| return -ENOMEM; | |
| } | |
| static int scm_alloc_rqs(unsigned int nrqs) | |
| { | |
| int ret = 0; | |
| aidaw_pool = mempool_create_page_pool(max(nrqs/8, 1U), 0); | |
| if (!aidaw_pool) | |
| return -ENOMEM; | |
| while (nrqs-- && !ret) | |
| ret = __scm_alloc_rq(); | |
| return ret; | |
| } | |
| static struct scm_request *scm_request_fetch(void) | |
| { | |
| struct scm_request *scmrq = NULL; | |
| spin_lock_irq(&list_lock); | |
| if (list_empty(&inactive_requests)) | |
| goto out; | |
| scmrq = list_first_entry(&inactive_requests, struct scm_request, list); | |
| list_del(&scmrq->list); | |
| out: | |
| spin_unlock_irq(&list_lock); | |
| return scmrq; | |
| } | |
| static void scm_request_done(struct scm_request *scmrq) | |
| { | |
| unsigned long flags; | |
| struct msb *msb; | |
| u64 aidaw; | |
| int i; | |
| for (i = 0; i < nr_requests_per_io && scmrq->request[i]; i++) { | |
| msb = &scmrq->aob->msb[i]; | |
| aidaw = msb->data_addr; | |
| if ((msb->flags & MSB_FLAG_IDA) && aidaw && | |
| IS_ALIGNED(aidaw, PAGE_SIZE)) | |
| mempool_free(virt_to_page(aidaw), aidaw_pool); | |
| } | |
| spin_lock_irqsave(&list_lock, flags); | |
| list_add(&scmrq->list, &inactive_requests); | |
| spin_unlock_irqrestore(&list_lock, flags); | |
| } | |
| static bool scm_permit_request(struct scm_blk_dev *bdev, struct request *req) | |
| { | |
| return rq_data_dir(req) != WRITE || bdev->state != SCM_WR_PROHIBIT; | |
| } | |
| static inline struct aidaw *scm_aidaw_alloc(void) | |
| { | |
| struct page *page = mempool_alloc(aidaw_pool, GFP_ATOMIC); | |
| return page ? page_address(page) : NULL; | |
| } | |
| static inline unsigned long scm_aidaw_bytes(struct aidaw *aidaw) | |
| { | |
| unsigned long _aidaw = (unsigned long) aidaw; | |
| unsigned long bytes = ALIGN(_aidaw, PAGE_SIZE) - _aidaw; | |
| return (bytes / sizeof(*aidaw)) * PAGE_SIZE; | |
| } | |
| struct aidaw *scm_aidaw_fetch(struct scm_request *scmrq, unsigned int bytes) | |
| { | |
| struct aidaw *aidaw; | |
| if (scm_aidaw_bytes(scmrq->next_aidaw) >= bytes) | |
| return scmrq->next_aidaw; | |
| aidaw = scm_aidaw_alloc(); | |
| if (aidaw) | |
| memset(aidaw, 0, PAGE_SIZE); | |
| return aidaw; | |
| } | |
| static int scm_request_prepare(struct scm_request *scmrq) | |
| { | |
| struct scm_blk_dev *bdev = scmrq->bdev; | |
| struct scm_device *scmdev = bdev->gendisk->private_data; | |
| int pos = scmrq->aob->request.msb_count; | |
| struct msb *msb = &scmrq->aob->msb[pos]; | |
| struct request *req = scmrq->request[pos]; | |
| struct req_iterator iter; | |
| struct aidaw *aidaw; | |
| struct bio_vec bv; | |
| aidaw = scm_aidaw_fetch(scmrq, blk_rq_bytes(req)); | |
| if (!aidaw) | |
| return -ENOMEM; | |
| msb->bs = MSB_BS_4K; | |
| scmrq->aob->request.msb_count++; | |
| msb->scm_addr = scmdev->address + ((u64) blk_rq_pos(req) << 9); | |
| msb->oc = (rq_data_dir(req) == READ) ? MSB_OC_READ : MSB_OC_WRITE; | |
| msb->flags |= MSB_FLAG_IDA; | |
| msb->data_addr = (u64) aidaw; | |
| rq_for_each_segment(bv, req, iter) { | |
| WARN_ON(bv.bv_offset); | |
| msb->blk_count += bv.bv_len >> 12; | |
| aidaw->data_addr = (u64) page_address(bv.bv_page); | |
| aidaw++; | |
| } | |
| scmrq->next_aidaw = aidaw; | |
| return 0; | |
| } | |
| static inline void scm_request_set(struct scm_request *scmrq, | |
| struct request *req) | |
| { | |
| scmrq->request[scmrq->aob->request.msb_count] = req; | |
| } | |
| static inline void scm_request_init(struct scm_blk_dev *bdev, | |
| struct scm_request *scmrq) | |
| { | |
| struct aob_rq_header *aobrq = to_aobrq(scmrq); | |
| struct aob *aob = scmrq->aob; | |
| memset(scmrq->request, 0, | |
| nr_requests_per_io * sizeof(scmrq->request[0])); | |
| memset(aob, 0, sizeof(*aob)); | |
| aobrq->scmdev = bdev->scmdev; | |
| aob->request.cmd_code = ARQB_CMD_MOVE; | |
| aob->request.data = (u64) aobrq; | |
| scmrq->bdev = bdev; | |
| scmrq->retries = 4; | |
| scmrq->error = BLK_STS_OK; | |
| /* We don't use all msbs - place aidaws at the end of the aob page. */ | |
| scmrq->next_aidaw = (void *) &aob->msb[nr_requests_per_io]; | |
| } | |
| static void scm_request_requeue(struct scm_request *scmrq) | |
| { | |
| struct scm_blk_dev *bdev = scmrq->bdev; | |
| int i; | |
| for (i = 0; i < nr_requests_per_io && scmrq->request[i]; i++) | |
| blk_mq_requeue_request(scmrq->request[i], false); | |
| atomic_dec(&bdev->queued_reqs); | |
| scm_request_done(scmrq); | |
| blk_mq_kick_requeue_list(bdev->rq); | |
| } | |
| static void scm_request_finish(struct scm_request *scmrq) | |
| { | |
| struct scm_blk_dev *bdev = scmrq->bdev; | |
| blk_status_t *error; | |
| int i; | |
| for (i = 0; i < nr_requests_per_io && scmrq->request[i]; i++) { | |
| error = blk_mq_rq_to_pdu(scmrq->request[i]); | |
| *error = scmrq->error; | |
| blk_mq_complete_request(scmrq->request[i]); | |
| } | |
| atomic_dec(&bdev->queued_reqs); | |
| scm_request_done(scmrq); | |
| } | |
| static void scm_request_start(struct scm_request *scmrq) | |
| { | |
| struct scm_blk_dev *bdev = scmrq->bdev; | |
| atomic_inc(&bdev->queued_reqs); | |
| if (eadm_start_aob(scmrq->aob)) { | |
| SCM_LOG(5, "no subchannel"); | |
| scm_request_requeue(scmrq); | |
| } | |
| } | |
| struct scm_queue { | |
| struct scm_request *scmrq; | |
| spinlock_t lock; | |
| }; | |
| static blk_status_t scm_blk_request(struct blk_mq_hw_ctx *hctx, | |
| const struct blk_mq_queue_data *qd) | |
| { | |
| struct scm_device *scmdev = hctx->queue->queuedata; | |
| struct scm_blk_dev *bdev = dev_get_drvdata(&scmdev->dev); | |
| struct scm_queue *sq = hctx->driver_data; | |
| struct request *req = qd->rq; | |
| struct scm_request *scmrq; | |
| spin_lock(&sq->lock); | |
| if (!scm_permit_request(bdev, req)) { | |
| spin_unlock(&sq->lock); | |
| return BLK_STS_RESOURCE; | |
| } | |
| scmrq = sq->scmrq; | |
| if (!scmrq) { | |
| scmrq = scm_request_fetch(); | |
| if (!scmrq) { | |
| SCM_LOG(5, "no request"); | |
| spin_unlock(&sq->lock); | |
| return BLK_STS_RESOURCE; | |
| } | |
| scm_request_init(bdev, scmrq); | |
| sq->scmrq = scmrq; | |
| } | |
| scm_request_set(scmrq, req); | |
| if (scm_request_prepare(scmrq)) { | |
| SCM_LOG(5, "aidaw alloc failed"); | |
| scm_request_set(scmrq, NULL); | |
| if (scmrq->aob->request.msb_count) | |
| scm_request_start(scmrq); | |
| sq->scmrq = NULL; | |
| spin_unlock(&sq->lock); | |
| return BLK_STS_RESOURCE; | |
| } | |
| blk_mq_start_request(req); | |
| if (qd->last || scmrq->aob->request.msb_count == nr_requests_per_io) { | |
| scm_request_start(scmrq); | |
| sq->scmrq = NULL; | |
| } | |
| spin_unlock(&sq->lock); | |
| return BLK_STS_OK; | |
| } | |
| static int scm_blk_init_hctx(struct blk_mq_hw_ctx *hctx, void *data, | |
| unsigned int idx) | |
| { | |
| struct scm_queue *qd = kzalloc(sizeof(*qd), GFP_KERNEL); | |
| if (!qd) | |
| return -ENOMEM; | |
| spin_lock_init(&qd->lock); | |
| hctx->driver_data = qd; | |
| return 0; | |
| } | |
| static void scm_blk_exit_hctx(struct blk_mq_hw_ctx *hctx, unsigned int idx) | |
| { | |
| struct scm_queue *qd = hctx->driver_data; | |
| WARN_ON(qd->scmrq); | |
| kfree(hctx->driver_data); | |
| hctx->driver_data = NULL; | |
| } | |
| static void __scmrq_log_error(struct scm_request *scmrq) | |
| { | |
| struct aob *aob = scmrq->aob; | |
| if (scmrq->error == BLK_STS_TIMEOUT) | |
| SCM_LOG(1, "Request timeout"); | |
| else { | |
| SCM_LOG(1, "Request error"); | |
| SCM_LOG_HEX(1, &aob->response, sizeof(aob->response)); | |
| } | |
| if (scmrq->retries) | |
| SCM_LOG(1, "Retry request"); | |
| else | |
| pr_err("An I/O operation to SCM failed with rc=%d\n", | |
| scmrq->error); | |
| } | |
| static void scm_blk_handle_error(struct scm_request *scmrq) | |
| { | |
| struct scm_blk_dev *bdev = scmrq->bdev; | |
| unsigned long flags; | |
| if (scmrq->error != BLK_STS_IOERR) | |
| goto restart; | |
| /* For -EIO the response block is valid. */ | |
| switch (scmrq->aob->response.eqc) { | |
| case EQC_WR_PROHIBIT: | |
| spin_lock_irqsave(&bdev->lock, flags); | |
| if (bdev->state != SCM_WR_PROHIBIT) | |
| pr_info("%lx: Write access to the SCM increment is suspended\n", | |
| (unsigned long) bdev->scmdev->address); | |
| bdev->state = SCM_WR_PROHIBIT; | |
| spin_unlock_irqrestore(&bdev->lock, flags); | |
| goto requeue; | |
| default: | |
| break; | |
| } | |
| restart: | |
| if (!eadm_start_aob(scmrq->aob)) | |
| return; | |
| requeue: | |
| scm_request_requeue(scmrq); | |
| } | |
| void scm_blk_irq(struct scm_device *scmdev, void *data, blk_status_t error) | |
| { | |
| struct scm_request *scmrq = data; | |
| scmrq->error = error; | |
| if (error) { | |
| __scmrq_log_error(scmrq); | |
| if (scmrq->retries-- > 0) { | |
| scm_blk_handle_error(scmrq); | |
| return; | |
| } | |
| } | |
| scm_request_finish(scmrq); | |
| } | |
| static void scm_blk_request_done(struct request *req) | |
| { | |
| blk_status_t *error = blk_mq_rq_to_pdu(req); | |
| blk_mq_end_request(req, *error); | |
| } | |
| static const struct block_device_operations scm_blk_devops = { | |
| .owner = THIS_MODULE, | |
| }; | |
| static const struct blk_mq_ops scm_mq_ops = { | |
| .queue_rq = scm_blk_request, | |
| .complete = scm_blk_request_done, | |
| .init_hctx = scm_blk_init_hctx, | |
| .exit_hctx = scm_blk_exit_hctx, | |
| }; | |
| int scm_blk_dev_setup(struct scm_blk_dev *bdev, struct scm_device *scmdev) | |
| { | |
| unsigned int devindex, nr_max_blk; | |
| struct request_queue *rq; | |
| int len, ret; | |
| devindex = atomic_inc_return(&nr_devices) - 1; | |
| /* scma..scmz + scmaa..scmzz */ | |
| if (devindex > 701) { | |
| ret = -ENODEV; | |
| goto out; | |
| } | |
| bdev->scmdev = scmdev; | |
| bdev->state = SCM_OPER; | |
| spin_lock_init(&bdev->lock); | |
| atomic_set(&bdev->queued_reqs, 0); | |
| bdev->tag_set.ops = &scm_mq_ops; | |
| bdev->tag_set.cmd_size = sizeof(blk_status_t); | |
| bdev->tag_set.nr_hw_queues = nr_requests; | |
| bdev->tag_set.queue_depth = nr_requests_per_io * nr_requests; | |
| bdev->tag_set.flags = BLK_MQ_F_SHOULD_MERGE; | |
| ret = blk_mq_alloc_tag_set(&bdev->tag_set); | |
| if (ret) | |
| goto out; | |
| rq = blk_mq_init_queue(&bdev->tag_set); | |
| if (IS_ERR(rq)) { | |
| ret = PTR_ERR(rq); | |
| goto out_tag; | |
| } | |
| bdev->rq = rq; | |
| nr_max_blk = min(scmdev->nr_max_block, | |
| (unsigned int) (PAGE_SIZE / sizeof(struct aidaw))); | |
| blk_queue_logical_block_size(rq, 1 << 12); | |
| blk_queue_max_hw_sectors(rq, nr_max_blk << 3); /* 8 * 512 = blk_size */ | |
| blk_queue_max_segments(rq, nr_max_blk); | |
| queue_flag_set_unlocked(QUEUE_FLAG_NONROT, rq); | |
| queue_flag_clear_unlocked(QUEUE_FLAG_ADD_RANDOM, rq); | |
| bdev->gendisk = alloc_disk(SCM_NR_PARTS); | |
| if (!bdev->gendisk) { | |
| ret = -ENOMEM; | |
| goto out_queue; | |
| } | |
| rq->queuedata = scmdev; | |
| bdev->gendisk->private_data = scmdev; | |
| bdev->gendisk->fops = &scm_blk_devops; | |
| bdev->gendisk->queue = rq; | |
| bdev->gendisk->major = scm_major; | |
| bdev->gendisk->first_minor = devindex * SCM_NR_PARTS; | |
| len = snprintf(bdev->gendisk->disk_name, DISK_NAME_LEN, "scm"); | |
| if (devindex > 25) { | |
| len += snprintf(bdev->gendisk->disk_name + len, | |
| DISK_NAME_LEN - len, "%c", | |
| 'a' + (devindex / 26) - 1); | |
| devindex = devindex % 26; | |
| } | |
| snprintf(bdev->gendisk->disk_name + len, DISK_NAME_LEN - len, "%c", | |
| 'a' + devindex); | |
| /* 512 byte sectors */ | |
| set_capacity(bdev->gendisk, scmdev->size >> 9); | |
| device_add_disk(&scmdev->dev, bdev->gendisk); | |
| return 0; | |
| out_queue: | |
| blk_cleanup_queue(rq); | |
| out_tag: | |
| blk_mq_free_tag_set(&bdev->tag_set); | |
| out: | |
| atomic_dec(&nr_devices); | |
| return ret; | |
| } | |
| void scm_blk_dev_cleanup(struct scm_blk_dev *bdev) | |
| { | |
| del_gendisk(bdev->gendisk); | |
| blk_cleanup_queue(bdev->gendisk->queue); | |
| blk_mq_free_tag_set(&bdev->tag_set); | |
| put_disk(bdev->gendisk); | |
| } | |
| void scm_blk_set_available(struct scm_blk_dev *bdev) | |
| { | |
| unsigned long flags; | |
| spin_lock_irqsave(&bdev->lock, flags); | |
| if (bdev->state == SCM_WR_PROHIBIT) | |
| pr_info("%lx: Write access to the SCM increment is restored\n", | |
| (unsigned long) bdev->scmdev->address); | |
| bdev->state = SCM_OPER; | |
| spin_unlock_irqrestore(&bdev->lock, flags); | |
| } | |
| static bool __init scm_blk_params_valid(void) | |
| { | |
| if (!nr_requests_per_io || nr_requests_per_io > 64) | |
| return false; | |
| return true; | |
| } | |
| static int __init scm_blk_init(void) | |
| { | |
| int ret = -EINVAL; | |
| if (!scm_blk_params_valid()) | |
| goto out; | |
| ret = register_blkdev(0, "scm"); | |
| if (ret < 0) | |
| goto out; | |
| scm_major = ret; | |
| ret = scm_alloc_rqs(nr_requests); | |
| if (ret) | |
| goto out_free; | |
| scm_debug = debug_register("scm_log", 16, 1, 16); | |
| if (!scm_debug) { | |
| ret = -ENOMEM; | |
| goto out_free; | |
| } | |
| debug_register_view(scm_debug, &debug_hex_ascii_view); | |
| debug_set_level(scm_debug, 2); | |
| ret = scm_drv_init(); | |
| if (ret) | |
| goto out_dbf; | |
| return ret; | |
| out_dbf: | |
| debug_unregister(scm_debug); | |
| out_free: | |
| scm_free_rqs(); | |
| unregister_blkdev(scm_major, "scm"); | |
| out: | |
| return ret; | |
| } | |
| module_init(scm_blk_init); | |
| static void __exit scm_blk_cleanup(void) | |
| { | |
| scm_drv_cleanup(); | |
| debug_unregister(scm_debug); | |
| scm_free_rqs(); | |
| unregister_blkdev(scm_major, "scm"); | |
| } | |
| module_exit(scm_blk_cleanup); |