diff mbox series

[v3,2/2] vfio: add edid support to mbochs sample driver

Message ID 20180921083013.15028-3-kraxel@redhat.com (mailing list archive)
State New, archived
Headers show
Series vfio: add vgpu edid support | expand

Commit Message

Gerd Hoffmann Sept. 21, 2018, 8:30 a.m. UTC
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 samples/vfio-mdev/mbochs.c | 136 ++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 117 insertions(+), 19 deletions(-)

Comments

Kirti Wankhede Sept. 27, 2018, 7:57 p.m. UTC | #1
On 9/21/2018 2:00 PM, Gerd Hoffmann wrote:
> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
> ---
>  samples/vfio-mdev/mbochs.c | 136 ++++++++++++++++++++++++++++++++++++++-------
>  1 file changed, 117 insertions(+), 19 deletions(-)
> 
> diff --git a/samples/vfio-mdev/mbochs.c b/samples/vfio-mdev/mbochs.c
> index 2535c3677c..ca7960adf5 100644
> --- a/samples/vfio-mdev/mbochs.c
> +++ b/samples/vfio-mdev/mbochs.c
> @@ -71,11 +71,19 @@
>  #define MBOCHS_NAME		  "mbochs"
>  #define MBOCHS_CLASS_NAME	  "mbochs"
>  
> +#define MBOCHS_EDID_REGION_INDEX  VFIO_PCI_NUM_REGIONS
> +#define MBOCHS_NUM_REGIONS        (MBOCHS_EDID_REGION_INDEX+1)
> +
>  #define MBOCHS_CONFIG_SPACE_SIZE  0xff
>  #define MBOCHS_MMIO_BAR_OFFSET	  PAGE_SIZE
>  #define MBOCHS_MMIO_BAR_SIZE	  PAGE_SIZE
> -#define MBOCHS_MEMORY_BAR_OFFSET  (MBOCHS_MMIO_BAR_OFFSET + \
> +#define MBOCHS_EDID_OFFSET	  (MBOCHS_MMIO_BAR_OFFSET +	\
>  				   MBOCHS_MMIO_BAR_SIZE)
> +#define MBOCHS_EDID_SIZE	  PAGE_SIZE
> +#define MBOCHS_MEMORY_BAR_OFFSET  (MBOCHS_EDID_OFFSET + \
> +				   MBOCHS_EDID_SIZE)
> +
> +#define MBOCHS_EDID_BLOB_OFFSET   (MBOCHS_EDID_SIZE/2)
>  
>  #define STORE_LE16(addr, val)	(*(u16 *)addr = val)
>  #define STORE_LE32(addr, val)	(*(u32 *)addr = val)
> @@ -95,16 +103,24 @@ MODULE_PARM_DESC(mem, "megabytes available to " MBOCHS_NAME " devices");
>  static const struct mbochs_type {
>  	const char *name;
>  	u32 mbytes;
> +	u32 max_x;
> +	u32 max_y;
>  } mbochs_types[] = {
>  	{
>  		.name	= MBOCHS_CLASS_NAME "-" MBOCHS_TYPE_1,
>  		.mbytes = 4,
> +		.max_x  = 800,
> +		.max_y  = 600,
>  	}, {
>  		.name	= MBOCHS_CLASS_NAME "-" MBOCHS_TYPE_2,
>  		.mbytes = 16,
> +		.max_x  = 1920,
> +		.max_y  = 1440,
>  	}, {
>  		.name	= MBOCHS_CLASS_NAME "-" MBOCHS_TYPE_3,
>  		.mbytes = 64,
> +		.max_x  = 0,
> +		.max_y  = 0,
>  	},
>  };
>  
> @@ -115,6 +131,11 @@ static struct cdev	mbochs_cdev;
>  static struct device	mbochs_dev;
>  static int		mbochs_used_mbytes;
>  
> +struct vfio_region_info_ext {
> +	struct vfio_region_info          base;
> +	struct vfio_region_info_cap_type type;
> +};
> +
>  struct mbochs_mode {
>  	u32 drm_format;
>  	u32 bytepp;
> @@ -144,13 +165,14 @@ struct mdev_state {
>  	u32 memory_bar_mask;
>  	struct mutex ops_lock;
>  	struct mdev_device *mdev;
> -	struct vfio_device_info dev_info;
>  
>  	const struct mbochs_type *type;
>  	u16 vbe[VBE_DISPI_INDEX_COUNT];
>  	u64 memsize;
>  	struct page **pages;
>  	pgoff_t pagecount;
> +	struct vfio_region_gfx_edid edid_regs;
> +	u8 edid_blob[0x400];
>  
>  	struct list_head dmabufs;
>  	u32 active_id;
> @@ -342,10 +364,20 @@ static void handle_mmio_read(struct mdev_state *mdev_state, u16 offset,
>  			     char *buf, u32 count)
>  {
>  	struct device *dev = mdev_dev(mdev_state->mdev);
> +	struct vfio_region_gfx_edid *edid;
>  	u16 reg16 = 0;
>  	int index;
>  
>  	switch (offset) {
> +	case 0x000 ... 0x3ff: /* edid block */
> +		edid = &mdev_state->edid_regs;
> +		if (edid->link_state != VFIO_DEVICE_GFX_LINK_STATE_UP ||
> +		    offset >= edid->edid_size) {
> +			memset(buf, 0, count);
> +			break;
> +		}
> +		memcpy(buf, mdev_state->edid_blob + offset, count);
> +		break;
>  	case 0x500 ... 0x515: /* bochs dispi interface */
>  		if (count != 2)
>  			goto unhandled;
> @@ -365,6 +397,44 @@ static void handle_mmio_read(struct mdev_state *mdev_state, u16 offset,
>  	}
>  }
>  
> +static void handle_edid_regs(struct mdev_state *mdev_state, u16 offset,
> +			     char *buf, u32 count, bool is_write)
> +{
> +	char *regs = (void *)&mdev_state->edid_regs;
> +
> +	if (offset + count > sizeof(mdev_state->edid_regs))
> +		return;
> +	if (count != 4)
> +		return;
> +	if (offset % 4)
> +		return;
> +
> +	if (is_write) {
> +		switch (offset) {
> +		case offsetof(struct vfio_region_gfx_edid, link_state):
> +		case offsetof(struct vfio_region_gfx_edid, edid_size):
> +			memcpy(regs + offset, buf, count);
> +			break;
> +		default:
> +			/* read-only regs */
> +			break;
> +		}
> +	} else {
> +		memcpy(buf, regs + offset, count);
> +	}
> +}
> +
> +static void handle_edid_blob(struct mdev_state *mdev_state, u16 offset,
> +			     char *buf, u32 count, bool is_write)
> +{
> +	if (offset + count > mdev_state->edid_regs.edid_max_size)
> +		return;
> +	if (is_write)
> +		memcpy(mdev_state->edid_blob + offset, buf, count);
> +	else
> +		memcpy(buf, mdev_state->edid_blob + offset, count);
> +}
> +
>  static ssize_t mdev_access(struct mdev_device *mdev, char *buf, size_t count,
>  			   loff_t pos, bool is_write)
>  {
> @@ -384,13 +454,25 @@ static ssize_t mdev_access(struct mdev_device *mdev, char *buf, size_t count,
>  			memcpy(buf, (mdev_state->vconfig + pos), count);
>  
>  	} else if (pos >= MBOCHS_MMIO_BAR_OFFSET &&
> -		   pos + count <= MBOCHS_MEMORY_BAR_OFFSET) {
> +		   pos + count <= (MBOCHS_MMIO_BAR_OFFSET +
> +				   MBOCHS_MMIO_BAR_SIZE)) {
>  		pos -= MBOCHS_MMIO_BAR_OFFSET;
>  		if (is_write)
>  			handle_mmio_write(mdev_state, pos, buf, count);
>  		else
>  			handle_mmio_read(mdev_state, pos, buf, count);
>  
> +	} else if (pos >= MBOCHS_EDID_OFFSET &&
> +		   pos + count <= (MBOCHS_EDID_OFFSET +
> +				   MBOCHS_EDID_SIZE)) {
> +		pos -= MBOCHS_EDID_OFFSET;
> +		if (pos < MBOCHS_EDID_BLOB_OFFSET) {
> +			handle_edid_regs(mdev_state, pos, buf, count, is_write);
> +		} else {
> +			pos -= MBOCHS_EDID_BLOB_OFFSET;
> +			handle_edid_blob(mdev_state, pos, buf, count, is_write);
> +		}
> +
>  	} else if (pos >= MBOCHS_MEMORY_BAR_OFFSET &&
>  		   pos + count <=
>  		   MBOCHS_MEMORY_BAR_OFFSET + mdev_state->memsize) {
> @@ -471,6 +553,10 @@ static int mbochs_create(struct kobject *kobj, struct mdev_device *mdev)
>  	mdev_state->next_id = 1;
>  
>  	mdev_state->type = type;
> +	mdev_state->edid_regs.max_xres = type->max_x;
> +	mdev_state->edid_regs.max_yres = type->max_y;
> +	mdev_state->edid_regs.edid_offset = MBOCHS_EDID_BLOB_OFFSET;
> +	mdev_state->edid_regs.edid_max_size = sizeof(mdev_state->edid_blob);
>  	mbochs_create_config_space(mdev_state);
>  	mbochs_reset(mdev);
>  
> @@ -932,16 +1018,16 @@ static int mbochs_dmabuf_export(struct mbochs_dmabuf *dmabuf)
>  }
>  
>  static int mbochs_get_region_info(struct mdev_device *mdev,
> -				  struct vfio_region_info *region_info,
> -				  u16 *cap_type_id, void **cap_type)
> +				  struct vfio_region_info_ext *ext)
>  {
> +	struct vfio_region_info *region_info = &ext->base;
>  	struct mdev_state *mdev_state;
>  
>  	mdev_state = mdev_get_drvdata(mdev);
>  	if (!mdev_state)
>  		return -EINVAL;
>  
> -	if (region_info->index >= VFIO_PCI_NUM_REGIONS)
> +	if (region_info->index >= MBOCHS_NUM_REGIONS)
>  		return -EINVAL;
>  
>  	switch (region_info->index) {
> @@ -964,6 +1050,20 @@ static int mbochs_get_region_info(struct mdev_device *mdev,
>  		region_info->flags  = (VFIO_REGION_INFO_FLAG_READ  |
>  				       VFIO_REGION_INFO_FLAG_WRITE);
>  		break;
> +	case MBOCHS_EDID_REGION_INDEX:
> +		ext->base.argsz = sizeof(*ext);
> +		ext->base.offset = MBOCHS_EDID_OFFSET;
> +		ext->base.size = MBOCHS_EDID_SIZE;
> +		ext->base.flags = (VFIO_REGION_INFO_FLAG_READ  |
> +				   VFIO_REGION_INFO_FLAG_WRITE |
> +				   VFIO_REGION_INFO_FLAG_CAPS);

Any reason to not to use _MMAP flag?
How would QEMU side code read this region? will it be always trapped?
If vendor driver sets _MMAP flag, will QEMU side handle that case as well?
I think since its blob, edid could be read by QEMU using one memcpy
rather than adding multiple memcpy of 4 or 8 bytes.

Thanks,
Kirti

> +		ext->base.cap_offset = offsetof(typeof(*ext), type);
> +		ext->type.header.id = VFIO_REGION_INFO_CAP_TYPE;
> +		ext->type.header.version = 1;
> +		ext->type.header.next = 0;
> +		ext->type.type = VFIO_REGION_TYPE_GFX;
> +		ext->type.subtype = VFIO_REGION_SUBTYPE_GFX_EDID;
> +		break;
>  	default:
>  		region_info->size   = 0;
>  		region_info->offset = 0;
> @@ -984,7 +1084,7 @@ static int mbochs_get_device_info(struct mdev_device *mdev,
>  				  struct vfio_device_info *dev_info)
>  {
>  	dev_info->flags = VFIO_DEVICE_FLAGS_PCI;
> -	dev_info->num_regions = VFIO_PCI_NUM_REGIONS;
> +	dev_info->num_regions = MBOCHS_NUM_REGIONS;
>  	dev_info->num_irqs = VFIO_PCI_NUM_IRQS;
>  	return 0;
>  }
> @@ -1084,7 +1184,7 @@ static long mbochs_ioctl(struct mdev_device *mdev, unsigned int cmd,
>  			unsigned long arg)
>  {
>  	int ret = 0;
> -	unsigned long minsz;
> +	unsigned long minsz, outsz;
>  	struct mdev_state *mdev_state;
>  
>  	mdev_state = mdev_get_drvdata(mdev);
> @@ -1106,8 +1206,6 @@ static long mbochs_ioctl(struct mdev_device *mdev, unsigned int cmd,
>  		if (ret)
>  			return ret;
>  
> -		memcpy(&mdev_state->dev_info, &info, sizeof(info));
> -
>  		if (copy_to_user((void __user *)arg, &info, minsz))
>  			return -EFAULT;
>  
> @@ -1115,24 +1213,24 @@ static long mbochs_ioctl(struct mdev_device *mdev, unsigned int cmd,
>  	}
>  	case VFIO_DEVICE_GET_REGION_INFO:
>  	{
> -		struct vfio_region_info info;
> -		u16 cap_type_id = 0;
> -		void *cap_type = NULL;
> +		struct vfio_region_info_ext info;
>  
> -		minsz = offsetofend(struct vfio_region_info, offset);
> +		minsz = offsetofend(typeof(info), base.offset);
>  
>  		if (copy_from_user(&info, (void __user *)arg, minsz))
>  			return -EFAULT;
>  
> -		if (info.argsz < minsz)
> +		outsz = info.base.argsz;
> +		if (outsz < minsz)
> +			return -EINVAL;
> +		if (outsz > sizeof(info))
>  			return -EINVAL;
>  
> -		ret = mbochs_get_region_info(mdev, &info, &cap_type_id,
> -					   &cap_type);
> +		ret = mbochs_get_region_info(mdev, &info);
>  		if (ret)
>  			return ret;
>  
> -		if (copy_to_user((void __user *)arg, &info, minsz))
> +		if (copy_to_user((void __user *)arg, &info, outsz))
>  			return -EFAULT;
>  
>  		return 0;
> @@ -1148,7 +1246,7 @@ static long mbochs_ioctl(struct mdev_device *mdev, unsigned int cmd,
>  			return -EFAULT;
>  
>  		if ((info.argsz < minsz) ||
> -		    (info.index >= mdev_state->dev_info.num_irqs))
> +		    (info.index >= VFIO_PCI_NUM_IRQS))
>  			return -EINVAL;
>  
>  		ret = mbochs_get_irq_info(mdev, &info);
>
Alex Williamson Sept. 27, 2018, 8:17 p.m. UTC | #2
On Fri, 28 Sep 2018 01:27:16 +0530
Kirti Wankhede <kwankhede@nvidia.com> wrote:

> On 9/21/2018 2:00 PM, Gerd Hoffmann wrote:
> > Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
> > @@ -964,6 +1050,20 @@ static int mbochs_get_region_info(struct mdev_device *mdev,
> >  		region_info->flags  = (VFIO_REGION_INFO_FLAG_READ  |
> >  				       VFIO_REGION_INFO_FLAG_WRITE);
> >  		break;
> > +	case MBOCHS_EDID_REGION_INDEX:
> > +		ext->base.argsz = sizeof(*ext);
> > +		ext->base.offset = MBOCHS_EDID_OFFSET;
> > +		ext->base.size = MBOCHS_EDID_SIZE;
> > +		ext->base.flags = (VFIO_REGION_INFO_FLAG_READ  |
> > +				   VFIO_REGION_INFO_FLAG_WRITE |
> > +				   VFIO_REGION_INFO_FLAG_CAPS);  
> 
> Any reason to not to use _MMAP flag?
> How would QEMU side code read this region? will it be always trapped?
> If vendor driver sets _MMAP flag, will QEMU side handle that case as well?
> I think since its blob, edid could be read by QEMU using one memcpy
> rather than adding multiple memcpy of 4 or 8 bytes.

"Trapping" would only come into play if the region were exposed to the
VM, which there's no intention to do here afaik.  Also, just because
it doesn't support mmap doesn't mean that QEMU necessarily needs to
break down accesses into smaller words, QEMU could do:

pwrite(fd, buf, edid_max_size, region_offset + edid_offset)

ie. write the entire edid area with one operation.  I don't think
there's anything in the specification that prevents mmap now,
edid_offset could be at a page alignment, edid_max_size could be
PAGE_SIZE, and a sparse mmap capability could indicate that only the
EDID area is mmap'able, but is it worth the code to support that?
Thanks,

Alex
Gerd Hoffmann Sept. 28, 2018, 5:40 a.m. UTC | #3
> > +	case MBOCHS_EDID_REGION_INDEX:
> > +		ext->base.argsz = sizeof(*ext);
> > +		ext->base.offset = MBOCHS_EDID_OFFSET;
> > +		ext->base.size = MBOCHS_EDID_SIZE;
> > +		ext->base.flags = (VFIO_REGION_INFO_FLAG_READ  |
> > +				   VFIO_REGION_INFO_FLAG_WRITE |
> > +				   VFIO_REGION_INFO_FLAG_CAPS);
> 
> Any reason to not to use _MMAP flag?

There is no page backing this.  Also it is not performance-critical,
edid updates should be rare, so the extra code for mmap support doesn't
look like it is worth it.

Also for the virtual registers (especially link_state) it is probably
useful to have the write callback of the mdev driver called to get
notified about the change.

> How would QEMU side code read this region? will it be always trapped?

qemu uses read & write syscalls (well, pread & pwrite actually).

> If vendor driver sets _MMAP flag, will QEMU side handle that case as well?

The current test branch doesn't, it expects read+write to work.
  https://git.kraxel.org/cgit/qemu/log/?h=sirius/edid-vfio

> I think since its blob, edid could be read by QEMU using one memcpy
> rather than adding multiple memcpy of 4 or 8 bytes.

From qemu it's a single pwrite syscall actually.  mbochs_write() splits
it into 4 byte writes and calls mbochs_access() for each of them.  One
could probably add a special case for the EDID blob to mbochs_write().
But again: doesn't seem worth the effort given that edid updates should
be a rare event.

cheers,
  Gerd
Kirti Wankhede Sept. 28, 2018, 8:40 a.m. UTC | #4
On 9/28/2018 11:10 AM, Gerd Hoffmann wrote:
>>> +	case MBOCHS_EDID_REGION_INDEX:
>>> +		ext->base.argsz = sizeof(*ext);
>>> +		ext->base.offset = MBOCHS_EDID_OFFSET;
>>> +		ext->base.size = MBOCHS_EDID_SIZE;
>>> +		ext->base.flags = (VFIO_REGION_INFO_FLAG_READ  |
>>> +				   VFIO_REGION_INFO_FLAG_WRITE |
>>> +				   VFIO_REGION_INFO_FLAG_CAPS);
>>
>> Any reason to not to use _MMAP flag?
> 
> There is no page backing this.  Also it is not performance-critical,
> edid updates should be rare, so the extra code for mmap support doesn't
> look like it is worth it.
> 
> Also for the virtual registers (especially link_state) it is probably
> useful to have the write callback of the mdev driver called to get
> notified about the change.
> 
>> How would QEMU side code read this region? will it be always trapped?
> 
> qemu uses read & write syscalls (well, pread & pwrite actually).
> 
>> If vendor driver sets _MMAP flag, will QEMU side handle that case as well?
> 
> The current test branch doesn't, it expects read+write to work.
>   https://git.kraxel.org/cgit/qemu/log/?h=sirius/edid-vfio
> 

Ok.
Can you add a comment in vfio.h that this region is non-mmappable?

>> I think since its blob, edid could be read by QEMU using one memcpy
>> rather than adding multiple memcpy of 4 or 8 bytes.
> 
> From qemu it's a single pwrite syscall actually.  mbochs_write() splits
> it into 4 byte writes and calls mbochs_access() for each of them.  One
> could probably add a special case for the EDID blob to mbochs_write().
> But again: doesn't seem worth the effort given that edid updates should
> be a rare event.
> 

Ok.

Thanks,
Kirti

> cheers,
>   Gerd
>
Alex Williamson Sept. 28, 2018, 12:49 p.m. UTC | #5
On Fri, 28 Sep 2018 14:10:26 +0530
Kirti Wankhede <kwankhede@nvidia.com> wrote:

> On 9/28/2018 11:10 AM, Gerd Hoffmann wrote:
> >>> +	case MBOCHS_EDID_REGION_INDEX:
> >>> +		ext->base.argsz = sizeof(*ext);
> >>> +		ext->base.offset = MBOCHS_EDID_OFFSET;
> >>> +		ext->base.size = MBOCHS_EDID_SIZE;
> >>> +		ext->base.flags = (VFIO_REGION_INFO_FLAG_READ  |
> >>> +				   VFIO_REGION_INFO_FLAG_WRITE |
> >>> +				   VFIO_REGION_INFO_FLAG_CAPS);  
> >>
> >> Any reason to not to use _MMAP flag?  
> > 
> > There is no page backing this.  Also it is not performance-critical,
> > edid updates should be rare, so the extra code for mmap support doesn't
> > look like it is worth it.
> > 
> > Also for the virtual registers (especially link_state) it is probably
> > useful to have the write callback of the mdev driver called to get
> > notified about the change.
> >   
> >> How would QEMU side code read this region? will it be always trapped?  
> > 
> > qemu uses read & write syscalls (well, pread & pwrite actually).
> >   
> >> If vendor driver sets _MMAP flag, will QEMU side handle that case as well?  
> > 
> > The current test branch doesn't, it expects read+write to work.
> >   https://git.kraxel.org/cgit/qemu/log/?h=sirius/edid-vfio
> >   
> 
> Ok.
> Can you add a comment in vfio.h that this region is non-mmappable?

No, region access mechanisms are self describing through the region
info ioctl, it's left to the implementation to decide what to support.
The region definition should not impose such a restriction.  Thanks,

Alex
diff mbox series

Patch

diff --git a/samples/vfio-mdev/mbochs.c b/samples/vfio-mdev/mbochs.c
index 2535c3677c..ca7960adf5 100644
--- a/samples/vfio-mdev/mbochs.c
+++ b/samples/vfio-mdev/mbochs.c
@@ -71,11 +71,19 @@ 
 #define MBOCHS_NAME		  "mbochs"
 #define MBOCHS_CLASS_NAME	  "mbochs"
 
+#define MBOCHS_EDID_REGION_INDEX  VFIO_PCI_NUM_REGIONS
+#define MBOCHS_NUM_REGIONS        (MBOCHS_EDID_REGION_INDEX+1)
+
 #define MBOCHS_CONFIG_SPACE_SIZE  0xff
 #define MBOCHS_MMIO_BAR_OFFSET	  PAGE_SIZE
 #define MBOCHS_MMIO_BAR_SIZE	  PAGE_SIZE
-#define MBOCHS_MEMORY_BAR_OFFSET  (MBOCHS_MMIO_BAR_OFFSET + \
+#define MBOCHS_EDID_OFFSET	  (MBOCHS_MMIO_BAR_OFFSET +	\
 				   MBOCHS_MMIO_BAR_SIZE)
+#define MBOCHS_EDID_SIZE	  PAGE_SIZE
+#define MBOCHS_MEMORY_BAR_OFFSET  (MBOCHS_EDID_OFFSET + \
+				   MBOCHS_EDID_SIZE)
+
+#define MBOCHS_EDID_BLOB_OFFSET   (MBOCHS_EDID_SIZE/2)
 
 #define STORE_LE16(addr, val)	(*(u16 *)addr = val)
 #define STORE_LE32(addr, val)	(*(u32 *)addr = val)
@@ -95,16 +103,24 @@  MODULE_PARM_DESC(mem, "megabytes available to " MBOCHS_NAME " devices");
 static const struct mbochs_type {
 	const char *name;
 	u32 mbytes;
+	u32 max_x;
+	u32 max_y;
 } mbochs_types[] = {
 	{
 		.name	= MBOCHS_CLASS_NAME "-" MBOCHS_TYPE_1,
 		.mbytes = 4,
+		.max_x  = 800,
+		.max_y  = 600,
 	}, {
 		.name	= MBOCHS_CLASS_NAME "-" MBOCHS_TYPE_2,
 		.mbytes = 16,
+		.max_x  = 1920,
+		.max_y  = 1440,
 	}, {
 		.name	= MBOCHS_CLASS_NAME "-" MBOCHS_TYPE_3,
 		.mbytes = 64,
+		.max_x  = 0,
+		.max_y  = 0,
 	},
 };
 
@@ -115,6 +131,11 @@  static struct cdev	mbochs_cdev;
 static struct device	mbochs_dev;
 static int		mbochs_used_mbytes;
 
+struct vfio_region_info_ext {
+	struct vfio_region_info          base;
+	struct vfio_region_info_cap_type type;
+};
+
 struct mbochs_mode {
 	u32 drm_format;
 	u32 bytepp;
@@ -144,13 +165,14 @@  struct mdev_state {
 	u32 memory_bar_mask;
 	struct mutex ops_lock;
 	struct mdev_device *mdev;
-	struct vfio_device_info dev_info;
 
 	const struct mbochs_type *type;
 	u16 vbe[VBE_DISPI_INDEX_COUNT];
 	u64 memsize;
 	struct page **pages;
 	pgoff_t pagecount;
+	struct vfio_region_gfx_edid edid_regs;
+	u8 edid_blob[0x400];
 
 	struct list_head dmabufs;
 	u32 active_id;
@@ -342,10 +364,20 @@  static void handle_mmio_read(struct mdev_state *mdev_state, u16 offset,
 			     char *buf, u32 count)
 {
 	struct device *dev = mdev_dev(mdev_state->mdev);
+	struct vfio_region_gfx_edid *edid;
 	u16 reg16 = 0;
 	int index;
 
 	switch (offset) {
+	case 0x000 ... 0x3ff: /* edid block */
+		edid = &mdev_state->edid_regs;
+		if (edid->link_state != VFIO_DEVICE_GFX_LINK_STATE_UP ||
+		    offset >= edid->edid_size) {
+			memset(buf, 0, count);
+			break;
+		}
+		memcpy(buf, mdev_state->edid_blob + offset, count);
+		break;
 	case 0x500 ... 0x515: /* bochs dispi interface */
 		if (count != 2)
 			goto unhandled;
@@ -365,6 +397,44 @@  static void handle_mmio_read(struct mdev_state *mdev_state, u16 offset,
 	}
 }
 
+static void handle_edid_regs(struct mdev_state *mdev_state, u16 offset,
+			     char *buf, u32 count, bool is_write)
+{
+	char *regs = (void *)&mdev_state->edid_regs;
+
+	if (offset + count > sizeof(mdev_state->edid_regs))
+		return;
+	if (count != 4)
+		return;
+	if (offset % 4)
+		return;
+
+	if (is_write) {
+		switch (offset) {
+		case offsetof(struct vfio_region_gfx_edid, link_state):
+		case offsetof(struct vfio_region_gfx_edid, edid_size):
+			memcpy(regs + offset, buf, count);
+			break;
+		default:
+			/* read-only regs */
+			break;
+		}
+	} else {
+		memcpy(buf, regs + offset, count);
+	}
+}
+
+static void handle_edid_blob(struct mdev_state *mdev_state, u16 offset,
+			     char *buf, u32 count, bool is_write)
+{
+	if (offset + count > mdev_state->edid_regs.edid_max_size)
+		return;
+	if (is_write)
+		memcpy(mdev_state->edid_blob + offset, buf, count);
+	else
+		memcpy(buf, mdev_state->edid_blob + offset, count);
+}
+
 static ssize_t mdev_access(struct mdev_device *mdev, char *buf, size_t count,
 			   loff_t pos, bool is_write)
 {
@@ -384,13 +454,25 @@  static ssize_t mdev_access(struct mdev_device *mdev, char *buf, size_t count,
 			memcpy(buf, (mdev_state->vconfig + pos), count);
 
 	} else if (pos >= MBOCHS_MMIO_BAR_OFFSET &&
-		   pos + count <= MBOCHS_MEMORY_BAR_OFFSET) {
+		   pos + count <= (MBOCHS_MMIO_BAR_OFFSET +
+				   MBOCHS_MMIO_BAR_SIZE)) {
 		pos -= MBOCHS_MMIO_BAR_OFFSET;
 		if (is_write)
 			handle_mmio_write(mdev_state, pos, buf, count);
 		else
 			handle_mmio_read(mdev_state, pos, buf, count);
 
+	} else if (pos >= MBOCHS_EDID_OFFSET &&
+		   pos + count <= (MBOCHS_EDID_OFFSET +
+				   MBOCHS_EDID_SIZE)) {
+		pos -= MBOCHS_EDID_OFFSET;
+		if (pos < MBOCHS_EDID_BLOB_OFFSET) {
+			handle_edid_regs(mdev_state, pos, buf, count, is_write);
+		} else {
+			pos -= MBOCHS_EDID_BLOB_OFFSET;
+			handle_edid_blob(mdev_state, pos, buf, count, is_write);
+		}
+
 	} else if (pos >= MBOCHS_MEMORY_BAR_OFFSET &&
 		   pos + count <=
 		   MBOCHS_MEMORY_BAR_OFFSET + mdev_state->memsize) {
@@ -471,6 +553,10 @@  static int mbochs_create(struct kobject *kobj, struct mdev_device *mdev)
 	mdev_state->next_id = 1;
 
 	mdev_state->type = type;
+	mdev_state->edid_regs.max_xres = type->max_x;
+	mdev_state->edid_regs.max_yres = type->max_y;
+	mdev_state->edid_regs.edid_offset = MBOCHS_EDID_BLOB_OFFSET;
+	mdev_state->edid_regs.edid_max_size = sizeof(mdev_state->edid_blob);
 	mbochs_create_config_space(mdev_state);
 	mbochs_reset(mdev);
 
@@ -932,16 +1018,16 @@  static int mbochs_dmabuf_export(struct mbochs_dmabuf *dmabuf)
 }
 
 static int mbochs_get_region_info(struct mdev_device *mdev,
-				  struct vfio_region_info *region_info,
-				  u16 *cap_type_id, void **cap_type)
+				  struct vfio_region_info_ext *ext)
 {
+	struct vfio_region_info *region_info = &ext->base;
 	struct mdev_state *mdev_state;
 
 	mdev_state = mdev_get_drvdata(mdev);
 	if (!mdev_state)
 		return -EINVAL;
 
-	if (region_info->index >= VFIO_PCI_NUM_REGIONS)
+	if (region_info->index >= MBOCHS_NUM_REGIONS)
 		return -EINVAL;
 
 	switch (region_info->index) {
@@ -964,6 +1050,20 @@  static int mbochs_get_region_info(struct mdev_device *mdev,
 		region_info->flags  = (VFIO_REGION_INFO_FLAG_READ  |
 				       VFIO_REGION_INFO_FLAG_WRITE);
 		break;
+	case MBOCHS_EDID_REGION_INDEX:
+		ext->base.argsz = sizeof(*ext);
+		ext->base.offset = MBOCHS_EDID_OFFSET;
+		ext->base.size = MBOCHS_EDID_SIZE;
+		ext->base.flags = (VFIO_REGION_INFO_FLAG_READ  |
+				   VFIO_REGION_INFO_FLAG_WRITE |
+				   VFIO_REGION_INFO_FLAG_CAPS);
+		ext->base.cap_offset = offsetof(typeof(*ext), type);
+		ext->type.header.id = VFIO_REGION_INFO_CAP_TYPE;
+		ext->type.header.version = 1;
+		ext->type.header.next = 0;
+		ext->type.type = VFIO_REGION_TYPE_GFX;
+		ext->type.subtype = VFIO_REGION_SUBTYPE_GFX_EDID;
+		break;
 	default:
 		region_info->size   = 0;
 		region_info->offset = 0;
@@ -984,7 +1084,7 @@  static int mbochs_get_device_info(struct mdev_device *mdev,
 				  struct vfio_device_info *dev_info)
 {
 	dev_info->flags = VFIO_DEVICE_FLAGS_PCI;
-	dev_info->num_regions = VFIO_PCI_NUM_REGIONS;
+	dev_info->num_regions = MBOCHS_NUM_REGIONS;
 	dev_info->num_irqs = VFIO_PCI_NUM_IRQS;
 	return 0;
 }
@@ -1084,7 +1184,7 @@  static long mbochs_ioctl(struct mdev_device *mdev, unsigned int cmd,
 			unsigned long arg)
 {
 	int ret = 0;
-	unsigned long minsz;
+	unsigned long minsz, outsz;
 	struct mdev_state *mdev_state;
 
 	mdev_state = mdev_get_drvdata(mdev);
@@ -1106,8 +1206,6 @@  static long mbochs_ioctl(struct mdev_device *mdev, unsigned int cmd,
 		if (ret)
 			return ret;
 
-		memcpy(&mdev_state->dev_info, &info, sizeof(info));
-
 		if (copy_to_user((void __user *)arg, &info, minsz))
 			return -EFAULT;
 
@@ -1115,24 +1213,24 @@  static long mbochs_ioctl(struct mdev_device *mdev, unsigned int cmd,
 	}
 	case VFIO_DEVICE_GET_REGION_INFO:
 	{
-		struct vfio_region_info info;
-		u16 cap_type_id = 0;
-		void *cap_type = NULL;
+		struct vfio_region_info_ext info;
 
-		minsz = offsetofend(struct vfio_region_info, offset);
+		minsz = offsetofend(typeof(info), base.offset);
 
 		if (copy_from_user(&info, (void __user *)arg, minsz))
 			return -EFAULT;
 
-		if (info.argsz < minsz)
+		outsz = info.base.argsz;
+		if (outsz < minsz)
+			return -EINVAL;
+		if (outsz > sizeof(info))
 			return -EINVAL;
 
-		ret = mbochs_get_region_info(mdev, &info, &cap_type_id,
-					   &cap_type);
+		ret = mbochs_get_region_info(mdev, &info);
 		if (ret)
 			return ret;
 
-		if (copy_to_user((void __user *)arg, &info, minsz))
+		if (copy_to_user((void __user *)arg, &info, outsz))
 			return -EFAULT;
 
 		return 0;
@@ -1148,7 +1246,7 @@  static long mbochs_ioctl(struct mdev_device *mdev, unsigned int cmd,
 			return -EFAULT;
 
 		if ((info.argsz < minsz) ||
-		    (info.index >= mdev_state->dev_info.num_irqs))
+		    (info.index >= VFIO_PCI_NUM_IRQS))
 			return -EINVAL;
 
 		ret = mbochs_get_irq_info(mdev, &info);