| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * vfio-ISM driver for s390 |
| * |
| * Copyright IBM Corp. |
| */ |
| |
| #include <linux/slab.h> |
| #include "../vfio_pci_priv.h" |
| |
| #define ISM_VFIO_PCI_OFFSET_SHIFT 48 |
| #define ISM_VFIO_PCI_OFFSET_TO_INDEX(off) ((off) >> ISM_VFIO_PCI_OFFSET_SHIFT) |
| #define ISM_VFIO_PCI_INDEX_TO_OFFSET(index) ((u64)(index) << ISM_VFIO_PCI_OFFSET_SHIFT) |
| #define ISM_VFIO_PCI_OFFSET_MASK (((u64)(1) << ISM_VFIO_PCI_OFFSET_SHIFT) - 1) |
| |
| /* |
| * Use __zpci_load() to bypass automatic use of |
| * PCI MIO instructions which are not supported on ISM devices |
| */ |
| #define ISM_READ(size) \ |
| static int ism_read##size(struct zpci_dev *zdev, int bar, \ |
| size_t *filled, char __user *buf, \ |
| loff_t off) \ |
| { \ |
| u64 req, tmp; \ |
| u##size val; \ |
| int ret; \ |
| \ |
| req = ZPCI_CREATE_REQ(READ_ONCE(zdev->fh), bar, sizeof(val)); \ |
| ret = __zpci_load(&tmp, req, off); \ |
| if (ret) \ |
| return ret; \ |
| val = (u##size)tmp; \ |
| if (copy_to_user(buf, &val, sizeof(val))) \ |
| return -EFAULT; \ |
| *filled = sizeof(val); \ |
| return 0; \ |
| } |
| |
| ISM_READ(64); |
| ISM_READ(32); |
| ISM_READ(16); |
| ISM_READ(8); |
| |
| struct ism_vfio_pci_core_device { |
| struct vfio_pci_core_device core_device; |
| struct kmem_cache *store_block_cache; |
| }; |
| |
| static int ism_vfio_pci_open_device(struct vfio_device *core_vdev) |
| { |
| struct ism_vfio_pci_core_device *ivpcd; |
| struct vfio_pci_core_device *vdev; |
| int ret; |
| |
| ivpcd = container_of(core_vdev, struct ism_vfio_pci_core_device, |
| core_device.vdev); |
| vdev = &ivpcd->core_device; |
| |
| ret = vfio_pci_core_enable(vdev); |
| if (ret) |
| return ret; |
| |
| vfio_pci_core_finish_enable(vdev); |
| return 0; |
| } |
| |
| /* |
| * ism_vfio_pci_do_io_r() |
| * |
| * On s390, kernel primitives such as ioread() and iowrite() are switched over |
| * from function-handle-based PCI load/stores instructions to PCI memory-I/O (MIO) |
| * loads/stores when these are available and not explicitly disabled. Since these |
| * instructions cannot be used with ISM devices, ensure that classic |
| * function-handle-based PCI instructions are used instead. |
| */ |
| static ssize_t ism_vfio_pci_do_io_r(struct vfio_pci_core_device *vdev, |
| char __user *buf, loff_t off, size_t count, |
| int bar) |
| { |
| struct zpci_dev *zdev = to_zpci(vdev->pdev); |
| ssize_t done = 0; |
| int ret; |
| |
| while (count) { |
| size_t filled; |
| |
| if (count >= 8 && IS_ALIGNED(off, 8)) { |
| ret = ism_read64(zdev, bar, &filled, buf, off); |
| if (ret) |
| return ret; |
| } else if (count >= 4 && IS_ALIGNED(off, 4)) { |
| ret = ism_read32(zdev, bar, &filled, buf, off); |
| if (ret) |
| return ret; |
| } else if (count >= 2 && IS_ALIGNED(off, 2)) { |
| ret = ism_read16(zdev, bar, &filled, buf, off); |
| if (ret) |
| return ret; |
| } else { |
| ret = ism_read8(zdev, bar, &filled, buf, off); |
| if (ret) |
| return ret; |
| } |
| |
| count -= filled; |
| done += filled; |
| off += filled; |
| buf += filled; |
| } |
| |
| return done; |
| } |
| |
| /* |
| * ism_vfio_pci_do_io_w() |
| * |
| * Ensure that the PCI store block (PCISTB) instruction is used as required by the |
| * ISM device. The ISM device also uses a 256 TiB BAR 0 for write operations, |
| * which requires a 48bit region address space (ISM_VFIO_PCI_OFFSET_SHIFT). |
| */ |
| static ssize_t ism_vfio_pci_do_io_w(struct vfio_pci_core_device *vdev, |
| char __user *buf, loff_t off, size_t count, |
| int bar) |
| { |
| struct zpci_dev *zdev = to_zpci(vdev->pdev); |
| struct ism_vfio_pci_core_device *ivpcd; |
| ssize_t ret; |
| void *data; |
| u64 req; |
| |
| if (count > zdev->maxstbl) |
| return -EINVAL; |
| if (((off % PAGE_SIZE) + count) > PAGE_SIZE) |
| return -EINVAL; |
| |
| ivpcd = container_of(vdev, struct ism_vfio_pci_core_device, |
| core_device); |
| data = kmem_cache_alloc(ivpcd->store_block_cache, GFP_KERNEL); |
| if (!data) |
| return -ENOMEM; |
| |
| if (copy_from_user(data, buf, count)) { |
| ret = -EFAULT; |
| goto out_free; |
| } |
| |
| req = ZPCI_CREATE_REQ(READ_ONCE(zdev->fh), bar, count); |
| ret = __zpci_store_block(data, req, off); |
| if (ret) |
| goto out_free; |
| |
| ret = count; |
| |
| out_free: |
| kmem_cache_free(ivpcd->store_block_cache, data); |
| return ret; |
| } |
| |
| static ssize_t ism_vfio_pci_bar_rw(struct vfio_pci_core_device *vdev, |
| char __user *buf, size_t count, loff_t *ppos, |
| bool iswrite) |
| { |
| int bar = ISM_VFIO_PCI_OFFSET_TO_INDEX(*ppos); |
| loff_t pos = *ppos & ISM_VFIO_PCI_OFFSET_MASK; |
| resource_size_t end; |
| ssize_t done = 0; |
| |
| if (pci_resource_start(vdev->pdev, bar)) |
| end = pci_resource_len(vdev->pdev, bar); |
| else |
| return -EINVAL; |
| |
| if (pos >= end) |
| return -EINVAL; |
| |
| count = min(count, (size_t)(end - pos)); |
| |
| if (iswrite) |
| done = ism_vfio_pci_do_io_w(vdev, buf, pos, count, bar); |
| else |
| done = ism_vfio_pci_do_io_r(vdev, buf, pos, count, bar); |
| |
| if (done >= 0) |
| *ppos += done; |
| |
| return done; |
| } |
| |
| static ssize_t ism_vfio_pci_config_rw(struct vfio_pci_core_device *vdev, |
| char __user *buf, size_t count, |
| loff_t *ppos, bool iswrite) |
| { |
| loff_t pos = *ppos; |
| size_t done = 0; |
| int ret = 0; |
| |
| pos &= ISM_VFIO_PCI_OFFSET_MASK; |
| |
| while (count) { |
| /* |
| * zPCI must not use MIO instructions for config space access, |
| * so we can use common code path here. |
| */ |
| ret = vfio_pci_config_rw_single(vdev, buf, count, &pos, iswrite); |
| if (ret < 0) |
| return ret; |
| |
| count -= ret; |
| done += ret; |
| buf += ret; |
| pos += ret; |
| } |
| |
| *ppos += done; |
| |
| return done; |
| } |
| |
| static ssize_t ism_vfio_pci_rw(struct vfio_device *core_vdev, char __user *buf, |
| size_t count, loff_t *ppos, bool iswrite) |
| { |
| unsigned int index = ISM_VFIO_PCI_OFFSET_TO_INDEX(*ppos); |
| struct vfio_pci_core_device *vdev; |
| int ret; |
| |
| vdev = container_of(core_vdev, struct vfio_pci_core_device, vdev); |
| |
| if (!count) |
| return 0; |
| |
| switch (index) { |
| case VFIO_PCI_CONFIG_REGION_INDEX: |
| ret = ism_vfio_pci_config_rw(vdev, buf, count, ppos, iswrite); |
| break; |
| |
| case VFIO_PCI_BAR0_REGION_INDEX ... VFIO_PCI_BAR5_REGION_INDEX: |
| ret = ism_vfio_pci_bar_rw(vdev, buf, count, ppos, iswrite); |
| break; |
| |
| default: |
| return -EINVAL; |
| } |
| |
| return ret; |
| } |
| |
| static ssize_t ism_vfio_pci_read(struct vfio_device *core_vdev, |
| char __user *buf, size_t count, loff_t *ppos) |
| { |
| return ism_vfio_pci_rw(core_vdev, buf, count, ppos, false); |
| } |
| |
| static ssize_t ism_vfio_pci_write(struct vfio_device *core_vdev, |
| const char __user *buf, size_t count, |
| loff_t *ppos) |
| { |
| return ism_vfio_pci_rw(core_vdev, (char __user *)buf, count, ppos, |
| true); |
| } |
| |
| static int ism_vfio_pci_ioctl_get_region_info(struct vfio_device *core_vdev, |
| struct vfio_region_info *info, |
| struct vfio_info_cap *caps) |
| { |
| struct vfio_pci_core_device *vdev = |
| container_of(core_vdev, struct vfio_pci_core_device, vdev); |
| struct pci_dev *pdev = vdev->pdev; |
| |
| switch (info->index) { |
| case VFIO_PCI_CONFIG_REGION_INDEX: |
| info->offset = ISM_VFIO_PCI_INDEX_TO_OFFSET(info->index); |
| info->size = pdev->cfg_size; |
| info->flags = VFIO_REGION_INFO_FLAG_READ | |
| VFIO_REGION_INFO_FLAG_WRITE; |
| break; |
| case VFIO_PCI_BAR0_REGION_INDEX ... VFIO_PCI_BAR5_REGION_INDEX: |
| info->offset = ISM_VFIO_PCI_INDEX_TO_OFFSET(info->index); |
| info->size = pci_resource_len(pdev, info->index); |
| if (!info->size) { |
| info->flags = 0; |
| break; |
| } |
| info->flags = VFIO_REGION_INFO_FLAG_READ | |
| VFIO_REGION_INFO_FLAG_WRITE; |
| break; |
| default: |
| info->offset = 0; |
| info->size = 0; |
| info->flags = 0; |
| return -EINVAL; |
| } |
| return 0; |
| } |
| |
| static int ism_vfio_pci_init_dev(struct vfio_device *core_vdev) |
| { |
| struct zpci_dev *zdev = to_zpci(to_pci_dev(core_vdev->dev)); |
| struct ism_vfio_pci_core_device *ivpcd; |
| char cache_name[20]; |
| int ret; |
| |
| ivpcd = container_of(core_vdev, struct ism_vfio_pci_core_device, |
| core_device.vdev); |
| |
| snprintf(cache_name, sizeof(cache_name), "ism_sb_fid_%08x", zdev->fid); |
| |
| ivpcd->store_block_cache = |
| kmem_cache_create(cache_name, zdev->maxstbl, |
| (&(struct kmem_cache_args){ |
| .align = PAGE_SIZE, |
| .useroffset = 0, |
| .usersize = zdev->maxstbl, |
| }), |
| (SLAB_RECLAIM_ACCOUNT | SLAB_ACCOUNT)); |
| if (!ivpcd->store_block_cache) |
| return -ENOMEM; |
| |
| ret = vfio_pci_core_init_dev(core_vdev); |
| if (ret) |
| kmem_cache_destroy(ivpcd->store_block_cache); |
| |
| return ret; |
| } |
| |
| static void ism_vfio_pci_release_dev(struct vfio_device *core_vdev) |
| { |
| struct ism_vfio_pci_core_device *ivpcd = container_of( |
| core_vdev, struct ism_vfio_pci_core_device, core_device.vdev); |
| |
| kmem_cache_destroy(ivpcd->store_block_cache); |
| vfio_pci_core_release_dev(core_vdev); |
| } |
| |
| static const struct vfio_device_ops ism_pci_ops = { |
| .name = "ism-vfio-pci", |
| .init = ism_vfio_pci_init_dev, |
| .release = ism_vfio_pci_release_dev, |
| .open_device = ism_vfio_pci_open_device, |
| .close_device = vfio_pci_core_close_device, |
| .ioctl = vfio_pci_core_ioctl, |
| .get_region_info_caps = ism_vfio_pci_ioctl_get_region_info, |
| .device_feature = vfio_pci_core_ioctl_feature, |
| .read = ism_vfio_pci_read, |
| .write = ism_vfio_pci_write, |
| .request = vfio_pci_core_request, |
| .match = vfio_pci_core_match, |
| .match_token_uuid = vfio_pci_core_match_token_uuid, |
| .bind_iommufd = vfio_iommufd_physical_bind, |
| .unbind_iommufd = vfio_iommufd_physical_unbind, |
| .attach_ioas = vfio_iommufd_physical_attach_ioas, |
| .detach_ioas = vfio_iommufd_physical_detach_ioas, |
| }; |
| |
| static int ism_vfio_pci_probe(struct pci_dev *pdev, |
| const struct pci_device_id *id) |
| { |
| struct ism_vfio_pci_core_device *ivpcd; |
| int ret; |
| |
| ivpcd = vfio_alloc_device(ism_vfio_pci_core_device, core_device.vdev, |
| &pdev->dev, &ism_pci_ops); |
| if (IS_ERR(ivpcd)) |
| return PTR_ERR(ivpcd); |
| |
| dev_set_drvdata(&pdev->dev, &ivpcd->core_device); |
| |
| ret = vfio_pci_core_register_device(&ivpcd->core_device); |
| if (ret) |
| vfio_put_device(&ivpcd->core_device.vdev); |
| |
| return ret; |
| } |
| |
| static void ism_vfio_pci_remove(struct pci_dev *pdev) |
| { |
| struct vfio_pci_core_device *core_device; |
| struct ism_vfio_pci_core_device *ivpcd; |
| |
| core_device = dev_get_drvdata(&pdev->dev); |
| ivpcd = container_of(core_device, struct ism_vfio_pci_core_device, |
| core_device); |
| |
| vfio_pci_core_unregister_device(&ivpcd->core_device); |
| vfio_put_device(&ivpcd->core_device.vdev); |
| } |
| |
| static const struct pci_device_id ism_device_table[] = { |
| { PCI_DRIVER_OVERRIDE_DEVICE_VFIO(PCI_VENDOR_ID_IBM, |
| PCI_DEVICE_ID_IBM_ISM) }, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(pci, ism_device_table); |
| |
| static struct pci_driver ism_vfio_pci_driver = { |
| .name = KBUILD_MODNAME, |
| .id_table = ism_device_table, |
| .probe = ism_vfio_pci_probe, |
| .remove = ism_vfio_pci_remove, |
| .err_handler = &vfio_pci_core_err_handlers, |
| .driver_managed_dma = true, |
| }; |
| |
| module_pci_driver(ism_vfio_pci_driver); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_DESCRIPTION("vfio-pci variant driver for the IBM Internal Shared Memory (ISM) device"); |
| MODULE_AUTHOR("IBM Corporation"); |