/* * Copyright 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "DrmPresenter.h" #include #include #include using android::base::guest::AutoReadLock; using android::base::guest::AutoWriteLock; using android::base::guest::ReadWriteLock; namespace android { bool DrmPresenter::init(const HotplugCallback& cb) { DEBUG_LOG("%s", __FUNCTION__); mHotplugCallback = cb; mFd = android::base::unique_fd(open("/dev/dri/card0", O_RDWR | O_CLOEXEC)); if (mFd < 0) { ALOGE("%s HWC2::Error opening DrmPresenter device: %d", __FUNCTION__, errno); return false; } int univRet = drmSetClientCap(mFd.get(), DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1); if (univRet) { ALOGE("%s: fail to set universal plane %d\n", __FUNCTION__, univRet); return false; } int atomicRet = drmSetClientCap(mFd.get(), DRM_CLIENT_CAP_ATOMIC, 1); if (atomicRet) { ALOGE("%s: fail to set atomic operation %d, %d\n", __FUNCTION__, atomicRet, errno); return false; } { AutoWriteLock lock(mStateMutex); bool initDrmRet = initDrmElementsLocked(); if (initDrmRet) { ALOGD("%s: Successfully initialized DRM backend", __FUNCTION__); } else { ALOGE("%s: Failed to initialize DRM backend", __FUNCTION__); return false; } } mDrmEventListener = sp::make(*this); if (mDrmEventListener->init()) { ALOGD("%s: Successfully initialized DRM event listener", __FUNCTION__); } else { ALOGE("%s: Failed to initialize DRM event listener", __FUNCTION__); } mDrmEventListener->run("", ANDROID_PRIORITY_URGENT_DISPLAY); return true; } bool DrmPresenter::initDrmElementsLocked() { drmModeRes* res; static const int32_t kUmPerInch = 25400; res = drmModeGetResources(mFd.get()); if (res == nullptr) { ALOGE("%s HWC2::Error reading drm resources: %d", __FUNCTION__, errno); mFd.reset(); return false; } ALOGD( "drmModeRes count fbs %d crtc %d connector %d encoder %d min w %d max w " "%d min h %d max h %d", res->count_fbs, res->count_crtcs, res->count_connectors, res->count_encoders, res->min_width, res->max_width, res->min_height, res->max_height); for (uint32_t i = 0; i < res->count_crtcs; i++) { DrmCrtc crtc = {}; drmModeCrtcPtr c = drmModeGetCrtc(mFd.get(), res->crtcs[i]); crtc.mId = c->crtc_id; drmModeObjectPropertiesPtr crtcProps = drmModeObjectGetProperties(mFd.get(), c->crtc_id, DRM_MODE_OBJECT_CRTC); for (uint32_t crtcPropsIndex = 0; crtcPropsIndex < crtcProps->count_props; crtcPropsIndex++) { drmModePropertyPtr crtcProp = drmModeGetProperty(mFd.get(), crtcProps->props[crtcPropsIndex]); if (!strcmp(crtcProp->name, "OUT_FENCE_PTR")) { crtc.mOutFencePtrPropertyId = crtcProp->prop_id; } else if (!strcmp(crtcProp->name, "ACTIVE")) { crtc.mActivePropertyId = crtcProp->prop_id; } else if (!strcmp(crtcProp->name, "MODE_ID")) { crtc.mModePropertyId = crtcProp->prop_id; } drmModeFreeProperty(crtcProp); } drmModeFreeObjectProperties(crtcProps); mCrtcs.push_back(crtc); } drmModePlaneResPtr planeRes = drmModeGetPlaneResources(mFd.get()); for (uint32_t i = 0; i < planeRes->count_planes; ++i) { DrmPlane plane = {}; drmModePlanePtr p = drmModeGetPlane(mFd.get(), planeRes->planes[i]); plane.mId = p->plane_id; ALOGD( "%s: plane id: %u crtcid %u fbid %u crtc xy %d %d xy %d %d " "possible ctrcs 0x%x", __FUNCTION__, p->plane_id, p->crtc_id, p->fb_id, p->crtc_x, p->crtc_y, p->x, p->y, p->possible_crtcs); drmModeObjectPropertiesPtr planeProps = drmModeObjectGetProperties(mFd.get(), plane.mId, DRM_MODE_OBJECT_PLANE); for (uint32_t planePropIndex = 0; planePropIndex < planeProps->count_props; ++planePropIndex) { drmModePropertyPtr planeProp = drmModeGetProperty(mFd.get(), planeProps->props[planePropIndex]); if (!strcmp(planeProp->name, "CRTC_ID")) { plane.mCrtcPropertyId = planeProp->prop_id; } else if (!strcmp(planeProp->name, "IN_FENCE_FD")) { plane.mInFenceFdPropertyId = planeProp->prop_id; } else if (!strcmp(planeProp->name, "FB_ID")) { plane.mFbPropertyId = planeProp->prop_id; } else if (!strcmp(planeProp->name, "CRTC_X")) { plane.mCrtcXPropertyId = planeProp->prop_id; } else if (!strcmp(planeProp->name, "CRTC_Y")) { plane.mCrtcYPropertyId = planeProp->prop_id; } else if (!strcmp(planeProp->name, "CRTC_W")) { plane.mCrtcWPropertyId = planeProp->prop_id; } else if (!strcmp(planeProp->name, "CRTC_H")) { plane.mCrtcHPropertyId = planeProp->prop_id; } else if (!strcmp(planeProp->name, "SRC_X")) { plane.mSrcXPropertyId = planeProp->prop_id; } else if (!strcmp(planeProp->name, "SRC_Y")) { plane.mSrcYPropertyId = planeProp->prop_id; } else if (!strcmp(planeProp->name, "SRC_W")) { plane.mSrcWPropertyId = planeProp->prop_id; } else if (!strcmp(planeProp->name, "SRC_H")) { plane.mSrcHPropertyId = planeProp->prop_id; } else if (!strcmp(planeProp->name, "type")) { plane.mTypePropertyId = planeProp->prop_id; uint64_t type = planeProp->values[0]; switch (type) { case DRM_PLANE_TYPE_OVERLAY: plane.mType = type; ALOGD("%s: plane %" PRIu32 " is DRM_PLANE_TYPE_OVERLAY", __FUNCTION__, plane.mId); break; case DRM_PLANE_TYPE_PRIMARY: plane.mType = type; ALOGD("%s: plane %" PRIu32 " is DRM_PLANE_TYPE_PRIMARY", __FUNCTION__, plane.mId); break; default: break; } } drmModeFreeProperty(planeProp); } drmModeFreeObjectProperties(planeProps); bool isPrimaryOrOverlay = plane.mType == DRM_PLANE_TYPE_OVERLAY || plane.mType == DRM_PLANE_TYPE_PRIMARY; if (isPrimaryOrOverlay) { for (uint32_t j = 0; j < mCrtcs.size(); j++) { if ((0x1 << j) & p->possible_crtcs) { ALOGD("%s: plane %" PRIu32 " compatible with crtc mask %" PRIu32, __FUNCTION__, plane.mId, p->possible_crtcs); if (mCrtcs[j].mPlaneId == -1) { mCrtcs[j].mPlaneId = plane.mId; ALOGD("%s: plane %" PRIu32 " associated with crtc %" PRIu32, __FUNCTION__, plane.mId, j); break; } } } } drmModeFreePlane(p); mPlanes[plane.mId] = plane; } drmModeFreePlaneResources(planeRes); for (uint32_t i = 0; i < res->count_connectors; ++i) { DrmConnector connector = {}; connector.mId = res->connectors[i]; { drmModeObjectPropertiesPtr connectorProps = drmModeObjectGetProperties( mFd.get(), connector.mId, DRM_MODE_OBJECT_CONNECTOR); for (uint32_t connectorPropIndex = 0; connectorPropIndex < connectorProps->count_props; ++connectorPropIndex) { drmModePropertyPtr connectorProp = drmModeGetProperty( mFd.get(), connectorProps->props[connectorPropIndex]); if (!strcmp(connectorProp->name, "CRTC_ID")) { connector.mCrtcPropertyId = connectorProp->prop_id; } else if (!strcmp(connectorProp->name, "EDID")) { connector.mEdidBlobId = connectorProps->prop_values[connectorPropIndex]; } drmModeFreeProperty(connectorProp); } drmModeFreeObjectProperties(connectorProps); } { drmModeConnector* c = drmModeGetConnector(mFd.get(), connector.mId); if (c == nullptr) { ALOGE("%s: Failed to get connector %" PRIu32 ": %d", __FUNCTION__, connector.mId, errno); return false; } connector.connection = c->connection; if (c->count_modes > 0) { memcpy(&connector.mMode, &c->modes[0], sizeof(drmModeModeInfo)); drmModeCreatePropertyBlob(mFd.get(), &connector.mMode, sizeof(connector.mMode), &connector.mModeBlobId); // Dots per 1000 inches connector.dpiX = c->mmWidth ? (c->modes[0].hdisplay * kUmPerInch) / (c->mmWidth) : -1; // Dots per 1000 inches connector.dpiY = c->mmHeight ? (c->modes[0].vdisplay * kUmPerInch) / (c->mmHeight) : -1; } ALOGD("%s connector %" PRIu32 " dpiX %" PRIi32 " dpiY %" PRIi32 " connection %d", __FUNCTION__, connector.mId, connector.dpiX, connector.dpiY, connector.connection); drmModeFreeConnector(c); connector.mRefreshRateAsFloat = 1000.0f * connector.mMode.clock / ((float)connector.mMode.vtotal * (float)connector.mMode.htotal); connector.mRefreshRateAsInteger = (uint32_t)(connector.mRefreshRateAsFloat + 0.5f); } mConnectors.push_back(connector); } drmModeFreeResources(res); return true; } void DrmPresenter::resetDrmElementsLocked() { for (auto& c : mConnectors) { if (c.mModeBlobId) { if (drmModeDestroyPropertyBlob(mFd.get(), c.mModeBlobId)) { ALOGE("%s: Error destroy PropertyBlob %" PRIu32, __func__, c.mModeBlobId); } } } mConnectors.clear(); mCrtcs.clear(); mPlanes.clear(); } int DrmPresenter::getDrmFB(hwc_drm_bo_t& bo) { int ret = drmPrimeFDToHandle(mFd.get(), bo.prime_fds[0], &bo.gem_handles[0]); if (ret) { ALOGE("%s: drmPrimeFDToHandle failed: %s (errno %d)", __FUNCTION__, strerror(errno), errno); return -1; } ret = drmModeAddFB2(mFd.get(), bo.width, bo.height, bo.format, bo.gem_handles, bo.pitches, bo.offsets, &bo.fb_id, 0); if (ret) { ALOGE("%s: drmModeAddFB2 failed: %s (errno %d)", __FUNCTION__, strerror(errno), errno); return -1; } return 0; } int DrmPresenter::clearDrmFB(hwc_drm_bo_t& bo) { int ret = 0; if (bo.fb_id) { if (drmModeRmFB(mFd.get(), bo.fb_id)) { ALOGE("%s: drmModeRmFB failed: %s (errno %d)", __FUNCTION__, strerror(errno), errno); } ret = -1; } if (bo.gem_handles[0]) { struct drm_gem_close gem_close = {}; gem_close.handle = bo.gem_handles[0]; if (drmIoctl(mFd.get(), DRM_IOCTL_GEM_CLOSE, &gem_close)) { ALOGE("%s: DRM_IOCTL_GEM_CLOSE failed: %s (errno %d)", __FUNCTION__, strerror(errno), errno); } ret = -1; } ALOGV("%s: drm FB %d", __FUNCTION__, bo.fb_id); return ret; } bool DrmPresenter::handleHotplug() { std::vector oldConnectors(mConnectors); { AutoReadLock lock(mStateMutex); oldConnectors.assign(mConnectors.begin(), mConnectors.end()); } { AutoWriteLock lock(mStateMutex); resetDrmElementsLocked(); if (!initDrmElementsLocked()) { ALOGE( "%s: failed to initialize drm elements during hotplug. Displays may " "not function correctly!", __FUNCTION__); return false; } } AutoReadLock lock(mStateMutex); for (int i = 0; i < mConnectors.size(); i++) { bool changed = oldConnectors[i].dpiX != mConnectors[i].dpiX || oldConnectors[i].dpiY != mConnectors[i].dpiY || oldConnectors[i].connection != mConnectors[i].connection || oldConnectors[i].mMode.hdisplay != mConnectors[i].mMode.hdisplay || oldConnectors[i].mMode.vdisplay != mConnectors[i].mMode.vdisplay; if (changed) { if (i == 0) { ALOGE( "%s: Ignoring changes to display:0 which is not configurable by " "multi-display interface.", __FUNCTION__); continue; } bool connected = mConnectors[i].connection == DRM_MODE_CONNECTED ? true : false; if (mHotplugCallback) { mHotplugCallback(connected, i, mConnectors[i].mMode.hdisplay, mConnectors[i].mMode.vdisplay, mConnectors[i].dpiX, mConnectors[i].dpiY, mConnectors[i].mRefreshRateAsInteger); } } } return true; } std::tuple DrmPresenter::flushToDisplay( int display, hwc_drm_bo_t& bo, base::borrowed_fd inSyncFd) { ATRACE_CALL(); AutoReadLock lock(mStateMutex); DrmConnector& connector = mConnectors[display]; DrmCrtc& crtc = mCrtcs[display]; HWC2::Error error = HWC2::Error::None; drmModeAtomicReqPtr pset = drmModeAtomicAlloc(); int ret; if (!crtc.mDidSetCrtc) { DEBUG_LOG("%s: Setting crtc.\n", __FUNCTION__); ret = drmModeAtomicAddProperty(pset, crtc.mId, crtc.mActivePropertyId, 1); if (ret < 0) { ALOGE("%s:%d: failed %d errno %d\n", __FUNCTION__, __LINE__, ret, errno); } ret = drmModeAtomicAddProperty(pset, crtc.mId, crtc.mModePropertyId, connector.mModeBlobId); if (ret < 0) { ALOGE("%s:%d: failed %d errno %d\n", __FUNCTION__, __LINE__, ret, errno); } ret = drmModeAtomicAddProperty(pset, connector.mId, connector.mCrtcPropertyId, crtc.mId); if (ret < 0) { ALOGE("%s:%d: failed %d errno %d\n", __FUNCTION__, __LINE__, ret, errno); } crtc.mDidSetCrtc = true; } else { DEBUG_LOG("%s: Already set crtc\n", __FUNCTION__); } int rawOutSyncFd; uint64_t outSyncFdUint = static_cast(reinterpret_cast(&rawOutSyncFd)); ret = drmModeAtomicAddProperty(pset, crtc.mId, crtc.mOutFencePtrPropertyId, outSyncFdUint); if (ret < 0) { ALOGE("%s:%d: set OUT_FENCE_PTR failed %d errno %d\n", __FUNCTION__, __LINE__, ret, errno); } if (crtc.mPlaneId == -1) { ALOGE("%s:%d: no plane available for crtc id %" PRIu32, __FUNCTION__, __LINE__, crtc.mId); return std::make_tuple(HWC2::Error::NoResources, base::unique_fd()); } DrmPlane& plane = mPlanes[crtc.mPlaneId]; DEBUG_LOG("%s: set plane: plane id %d crtc id %d fbid %d bo w h %d %d\n", __FUNCTION__, plane.mId, crtc.mId, bo.fb_id, bo.width, bo.height); ret = drmModeAtomicAddProperty(pset, plane.mId, plane.mCrtcPropertyId, crtc.mId); if (ret < 0) { ALOGE("%s:%d: failed %d errno %d\n", __FUNCTION__, __LINE__, ret, errno); } ret = drmModeAtomicAddProperty(pset, plane.mId, plane.mInFenceFdPropertyId, inSyncFd.get()); if (ret < 0) { ALOGE("%s:%d: set IN_FENCE_FD failed %d errno %d\n", __FUNCTION__, __LINE__, ret, errno); } ret = drmModeAtomicAddProperty(pset, plane.mId, plane.mFbPropertyId, bo.fb_id); if (ret < 0) { ALOGE("%s:%d: failed %d errno %d\n", __FUNCTION__, __LINE__, ret, errno); } ret = drmModeAtomicAddProperty(pset, plane.mId, plane.mCrtcXPropertyId, 0); if (ret < 0) { ALOGE("%s:%d: failed %d errno %d\n", __FUNCTION__, __LINE__, ret, errno); } ret = drmModeAtomicAddProperty(pset, plane.mId, plane.mCrtcYPropertyId, 0); if (ret < 0) { ALOGE("%s:%d: failed %d errno %d\n", __FUNCTION__, __LINE__, ret, errno); } ret = drmModeAtomicAddProperty(pset, plane.mId, plane.mCrtcWPropertyId, bo.width); if (ret < 0) { ALOGE("%s:%d: failed %d errno %d\n", __FUNCTION__, __LINE__, ret, errno); } ret = drmModeAtomicAddProperty(pset, plane.mId, plane.mCrtcHPropertyId, bo.height); if (ret < 0) { ALOGE("%s:%d: failed %d errno %d\n", __FUNCTION__, __LINE__, ret, errno); } ret = drmModeAtomicAddProperty(pset, plane.mId, plane.mSrcXPropertyId, 0); if (ret < 0) { ALOGE("%s:%d: failed %d errno %d\n", __FUNCTION__, __LINE__, ret, errno); } ret = drmModeAtomicAddProperty(pset, plane.mId, plane.mSrcYPropertyId, 0); if (ret < 0) { ALOGE("%s:%d: failed %d errno %d\n", __FUNCTION__, __LINE__, ret, errno); } ret = drmModeAtomicAddProperty(pset, plane.mId, plane.mSrcWPropertyId, bo.width << 16); if (ret < 0) { ALOGE("%s:%d: failed %d errno %d\n", __FUNCTION__, __LINE__, ret, errno); } ret = drmModeAtomicAddProperty(pset, plane.mId, plane.mSrcHPropertyId, bo.height << 16); if (ret < 0) { ALOGE("%s:%d: failed %d errno %d\n", __FUNCTION__, __LINE__, ret, errno); } uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET; ret = drmModeAtomicCommit(mFd.get(), pset, flags, 0); if (ret) { ALOGE("%s: Atomic commit failed with %d %d\n", __FUNCTION__, ret, errno); error = HWC2::Error::NoResources; } base::unique_fd outSyncFd(rawOutSyncFd); if (pset) { drmModeAtomicFree(pset); } DEBUG_LOG("%s: out fence: %d\n", __FUNCTION__, outSyncFd.get()); return std::make_tuple(error, std::move(outSyncFd)); } std::optional> DrmPresenter::getEdid(uint32_t id) { AutoReadLock lock(mStateMutex); if (mConnectors[id].mEdidBlobId == -1) { ALOGW("%s: EDID not supported", __func__); return std::nullopt; } drmModePropertyBlobPtr blob = drmModeGetPropertyBlob(mFd.get(), mConnectors[id].mEdidBlobId); if (!blob) { ALOGE("%s: fail to read EDID from DRM", __func__); return std::nullopt; } std::vector edid; uint8_t* start = static_cast(blob->data); edid.insert(edid.begin(), start, start + blob->length); drmModeFreePropertyBlob(blob); return edid; } DrmBuffer::DrmBuffer(const native_handle_t* handle, DrmPresenter* drmPresenter) : mDrmPresenter(drmPresenter), mBo({}) { if (!convertBoInfo(handle)) { mDrmPresenter->getDrmFB(mBo); } } DrmBuffer::~DrmBuffer() { mDrmPresenter->clearDrmFB(mBo); } int DrmBuffer::convertBoInfo(const native_handle_t* handle) { cros_gralloc_handle* gr_handle = (cros_gralloc_handle*)handle; if (!gr_handle) { ALOGE("%s: Null buffer handle", __FUNCTION__); return -1; } mBo.width = gr_handle->width; mBo.height = gr_handle->height; mBo.hal_format = gr_handle->droid_format; mBo.format = gr_handle->format; mBo.usage = gr_handle->usage; mBo.prime_fds[0] = gr_handle->fds[0]; mBo.pitches[0] = gr_handle->strides[0]; return 0; } std::tuple DrmBuffer::flushToDisplay( int display, base::borrowed_fd inWaitSyncFd) { return mDrmPresenter->flushToDisplay(display, mBo, inWaitSyncFd); } DrmPresenter::DrmEventListener::DrmEventListener(DrmPresenter& presenter) : mPresenter(presenter) {} DrmPresenter::DrmEventListener::~DrmEventListener() {} bool DrmPresenter::DrmEventListener::init() { mEventFd = android::base::unique_fd( socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT)); if (!mEventFd.ok()) { ALOGE("Failed to open uevent socket: %s", strerror(errno)); return false; } struct sockaddr_nl addr; memset(&addr, 0, sizeof(addr)); addr.nl_family = AF_NETLINK; addr.nl_pid = 0; addr.nl_groups = 0xFFFFFFFF; int ret = bind(mEventFd, (struct sockaddr*)&addr, sizeof(addr)); if (ret) { ALOGE("Failed to bind uevent socket: %s", strerror(errno)); return false; } FD_ZERO(&mMonitoredFds); FD_SET(mPresenter.mFd.get(), &mMonitoredFds); FD_SET(mEventFd.get(), &mMonitoredFds); mMaxFd = std::max(mPresenter.mFd.get(), mEventFd.get()); return true; } bool DrmPresenter::DrmEventListener::threadLoop() { int ret; do { ret = select(mMaxFd + 1, &mMonitoredFds, NULL, NULL, NULL); } while (ret == -1 && errno == EINTR); // if (FD_ISSET(mPresenter.mFd, &mFds)) { // TODO: handle drm related events // } if (FD_ISSET(mEventFd.get(), &mMonitoredFds)) { eventThreadLoop(); } return true; } void DrmPresenter::DrmEventListener::eventThreadLoop() { char buffer[1024]; int ret; struct timespec ts; uint64_t timestamp = 0; ret = clock_gettime(CLOCK_MONOTONIC, &ts); if (!ret) { timestamp = ts.tv_sec * 1000 * 1000 * 1000 + ts.tv_nsec; } else { ALOGE("Failed to get monotonic clock on hotplug %d", ret); } while (true) { ret = read(mEventFd.get(), &buffer, sizeof(buffer)); if (ret == 0) { return; } else if (ret < 0) { ALOGE("Got error reading uevent %d", ret); return; } bool drmEvent = false, hotplugEvent = false; for (int i = 0; i < ret;) { char* event = buffer + i; if (strcmp(event, "DEVTYPE=drm_minor")) { drmEvent = true; } else if (strcmp(event, "HOTPLUG=1")) { hotplugEvent = true; } i += strlen(event) + 1; } if (drmEvent && hotplugEvent) { processHotplug(timestamp); } } } void DrmPresenter::DrmEventListener::processHotplug(uint64_t timestamp) { ALOGD("DrmEventListener detected hotplug event %" PRIu64, timestamp); mPresenter.handleHotplug(); } } // namespace android