@@ -381,6 +381,59 @@ void xen_drm_front_on_frame_done(struct xen_drm_front_info *front_info,
fb_cookie);
}
+int xen_drm_front_get_edid(struct xen_drm_front_info *front_info,
+ int conn_idx, struct page **pages,
+ u32 buffer_sz, u32 *edid_sz)
+{
+ struct xen_drm_front_evtchnl *evtchnl;
+ struct xen_front_pgdir_shbuf_cfg buf_cfg;
+ struct xen_front_pgdir_shbuf shbuf;
+ struct xendispl_req *req;
+ unsigned long flags;
+ int ret;
+
+ if (unlikely(conn_idx >= front_info->num_evt_pairs))
+ return -EINVAL;
+
+ memset(&buf_cfg, 0, sizeof(buf_cfg));
+ buf_cfg.xb_dev = front_info->xb_dev;
+ buf_cfg.num_pages = DIV_ROUND_UP(buffer_sz, PAGE_SIZE);
+ buf_cfg.pages = pages;
+ buf_cfg.pgdir = &shbuf;
+ buf_cfg.be_alloc = false;
+
+ ret = xen_front_pgdir_shbuf_alloc(&buf_cfg);
+ if (ret < 0)
+ return ret;
+
+ evtchnl = &front_info->evt_pairs[conn_idx].req;
+
+ mutex_lock(&evtchnl->u.req.req_io_lock);
+
+ spin_lock_irqsave(&front_info->io_lock, flags);
+ req = be_prepare_req(evtchnl, XENDISPL_OP_GET_EDID);
+ req->op.get_edid.gref_directory =
+ xen_front_pgdir_shbuf_get_dir_start(&shbuf);
+ req->op.get_edid.buffer_sz = buffer_sz;
+
+ ret = be_stream_do_io(evtchnl, req);
+ spin_unlock_irqrestore(&front_info->io_lock, flags);
+
+ if (ret < 0)
+ goto fail;
+
+ ret = be_stream_wait_io(evtchnl);
+ if (ret < 0)
+ goto fail;
+
+ *edid_sz = evtchnl->u.req.resp.get_edid.edid_sz;
+
+fail:
+ mutex_unlock(&evtchnl->u.req.req_io_lock);
+ xen_front_pgdir_shbuf_free(&shbuf);
+ return ret;
+}
+
static int xen_drm_drv_dumb_create(struct drm_file *filp,
struct drm_device *dev,
struct drm_mode_create_dumb *args)
@@ -466,6 +519,7 @@ static void xen_drm_drv_release(struct drm_device *dev)
xenbus_switch_state(front_info->xb_dev,
XenbusStateInitialising);
+ xen_drm_front_cfg_free(front_info, &front_info->cfg);
kfree(drm_info);
}
@@ -562,6 +616,7 @@ static int xen_drm_drv_init(struct xen_drm_front_info *front_info)
drm_mode_config_cleanup(drm_dev);
drm_dev_put(drm_dev);
fail:
+ xen_drm_front_cfg_free(front_info, &front_info->cfg);
kfree(drm_info);
return ret;
}
@@ -622,7 +677,14 @@ static int displback_initwait(struct xen_drm_front_info *front_info)
static int displback_connect(struct xen_drm_front_info *front_info)
{
+ int ret;
+
xen_drm_front_evtchnl_set_state(front_info, EVTCHNL_STATE_CONNECTED);
+
+ /* We are all set to read additional configuration from the backend. */
+ ret = xen_drm_front_cfg_tail(front_info, &front_info->cfg);
+ if (ret < 0)
+ return ret;
return xen_drm_drv_init(front_info);
}
@@ -112,9 +112,12 @@ struct xen_drm_front_drm_pipeline {
struct drm_simple_display_pipe pipe;
struct drm_connector conn;
- /* These are only for connector mode checking */
+ /* These are only for connector mode checking if no EDID present */
int width, height;
+ /* Is not NULL if EDID is used for connector configuration. */
+ struct edid *edid;
+
struct drm_pending_vblank_event *pending_event;
struct delayed_work pflip_to_worker;
@@ -160,4 +163,8 @@ int xen_drm_front_page_flip(struct xen_drm_front_info *front_info,
void xen_drm_front_on_frame_done(struct xen_drm_front_info *front_info,
int conn_idx, u64 fb_cookie);
+int xen_drm_front_get_edid(struct xen_drm_front_info *front_info,
+ int conn_idx, struct page **pages,
+ u32 buffer_sz, u32 *edid_sz);
+
#endif /* __XEN_DRM_FRONT_H_ */
@@ -45,6 +45,64 @@ static int cfg_connector(struct xen_drm_front_info *front_info,
return 0;
}
+static void
+cfg_connector_free_edid(struct xen_drm_front_cfg_connector *connector)
+{
+ vfree(connector->edid);
+ connector->edid = NULL;
+}
+
+static void cfg_connector_edid(struct xen_drm_front_info *front_info,
+ struct xen_drm_front_cfg_connector *connector,
+ int index)
+{
+ struct page **pages;
+ u32 edid_sz;
+ int i, npages, ret = -ENOMEM;
+
+ connector->edid = vmalloc(XENDISPL_EDID_MAX_SIZE);
+ if (!connector->edid)
+ goto fail;
+
+ npages = DIV_ROUND_UP(XENDISPL_EDID_MAX_SIZE, PAGE_SIZE);
+ pages = kvmalloc_array(npages, sizeof(struct page *), GFP_KERNEL);
+ if (!pages)
+ goto fail_free_edid;
+
+ for (i = 0; i < npages; i++)
+ pages[i] = vmalloc_to_page((u8 *)connector->edid +
+ i * PAGE_SIZE);
+
+ ret = xen_drm_front_get_edid(front_info, index, pages,
+ XENDISPL_EDID_MAX_SIZE, &edid_sz);
+
+ kvfree(pages);
+
+ if (ret < 0)
+ goto fail_free_edid;
+
+ ret = -EINVAL;
+ if (!edid_sz || (edid_sz % EDID_LENGTH))
+ goto fail_free_edid;
+
+ if (!drm_edid_is_valid(connector->edid))
+ goto fail_free_edid;
+
+ DRM_INFO("Connector %s: using EDID for configuration, size %d\n",
+ connector->xenstore_path, edid_sz);
+ return;
+
+fail_free_edid:
+ cfg_connector_free_edid(connector);
+fail:
+ /*
+ * If any error this is not critical as we can still read
+ * connector settings from XenStore, so just warn.
+ */
+ DRM_WARN("Connector %s: cannot read or wrong EDID: %d\n",
+ connector->xenstore_path, ret);
+}
+
int xen_drm_front_cfg_card(struct xen_drm_front_info *front_info,
struct xen_drm_front_cfg *cfg)
{
@@ -75,3 +133,27 @@ int xen_drm_front_cfg_card(struct xen_drm_front_info *front_info,
return 0;
}
+int xen_drm_front_cfg_tail(struct xen_drm_front_info *front_info,
+ struct xen_drm_front_cfg *cfg)
+{
+ int i;
+
+ /*
+ * Try reading EDID(s) from the backend: it is not an error
+ * if backend doesn't support or provides no EDID.
+ */
+ for (i = 0; i < cfg->num_connectors; i++)
+ cfg_connector_edid(front_info, &cfg->connectors[i], i);
+
+ return 0;
+}
+
+void xen_drm_front_cfg_free(struct xen_drm_front_info *front_info,
+ struct xen_drm_front_cfg *cfg)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(cfg->connectors); i++)
+ cfg_connector_free_edid(&cfg->connectors[i]);
+}
+
@@ -19,6 +19,7 @@ struct xen_drm_front_cfg_connector {
int width;
int height;
char *xenstore_path;
+ struct edid *edid;
};
struct xen_drm_front_cfg {
@@ -34,4 +35,10 @@ struct xen_drm_front_cfg {
int xen_drm_front_cfg_card(struct xen_drm_front_info *front_info,
struct xen_drm_front_cfg *cfg);
+int xen_drm_front_cfg_tail(struct xen_drm_front_info *front_info,
+ struct xen_drm_front_cfg *cfg);
+
+void xen_drm_front_cfg_free(struct xen_drm_front_info *front_info,
+ struct xen_drm_front_cfg *cfg);
+
#endif /* __XEN_DRM_FRONT_CFG_H_ */
@@ -66,6 +66,16 @@ static int connector_get_modes(struct drm_connector *connector)
struct videomode videomode;
int width, height;
+ if (pipeline->edid) {
+ int count;
+
+ drm_connector_update_edid_property(connector,
+ pipeline->edid);
+ count = drm_add_edid_modes(connector, pipeline->edid);
+ if (count)
+ return count;
+ }
+
mode = drm_mode_create(connector->dev);
if (!mode)
return 0;
@@ -103,6 +113,7 @@ int xen_drm_front_conn_init(struct xen_drm_front_drm_info *drm_info,
{
struct xen_drm_front_drm_pipeline *pipeline =
to_xen_drm_pipeline(connector);
+ int ret;
drm_connector_helper_add(connector, &connector_helper_funcs);
@@ -111,6 +122,17 @@ int xen_drm_front_conn_init(struct xen_drm_front_drm_info *drm_info,
connector->polled = DRM_CONNECTOR_POLL_CONNECT |
DRM_CONNECTOR_POLL_DISCONNECT;
- return drm_connector_init(drm_info->drm_dev, connector,
- &connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
+ ret = drm_connector_init(drm_info->drm_dev, connector,
+ &connector_funcs, DRM_MODE_CONNECTOR_VIRTUAL);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Virtual connectors do not have EDID property, but we do,
+ * so add it manually if EDID is present.
+ */
+ if (pipeline->edid)
+ drm_connector_attach_edid_property(connector);
+
+ return 0;
}
@@ -44,6 +44,10 @@ static irqreturn_t evtchnl_interrupt_ctrl(int irq, void *dev_id)
continue;
switch (resp->operation) {
+ case XENDISPL_OP_GET_EDID:
+ evtchnl->u.req.resp.get_edid =
+ resp->op.get_edid;
+ fallthrough;
case XENDISPL_OP_PG_FLIP:
case XENDISPL_OP_FB_ATTACH:
case XENDISPL_OP_FB_DETACH:
@@ -53,6 +53,9 @@ struct xen_drm_front_evtchnl {
struct completion completion;
/* latest response status */
int resp_status;
+ union {
+ struct xendispl_get_edid_resp get_edid;
+ } resp;
/* serializer for backend IO: request/response */
struct mutex req_io_lock;
} req;
@@ -288,6 +288,10 @@ display_mode_valid(struct drm_simple_display_pipe *pipe,
container_of(pipe, struct xen_drm_front_drm_pipeline,
pipe);
+ /* We have nothing to check if EDID is present. */
+ if (pipeline->edid)
+ return MODE_OK;
+
if (mode->hdisplay != pipeline->width)
return MODE_ERROR;
@@ -319,6 +323,7 @@ static int display_pipe_init(struct xen_drm_front_drm_info *drm_info,
pipeline->index = index;
pipeline->height = cfg->height;
pipeline->width = cfg->width;
+ pipeline->edid = cfg->edid;
INIT_DELAYED_WORK(&pipeline->pflip_to_worker, pflip_to_worker);