diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-19 10:09:22 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-19 10:09:22 -0700 |
| commit | f4cdf7ca9a1fdcca413157df19753f388a5a224e (patch) | |
| tree | 60a31895b2446cd7fa27514e8c2843534a0de0da /block/bsg.c | |
| download | linux-stable-f4cdf7ca9a1fdcca413157df19753f388a5a224e.tar.gz linux-stable-f4cdf7ca9a1fdcca413157df19753f388a5a224e.zip | |
Merge tag 'media/v7.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-mediagrafted
Pull media updates from Mauro Carvalho Chehab:
- v4l2-core: added ISP statistics support and per-block validation
- v4l2-core: Allow unknown HDR10 white point and luminance
- New camera sensors: Sony IMX678 and IMX471m, Himax HM1092 IR sensor
- New codec: Milos: VPU v2.0 codec support
- isp driver: gained support for Dreamchip RPPX1 ISP framework
- vsp1 driver: gained support for RZ/T2H and RZ/N2H
- Novalake driver: gained CVS support for new NVL hardware
- dvb-core: fix feed leak on failed DMX_ADD_PID
- several driver fixes, cleanups and minor improvements
* tag 'media/v7.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media: (308 commits)
media: ipu-bridge: check all DMI entries when overriding sensor rotation
media: v4l2-async: avoid deleting unlinked ASC entry on link error
media: rzg2l-cru: Align bytesperline to hardware DMA stride requirement
media: intel/ipu6: fix async notifier cleanup leak on parse error
media: staging/ipu7: fix async notifier UAF on probe error path
media: amd: isp4: fix self-deadlock in isp4sd_pwron_and_init() error path
media: amd: isp4: release partial allocations in isp4if_alloc_fw_gpumem()
media: rcar-isp: Fix VSPX reference leaks
media: rcar-isp: Release ISPCORE resources
media: i2c: imx415: Release runtime PM reference on VBLANK error
media: i2c: imx415: Return test pattern write errors
media: renesas: vsp1: Declare index variables in for loop statement
media: renesas: vsp1: Make reset control optional to support platforms without a reset line
media: dt-bindings: media: renesas,vsp1: Document RZ/T2H and RZ/N2H SoCs
media: dt-bindings: media: renesas,fcp: Document RZ/T2H and RZ/N2H SoCs
media: nxp: imx8-isi: Add additional 32-bit RGB format support
media: nxp: imx8-isi: Add 16-bit raw Bayer format support
media: nxp: imx8-isi: Implement per-stream reference counting for multiplexed streams
media: nxp: imx8-isi: Use BIT_ULL() for 64-bit stream masks
media: nxp: imx8-isi: Correct color map between V4L2 and ISI
...
Diffstat (limited to 'block/bsg.c')
| -rw-r--r-- | block/bsg.c | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/block/bsg.c b/block/bsg.c new file mode 100644 index 000000000..82aaf3cee --- /dev/null +++ b/block/bsg.c @@ -0,0 +1,308 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * bsg.c - block layer implementation of the sg v4 interface + */ +#include <linux/module.h> +#include <linux/init.h> +#include <linux/file.h> +#include <linux/blkdev.h> +#include <linux/cdev.h> +#include <linux/jiffies.h> +#include <linux/percpu.h> +#include <linux/idr.h> +#include <linux/bsg.h> +#include <linux/slab.h> +#include <linux/io_uring/cmd.h> + +#include <scsi/scsi.h> +#include <scsi/scsi_ioctl.h> +#include <scsi/sg.h> + +#define BSG_DESCRIPTION "Block layer SCSI generic (bsg) driver" +#define BSG_VERSION "0.4" + +struct bsg_device { + struct request_queue *queue; + struct device device; + struct cdev cdev; + int max_queue; + unsigned int timeout; + unsigned int reserved_size; + bsg_sg_io_fn *sg_io_fn; + bsg_uring_cmd_fn *uring_cmd_fn; +}; + +static inline struct bsg_device *to_bsg_device(struct inode *inode) +{ + return container_of(inode->i_cdev, struct bsg_device, cdev); +} + +#define BSG_DEFAULT_CMDS 64 +#define BSG_MAX_DEVS (1 << MINORBITS) + +static DEFINE_IDA(bsg_minor_ida); +static const struct class bsg_class; +static int bsg_major; + +static unsigned int bsg_timeout(struct bsg_device *bd, struct sg_io_v4 *hdr) +{ + unsigned int timeout = BLK_DEFAULT_SG_TIMEOUT; + + if (hdr->timeout) + timeout = msecs_to_jiffies(hdr->timeout); + else if (bd->timeout) + timeout = bd->timeout; + + return max_t(unsigned int, timeout, BLK_MIN_SG_TIMEOUT); +} + +static int bsg_sg_io(struct bsg_device *bd, bool open_for_write, + void __user *uarg) +{ + struct sg_io_v4 hdr; + int ret; + + if (copy_from_user(&hdr, uarg, sizeof(hdr))) + return -EFAULT; + if (hdr.guard != 'Q') + return -EINVAL; + ret = bd->sg_io_fn(bd->queue, &hdr, open_for_write, + bsg_timeout(bd, &hdr)); + if (!ret && copy_to_user(uarg, &hdr, sizeof(hdr))) + return -EFAULT; + return ret; +} + +static int bsg_open(struct inode *inode, struct file *file) +{ + if (!blk_get_queue(to_bsg_device(inode)->queue)) + return -ENXIO; + return 0; +} + +static int bsg_release(struct inode *inode, struct file *file) +{ + blk_put_queue(to_bsg_device(inode)->queue); + return 0; +} + +static int bsg_get_command_q(struct bsg_device *bd, int __user *uarg) +{ + return put_user(READ_ONCE(bd->max_queue), uarg); +} + +static int bsg_set_command_q(struct bsg_device *bd, int __user *uarg) +{ + int max_queue; + + if (get_user(max_queue, uarg)) + return -EFAULT; + if (max_queue < 1) + return -EINVAL; + WRITE_ONCE(bd->max_queue, max_queue); + return 0; +} + +static long bsg_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct bsg_device *bd = to_bsg_device(file_inode(file)); + struct request_queue *q = bd->queue; + void __user *uarg = (void __user *) arg; + int __user *intp = uarg; + int val; + + switch (cmd) { + /* + * Our own ioctls + */ + case SG_GET_COMMAND_Q: + return bsg_get_command_q(bd, uarg); + case SG_SET_COMMAND_Q: + return bsg_set_command_q(bd, uarg); + + /* + * SCSI/sg ioctls + */ + case SG_GET_VERSION_NUM: + return put_user(30527, intp); + case SCSI_IOCTL_GET_IDLUN: + return put_user(0, intp); + case SCSI_IOCTL_GET_BUS_NUMBER: + return put_user(0, intp); + case SG_SET_TIMEOUT: + if (get_user(val, intp)) + return -EFAULT; + bd->timeout = clock_t_to_jiffies(val); + return 0; + case SG_GET_TIMEOUT: + return jiffies_to_clock_t(bd->timeout); + case SG_GET_RESERVED_SIZE: + return put_user(min(bd->reserved_size, queue_max_bytes(q)), + intp); + case SG_SET_RESERVED_SIZE: + if (get_user(val, intp)) + return -EFAULT; + if (val < 0) + return -EINVAL; + bd->reserved_size = + min_t(unsigned int, val, queue_max_bytes(q)); + return 0; + case SG_EMULATED_HOST: + return put_user(1, intp); + case SG_IO: + return bsg_sg_io(bd, file->f_mode & FMODE_WRITE, uarg); + case SCSI_IOCTL_SEND_COMMAND: + pr_warn_ratelimited("%s: calling unsupported SCSI_IOCTL_SEND_COMMAND\n", + current->comm); + return -EINVAL; + default: + return -ENOTTY; + } +} + +static int bsg_check_uring_features(unsigned int issue_flags) +{ + /* BSG passthrough requires big SQE/CQE support */ + if ((issue_flags & (IO_URING_F_SQE128|IO_URING_F_CQE32)) != + (IO_URING_F_SQE128|IO_URING_F_CQE32)) + return -EOPNOTSUPP; + return 0; +} + +static int bsg_uring_cmd(struct io_uring_cmd *ioucmd, unsigned int issue_flags) +{ + struct bsg_device *bd = to_bsg_device(file_inode(ioucmd->file)); + bool open_for_write = ioucmd->file->f_mode & FMODE_WRITE; + struct request_queue *q = bd->queue; + int ret; + + ret = bsg_check_uring_features(issue_flags); + if (ret) + return ret; + + if (!bd->uring_cmd_fn) + return -EOPNOTSUPP; + + return bd->uring_cmd_fn(q, ioucmd, issue_flags, open_for_write); +} + +static const struct file_operations bsg_fops = { + .open = bsg_open, + .release = bsg_release, + .unlocked_ioctl = bsg_ioctl, + .compat_ioctl = compat_ptr_ioctl, + .uring_cmd = bsg_uring_cmd, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static void bsg_device_release(struct device *dev) +{ + struct bsg_device *bd = container_of(dev, struct bsg_device, device); + + ida_free(&bsg_minor_ida, MINOR(bd->device.devt)); + kfree(bd); +} + +void bsg_unregister_queue(struct bsg_device *bd) +{ + struct gendisk *disk = bd->queue->disk; + + if (disk && disk->queue_kobj.sd) + sysfs_remove_link(&disk->queue_kobj, "bsg"); + cdev_device_del(&bd->cdev, &bd->device); + put_device(&bd->device); +} +EXPORT_SYMBOL_GPL(bsg_unregister_queue); + +struct bsg_device *bsg_register_queue(struct request_queue *q, + struct device *parent, const char *name, bsg_sg_io_fn *sg_io_fn, + bsg_uring_cmd_fn *uring_cmd_fn) +{ + struct bsg_device *bd; + int ret; + + bd = kzalloc_obj(*bd); + if (!bd) + return ERR_PTR(-ENOMEM); + bd->max_queue = BSG_DEFAULT_CMDS; + bd->reserved_size = INT_MAX; + bd->queue = q; + bd->sg_io_fn = sg_io_fn; + bd->uring_cmd_fn = uring_cmd_fn; + + ret = ida_alloc_max(&bsg_minor_ida, BSG_MAX_DEVS - 1, GFP_KERNEL); + if (ret < 0) { + if (ret == -ENOSPC) + dev_err(parent, "bsg: too many bsg devices\n"); + kfree(bd); + return ERR_PTR(ret); + } + bd->device.devt = MKDEV(bsg_major, ret); + bd->device.class = &bsg_class; + bd->device.parent = parent; + bd->device.release = bsg_device_release; + dev_set_name(&bd->device, "%s", name); + device_initialize(&bd->device); + + cdev_init(&bd->cdev, &bsg_fops); + bd->cdev.owner = THIS_MODULE; + ret = cdev_device_add(&bd->cdev, &bd->device); + if (ret) + goto out_put_device; + + if (q->disk && q->disk->queue_kobj.sd) { + ret = sysfs_create_link(&q->disk->queue_kobj, &bd->device.kobj, + "bsg"); + if (ret) + goto out_device_del; + } + + return bd; + +out_device_del: + cdev_device_del(&bd->cdev, &bd->device); +out_put_device: + put_device(&bd->device); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(bsg_register_queue); + +static char *bsg_devnode(const struct device *dev, umode_t *mode) +{ + return kasprintf(GFP_KERNEL, "bsg/%s", dev_name(dev)); +} + +static const struct class bsg_class = { + .name = "bsg", + .devnode = bsg_devnode, +}; + +static int __init bsg_init(void) +{ + dev_t devid; + int ret; + + ret = class_register(&bsg_class); + if (ret) + return ret; + + ret = alloc_chrdev_region(&devid, 0, BSG_MAX_DEVS, "bsg"); + if (ret) + goto destroy_bsg_class; + bsg_major = MAJOR(devid); + + printk(KERN_INFO BSG_DESCRIPTION " version " BSG_VERSION + " loaded (major %d)\n", bsg_major); + return 0; + +destroy_bsg_class: + class_unregister(&bsg_class); + return ret; +} + +MODULE_AUTHOR("Jens Axboe"); +MODULE_DESCRIPTION(BSG_DESCRIPTION); +MODULE_LICENSE("GPL"); + +device_initcall(bsg_init); |
