Linux kernel module for block device redirect freezes in make_request on submit_bio/submit_bio_wait

122 Views Asked by At

I'm currently working on a Linux kernel module that creates a block device my_device, which redirects all bio requests to a physical device /dev/sdb.

My guess is that the issue lies within the handling of the bio structure in my alternative make_request function.

I began learning about block devices by creating a simple RAM block device, which functions well. It utilizes blk_queue_make_request to define an alternative make_request function for a device. This function simply parses the bio and performs read/write operations using memcpy to a buffer.

I create my block device as follows (error checking has been omitted to shorten the code):

  #define NR_SECTORS              128
  #define KERNEL_SECTOR_SIZE      512
  #define MY_BLKDEV_NAME          "my_device"

  static struct my_device {
          sector_t                capacity;
          u8                      *data;
          struct gendisk          *gd;
          struct request_queue    *q;
  } my_dev;

  static sector_t my_dev_xfer(char *buff, unsigned int bytes, sector_t pos,
                              int write)
  {
          sector_t sectors = bytes / SBDD_SECTOR_SIZE;
          size_t offset = pos * SBDD_SECTOR_SIZE;
  
          sectors = min(sectors, my_dev.capacity - pos);
          bytes = sectors * SBDD_SECTOR_SIZE;
  
          if (write)
                  memcpy(my_dev.data + offset, buff, bytes);
          else
                  memcpy(buff, my_dev.data + offset, bytes);
  
          pr_debug("pos=%6llu sectors=%4llu %s\n", pos, sectors,
                   write ? "written" : "read");
  
          return sectors;
  }
  
  static blk_qc_t my_dev_make_request(struct request_queue *q, struct bio *bio)
  {
          struct bvec_iter iter;
          struct bio_vec bvec;
          int write = bio_data_dir(bio);
  
          bio_for_each_segment(bvec, bio, iter) {
                  sector_t pos = iter.bi_sector;
                  char *buff = kmap_atomic(bvec.bv_page);
                  unsigned int offset = bvec.bv_offset;
                  size_t bytes = bvec.bv_len;
  
                  pos += my_dev_xfer(buff + offset, bytes, pos, write);
  
                  kunmap_atomic(buff);
          }
  
          bio_endio(bio);
  
          return BLK_STS_OK;
  }
  
  /*
   * There are no read or write operations. These operations are performed by
   * the request() function associated with the request queue of the disk.
   */
  static struct block_device_operations const my_dev_bdev_ops = {
          .owner = THIS_MODULE,
  };
  
  static void mydev_create(make_request_fn *mfn)
  {
          memset(&my_dev, 0, sizeof(struct sbdd));
  
          my_dev.capacity = NR_SECTORS; // 64 Kilobytes
          my_dev.data = vzalloc(my_dev.capacity * KERNEL_SECTOR_SIZE);
  
          my_dev.q = blk_alloc_queue(GFP_KERNEL);
          blk_queue_make_request(my_dev.q, mfn);
          blk_queue_logical_block_size(my_dev.q, KERNEL_SECTOR_SIZE);
  
          my_dev.gd = alloc_disk(1);
          my_dev.gd->queue = my_dev.q;
          my_dev.gd->major = register_blkdev(0, MY_BLKDEV_NAME);
          my_dev.gd->first_minor = 0;
          my_dev.gd->fops = &my_dev_bdev_ops;
          scnprintf(my_dev.gd->disk_name, DISK_NAME_LEN, MY_BLKDEV_NAME);
          set_capacity(my_dev.gd, my_dev.capacity);
          add_disk(my_dev.gd);
  }

It works fine. For example, I'm able to write with echo "Hello" > /dev/my_device and read with dd if=/dev/my_device bs=512 skip=0 count=1.

Now, I want to use a real block device as the backend for my device, instead of a simple buffer. The concept is the same as with the RAM device -- handle the bio in the my_dev_make_request function. I do it like this:


/*
 * Create a copy of bio, change it's device to the physical device,
 * submit it and end io on the original one
 */ 
static blk_qc_t mydev_make_request(struct request_queue *q,
                                   struct bio *bio)
{
        struct bio *proxy_bio;
        int rc = BLK_STS_OK;

        proxy_bio = bio_clone_fast(bio, GFP_KERNEL, NULL);
        bio_set_dev(proxy_bio, bdev);

        pr_info("submitting proxy bio to physical device");
        rc = submit_bio(proxy_bio);
        if (rc)
                return rc;
        pr_info("bio done");
  
        bio_put(proxy_bio);
        bio_endio(bio);
  
        return rc;
}

static int __init mydev_init(void)
{
        struct block_device *bdev;
  
        bdev = blkdev_get_by_path("/dev/sdb",
                                  FMODE_READ | FMODE_WRITE | FMODE_EXCL,
                                  THIS_MODULE);
        /*
         * creation of my_device is the same, except I do not allocate my_dev.data
         * and set capacity to get_capacity(bdev->bd_disk)
         */
        mydev_create(mydev_make_request);

}

However, after loading the module, dmesg shows "submitting proxy bio to physical device", i.e., the code freezes on submit_bio. I expect the real device to process the bio as usual.

Issuing dd if=/dev/my_device bs=512 skip=0 count=1 results in an infinite halt, and I was unable to terminate it with CTRL-C.

I've tried (unsuccessfully) to resolve this by:

  • replacing submit_bio with submit_bio_wait
  • change GFP_KERNEL to GFP_NOIO (as I've seen some drives do)
  • replace bio_set_dev to bio->bi_disk = bdev->bd_disk; and bio->bi_partno = bdev->bd_partno;, as I thought it might relate to the blkg.

I found a somewhat similar question on Stack Overflow from 10 years ago, but it hasn't received any answers: hang when make_request calls submit_bio

0

There are 0 best solutions below