162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Mediated virtual PCI display host device driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * See mdpy-defs.h for device specs 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * (c) Gerd Hoffmann <kraxel@redhat.com> 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * based on mtty driver which is: 1062306a36Sopenharmony_ci * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. 1162306a36Sopenharmony_ci * Author: Neo Jia <cjia@nvidia.com> 1262306a36Sopenharmony_ci * Kirti Wankhede <kwankhede@nvidia.com> 1362306a36Sopenharmony_ci * 1462306a36Sopenharmony_ci * This program is free software; you can redistribute it and/or modify 1562306a36Sopenharmony_ci * it under the terms of the GNU General Public License version 2 as 1662306a36Sopenharmony_ci * published by the Free Software Foundation. 1762306a36Sopenharmony_ci */ 1862306a36Sopenharmony_ci#include <linux/init.h> 1962306a36Sopenharmony_ci#include <linux/module.h> 2062306a36Sopenharmony_ci#include <linux/kernel.h> 2162306a36Sopenharmony_ci#include <linux/slab.h> 2262306a36Sopenharmony_ci#include <linux/vmalloc.h> 2362306a36Sopenharmony_ci#include <linux/cdev.h> 2462306a36Sopenharmony_ci#include <linux/vfio.h> 2562306a36Sopenharmony_ci#include <linux/iommu.h> 2662306a36Sopenharmony_ci#include <linux/sysfs.h> 2762306a36Sopenharmony_ci#include <linux/mdev.h> 2862306a36Sopenharmony_ci#include <linux/pci.h> 2962306a36Sopenharmony_ci#include <drm/drm_fourcc.h> 3062306a36Sopenharmony_ci#include "mdpy-defs.h" 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_ci#define MDPY_NAME "mdpy" 3362306a36Sopenharmony_ci#define MDPY_CLASS_NAME "mdpy" 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#define MDPY_CONFIG_SPACE_SIZE 0xff 3662306a36Sopenharmony_ci#define MDPY_MEMORY_BAR_OFFSET PAGE_SIZE 3762306a36Sopenharmony_ci#define MDPY_DISPLAY_REGION 16 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci#define STORE_LE16(addr, val) (*(u16 *)addr = val) 4062306a36Sopenharmony_ci#define STORE_LE32(addr, val) (*(u32 *)addr = val) 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci#define MDPY_TYPE_1 "vga" 4662306a36Sopenharmony_ci#define MDPY_TYPE_2 "xga" 4762306a36Sopenharmony_ci#define MDPY_TYPE_3 "hd" 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistatic struct mdpy_type { 5062306a36Sopenharmony_ci struct mdev_type type; 5162306a36Sopenharmony_ci u32 format; 5262306a36Sopenharmony_ci u32 bytepp; 5362306a36Sopenharmony_ci u32 width; 5462306a36Sopenharmony_ci u32 height; 5562306a36Sopenharmony_ci} mdpy_types[] = { 5662306a36Sopenharmony_ci { 5762306a36Sopenharmony_ci .type.sysfs_name = MDPY_TYPE_1, 5862306a36Sopenharmony_ci .type.pretty_name = MDPY_CLASS_NAME "-" MDPY_TYPE_1, 5962306a36Sopenharmony_ci .format = DRM_FORMAT_XRGB8888, 6062306a36Sopenharmony_ci .bytepp = 4, 6162306a36Sopenharmony_ci .width = 640, 6262306a36Sopenharmony_ci .height = 480, 6362306a36Sopenharmony_ci }, { 6462306a36Sopenharmony_ci .type.sysfs_name = MDPY_TYPE_2, 6562306a36Sopenharmony_ci .type.pretty_name = MDPY_CLASS_NAME "-" MDPY_TYPE_2, 6662306a36Sopenharmony_ci .format = DRM_FORMAT_XRGB8888, 6762306a36Sopenharmony_ci .bytepp = 4, 6862306a36Sopenharmony_ci .width = 1024, 6962306a36Sopenharmony_ci .height = 768, 7062306a36Sopenharmony_ci }, { 7162306a36Sopenharmony_ci .type.sysfs_name = MDPY_TYPE_3, 7262306a36Sopenharmony_ci .type.pretty_name = MDPY_CLASS_NAME "-" MDPY_TYPE_3, 7362306a36Sopenharmony_ci .format = DRM_FORMAT_XRGB8888, 7462306a36Sopenharmony_ci .bytepp = 4, 7562306a36Sopenharmony_ci .width = 1920, 7662306a36Sopenharmony_ci .height = 1080, 7762306a36Sopenharmony_ci }, 7862306a36Sopenharmony_ci}; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic struct mdev_type *mdpy_mdev_types[] = { 8162306a36Sopenharmony_ci &mdpy_types[0].type, 8262306a36Sopenharmony_ci &mdpy_types[1].type, 8362306a36Sopenharmony_ci &mdpy_types[2].type, 8462306a36Sopenharmony_ci}; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic dev_t mdpy_devt; 8762306a36Sopenharmony_cistatic struct class *mdpy_class; 8862306a36Sopenharmony_cistatic struct cdev mdpy_cdev; 8962306a36Sopenharmony_cistatic struct device mdpy_dev; 9062306a36Sopenharmony_cistatic struct mdev_parent mdpy_parent; 9162306a36Sopenharmony_cistatic const struct vfio_device_ops mdpy_dev_ops; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci/* State of each mdev device */ 9462306a36Sopenharmony_cistruct mdev_state { 9562306a36Sopenharmony_ci struct vfio_device vdev; 9662306a36Sopenharmony_ci u8 *vconfig; 9762306a36Sopenharmony_ci u32 bar_mask; 9862306a36Sopenharmony_ci struct mutex ops_lock; 9962306a36Sopenharmony_ci struct mdev_device *mdev; 10062306a36Sopenharmony_ci struct vfio_device_info dev_info; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci const struct mdpy_type *type; 10362306a36Sopenharmony_ci u32 memsize; 10462306a36Sopenharmony_ci void *memblk; 10562306a36Sopenharmony_ci}; 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_cistatic void mdpy_create_config_space(struct mdev_state *mdev_state) 10862306a36Sopenharmony_ci{ 10962306a36Sopenharmony_ci STORE_LE16((u16 *) &mdev_state->vconfig[PCI_VENDOR_ID], 11062306a36Sopenharmony_ci MDPY_PCI_VENDOR_ID); 11162306a36Sopenharmony_ci STORE_LE16((u16 *) &mdev_state->vconfig[PCI_DEVICE_ID], 11262306a36Sopenharmony_ci MDPY_PCI_DEVICE_ID); 11362306a36Sopenharmony_ci STORE_LE16((u16 *) &mdev_state->vconfig[PCI_SUBSYSTEM_VENDOR_ID], 11462306a36Sopenharmony_ci MDPY_PCI_SUBVENDOR_ID); 11562306a36Sopenharmony_ci STORE_LE16((u16 *) &mdev_state->vconfig[PCI_SUBSYSTEM_ID], 11662306a36Sopenharmony_ci MDPY_PCI_SUBDEVICE_ID); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci STORE_LE16((u16 *) &mdev_state->vconfig[PCI_COMMAND], 11962306a36Sopenharmony_ci PCI_COMMAND_IO | PCI_COMMAND_MEMORY); 12062306a36Sopenharmony_ci STORE_LE16((u16 *) &mdev_state->vconfig[PCI_STATUS], 12162306a36Sopenharmony_ci PCI_STATUS_CAP_LIST); 12262306a36Sopenharmony_ci STORE_LE16((u16 *) &mdev_state->vconfig[PCI_CLASS_DEVICE], 12362306a36Sopenharmony_ci PCI_CLASS_DISPLAY_OTHER); 12462306a36Sopenharmony_ci mdev_state->vconfig[PCI_CLASS_REVISION] = 0x01; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci STORE_LE32((u32 *) &mdev_state->vconfig[PCI_BASE_ADDRESS_0], 12762306a36Sopenharmony_ci PCI_BASE_ADDRESS_SPACE_MEMORY | 12862306a36Sopenharmony_ci PCI_BASE_ADDRESS_MEM_TYPE_32 | 12962306a36Sopenharmony_ci PCI_BASE_ADDRESS_MEM_PREFETCH); 13062306a36Sopenharmony_ci mdev_state->bar_mask = ~(mdev_state->memsize) + 1; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci /* vendor specific capability for the config registers */ 13362306a36Sopenharmony_ci mdev_state->vconfig[PCI_CAPABILITY_LIST] = MDPY_VENDORCAP_OFFSET; 13462306a36Sopenharmony_ci mdev_state->vconfig[MDPY_VENDORCAP_OFFSET + 0] = 0x09; /* vendor cap */ 13562306a36Sopenharmony_ci mdev_state->vconfig[MDPY_VENDORCAP_OFFSET + 1] = 0x00; /* next ptr */ 13662306a36Sopenharmony_ci mdev_state->vconfig[MDPY_VENDORCAP_OFFSET + 2] = MDPY_VENDORCAP_SIZE; 13762306a36Sopenharmony_ci STORE_LE32((u32 *) &mdev_state->vconfig[MDPY_FORMAT_OFFSET], 13862306a36Sopenharmony_ci mdev_state->type->format); 13962306a36Sopenharmony_ci STORE_LE32((u32 *) &mdev_state->vconfig[MDPY_WIDTH_OFFSET], 14062306a36Sopenharmony_ci mdev_state->type->width); 14162306a36Sopenharmony_ci STORE_LE32((u32 *) &mdev_state->vconfig[MDPY_HEIGHT_OFFSET], 14262306a36Sopenharmony_ci mdev_state->type->height); 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_cistatic void handle_pci_cfg_write(struct mdev_state *mdev_state, u16 offset, 14662306a36Sopenharmony_ci char *buf, u32 count) 14762306a36Sopenharmony_ci{ 14862306a36Sopenharmony_ci struct device *dev = mdev_dev(mdev_state->mdev); 14962306a36Sopenharmony_ci u32 cfg_addr; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci switch (offset) { 15262306a36Sopenharmony_ci case PCI_BASE_ADDRESS_0: 15362306a36Sopenharmony_ci cfg_addr = *(u32 *)buf; 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci if (cfg_addr == 0xffffffff) { 15662306a36Sopenharmony_ci cfg_addr = (cfg_addr & mdev_state->bar_mask); 15762306a36Sopenharmony_ci } else { 15862306a36Sopenharmony_ci cfg_addr &= PCI_BASE_ADDRESS_MEM_MASK; 15962306a36Sopenharmony_ci if (cfg_addr) 16062306a36Sopenharmony_ci dev_info(dev, "BAR0 @ 0x%x\n", cfg_addr); 16162306a36Sopenharmony_ci } 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci cfg_addr |= (mdev_state->vconfig[offset] & 16462306a36Sopenharmony_ci ~PCI_BASE_ADDRESS_MEM_MASK); 16562306a36Sopenharmony_ci STORE_LE32(&mdev_state->vconfig[offset], cfg_addr); 16662306a36Sopenharmony_ci break; 16762306a36Sopenharmony_ci } 16862306a36Sopenharmony_ci} 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_cistatic ssize_t mdev_access(struct mdev_state *mdev_state, char *buf, 17162306a36Sopenharmony_ci size_t count, loff_t pos, bool is_write) 17262306a36Sopenharmony_ci{ 17362306a36Sopenharmony_ci int ret = 0; 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_ci mutex_lock(&mdev_state->ops_lock); 17662306a36Sopenharmony_ci 17762306a36Sopenharmony_ci if (pos < MDPY_CONFIG_SPACE_SIZE) { 17862306a36Sopenharmony_ci if (is_write) 17962306a36Sopenharmony_ci handle_pci_cfg_write(mdev_state, pos, buf, count); 18062306a36Sopenharmony_ci else 18162306a36Sopenharmony_ci memcpy(buf, (mdev_state->vconfig + pos), count); 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci } else if ((pos >= MDPY_MEMORY_BAR_OFFSET) && 18462306a36Sopenharmony_ci (pos + count <= 18562306a36Sopenharmony_ci MDPY_MEMORY_BAR_OFFSET + mdev_state->memsize)) { 18662306a36Sopenharmony_ci pos -= MDPY_MEMORY_BAR_OFFSET; 18762306a36Sopenharmony_ci if (is_write) 18862306a36Sopenharmony_ci memcpy(mdev_state->memblk, buf, count); 18962306a36Sopenharmony_ci else 19062306a36Sopenharmony_ci memcpy(buf, mdev_state->memblk, count); 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci } else { 19362306a36Sopenharmony_ci dev_info(mdev_state->vdev.dev, 19462306a36Sopenharmony_ci "%s: %s @0x%llx (unhandled)\n", __func__, 19562306a36Sopenharmony_ci is_write ? "WR" : "RD", pos); 19662306a36Sopenharmony_ci ret = -1; 19762306a36Sopenharmony_ci goto accessfailed; 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci ret = count; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ciaccessfailed: 20462306a36Sopenharmony_ci mutex_unlock(&mdev_state->ops_lock); 20562306a36Sopenharmony_ci 20662306a36Sopenharmony_ci return ret; 20762306a36Sopenharmony_ci} 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_cistatic int mdpy_reset(struct mdev_state *mdev_state) 21062306a36Sopenharmony_ci{ 21162306a36Sopenharmony_ci u32 stride, i; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci /* initialize with gray gradient */ 21462306a36Sopenharmony_ci stride = mdev_state->type->width * mdev_state->type->bytepp; 21562306a36Sopenharmony_ci for (i = 0; i < mdev_state->type->height; i++) 21662306a36Sopenharmony_ci memset(mdev_state->memblk + i * stride, 21762306a36Sopenharmony_ci i * 255 / mdev_state->type->height, 21862306a36Sopenharmony_ci stride); 21962306a36Sopenharmony_ci return 0; 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_cistatic int mdpy_init_dev(struct vfio_device *vdev) 22362306a36Sopenharmony_ci{ 22462306a36Sopenharmony_ci struct mdev_state *mdev_state = 22562306a36Sopenharmony_ci container_of(vdev, struct mdev_state, vdev); 22662306a36Sopenharmony_ci struct mdev_device *mdev = to_mdev_device(vdev->dev); 22762306a36Sopenharmony_ci const struct mdpy_type *type = 22862306a36Sopenharmony_ci container_of(mdev->type, struct mdpy_type, type); 22962306a36Sopenharmony_ci u32 fbsize; 23062306a36Sopenharmony_ci int ret = -ENOMEM; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci mdev_state->vconfig = kzalloc(MDPY_CONFIG_SPACE_SIZE, GFP_KERNEL); 23362306a36Sopenharmony_ci if (!mdev_state->vconfig) 23462306a36Sopenharmony_ci return ret; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci fbsize = roundup_pow_of_two(type->width * type->height * type->bytepp); 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci mdev_state->memblk = vmalloc_user(fbsize); 23962306a36Sopenharmony_ci if (!mdev_state->memblk) 24062306a36Sopenharmony_ci goto out_vconfig; 24162306a36Sopenharmony_ci 24262306a36Sopenharmony_ci mutex_init(&mdev_state->ops_lock); 24362306a36Sopenharmony_ci mdev_state->mdev = mdev; 24462306a36Sopenharmony_ci mdev_state->type = type; 24562306a36Sopenharmony_ci mdev_state->memsize = fbsize; 24662306a36Sopenharmony_ci mdpy_create_config_space(mdev_state); 24762306a36Sopenharmony_ci mdpy_reset(mdev_state); 24862306a36Sopenharmony_ci 24962306a36Sopenharmony_ci dev_info(vdev->dev, "%s: %s (%dx%d)\n", __func__, type->type.pretty_name, 25062306a36Sopenharmony_ci type->width, type->height); 25162306a36Sopenharmony_ci return 0; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ciout_vconfig: 25462306a36Sopenharmony_ci kfree(mdev_state->vconfig); 25562306a36Sopenharmony_ci return ret; 25662306a36Sopenharmony_ci} 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_cistatic int mdpy_probe(struct mdev_device *mdev) 25962306a36Sopenharmony_ci{ 26062306a36Sopenharmony_ci struct mdev_state *mdev_state; 26162306a36Sopenharmony_ci int ret; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci mdev_state = vfio_alloc_device(mdev_state, vdev, &mdev->dev, 26462306a36Sopenharmony_ci &mdpy_dev_ops); 26562306a36Sopenharmony_ci if (IS_ERR(mdev_state)) 26662306a36Sopenharmony_ci return PTR_ERR(mdev_state); 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci ret = vfio_register_emulated_iommu_dev(&mdev_state->vdev); 26962306a36Sopenharmony_ci if (ret) 27062306a36Sopenharmony_ci goto err_put_vdev; 27162306a36Sopenharmony_ci dev_set_drvdata(&mdev->dev, mdev_state); 27262306a36Sopenharmony_ci return 0; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_cierr_put_vdev: 27562306a36Sopenharmony_ci vfio_put_device(&mdev_state->vdev); 27662306a36Sopenharmony_ci return ret; 27762306a36Sopenharmony_ci} 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_cistatic void mdpy_release_dev(struct vfio_device *vdev) 28062306a36Sopenharmony_ci{ 28162306a36Sopenharmony_ci struct mdev_state *mdev_state = 28262306a36Sopenharmony_ci container_of(vdev, struct mdev_state, vdev); 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_ci vfree(mdev_state->memblk); 28562306a36Sopenharmony_ci kfree(mdev_state->vconfig); 28662306a36Sopenharmony_ci} 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_cistatic void mdpy_remove(struct mdev_device *mdev) 28962306a36Sopenharmony_ci{ 29062306a36Sopenharmony_ci struct mdev_state *mdev_state = dev_get_drvdata(&mdev->dev); 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci dev_info(&mdev->dev, "%s\n", __func__); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_ci vfio_unregister_group_dev(&mdev_state->vdev); 29562306a36Sopenharmony_ci vfio_put_device(&mdev_state->vdev); 29662306a36Sopenharmony_ci} 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_cistatic ssize_t mdpy_read(struct vfio_device *vdev, char __user *buf, 29962306a36Sopenharmony_ci size_t count, loff_t *ppos) 30062306a36Sopenharmony_ci{ 30162306a36Sopenharmony_ci struct mdev_state *mdev_state = 30262306a36Sopenharmony_ci container_of(vdev, struct mdev_state, vdev); 30362306a36Sopenharmony_ci unsigned int done = 0; 30462306a36Sopenharmony_ci int ret; 30562306a36Sopenharmony_ci 30662306a36Sopenharmony_ci while (count) { 30762306a36Sopenharmony_ci size_t filled; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci if (count >= 4 && !(*ppos % 4)) { 31062306a36Sopenharmony_ci u32 val; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci ret = mdev_access(mdev_state, (char *)&val, sizeof(val), 31362306a36Sopenharmony_ci *ppos, false); 31462306a36Sopenharmony_ci if (ret <= 0) 31562306a36Sopenharmony_ci goto read_err; 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci if (copy_to_user(buf, &val, sizeof(val))) 31862306a36Sopenharmony_ci goto read_err; 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci filled = 4; 32162306a36Sopenharmony_ci } else if (count >= 2 && !(*ppos % 2)) { 32262306a36Sopenharmony_ci u16 val; 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci ret = mdev_access(mdev_state, (char *)&val, sizeof(val), 32562306a36Sopenharmony_ci *ppos, false); 32662306a36Sopenharmony_ci if (ret <= 0) 32762306a36Sopenharmony_ci goto read_err; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci if (copy_to_user(buf, &val, sizeof(val))) 33062306a36Sopenharmony_ci goto read_err; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci filled = 2; 33362306a36Sopenharmony_ci } else { 33462306a36Sopenharmony_ci u8 val; 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_ci ret = mdev_access(mdev_state, (char *)&val, sizeof(val), 33762306a36Sopenharmony_ci *ppos, false); 33862306a36Sopenharmony_ci if (ret <= 0) 33962306a36Sopenharmony_ci goto read_err; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci if (copy_to_user(buf, &val, sizeof(val))) 34262306a36Sopenharmony_ci goto read_err; 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci filled = 1; 34562306a36Sopenharmony_ci } 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci count -= filled; 34862306a36Sopenharmony_ci done += filled; 34962306a36Sopenharmony_ci *ppos += filled; 35062306a36Sopenharmony_ci buf += filled; 35162306a36Sopenharmony_ci } 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci return done; 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ciread_err: 35662306a36Sopenharmony_ci return -EFAULT; 35762306a36Sopenharmony_ci} 35862306a36Sopenharmony_ci 35962306a36Sopenharmony_cistatic ssize_t mdpy_write(struct vfio_device *vdev, const char __user *buf, 36062306a36Sopenharmony_ci size_t count, loff_t *ppos) 36162306a36Sopenharmony_ci{ 36262306a36Sopenharmony_ci struct mdev_state *mdev_state = 36362306a36Sopenharmony_ci container_of(vdev, struct mdev_state, vdev); 36462306a36Sopenharmony_ci unsigned int done = 0; 36562306a36Sopenharmony_ci int ret; 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci while (count) { 36862306a36Sopenharmony_ci size_t filled; 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_ci if (count >= 4 && !(*ppos % 4)) { 37162306a36Sopenharmony_ci u32 val; 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci if (copy_from_user(&val, buf, sizeof(val))) 37462306a36Sopenharmony_ci goto write_err; 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_ci ret = mdev_access(mdev_state, (char *)&val, sizeof(val), 37762306a36Sopenharmony_ci *ppos, true); 37862306a36Sopenharmony_ci if (ret <= 0) 37962306a36Sopenharmony_ci goto write_err; 38062306a36Sopenharmony_ci 38162306a36Sopenharmony_ci filled = 4; 38262306a36Sopenharmony_ci } else if (count >= 2 && !(*ppos % 2)) { 38362306a36Sopenharmony_ci u16 val; 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci if (copy_from_user(&val, buf, sizeof(val))) 38662306a36Sopenharmony_ci goto write_err; 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci ret = mdev_access(mdev_state, (char *)&val, sizeof(val), 38962306a36Sopenharmony_ci *ppos, true); 39062306a36Sopenharmony_ci if (ret <= 0) 39162306a36Sopenharmony_ci goto write_err; 39262306a36Sopenharmony_ci 39362306a36Sopenharmony_ci filled = 2; 39462306a36Sopenharmony_ci } else { 39562306a36Sopenharmony_ci u8 val; 39662306a36Sopenharmony_ci 39762306a36Sopenharmony_ci if (copy_from_user(&val, buf, sizeof(val))) 39862306a36Sopenharmony_ci goto write_err; 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_ci ret = mdev_access(mdev_state, (char *)&val, sizeof(val), 40162306a36Sopenharmony_ci *ppos, true); 40262306a36Sopenharmony_ci if (ret <= 0) 40362306a36Sopenharmony_ci goto write_err; 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci filled = 1; 40662306a36Sopenharmony_ci } 40762306a36Sopenharmony_ci count -= filled; 40862306a36Sopenharmony_ci done += filled; 40962306a36Sopenharmony_ci *ppos += filled; 41062306a36Sopenharmony_ci buf += filled; 41162306a36Sopenharmony_ci } 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci return done; 41462306a36Sopenharmony_ciwrite_err: 41562306a36Sopenharmony_ci return -EFAULT; 41662306a36Sopenharmony_ci} 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_cistatic int mdpy_mmap(struct vfio_device *vdev, struct vm_area_struct *vma) 41962306a36Sopenharmony_ci{ 42062306a36Sopenharmony_ci struct mdev_state *mdev_state = 42162306a36Sopenharmony_ci container_of(vdev, struct mdev_state, vdev); 42262306a36Sopenharmony_ci 42362306a36Sopenharmony_ci if (vma->vm_pgoff != MDPY_MEMORY_BAR_OFFSET >> PAGE_SHIFT) 42462306a36Sopenharmony_ci return -EINVAL; 42562306a36Sopenharmony_ci if (vma->vm_end < vma->vm_start) 42662306a36Sopenharmony_ci return -EINVAL; 42762306a36Sopenharmony_ci if (vma->vm_end - vma->vm_start > mdev_state->memsize) 42862306a36Sopenharmony_ci return -EINVAL; 42962306a36Sopenharmony_ci if ((vma->vm_flags & VM_SHARED) == 0) 43062306a36Sopenharmony_ci return -EINVAL; 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_ci return remap_vmalloc_range(vma, mdev_state->memblk, 0); 43362306a36Sopenharmony_ci} 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_cistatic int mdpy_get_region_info(struct mdev_state *mdev_state, 43662306a36Sopenharmony_ci struct vfio_region_info *region_info, 43762306a36Sopenharmony_ci u16 *cap_type_id, void **cap_type) 43862306a36Sopenharmony_ci{ 43962306a36Sopenharmony_ci if (region_info->index >= VFIO_PCI_NUM_REGIONS && 44062306a36Sopenharmony_ci region_info->index != MDPY_DISPLAY_REGION) 44162306a36Sopenharmony_ci return -EINVAL; 44262306a36Sopenharmony_ci 44362306a36Sopenharmony_ci switch (region_info->index) { 44462306a36Sopenharmony_ci case VFIO_PCI_CONFIG_REGION_INDEX: 44562306a36Sopenharmony_ci region_info->offset = 0; 44662306a36Sopenharmony_ci region_info->size = MDPY_CONFIG_SPACE_SIZE; 44762306a36Sopenharmony_ci region_info->flags = (VFIO_REGION_INFO_FLAG_READ | 44862306a36Sopenharmony_ci VFIO_REGION_INFO_FLAG_WRITE); 44962306a36Sopenharmony_ci break; 45062306a36Sopenharmony_ci case VFIO_PCI_BAR0_REGION_INDEX: 45162306a36Sopenharmony_ci case MDPY_DISPLAY_REGION: 45262306a36Sopenharmony_ci region_info->offset = MDPY_MEMORY_BAR_OFFSET; 45362306a36Sopenharmony_ci region_info->size = mdev_state->memsize; 45462306a36Sopenharmony_ci region_info->flags = (VFIO_REGION_INFO_FLAG_READ | 45562306a36Sopenharmony_ci VFIO_REGION_INFO_FLAG_WRITE | 45662306a36Sopenharmony_ci VFIO_REGION_INFO_FLAG_MMAP); 45762306a36Sopenharmony_ci break; 45862306a36Sopenharmony_ci default: 45962306a36Sopenharmony_ci region_info->size = 0; 46062306a36Sopenharmony_ci region_info->offset = 0; 46162306a36Sopenharmony_ci region_info->flags = 0; 46262306a36Sopenharmony_ci } 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci return 0; 46562306a36Sopenharmony_ci} 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_cistatic int mdpy_get_irq_info(struct vfio_irq_info *irq_info) 46862306a36Sopenharmony_ci{ 46962306a36Sopenharmony_ci irq_info->count = 0; 47062306a36Sopenharmony_ci return 0; 47162306a36Sopenharmony_ci} 47262306a36Sopenharmony_ci 47362306a36Sopenharmony_cistatic int mdpy_get_device_info(struct vfio_device_info *dev_info) 47462306a36Sopenharmony_ci{ 47562306a36Sopenharmony_ci dev_info->flags = VFIO_DEVICE_FLAGS_PCI; 47662306a36Sopenharmony_ci dev_info->num_regions = VFIO_PCI_NUM_REGIONS; 47762306a36Sopenharmony_ci dev_info->num_irqs = VFIO_PCI_NUM_IRQS; 47862306a36Sopenharmony_ci return 0; 47962306a36Sopenharmony_ci} 48062306a36Sopenharmony_ci 48162306a36Sopenharmony_cistatic int mdpy_query_gfx_plane(struct mdev_state *mdev_state, 48262306a36Sopenharmony_ci struct vfio_device_gfx_plane_info *plane) 48362306a36Sopenharmony_ci{ 48462306a36Sopenharmony_ci if (plane->flags & VFIO_GFX_PLANE_TYPE_PROBE) { 48562306a36Sopenharmony_ci if (plane->flags == (VFIO_GFX_PLANE_TYPE_PROBE | 48662306a36Sopenharmony_ci VFIO_GFX_PLANE_TYPE_REGION)) 48762306a36Sopenharmony_ci return 0; 48862306a36Sopenharmony_ci return -EINVAL; 48962306a36Sopenharmony_ci } 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci if (plane->flags != VFIO_GFX_PLANE_TYPE_REGION) 49262306a36Sopenharmony_ci return -EINVAL; 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_ci plane->drm_format = mdev_state->type->format; 49562306a36Sopenharmony_ci plane->width = mdev_state->type->width; 49662306a36Sopenharmony_ci plane->height = mdev_state->type->height; 49762306a36Sopenharmony_ci plane->stride = (mdev_state->type->width * 49862306a36Sopenharmony_ci mdev_state->type->bytepp); 49962306a36Sopenharmony_ci plane->size = mdev_state->memsize; 50062306a36Sopenharmony_ci plane->region_index = MDPY_DISPLAY_REGION; 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci /* unused */ 50362306a36Sopenharmony_ci plane->drm_format_mod = 0; 50462306a36Sopenharmony_ci plane->x_pos = 0; 50562306a36Sopenharmony_ci plane->y_pos = 0; 50662306a36Sopenharmony_ci plane->x_hot = 0; 50762306a36Sopenharmony_ci plane->y_hot = 0; 50862306a36Sopenharmony_ci 50962306a36Sopenharmony_ci return 0; 51062306a36Sopenharmony_ci} 51162306a36Sopenharmony_ci 51262306a36Sopenharmony_cistatic long mdpy_ioctl(struct vfio_device *vdev, unsigned int cmd, 51362306a36Sopenharmony_ci unsigned long arg) 51462306a36Sopenharmony_ci{ 51562306a36Sopenharmony_ci int ret = 0; 51662306a36Sopenharmony_ci unsigned long minsz; 51762306a36Sopenharmony_ci struct mdev_state *mdev_state = 51862306a36Sopenharmony_ci container_of(vdev, struct mdev_state, vdev); 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci switch (cmd) { 52162306a36Sopenharmony_ci case VFIO_DEVICE_GET_INFO: 52262306a36Sopenharmony_ci { 52362306a36Sopenharmony_ci struct vfio_device_info info; 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci minsz = offsetofend(struct vfio_device_info, num_irqs); 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci if (copy_from_user(&info, (void __user *)arg, minsz)) 52862306a36Sopenharmony_ci return -EFAULT; 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci if (info.argsz < minsz) 53162306a36Sopenharmony_ci return -EINVAL; 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_ci ret = mdpy_get_device_info(&info); 53462306a36Sopenharmony_ci if (ret) 53562306a36Sopenharmony_ci return ret; 53662306a36Sopenharmony_ci 53762306a36Sopenharmony_ci memcpy(&mdev_state->dev_info, &info, sizeof(info)); 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_ci if (copy_to_user((void __user *)arg, &info, minsz)) 54062306a36Sopenharmony_ci return -EFAULT; 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci return 0; 54362306a36Sopenharmony_ci } 54462306a36Sopenharmony_ci case VFIO_DEVICE_GET_REGION_INFO: 54562306a36Sopenharmony_ci { 54662306a36Sopenharmony_ci struct vfio_region_info info; 54762306a36Sopenharmony_ci u16 cap_type_id = 0; 54862306a36Sopenharmony_ci void *cap_type = NULL; 54962306a36Sopenharmony_ci 55062306a36Sopenharmony_ci minsz = offsetofend(struct vfio_region_info, offset); 55162306a36Sopenharmony_ci 55262306a36Sopenharmony_ci if (copy_from_user(&info, (void __user *)arg, minsz)) 55362306a36Sopenharmony_ci return -EFAULT; 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci if (info.argsz < minsz) 55662306a36Sopenharmony_ci return -EINVAL; 55762306a36Sopenharmony_ci 55862306a36Sopenharmony_ci ret = mdpy_get_region_info(mdev_state, &info, &cap_type_id, 55962306a36Sopenharmony_ci &cap_type); 56062306a36Sopenharmony_ci if (ret) 56162306a36Sopenharmony_ci return ret; 56262306a36Sopenharmony_ci 56362306a36Sopenharmony_ci if (copy_to_user((void __user *)arg, &info, minsz)) 56462306a36Sopenharmony_ci return -EFAULT; 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci return 0; 56762306a36Sopenharmony_ci } 56862306a36Sopenharmony_ci 56962306a36Sopenharmony_ci case VFIO_DEVICE_GET_IRQ_INFO: 57062306a36Sopenharmony_ci { 57162306a36Sopenharmony_ci struct vfio_irq_info info; 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci minsz = offsetofend(struct vfio_irq_info, count); 57462306a36Sopenharmony_ci 57562306a36Sopenharmony_ci if (copy_from_user(&info, (void __user *)arg, minsz)) 57662306a36Sopenharmony_ci return -EFAULT; 57762306a36Sopenharmony_ci 57862306a36Sopenharmony_ci if ((info.argsz < minsz) || 57962306a36Sopenharmony_ci (info.index >= mdev_state->dev_info.num_irqs)) 58062306a36Sopenharmony_ci return -EINVAL; 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci ret = mdpy_get_irq_info(&info); 58362306a36Sopenharmony_ci if (ret) 58462306a36Sopenharmony_ci return ret; 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci if (copy_to_user((void __user *)arg, &info, minsz)) 58762306a36Sopenharmony_ci return -EFAULT; 58862306a36Sopenharmony_ci 58962306a36Sopenharmony_ci return 0; 59062306a36Sopenharmony_ci } 59162306a36Sopenharmony_ci 59262306a36Sopenharmony_ci case VFIO_DEVICE_QUERY_GFX_PLANE: 59362306a36Sopenharmony_ci { 59462306a36Sopenharmony_ci struct vfio_device_gfx_plane_info plane; 59562306a36Sopenharmony_ci 59662306a36Sopenharmony_ci minsz = offsetofend(struct vfio_device_gfx_plane_info, 59762306a36Sopenharmony_ci region_index); 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ci if (copy_from_user(&plane, (void __user *)arg, minsz)) 60062306a36Sopenharmony_ci return -EFAULT; 60162306a36Sopenharmony_ci 60262306a36Sopenharmony_ci if (plane.argsz < minsz) 60362306a36Sopenharmony_ci return -EINVAL; 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci ret = mdpy_query_gfx_plane(mdev_state, &plane); 60662306a36Sopenharmony_ci if (ret) 60762306a36Sopenharmony_ci return ret; 60862306a36Sopenharmony_ci 60962306a36Sopenharmony_ci if (copy_to_user((void __user *)arg, &plane, minsz)) 61062306a36Sopenharmony_ci return -EFAULT; 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci return 0; 61362306a36Sopenharmony_ci } 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_ci case VFIO_DEVICE_SET_IRQS: 61662306a36Sopenharmony_ci return -EINVAL; 61762306a36Sopenharmony_ci 61862306a36Sopenharmony_ci case VFIO_DEVICE_RESET: 61962306a36Sopenharmony_ci return mdpy_reset(mdev_state); 62062306a36Sopenharmony_ci } 62162306a36Sopenharmony_ci return -ENOTTY; 62262306a36Sopenharmony_ci} 62362306a36Sopenharmony_ci 62462306a36Sopenharmony_cistatic ssize_t 62562306a36Sopenharmony_ciresolution_show(struct device *dev, struct device_attribute *attr, 62662306a36Sopenharmony_ci char *buf) 62762306a36Sopenharmony_ci{ 62862306a36Sopenharmony_ci struct mdev_state *mdev_state = dev_get_drvdata(dev); 62962306a36Sopenharmony_ci 63062306a36Sopenharmony_ci return sprintf(buf, "%dx%d\n", 63162306a36Sopenharmony_ci mdev_state->type->width, 63262306a36Sopenharmony_ci mdev_state->type->height); 63362306a36Sopenharmony_ci} 63462306a36Sopenharmony_cistatic DEVICE_ATTR_RO(resolution); 63562306a36Sopenharmony_ci 63662306a36Sopenharmony_cistatic struct attribute *mdev_dev_attrs[] = { 63762306a36Sopenharmony_ci &dev_attr_resolution.attr, 63862306a36Sopenharmony_ci NULL, 63962306a36Sopenharmony_ci}; 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_cistatic const struct attribute_group mdev_dev_group = { 64262306a36Sopenharmony_ci .name = "vendor", 64362306a36Sopenharmony_ci .attrs = mdev_dev_attrs, 64462306a36Sopenharmony_ci}; 64562306a36Sopenharmony_ci 64662306a36Sopenharmony_cistatic const struct attribute_group *mdev_dev_groups[] = { 64762306a36Sopenharmony_ci &mdev_dev_group, 64862306a36Sopenharmony_ci NULL, 64962306a36Sopenharmony_ci}; 65062306a36Sopenharmony_ci 65162306a36Sopenharmony_cistatic ssize_t mdpy_show_description(struct mdev_type *mtype, char *buf) 65262306a36Sopenharmony_ci{ 65362306a36Sopenharmony_ci struct mdpy_type *type = container_of(mtype, struct mdpy_type, type); 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci return sprintf(buf, "virtual display, %dx%d framebuffer\n", 65662306a36Sopenharmony_ci type->width, type->height); 65762306a36Sopenharmony_ci} 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_cistatic const struct vfio_device_ops mdpy_dev_ops = { 66062306a36Sopenharmony_ci .init = mdpy_init_dev, 66162306a36Sopenharmony_ci .release = mdpy_release_dev, 66262306a36Sopenharmony_ci .read = mdpy_read, 66362306a36Sopenharmony_ci .write = mdpy_write, 66462306a36Sopenharmony_ci .ioctl = mdpy_ioctl, 66562306a36Sopenharmony_ci .mmap = mdpy_mmap, 66662306a36Sopenharmony_ci .bind_iommufd = vfio_iommufd_emulated_bind, 66762306a36Sopenharmony_ci .unbind_iommufd = vfio_iommufd_emulated_unbind, 66862306a36Sopenharmony_ci .attach_ioas = vfio_iommufd_emulated_attach_ioas, 66962306a36Sopenharmony_ci .detach_ioas = vfio_iommufd_emulated_detach_ioas, 67062306a36Sopenharmony_ci}; 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_cistatic struct mdev_driver mdpy_driver = { 67362306a36Sopenharmony_ci .device_api = VFIO_DEVICE_API_PCI_STRING, 67462306a36Sopenharmony_ci .max_instances = 4, 67562306a36Sopenharmony_ci .driver = { 67662306a36Sopenharmony_ci .name = "mdpy", 67762306a36Sopenharmony_ci .owner = THIS_MODULE, 67862306a36Sopenharmony_ci .mod_name = KBUILD_MODNAME, 67962306a36Sopenharmony_ci .dev_groups = mdev_dev_groups, 68062306a36Sopenharmony_ci }, 68162306a36Sopenharmony_ci .probe = mdpy_probe, 68262306a36Sopenharmony_ci .remove = mdpy_remove, 68362306a36Sopenharmony_ci .show_description = mdpy_show_description, 68462306a36Sopenharmony_ci}; 68562306a36Sopenharmony_ci 68662306a36Sopenharmony_cistatic const struct file_operations vd_fops = { 68762306a36Sopenharmony_ci .owner = THIS_MODULE, 68862306a36Sopenharmony_ci}; 68962306a36Sopenharmony_ci 69062306a36Sopenharmony_cistatic void mdpy_device_release(struct device *dev) 69162306a36Sopenharmony_ci{ 69262306a36Sopenharmony_ci /* nothing */ 69362306a36Sopenharmony_ci} 69462306a36Sopenharmony_ci 69562306a36Sopenharmony_cistatic int __init mdpy_dev_init(void) 69662306a36Sopenharmony_ci{ 69762306a36Sopenharmony_ci int ret = 0; 69862306a36Sopenharmony_ci 69962306a36Sopenharmony_ci ret = alloc_chrdev_region(&mdpy_devt, 0, MINORMASK + 1, MDPY_NAME); 70062306a36Sopenharmony_ci if (ret < 0) { 70162306a36Sopenharmony_ci pr_err("Error: failed to register mdpy_dev, err: %d\n", ret); 70262306a36Sopenharmony_ci return ret; 70362306a36Sopenharmony_ci } 70462306a36Sopenharmony_ci cdev_init(&mdpy_cdev, &vd_fops); 70562306a36Sopenharmony_ci cdev_add(&mdpy_cdev, mdpy_devt, MINORMASK + 1); 70662306a36Sopenharmony_ci pr_info("%s: major %d\n", __func__, MAJOR(mdpy_devt)); 70762306a36Sopenharmony_ci 70862306a36Sopenharmony_ci ret = mdev_register_driver(&mdpy_driver); 70962306a36Sopenharmony_ci if (ret) 71062306a36Sopenharmony_ci goto err_cdev; 71162306a36Sopenharmony_ci 71262306a36Sopenharmony_ci mdpy_class = class_create(MDPY_CLASS_NAME); 71362306a36Sopenharmony_ci if (IS_ERR(mdpy_class)) { 71462306a36Sopenharmony_ci pr_err("Error: failed to register mdpy_dev class\n"); 71562306a36Sopenharmony_ci ret = PTR_ERR(mdpy_class); 71662306a36Sopenharmony_ci goto err_driver; 71762306a36Sopenharmony_ci } 71862306a36Sopenharmony_ci mdpy_dev.class = mdpy_class; 71962306a36Sopenharmony_ci mdpy_dev.release = mdpy_device_release; 72062306a36Sopenharmony_ci dev_set_name(&mdpy_dev, "%s", MDPY_NAME); 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_ci ret = device_register(&mdpy_dev); 72362306a36Sopenharmony_ci if (ret) 72462306a36Sopenharmony_ci goto err_put; 72562306a36Sopenharmony_ci 72662306a36Sopenharmony_ci ret = mdev_register_parent(&mdpy_parent, &mdpy_dev, &mdpy_driver, 72762306a36Sopenharmony_ci mdpy_mdev_types, 72862306a36Sopenharmony_ci ARRAY_SIZE(mdpy_mdev_types)); 72962306a36Sopenharmony_ci if (ret) 73062306a36Sopenharmony_ci goto err_device; 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_ci return 0; 73362306a36Sopenharmony_ci 73462306a36Sopenharmony_cierr_device: 73562306a36Sopenharmony_ci device_del(&mdpy_dev); 73662306a36Sopenharmony_cierr_put: 73762306a36Sopenharmony_ci put_device(&mdpy_dev); 73862306a36Sopenharmony_ci class_destroy(mdpy_class); 73962306a36Sopenharmony_cierr_driver: 74062306a36Sopenharmony_ci mdev_unregister_driver(&mdpy_driver); 74162306a36Sopenharmony_cierr_cdev: 74262306a36Sopenharmony_ci cdev_del(&mdpy_cdev); 74362306a36Sopenharmony_ci unregister_chrdev_region(mdpy_devt, MINORMASK + 1); 74462306a36Sopenharmony_ci return ret; 74562306a36Sopenharmony_ci} 74662306a36Sopenharmony_ci 74762306a36Sopenharmony_cistatic void __exit mdpy_dev_exit(void) 74862306a36Sopenharmony_ci{ 74962306a36Sopenharmony_ci mdpy_dev.bus = NULL; 75062306a36Sopenharmony_ci mdev_unregister_parent(&mdpy_parent); 75162306a36Sopenharmony_ci 75262306a36Sopenharmony_ci device_unregister(&mdpy_dev); 75362306a36Sopenharmony_ci mdev_unregister_driver(&mdpy_driver); 75462306a36Sopenharmony_ci cdev_del(&mdpy_cdev); 75562306a36Sopenharmony_ci unregister_chrdev_region(mdpy_devt, MINORMASK + 1); 75662306a36Sopenharmony_ci class_destroy(mdpy_class); 75762306a36Sopenharmony_ci mdpy_class = NULL; 75862306a36Sopenharmony_ci} 75962306a36Sopenharmony_ci 76062306a36Sopenharmony_cimodule_param_named(count, mdpy_driver.max_instances, int, 0444); 76162306a36Sopenharmony_ciMODULE_PARM_DESC(count, "number of " MDPY_NAME " devices"); 76262306a36Sopenharmony_ci 76362306a36Sopenharmony_cimodule_init(mdpy_dev_init) 76462306a36Sopenharmony_cimodule_exit(mdpy_dev_exit) 765