diff --git a/drivers/gpu/drm/i915/gvt/display.c b/drivers/gpu/drm/i915/gvt/display.c
index df1e141..a9176a1 100644
--- a/drivers/gpu/drm/i915/gvt/display.c
+++ b/drivers/gpu/drm/i915/gvt/display.c
@@ -442,6 +442,152 @@ void intel_gvt_emulate_vblank(struct intel_gvt *gvt)
 	mutex_unlock(&gvt->lock);
 }
 
+struct intel_vgpu_fb_meta_data {
+	u32 vgpu_plane_id; /* vgpu_id(23:16):vgpu_pipe(15:8):vgpu_plane(7:0)*/
+	struct intel_vgpu *vgpu; // the vGPU this meta_fb belongs to
+};
+
+static void meta_fb_destroy(struct drm_framebuffer *fb)
+{
+	struct intel_framebuffer *intel_fb = to_intel_framebuffer(fb);
+
+	if (intel_fb->meta_fb.type_id != INTEL_META_FB_VGPU)
+		return;
+
+	kfree(intel_fb->meta_fb.private);
+	intel_fb->meta_fb.private = NULL;
+
+	drm_framebuffer_cleanup(fb);
+	kfree(intel_fb);
+}
+
+static void clean_meta_fb(struct intel_vgpu *vgpu)
+{
+	enum pipe pipe;
+	enum plane_id plane_id;
+	struct intel_framebuffer *intel_fb;
+
+	for (pipe = 0; pipe < I915_MAX_PIPES; pipe++) {
+		for (plane_id = 0; plane_id < I915_MAX_PLANES; plane_id++) {
+			intel_fb = vgpu->display.meta_fbs.meta_fb[pipe][plane_id];
+			if (!intel_fb)
+				drm_framebuffer_put(&intel_fb->base);
+
+			intel_fb = NULL;
+		}
+	}
+}
+
+static int meta_fb_create_handle(struct drm_framebuffer *fb,
+				 struct drm_file *file,
+				 unsigned int *handle)
+{
+	return -ENODEV;
+}
+
+static int meta_fb_dirty(struct drm_framebuffer *fb,
+			 struct drm_file *file,
+			 unsigned int flags,
+			 unsigned int color,
+			 struct drm_clip_rect *clips,
+			 unsigned int num_clips)
+{
+	return 0;
+}
+
+static const struct drm_framebuffer_funcs meta_fb_funcs = {
+	.destroy = meta_fb_destroy,
+	.create_handle = meta_fb_create_handle,
+	.dirty = meta_fb_dirty,
+};
+
+static void meta_fb_update(struct intel_framebuffer *intel_fb,
+			   enum pipe pipe, enum plane_id plane_id)
+{
+	struct intel_vgpu_fb_meta_data *meta_data;
+	struct intel_gvt *gvt;
+
+	if (!intel_fb || intel_fb->meta_fb.type_id != INTEL_META_FB_VGPU)
+		return;
+
+	meta_data = intel_fb->meta_fb.private;
+	gvt = meta_data->vgpu->gvt;
+
+	if (gvt->assigned_plane[pipe][plane_id].vgpu_plane_id !=
+						meta_data->vgpu_plane_id) {
+		gvt->assigned_plane[pipe][plane_id].vgpu_plane_id =
+						meta_data->vgpu_plane_id;
+		gvt->assigned_plane[pipe][plane_id].framebuffer_id =
+						intel_fb->base.base.id;
+		intel_fb->meta_fb.ggtt_offset = 0;
+		intel_fb->meta_fb.should_be_offscreen = true;
+	} else if (!intel_fb->meta_fb.ggtt_offset) {
+		intel_fb->meta_fb.should_be_offscreen = true;
+	} else {
+		intel_fb->meta_fb.should_be_offscreen = false;
+	}
+}
+
+static int init_meta_fb(struct intel_vgpu *vgpu)
+{
+	struct drm_i915_private *dev_priv = vgpu->gvt->dev_priv;
+	struct intel_vgpu_fb_meta_data *meta_data;
+	struct drm_mode_fb_cmd2 mode_cmd;
+	struct intel_framebuffer *intel_fb;
+	enum pipe pipe;
+	enum plane_id plane_id;
+	int ret = 0;
+
+	for (pipe = 0; pipe < I915_MAX_PIPES; pipe++) {
+		for (plane_id = 0; plane_id < I915_MAX_PLANES; plane_id++) {
+			intel_fb = kzalloc(sizeof(*intel_fb), GFP_KERNEL);
+			if (!intel_fb)
+				return -ENOMEM;
+
+			/*
+			 * Create a drm_framebuffer with defaults.
+			 */
+			mode_cmd.pixel_format = DRM_FORMAT_XRGB8888;
+			mode_cmd.width = dev_priv->drm.mode_config.max_width;
+			mode_cmd.height = dev_priv->drm.mode_config.max_height;
+			mode_cmd.flags = DRM_MODE_FB_MODIFIERS;
+			mode_cmd.handles[0] = 0;
+			mode_cmd.pitches[0] = mode_cmd.width * 4;
+			mode_cmd.offsets[0] = 0;
+			mode_cmd.modifier[0] = DRM_FORMAT_MOD_LINEAR;
+
+			drm_helper_mode_fill_fb_struct(&dev_priv->drm,
+						       &intel_fb->base, &mode_cmd);
+
+			ret = drm_framebuffer_init(&dev_priv->drm,
+						   &intel_fb->base, &meta_fb_funcs);
+			if (ret) {
+				DRM_ERROR("%s: framebuffer init failed %d\n",
+					  __func__, ret);
+				kfree(intel_fb);
+				return ret;
+			}
+
+			meta_data = kmalloc(sizeof(struct intel_vgpu_fb_meta_data),
+					    GFP_KERNEL);
+			if (unlikely(!meta_data)) {
+				return -ENOMEM;
+			}
+
+			meta_data->vgpu_plane_id = (vgpu->id << 16) |
+				(pipe << 8) | plane_id;
+			meta_data->vgpu = vgpu;
+
+			intel_fb->meta_fb.private = meta_data;
+			intel_fb->meta_fb.update = meta_fb_update;
+			intel_fb->meta_fb.type_id = INTEL_META_FB_VGPU;
+
+			vgpu->display.meta_fbs.meta_fb[pipe][plane_id] = intel_fb;
+		}
+	}
+	return 0;
+}
+
 /**
  * intel_vgpu_clean_display - clean vGPU virtual display emulation
  * @vgpu: a vGPU
@@ -457,6 +603,8 @@ void intel_vgpu_clean_display(struct intel_vgpu *vgpu)
 		clean_virtual_dp_monitor(vgpu, PORT_D);
 	else
 		clean_virtual_dp_monitor(vgpu, PORT_B);
+
+	clean_meta_fb(vgpu);
 }
 
 /**
@@ -476,6 +624,8 @@ int intel_vgpu_init_display(struct intel_vgpu *vgpu, u64 resolution)
 
 	intel_vgpu_init_i2c_edid(vgpu);
 
+	init_meta_fb(vgpu);
+
 	if (IS_SKYLAKE(dev_priv) || IS_KABYLAKE(dev_priv))
 		return setup_virtual_dp_monitor(vgpu, PORT_D, GVT_DP_D,
 						resolution);
diff --git a/drivers/gpu/drm/i915/gvt/gvt.h b/drivers/gpu/drm/i915/gvt/gvt.h
index 31f6cdb..0ab10b0 100644
--- a/drivers/gpu/drm/i915/gvt/gvt.h
+++ b/drivers/gpu/drm/i915/gvt/gvt.h
@@ -131,10 +131,16 @@ struct intel_vgpu_opregion {
 
 #define vgpu_opregion(vgpu) (&(vgpu->opregion))
 
+struct intel_vgpu_meta_fbs {
+	struct intel_framebuffer *meta_fb[I915_MAX_PIPES][I915_MAX_PLANES];
+	u32 plane_id_index;
+};
+
 struct intel_vgpu_display {
 	struct intel_vgpu_i2c_edid i2c_edid;
 	struct intel_vgpu_port ports[I915_MAX_PORTS];
 	struct intel_vgpu_sbi sbi;
+	struct intel_vgpu_meta_fbs meta_fbs;
 };
 
 struct vgpu_sched_ctl {
@@ -301,6 +307,13 @@ struct intel_vgpu_type {
 	enum intel_vgpu_edid resolution;
 };
 
+struct assigned_plane {
+	u32 vgpu_plane_id;
+
+	/* userspace visible identifier */
+	int framebuffer_id;
+};
+
 struct intel_gvt {
 	/* GVT scope lock, protect GVT itself, and all resource currently
 	 * not yet protected by special locks(vgpu and scheduler lock).
@@ -340,6 +353,9 @@ struct intel_gvt {
 	} engine_mmio_list;
 
 	struct dentry *debugfs_root;
+
+	/* vGPU plane assignment */
+	struct assigned_plane assigned_plane[I915_MAX_PIPES][I915_MAX_PLANES];
 };
 
 static inline struct intel_gvt *to_gvt(struct drm_i915_private *i915)
diff --git a/drivers/gpu/drm/i915/gvt/kvmgt.c b/drivers/gpu/drm/i915/gvt/kvmgt.c
index c107214..7f4704d 100644
--- a/drivers/gpu/drm/i915/gvt/kvmgt.c
+++ b/drivers/gpu/drm/i915/gvt/kvmgt.c
@@ -1420,12 +1420,58 @@ hw_id_show(struct device *dev, struct device_attribute *attr,
 	return sprintf(buf, "\n");
 }
 
+static ssize_t
+plane_id_index_show(struct device *dev, struct device_attribute *attr,
+	 char *buf)
+{
+	struct mdev_device *mdev = mdev_from_dev(dev);
+
+	if (mdev) {
+		struct intel_vgpu *vgpu = (struct intel_vgpu *)
+			mdev_get_drvdata(mdev);
+		enum pipe pipe = vgpu->display.meta_fbs.plane_id_index &
+			0x000000F0;
+		enum plane_id plane_id = vgpu->display.meta_fbs.plane_id_index &
+			0x0000000F;
+
+		if ((pipe < I915_MAX_PIPES || plane_id < I915_MAX_PLANES) &&
+			vgpu->display.meta_fbs.meta_fb[pipe][plane_id]) {
+			return sprintf(buf, "%u\n",
+			       vgpu->display.meta_fbs.meta_fb[pipe][plane_id]->base.base.id);
+		}
+	}
+	return sprintf(buf, "\n");
+}
+
+static ssize_t
+plane_id_index_store(struct device *dev, struct device_attribute *attr,
+			     const char *buf, size_t n)
+{
+	struct mdev_device *mdev = mdev_from_dev(dev);
+	ssize_t ret;
+	u32 val;
+
+	ret = kstrtou32(buf, 0, &val);
+	if (ret)
+		return ret;
+
+	if (mdev) {
+		struct intel_vgpu *vgpu = (struct intel_vgpu *)
+			mdev_get_drvdata(mdev);
+		vgpu->display.meta_fbs.plane_id_index = val;
+	}
+
+	return n;
+}
+
 static DEVICE_ATTR_RO(vgpu_id);
 static DEVICE_ATTR_RO(hw_id);
+static DEVICE_ATTR_RW(plane_id_index);
 
 static struct attribute *intel_vgpu_attrs[] = {
 	&dev_attr_vgpu_id.attr,
 	&dev_attr_hw_id.attr,
+	&dev_attr_plane_id_index.attr,
 	NULL
 };
 
