// SPDX-License-Identifier: GPL-2.0 OR MIT /* * Xen para-virtual DRM device * * Copyright (C) 2016-2018 EPAM Systems Inc. * * Author: Oleksandr Andrushchenko */ #include #include #include #include #include #include #include "xen_drm_front.h" #include "xen_drm_front_evtchnl.h" static irqreturn_t evtchnl_interrupt_ctrl(int irq, void *dev_id) { struct xen_drm_front_evtchnl *evtchnl = dev_id; struct xen_drm_front_info *front_info = evtchnl->front_info; struct xendispl_resp *resp; RING_IDX i, rp; unsigned long flags; if (unlikely(evtchnl->state != EVTCHNL_STATE_CONNECTED)) return IRQ_HANDLED; spin_lock_irqsave(&front_info->io_lock, flags); again: rp = evtchnl->u.req.ring.sring->rsp_prod; /* ensure we see queued responses up to rp */ virt_rmb(); for (i = evtchnl->u.req.ring.rsp_cons; i != rp; i++) { resp = RING_GET_RESPONSE(&evtchnl->u.req.ring, i); if (unlikely(resp->id != evtchnl->evt_id)) continue; switch (resp->operation) { case XENDISPL_OP_PG_FLIP: case XENDISPL_OP_FB_ATTACH: case XENDISPL_OP_FB_DETACH: case XENDISPL_OP_DBUF_CREATE: case XENDISPL_OP_DBUF_DESTROY: case XENDISPL_OP_SET_CONFIG: evtchnl->u.req.resp_status = resp->status; complete(&evtchnl->u.req.completion); break; default: DRM_ERROR("Operation %d is not supported\n", resp->operation); break; } } evtchnl->u.req.ring.rsp_cons = i; if (i != evtchnl->u.req.ring.req_prod_pvt) { int more_to_do; RING_FINAL_CHECK_FOR_RESPONSES(&evtchnl->u.req.ring, more_to_do); if (more_to_do) goto again; } else { evtchnl->u.req.ring.sring->rsp_event = i + 1; } spin_unlock_irqrestore(&front_info->io_lock, flags); return IRQ_HANDLED; } static irqreturn_t evtchnl_interrupt_evt(int irq, void *dev_id) { struct xen_drm_front_evtchnl *evtchnl = dev_id; struct xen_drm_front_info *front_info = evtchnl->front_info; struct xendispl_event_page *page = evtchnl->u.evt.page; u32 cons, prod; unsigned long flags; if (unlikely(evtchnl->state != EVTCHNL_STATE_CONNECTED)) return IRQ_HANDLED; spin_lock_irqsave(&front_info->io_lock, flags); prod = page->in_prod; /* ensure we see ring contents up to prod */ virt_rmb(); if (prod == page->in_cons) goto out; for (cons = page->in_cons; cons != prod; cons++) { struct xendispl_evt *event; event = &XENDISPL_IN_RING_REF(page, cons); if (unlikely(event->id != evtchnl->evt_id++)) continue; switch (event->type) { case XENDISPL_EVT_PG_FLIP: xen_drm_front_on_frame_done(front_info, evtchnl->index, event->op.pg_flip.fb_cookie); break; } } page->in_cons = cons; /* ensure ring contents */ virt_wmb(); out: spin_unlock_irqrestore(&front_info->io_lock, flags); return IRQ_HANDLED; } static void evtchnl_free(struct xen_drm_front_info *front_info, struct xen_drm_front_evtchnl *evtchnl) { unsigned long page = 0; if (evtchnl->type == EVTCHNL_TYPE_REQ) page = (unsigned long)evtchnl->u.req.ring.sring; else if (evtchnl->type == EVTCHNL_TYPE_EVT) page = (unsigned long)evtchnl->u.evt.page; if (!page) return; evtchnl->state = EVTCHNL_STATE_DISCONNECTED; if (evtchnl->type == EVTCHNL_TYPE_REQ) { /* release all who still waits for response if any */ evtchnl->u.req.resp_status = -EIO; complete_all(&evtchnl->u.req.completion); } if (evtchnl->irq) unbind_from_irqhandler(evtchnl->irq, evtchnl); if (evtchnl->port) xenbus_free_evtchn(front_info->xb_dev, evtchnl->port); /* end access and free the page */ if (evtchnl->gref != GRANT_INVALID_REF) gnttab_end_foreign_access(evtchnl->gref, 0, page); memset(evtchnl, 0, sizeof(*evtchnl)); } static int evtchnl_alloc(struct xen_drm_front_info *front_info, int index, struct xen_drm_front_evtchnl *evtchnl, enum xen_drm_front_evtchnl_type type) { struct xenbus_device *xb_dev = front_info->xb_dev; unsigned long page; grant_ref_t gref; irq_handler_t handler; int ret; memset(evtchnl, 0, sizeof(*evtchnl)); evtchnl->type = type; evtchnl->index = index; evtchnl->front_info = front_info; evtchnl->state = EVTCHNL_STATE_DISCONNECTED; evtchnl->gref = GRANT_INVALID_REF; page = get_zeroed_page(GFP_NOIO | __GFP_HIGH); if (!page) { ret = -ENOMEM; goto fail; } if (type == EVTCHNL_TYPE_REQ) { struct xen_displif_sring *sring; init_completion(&evtchnl->u.req.completion); mutex_init(&evtchnl->u.req.req_io_lock); sring = (struct xen_displif_sring *)page; SHARED_RING_INIT(sring); FRONT_RING_INIT(&evtchnl->u.req.ring, sring, XEN_PAGE_SIZE); ret = xenbus_grant_ring(xb_dev, sring, 1, &gref); if (ret < 0) { evtchnl->u.req.ring.sring = NULL; free_page(page); goto fail; } handler = evtchnl_interrupt_ctrl; } else { ret = gnttab_grant_foreign_access(xb_dev->otherend_id, virt_to_gfn((void *)page), 0); if (ret < 0) { free_page(page); goto fail; } evtchnl->u.evt.page = (struct xendispl_event_page *)page; gref = ret; handler = evtchnl_interrupt_evt; } evtchnl->gref = gref; ret = xenbus_alloc_evtchn(xb_dev, &evtchnl->port); if (ret < 0) goto fail; ret = bind_evtchn_to_irqhandler(evtchnl->port, handler, 0, xb_dev->devicetype, evtchnl); if (ret < 0) goto fail; evtchnl->irq = ret; return 0; fail: DRM_ERROR("Failed to allocate ring: %d\n", ret); return ret; } int xen_drm_front_evtchnl_create_all(struct xen_drm_front_info *front_info) { struct xen_drm_front_cfg *cfg; int ret, conn; cfg = &front_info->cfg; front_info->evt_pairs = kcalloc(cfg->num_connectors, sizeof(struct xen_drm_front_evtchnl_pair), GFP_KERNEL); if (!front_info->evt_pairs) { ret = -ENOMEM; goto fail; } for (conn = 0; conn < cfg->num_connectors; conn++) { ret = evtchnl_alloc(front_info, conn, &front_info->evt_pairs[conn].req, EVTCHNL_TYPE_REQ); if (ret < 0) { DRM_ERROR("Error allocating control channel\n"); goto fail; } ret = evtchnl_alloc(front_info, conn, &front_info->evt_pairs[conn].evt, EVTCHNL_TYPE_EVT); if (ret < 0) { DRM_ERROR("Error allocating in-event channel\n"); goto fail; } } front_info->num_evt_pairs = cfg->num_connectors; return 0; fail: xen_drm_front_evtchnl_free_all(front_info); return ret; } static int evtchnl_publish(struct xenbus_transaction xbt, struct xen_drm_front_evtchnl *evtchnl, const char *path, const char *node_ring, const char *node_chnl) { struct xenbus_device *xb_dev = evtchnl->front_info->xb_dev; int ret; /* write control channel ring reference */ ret = xenbus_printf(xbt, path, node_ring, "%u", evtchnl->gref); if (ret < 0) { xenbus_dev_error(xb_dev, ret, "writing ring-ref"); return ret; } /* write event channel ring reference */ ret = xenbus_printf(xbt, path, node_chnl, "%u", evtchnl->port); if (ret < 0) { xenbus_dev_error(xb_dev, ret, "writing event channel"); return ret; } return 0; } int xen_drm_front_evtchnl_publish_all(struct xen_drm_front_info *front_info) { struct xenbus_transaction xbt; struct xen_drm_front_cfg *plat_data; int ret, conn; plat_data = &front_info->cfg; again: ret = xenbus_transaction_start(&xbt); if (ret < 0) { xenbus_dev_fatal(front_info->xb_dev, ret, "starting transaction"); return ret; } for (conn = 0; conn < plat_data->num_connectors; conn++) { ret = evtchnl_publish(xbt, &front_info->evt_pairs[conn].req, plat_data->connectors[conn].xenstore_path, XENDISPL_FIELD_REQ_RING_REF, XENDISPL_FIELD_REQ_CHANNEL); if (ret < 0) goto fail; ret = evtchnl_publish(xbt, &front_info->evt_pairs[conn].evt, plat_data->connectors[conn].xenstore_path, XENDISPL_FIELD_EVT_RING_REF, XENDISPL_FIELD_EVT_CHANNEL); if (ret < 0) goto fail; } ret = xenbus_transaction_end(xbt, 0); if (ret < 0) { if (ret == -EAGAIN) goto again; xenbus_dev_fatal(front_info->xb_dev, ret, "completing transaction"); goto fail_to_end; } return 0; fail: xenbus_transaction_end(xbt, 1); fail_to_end: xenbus_dev_fatal(front_info->xb_dev, ret, "writing Xen store"); return ret; } void xen_drm_front_evtchnl_flush(struct xen_drm_front_evtchnl *evtchnl) { int notify; evtchnl->u.req.ring.req_prod_pvt++; RING_PUSH_REQUESTS_AND_CHECK_NOTIFY(&evtchnl->u.req.ring, notify); if (notify) notify_remote_via_irq(evtchnl->irq); } void xen_drm_front_evtchnl_set_state(struct xen_drm_front_info *front_info, enum xen_drm_front_evtchnl_state state) { unsigned long flags; int i; if (!front_info->evt_pairs) return; spin_lock_irqsave(&front_info->io_lock, flags); for (i = 0; i < front_info->num_evt_pairs; i++) { front_info->evt_pairs[i].req.state = state; front_info->evt_pairs[i].evt.state = state; } spin_unlock_irqrestore(&front_info->io_lock, flags); } void xen_drm_front_evtchnl_free_all(struct xen_drm_front_info *front_info) { int i; if (!front_info->evt_pairs) return; for (i = 0; i < front_info->num_evt_pairs; i++) { evtchnl_free(front_info, &front_info->evt_pairs[i].req); evtchnl_free(front_info, &front_info->evt_pairs[i].evt); } kfree(front_info->evt_pairs); front_info->evt_pairs = NULL; }