new file mode 100644
@@ -0,0 +1,348 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include "pcache_internal.h"
+#include "cache.h"
+#include "backing_dev.h"
+#include "logic_dev.h"
+
+static int pcache_major;
+static DEFINE_IDA(pcache_mapped_id_ida);
+
+static int minor_to_pcache_mapped_id(int minor)
+{
+ return minor >> PCACHE_PART_SHIFT;
+}
+
+static int logic_dev_open(struct gendisk *disk, blk_mode_t mode)
+{
+ struct pcache_logic_dev *logic_dev = disk->private_data;
+
+ mutex_lock(&logic_dev->lock);
+ logic_dev->open_count++;
+ mutex_unlock(&logic_dev->lock);
+
+ return 0;
+}
+
+static void logic_dev_release(struct gendisk *disk)
+{
+ struct pcache_logic_dev *logic_dev = disk->private_data;
+
+ mutex_lock(&logic_dev->lock);
+ logic_dev->open_count--;
+ mutex_unlock(&logic_dev->lock);
+}
+
+static const struct block_device_operations logic_dev_bd_ops = {
+ .owner = THIS_MODULE,
+ .open = logic_dev_open,
+ .release = logic_dev_release,
+};
+
+static inline bool pcache_req_nodata(struct pcache_request *pcache_req)
+{
+ switch (pcache_req->op) {
+ case REQ_OP_WRITE:
+ case REQ_OP_READ:
+ return false;
+ case REQ_OP_FLUSH:
+ return true;
+ default:
+ BUG();
+ }
+}
+
+static blk_status_t pcache_queue_rq(struct blk_mq_hw_ctx *hctx,
+ const struct blk_mq_queue_data *bd)
+{
+ struct request *req = bd->rq;
+ struct pcache_queue *queue = hctx->driver_data;
+ struct pcache_logic_dev *logic_dev = queue->logic_dev;
+ struct pcache_request *pcache_req = blk_mq_rq_to_pdu(bd->rq);
+ int ret;
+
+ memset(pcache_req, 0, sizeof(struct pcache_request));
+ kref_init(&pcache_req->ref);
+ blk_mq_start_request(bd->rq);
+
+ pcache_req->queue = queue;
+ pcache_req->req = req;
+ pcache_req->op = req_op(bd->rq);
+ pcache_req->off = (u64)blk_rq_pos(bd->rq) << SECTOR_SHIFT;
+ if (!pcache_req_nodata(pcache_req))
+ pcache_req->data_len = blk_rq_bytes(bd->rq);
+ else
+ pcache_req->data_len = 0;
+
+ ret = pcache_cache_handle_req(logic_dev->backing_dev->cache, pcache_req);
+ pcache_req_put(pcache_req, ret);
+
+ return BLK_STS_OK;
+}
+
+static int pcache_init_hctx(struct blk_mq_hw_ctx *hctx, void *driver_data,
+ unsigned int hctx_idx)
+{
+ struct pcache_logic_dev *logic_dev = driver_data;
+
+ hctx->driver_data = &logic_dev->queues[hctx_idx];
+
+ return 0;
+}
+
+const struct blk_mq_ops logic_dev_mq_ops = {
+ .queue_rq = pcache_queue_rq,
+ .init_hctx = pcache_init_hctx,
+};
+
+static int disk_start(struct pcache_logic_dev *logic_dev)
+{
+ struct gendisk *disk;
+ struct queue_limits lim = {
+ .max_hw_sectors = BIO_MAX_VECS * PAGE_SECTORS,
+ .io_min = 4096,
+ .io_opt = 4096,
+ .max_segments = BIO_MAX_VECS,
+ .max_segment_size = PAGE_SIZE,
+ .discard_granularity = 0,
+ .max_hw_discard_sectors = 0,
+ .max_write_zeroes_sectors = 0
+ };
+ int ret;
+
+ memset(&logic_dev->tag_set, 0, sizeof(logic_dev->tag_set));
+ logic_dev->tag_set.ops = &logic_dev_mq_ops;
+ logic_dev->tag_set.queue_depth = 128;
+ logic_dev->tag_set.numa_node = NUMA_NO_NODE;
+ logic_dev->tag_set.nr_hw_queues = logic_dev->num_queues;
+ logic_dev->tag_set.cmd_size = sizeof(struct pcache_request);
+ logic_dev->tag_set.timeout = 0;
+ logic_dev->tag_set.driver_data = logic_dev;
+
+ ret = blk_mq_alloc_tag_set(&logic_dev->tag_set);
+ if (ret) {
+ logic_dev_err(logic_dev, "failed to alloc tag set %d", ret);
+ goto err;
+ }
+
+ disk = blk_mq_alloc_disk(&logic_dev->tag_set, &lim, logic_dev);
+ if (IS_ERR(disk)) {
+ ret = PTR_ERR(disk);
+ logic_dev_err(logic_dev, "failed to alloc disk");
+ goto out_tag_set;
+ }
+
+ snprintf(disk->disk_name, sizeof(disk->disk_name), "pcache%d",
+ logic_dev->mapped_id);
+
+ disk->major = pcache_major;
+ disk->first_minor = logic_dev->mapped_id << PCACHE_PART_SHIFT;
+ disk->minors = (1 << PCACHE_PART_SHIFT);
+ disk->fops = &logic_dev_bd_ops;
+ disk->private_data = logic_dev;
+
+ logic_dev->disk = disk;
+
+ set_capacity(logic_dev->disk, logic_dev->dev_size);
+ set_disk_ro(logic_dev->disk, false);
+
+ /* Register the disk with the system */
+ ret = add_disk(logic_dev->disk);
+ if (ret)
+ goto put_disk;
+
+ return 0;
+
+put_disk:
+ put_disk(logic_dev->disk);
+out_tag_set:
+ blk_mq_free_tag_set(&logic_dev->tag_set);
+err:
+ return ret;
+}
+
+static void disk_stop(struct pcache_logic_dev *logic_dev)
+{
+ del_gendisk(logic_dev->disk);
+ put_disk(logic_dev->disk);
+ blk_mq_free_tag_set(&logic_dev->tag_set);
+}
+
+static struct pcache_logic_dev *logic_dev_alloc(struct pcache_backing_dev *backing_dev)
+{
+ struct pcache_logic_dev *logic_dev;
+ int ret;
+
+ logic_dev = kzalloc(sizeof(struct pcache_logic_dev), GFP_KERNEL);
+ if (!logic_dev)
+ return NULL;
+
+ logic_dev->backing_dev = backing_dev;
+ mutex_init(&logic_dev->lock);
+ INIT_LIST_HEAD(&logic_dev->node);
+
+ logic_dev->mapped_id = ida_simple_get(&pcache_mapped_id_ida, 0,
+ minor_to_pcache_mapped_id(1 << MINORBITS),
+ GFP_KERNEL);
+ if (logic_dev->mapped_id < 0) {
+ ret = -ENOENT;
+ goto logic_dev_free;
+ }
+
+ return logic_dev;
+
+logic_dev_free:
+ kfree(logic_dev);
+
+ return NULL;
+}
+
+static void logic_dev_free(struct pcache_logic_dev *logic_dev)
+{
+ ida_simple_remove(&pcache_mapped_id_ida, logic_dev->mapped_id);
+ kfree(logic_dev);
+}
+
+static void logic_dev_destroy_queues(struct pcache_logic_dev *logic_dev)
+{
+ struct pcache_queue *queue;
+ int i;
+
+ /* Stop each queue associated with the block device */
+ for (i = 0; i < logic_dev->num_queues; i++) {
+ queue = &logic_dev->queues[i];
+ if (queue->state == PCACHE_QUEUE_STATE_NONE)
+ continue;
+ }
+
+ /* Free the memory allocated for the queues */
+ kfree(logic_dev->queues);
+}
+
+static int logic_dev_create_queues(struct pcache_logic_dev *logic_dev)
+{
+ int i;
+ struct pcache_queue *queue;
+
+ logic_dev->queues = kcalloc(logic_dev->num_queues, sizeof(struct pcache_queue), GFP_KERNEL);
+ if (!logic_dev->queues)
+ return -ENOMEM;
+
+ for (i = 0; i < logic_dev->num_queues; i++) {
+ queue = &logic_dev->queues[i];
+ queue->logic_dev = logic_dev;
+ queue->index = i;
+
+ queue->state = PCACHE_QUEUE_STATE_RUNNING;
+ }
+
+ return 0;
+}
+
+static int logic_dev_init(struct pcache_logic_dev *logic_dev, u32 queues)
+{
+ int ret;
+
+ logic_dev->num_queues = queues;
+ logic_dev->dev_size = logic_dev->dev_size;
+
+ ret = logic_dev_create_queues(logic_dev);
+ if (ret < 0)
+ goto err;
+
+ return 0;
+err:
+ return ret;
+}
+
+static void logic_dev_destroy(struct pcache_logic_dev *logic_dev)
+{
+ logic_dev_destroy_queues(logic_dev);
+}
+
+int logic_dev_start(struct pcache_backing_dev *backing_dev, u32 queues)
+{
+ struct pcache_logic_dev *logic_dev;
+ int ret;
+
+ logic_dev = logic_dev_alloc(backing_dev);
+ if (!logic_dev)
+ return -ENOMEM;
+
+ logic_dev->dev_size = backing_dev->dev_size;
+ ret = logic_dev_init(logic_dev, queues);
+ if (ret)
+ goto logic_dev_free;
+
+ backing_dev->logic_dev = logic_dev;
+
+ ret = disk_start(logic_dev);
+ if (ret < 0)
+ goto logic_dev_destroy;
+
+ return 0;
+
+logic_dev_destroy:
+ logic_dev_destroy(logic_dev);
+logic_dev_free:
+ logic_dev_free(logic_dev);
+ return ret;
+}
+
+int logic_dev_stop(struct pcache_logic_dev *logic_dev)
+{
+ mutex_lock(&logic_dev->lock);
+ if (logic_dev->open_count > 0) {
+ mutex_unlock(&logic_dev->lock);
+ return -EBUSY;
+ }
+ mutex_unlock(&logic_dev->lock);
+
+ disk_stop(logic_dev);
+ logic_dev_destroy(logic_dev);
+ logic_dev_free(logic_dev);
+
+ return 0;
+}
+
+int pcache_blkdev_init(void)
+{
+ pcache_major = register_blkdev(0, "pcache");
+ if (pcache_major < 0)
+ return pcache_major;
+
+ return 0;
+}
+
+void pcache_blkdev_exit(void)
+{
+ unregister_blkdev(pcache_major, "pcache");
+}
+
+static void end_req(struct kref *ref)
+{
+ struct pcache_request *pcache_req = container_of(ref, struct pcache_request, ref);
+ struct request *req = pcache_req->req;
+ int ret = pcache_req->ret;
+
+ if (req) {
+ /* Complete the block layer request based on the return status */
+ if (ret == -ENOMEM || ret == -EBUSY)
+ blk_mq_requeue_request(req, true);
+ else
+ blk_mq_end_request(req, errno_to_blk_status(ret));
+ }
+}
+
+void pcache_req_get(struct pcache_request *pcache_req)
+{
+ kref_get(&pcache_req->ref);
+}
+
+void pcache_req_put(struct pcache_request *pcache_req, int ret)
+{
+ /* Set the return status if it is not already set */
+ if (ret && !pcache_req->ret)
+ pcache_req->ret = ret;
+
+ kref_put(&pcache_req->ref, end_req);
+}
new file mode 100644
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+#ifndef _PCACHE_LOGIC_DEV_H
+#define _PCACHE_LOGIC_DEV_H
+
+#include <linux/blk-mq.h>
+
+#include "pcache_internal.h"
+
+#define logic_dev_err(logic_dev, fmt, ...) \
+ cache_dev_err(logic_dev->backing_dev->cache_dev, "logic_dev%d: " fmt, \
+ logic_dev->mapped_id, ##__VA_ARGS__)
+#define logic_dev_info(logic_dev, fmt, ...) \
+ cache_dev_info(logic_dev->backing_dev->cache_dev, "logic_dev%d: " fmt, \
+ logic_dev->mapped_id, ##__VA_ARGS__)
+#define logic_dev_debug(logic_dev, fmt, ...) \
+ cache_dev_debug(logic_dev->backing_dev->cache_dev, "logic_dev%d: " fmt, \
+ logic_dev->mapped_id, ##__VA_ARGS__)
+
+#define PCACHE_QUEUE_STATE_NONE 0
+#define PCACHE_QUEUE_STATE_RUNNING 1
+
+struct pcache_queue {
+ struct pcache_logic_dev *logic_dev;
+ u32 index;
+
+ u8 state;
+};
+
+struct pcache_request {
+ struct pcache_queue *queue;
+ struct request *req;
+
+ u64 off;
+ u32 data_len;
+
+ u8 op;
+
+ struct kref ref;
+ int ret;
+};
+
+struct pcache_logic_dev {
+ int mapped_id; /* id in block device such as: /dev/pcache0 */
+
+ struct pcache_backing_dev *backing_dev;
+
+ int major; /* blkdev assigned major */
+ int minor;
+ struct gendisk *disk; /* blkdev's gendisk and rq */
+
+ struct mutex lock;
+ unsigned long open_count; /* protected by lock */
+
+ struct list_head node;
+
+ /* Block layer tags. */
+ struct blk_mq_tag_set tag_set;
+
+ uint32_t num_queues;
+ struct pcache_queue *queues;
+
+ u64 dev_size;
+};
+
+int logic_dev_start(struct pcache_backing_dev *backing_dev, u32 queues);
+int logic_dev_stop(struct pcache_logic_dev *logic_dev);
+
+void pcache_req_get(struct pcache_request *pcache_req);
+void pcache_req_put(struct pcache_request *pcache_req, int ret);
+
+int pcache_blkdev_init(void);
+void pcache_blkdev_exit(void);
+#endif /* _PCACHE_LOGIC_DEV_H */
This patch introduces the logic block device layer (`pcache_logic_dev`), which connects pcache to the kernel block layer through a standard gendisk interface. It implements the infrastructure to expose the cache as a Linux block device (e.g., /dev/pcache0), enabling I/O submission via standard block device APIs. Key components added: - pcache_logic_dev: Represents the logical block device and encapsulates associated state, such as queues, gendisk, tag set, and open count tracking. - Block I/O path: Implements `pcache_queue_rq()` to translate block layer requests into internal `pcache_request` objects. Handles data reads, writes, and flushes by dispatching them to `pcache_cache_handle_req()` and completing them via `pcache_req_put()`. - Queue management: Initializes per-hctx queues and associates them with `pcache_queue`. Ensures multi-queue support by allocating queues according to the backing device's configuration. - Device lifecycle: Provides `logic_dev_start()` and `logic_dev_stop()` to manage device creation, queue setup, and gendisk registration/unregistration. Tracks open_count to ensure safe teardown. - blkdev integration: Adds `pcache_blkdev_init()` and `pcache_blkdev_exit()` to register/unregister the pcache major number. This forms the upper layer of pcache's I/O path and makes the cache visible as a standard Linux block device. Signed-off-by: Dongsheng Yang <dongsheng.yang@linux.dev> --- drivers/block/pcache/logic_dev.c | 348 +++++++++++++++++++++++++++++++ drivers/block/pcache/logic_dev.h | 73 +++++++ 2 files changed, 421 insertions(+) create mode 100644 drivers/block/pcache/logic_dev.c create mode 100644 drivers/block/pcache/logic_dev.h