From: kettenis Date: Sat, 1 Jul 2017 16:14:10 +0000 (+0000) Subject: Update inteldrm(4) to code based on Linux 4.4.70. This brings us support for X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=3253c27b879535e81ed2c678559ce521afc6d720;p=openbsd Update inteldrm(4) to code based on Linux 4.4.70. This brings us support for Skylake and Cherryview and better support for Broadwell and Valleyview. Also adds MST support. Some tweaks to the TTM code and radeondrm(4) to keep it working with the updated generic DRM code needed for inteldrm(4). Tested by many. --- diff --git a/sys/dev/pci/drm/drm_irq.c b/sys/dev/pci/drm/drm_irq.c index 1f15e48cdeb..b323c3ca08f 100644 --- a/sys/dev/pci/drm/drm_irq.c +++ b/sys/dev/pci/drm/drm_irq.c @@ -1,6 +1,5 @@ -/** - * \file drm_irq.c - * IRQ support +/* + * drm_irq.c IRQ and vblank support * * \author Rickard E. (Rik) Faith * \author Gareth Hughes @@ -33,18 +32,21 @@ * OTHER DEALINGS IN THE SOFTWARE. */ -#include "drmP.h" +#include #include "drm_trace.h" +#include "drm_internal.h" -#ifdef DRM_VBLANK_DEBUG -#define VBL_DEBUG(x...) do { printf(x); } while(/* CONSTCOND */ 0) -#else -#define VBL_DEBUG(x...) do { } while(/* CONSTCOND */ 0) +#ifdef __linux__ +#include /* For task queue support */ +#include + +#include +#include #endif /* Access macro for slots in vblank timestamp ringbuffer. */ -#define vblanktimestamp(dev, crtc, count) \ - ((dev)->vblank[crtc].time[(count) % DRM_VBLANKTIME_RBSIZE]) +#define vblanktimestamp(dev, pipe, count) \ + ((dev)->vblank[pipe].time[(count) % DRM_VBLANKTIME_RBSIZE]) /* Retry timestamp calculation up to 3 times to satisfy * drm_timestamp_precision before giving up. @@ -56,49 +58,248 @@ */ #define DRM_REDUNDANT_VBLIRQ_THRESH_NS 1000000 -unsigned int drm_timestamp_precision = 20; /* Default to 20 usecs. */ -unsigned int drm_vblank_offdelay = 5000; /* Default to 5000 msecs. */ +static bool +drm_get_last_vbltimestamp(struct drm_device *dev, unsigned int pipe, + struct timeval *tvblank, unsigned flags); + +static unsigned int drm_timestamp_precision = 20; /* Default to 20 usecs. */ + /* * Default to use monotonic timestamps for wait-for-vblank and page-flip * complete events. */ unsigned int drm_timestamp_monotonic = 1; -#ifdef __linux__ +static int drm_vblank_offdelay = 5000; /* Default to 5000 msecs. */ + +module_param_named(vblankoffdelay, drm_vblank_offdelay, int, 0600); +module_param_named(timestamp_precision_usec, drm_timestamp_precision, int, 0600); +module_param_named(timestamp_monotonic, drm_timestamp_monotonic, int, 0600); + +static void store_vblank(struct drm_device *dev, unsigned int pipe, + u32 vblank_count_inc, + struct timeval *t_vblank, u32 last) +{ + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + u32 tslot; + + assert_spin_locked(&dev->vblank_time_lock); + + vblank->last = last; + + /* All writers hold the spinlock, but readers are serialized by + * the latching of vblank->count below. + */ + tslot = vblank->count + vblank_count_inc; + vblanktimestamp(dev, pipe, tslot) = *t_vblank; + + /* + * vblank timestamp updates are protected on the write side with + * vblank_time_lock, but on the read side done locklessly using a + * sequence-lock on the vblank counter. Ensure correct ordering using + * memory barrriers. We need the barrier both before and also after the + * counter update to synchronize with the next timestamp write. + * The read-side barriers for this are in drm_vblank_count_and_time. + */ + smp_wmb(); + vblank->count += vblank_count_inc; + smp_wmb(); +} + /** - * Get interrupt from bus id. + * drm_reset_vblank_timestamp - reset the last timestamp to the last vblank + * @dev: DRM device + * @pipe: index of CRTC for which to reset the timestamp * - * \param inode device inode. - * \param file_priv DRM file private. - * \param cmd command. - * \param arg user argument, pointing to a drm_irq_busid structure. - * \return zero on success or a negative number on failure. + * Reset the stored timestamp for the current vblank count to correspond + * to the last vblank occurred. + * + * Only to be called from drm_vblank_on(). * - * Finds the PCI device with the specified bus id and gets its IRQ number. - * This IOCTL is deprecated, and will now return EINVAL for any busid not equal - * to that of the device that this DRM instance attached to. + * Note: caller must hold dev->vbl_lock since this reads & writes + * device vblank fields. */ -int drm_irq_by_busid(struct drm_device *dev, void *data, - struct drm_file *file_priv) +static void drm_reset_vblank_timestamp(struct drm_device *dev, unsigned int pipe) { - struct drm_irq_busid *p = data; + u32 cur_vblank; + bool rc; + struct timeval t_vblank; + int count = DRM_TIMESTAMP_MAXRETRIES; - if (!dev->driver->bus->irq_by_busid) - return -EINVAL; + spin_lock(&dev->vblank_time_lock); - if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ)) - return -EINVAL; + /* + * sample the current counter to avoid random jumps + * when drm_vblank_enable() applies the diff + */ + do { + cur_vblank = dev->driver->get_vblank_counter(dev, pipe); + rc = drm_get_last_vbltimestamp(dev, pipe, &t_vblank, 0); + } while (cur_vblank != dev->driver->get_vblank_counter(dev, pipe) && --count > 0); + + /* + * Only reinitialize corresponding vblank timestamp if high-precision query + * available and didn't fail. Otherwise reinitialize delayed at next vblank + * interrupt and assign 0 for now, to mark the vblanktimestamp as invalid. + */ + if (!rc) + t_vblank = (struct timeval) {0, 0}; - return dev->driver->bus->irq_by_busid(dev, p); + /* + * +1 to make sure user will never see the same + * vblank counter value before and after a modeset + */ + store_vblank(dev, pipe, 1, &t_vblank, cur_vblank); + + spin_unlock(&dev->vblank_time_lock); } -#endif -/* - * Clear vblank timestamp buffer for a crtc. +/** + * drm_update_vblank_count - update the master vblank counter + * @dev: DRM device + * @pipe: counter to update + * + * Call back into the driver to update the appropriate vblank counter + * (specified by @pipe). Deal with wraparound, if it occurred, and + * update the last read value so we can deal with wraparound on the next + * call if necessary. + * + * Only necessary when going from off->on, to account for frames we + * didn't get an interrupt for. + * + * Note: caller must hold dev->vbl_lock since this reads & writes + * device vblank fields. */ -static void clear_vblank_timestamps(struct drm_device *dev, int crtc) +static void drm_update_vblank_count(struct drm_device *dev, unsigned int pipe, + unsigned long flags) { - memset(dev->vblank[crtc].time, 0, sizeof(dev->vblank[crtc].time)); + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + u32 cur_vblank, diff; + bool rc; + struct timeval t_vblank; + int count = DRM_TIMESTAMP_MAXRETRIES; + int framedur_ns = vblank->framedur_ns; + + /* + * Interrupts were disabled prior to this call, so deal with counter + * wrap if needed. + * NOTE! It's possible we lost a full dev->max_vblank_count + 1 events + * here if the register is small or we had vblank interrupts off for + * a long time. + * + * We repeat the hardware vblank counter & timestamp query until + * we get consistent results. This to prevent races between gpu + * updating its hardware counter while we are retrieving the + * corresponding vblank timestamp. + */ + do { + cur_vblank = dev->driver->get_vblank_counter(dev, pipe); + rc = drm_get_last_vbltimestamp(dev, pipe, &t_vblank, flags); + } while (cur_vblank != dev->driver->get_vblank_counter(dev, pipe) && --count > 0); + + if (dev->max_vblank_count != 0) { + /* trust the hw counter when it's around */ + diff = (cur_vblank - vblank->last) & dev->max_vblank_count; + } else if (rc && framedur_ns) { + const struct timeval *t_old; + u64 diff_ns; + + t_old = &vblanktimestamp(dev, pipe, vblank->count); + diff_ns = timeval_to_ns(&t_vblank) - timeval_to_ns(t_old); + + /* + * Figure out how many vblanks we've missed based + * on the difference in the timestamps and the + * frame/field duration. + */ + diff = DIV_ROUND_CLOSEST_ULL(diff_ns, framedur_ns); + + if (diff == 0 && flags & DRM_CALLED_FROM_VBLIRQ) + DRM_DEBUG_VBL("crtc %u: Redundant vblirq ignored." + " diff_ns = %lld, framedur_ns = %d)\n", + pipe, (long long) diff_ns, framedur_ns); + } else { + /* some kind of default for drivers w/o accurate vbl timestamping */ + diff = (flags & DRM_CALLED_FROM_VBLIRQ) != 0; + } + + /* + * Within a drm_vblank_pre_modeset - drm_vblank_post_modeset + * interval? If so then vblank irqs keep running and it will likely + * happen that the hardware vblank counter is not trustworthy as it + * might reset at some point in that interval and vblank timestamps + * are not trustworthy either in that interval. Iow. this can result + * in a bogus diff >> 1 which must be avoided as it would cause + * random large forward jumps of the software vblank counter. + */ + if (diff > 1 && (vblank->inmodeset & 0x2)) { + DRM_DEBUG_VBL("clamping vblank bump to 1 on crtc %u: diffr=%u" + " due to pre-modeset.\n", pipe, diff); + diff = 1; + } + + /* + * FIMXE: Need to replace this hack with proper seqlocks. + * + * Restrict the bump of the software vblank counter to a safe maximum + * value of +1 whenever there is the possibility that concurrent readers + * of vblank timestamps could be active at the moment, as the current + * implementation of the timestamp caching and updating is not safe + * against concurrent readers for calls to store_vblank() with a bump + * of anything but +1. A bump != 1 would very likely return corrupted + * timestamps to userspace, because the same slot in the cache could + * be concurrently written by store_vblank() and read by one of those + * readers without the read-retry logic detecting the collision. + * + * Concurrent readers can exist when we are called from the + * drm_vblank_off() or drm_vblank_on() functions and other non-vblank- + * irq callers. However, all those calls to us are happening with the + * vbl_lock locked to prevent drm_vblank_get(), so the vblank refcount + * can't increase while we are executing. Therefore a zero refcount at + * this point is safe for arbitrary counter bumps if we are called + * outside vblank irq, a non-zero count is not 100% safe. Unfortunately + * we must also accept a refcount of 1, as whenever we are called from + * drm_vblank_get() -> drm_vblank_enable() the refcount will be 1 and + * we must let that one pass through in order to not lose vblank counts + * during vblank irq off - which would completely defeat the whole + * point of this routine. + * + * Whenever we are called from vblank irq, we have to assume concurrent + * readers exist or can show up any time during our execution, even if + * the refcount is currently zero, as vblank irqs are usually only + * enabled due to the presence of readers, and because when we are called + * from vblank irq we can't hold the vbl_lock to protect us from sudden + * bumps in vblank refcount. Therefore also restrict bumps to +1 when + * called from vblank irq. + */ + if ((diff > 1) && (atomic_read(&vblank->refcount) > 1 || + (flags & DRM_CALLED_FROM_VBLIRQ))) { + DRM_DEBUG_VBL("clamping vblank bump to 1 on crtc %u: diffr=%u " + "refcount %u, vblirq %u\n", pipe, diff, + atomic_read(&vblank->refcount), + (flags & DRM_CALLED_FROM_VBLIRQ) != 0); + diff = 1; + } + + DRM_DEBUG_VBL("updating vblank count on crtc %u:" + " current=%u, diff=%u, hw=%u hw_last=%u\n", + pipe, vblank->count, diff, cur_vblank, vblank->last); + + if (diff == 0) { + WARN_ON_ONCE(cur_vblank != vblank->last); + return; + } + + /* + * Only reinitialize corresponding vblank timestamp if high-precision query + * available and didn't fail, or we were called from the vblank interrupt. + * Otherwise reinitialize delayed at next vblank interrupt and assign 0 + * for now, to mark the vblanktimestamp as invalid. + */ + if (!rc && (flags & DRM_CALLED_FROM_VBLIRQ) == 0) + t_vblank = (struct timeval) {0, 0}; + + store_vblank(dev, pipe, diff, &t_vblank, cur_vblank); } /* @@ -107,14 +308,10 @@ static void clear_vblank_timestamps(struct drm_device *dev, int crtc) * are preserved, even if there are any spurious vblank irq's after * disable. */ -static void vblank_disable_and_save(struct drm_device *dev, int crtc) +static void vblank_disable_and_save(struct drm_device *dev, unsigned int pipe) { + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; unsigned long irqflags; - u32 vblcount; - s64 diff_ns; - int vblrc; - struct timeval tvblank; - int count = DRM_TIMESTAMP_MAXRETRIES; /* Prevent vblank irq processing while disabling vblank irqs, * so no updates of timestamps or count can happen after we've @@ -122,88 +319,67 @@ static void vblank_disable_and_save(struct drm_device *dev, int crtc) */ spin_lock_irqsave(&dev->vblank_time_lock, irqflags); - dev->driver->disable_vblank(dev, crtc); - dev->vblank[crtc].enabled = false; - - /* No further vblank irq's will be processed after - * this point. Get current hardware vblank count and - * vblank timestamp, repeat until they are consistent. - * - * FIXME: There is still a race condition here and in - * drm_update_vblank_count() which can cause off-by-one - * reinitialization of software vblank counter. If gpu - * vblank counter doesn't increment exactly at the leading - * edge of a vblank interval, then we can lose 1 count if - * we happen to execute between start of vblank and the - * delayed gpu counter increment. - */ - do { - dev->vblank[crtc].last = dev->driver->get_vblank_counter(dev, crtc); - vblrc = drm_get_last_vbltimestamp(dev, crtc, &tvblank, 0); - } while (dev->vblank[crtc].last != dev->driver->get_vblank_counter(dev, crtc) && (--count) && vblrc); - - if (!count) - vblrc = 0; - - /* Compute time difference to stored timestamp of last vblank - * as updated by last invocation of drm_handle_vblank() in vblank irq. - */ - vblcount = atomic_read(&dev->vblank[crtc].count); - diff_ns = timeval_to_ns(&tvblank) - - timeval_to_ns(&vblanktimestamp(dev, crtc, vblcount)); - - /* If there is at least 1 msec difference between the last stored - * timestamp and tvblank, then we are currently executing our - * disable inside a new vblank interval, the tvblank timestamp - * corresponds to this new vblank interval and the irq handler - * for this vblank didn't run yet and won't run due to our disable. - * Therefore we need to do the job of drm_handle_vblank() and - * increment the vblank counter by one to account for this vblank. - * - * Skip this step if there isn't any high precision timestamp - * available. In that case we can't account for this and just - * hope for the best. + /* + * Only disable vblank interrupts if they're enabled. This avoids + * calling the ->disable_vblank() operation in atomic context with the + * hardware potentially runtime suspended. */ - if ((vblrc > 0) && (abs64(diff_ns) > 1000000)) { - atomic_inc(&dev->vblank[crtc].count); - smp_mb__after_atomic_inc(); + if (vblank->enabled) { + dev->driver->disable_vblank(dev, pipe); + vblank->enabled = false; } - /* Invalidate all timestamps while vblank irq's are off. */ - clear_vblank_timestamps(dev, crtc); + /* + * Always update the count and timestamp to maintain the + * appearance that the counter has been ticking all along until + * this time. This makes the count account for the entire time + * between drm_vblank_on() and drm_vblank_off(). + */ + drm_update_vblank_count(dev, pipe, 0); spin_unlock_irqrestore(&dev->vblank_time_lock, irqflags); } static void vblank_disable_fn(unsigned long arg) { - struct drm_device *dev = (struct drm_device *)arg; + struct drm_vblank_crtc *vblank = (void *)arg; + struct drm_device *dev = vblank->dev; + unsigned int pipe = vblank->pipe; unsigned long irqflags; - int i; if (!dev->vblank_disable_allowed) return; - for (i = 0; i < dev->num_crtcs; i++) { - spin_lock_irqsave(&dev->vbl_lock, irqflags); - if (atomic_read(&dev->vblank[i].refcount) == 0 && - dev->vblank[i].enabled) { - DRM_DEBUG("disabling vblank on crtc %d\n", i); - vblank_disable_and_save(dev, i); - } - spin_unlock_irqrestore(&dev->vbl_lock, irqflags); + spin_lock_irqsave(&dev->vbl_lock, irqflags); + if (atomic_read(&vblank->refcount) == 0 && vblank->enabled) { + DRM_DEBUG("disabling vblank on crtc %u\n", pipe); + vblank_disable_and_save(dev, pipe); } + spin_unlock_irqrestore(&dev->vbl_lock, irqflags); } +/** + * drm_vblank_cleanup - cleanup vblank support + * @dev: DRM device + * + * This function cleans up any resources allocated in drm_vblank_init. + */ void drm_vblank_cleanup(struct drm_device *dev) { + unsigned int pipe; + /* Bail if the driver didn't call drm_vblank_init() */ if (dev->num_crtcs == 0) return; - del_timer_sync(&dev->vblank_disable_timer); + for (pipe = 0; pipe < dev->num_crtcs; pipe++) { + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + + WARN_ON(vblank->enabled && + drm_core_check_feature(dev, DRIVER_MODESET)); - vblank_disable_fn((unsigned long)dev); + del_timer_sync(&vblank->disable_timer); + } kfree(dev->vblank); @@ -211,12 +387,21 @@ void drm_vblank_cleanup(struct drm_device *dev) } EXPORT_SYMBOL(drm_vblank_cleanup); -int drm_vblank_init(struct drm_device *dev, int num_crtcs) +/** + * drm_vblank_init - initialize vblank support + * @dev: DRM device + * @num_crtcs: number of CRTCs supported by @dev + * + * This function initializes vblank support for @num_crtcs display pipelines. + * + * Returns: + * Zero on success or a negative error code on failure. + */ +int drm_vblank_init(struct drm_device *dev, unsigned int num_crtcs) { - int i, ret = -ENOMEM; + int ret = -ENOMEM; + unsigned int i; - setup_timer(&dev->vblank_disable_timer, vblank_disable_fn, - (unsigned long)dev); mtx_init(&dev->vbl_lock, IPL_TTY); mtx_init(&dev->vblank_time_lock, IPL_TTY); @@ -226,8 +411,15 @@ int drm_vblank_init(struct drm_device *dev, int num_crtcs) if (!dev->vblank) goto err; - for (i = 0; i < num_crtcs; i++) - init_waitqueue_head(&dev->vblank[i].queue); + for (i = 0; i < num_crtcs; i++) { + struct drm_vblank_crtc *vblank = &dev->vblank[i]; + + vblank->dev = dev; + vblank->pipe = i; + init_waitqueue_head(&vblank->queue); + setup_timer(&vblank->disable_timer, vblank_disable_fn, + (unsigned long)vblank); + } DRM_INFO("Supports vblank timestamp caching Rev 2 (21.10.2013).\n"); @@ -237,12 +429,19 @@ int drm_vblank_init(struct drm_device *dev, int num_crtcs) else DRM_INFO("No driver support for vblank timestamp query.\n"); + /* Must have precise timestamping for reliable vblank instant disable */ + if (dev->vblank_disable_immediate && !dev->driver->get_vblank_timestamp) { + dev->vblank_disable_immediate = false; + DRM_INFO("Setting vblank_disable_immediate to false because " + "get_vblank_timestamp == NULL\n"); + } + dev->vblank_disable_allowed = false; return 0; err: - drm_vblank_cleanup(dev); + dev->num_crtcs = 0; return ret; } EXPORT_SYMBOL(drm_vblank_init); @@ -273,44 +472,44 @@ static void drm_irq_vgaarb_nokms(void *cookie, bool state) #endif /** - * Install IRQ handler. - * - * \param dev DRM device. + * drm_irq_install - install IRQ handler + * @dev: DRM device + * @irq: IRQ number to install the handler for * * Initializes the IRQ related data. Installs the handler, calling the driver - * \c irq_preinstall() and \c irq_postinstall() functions - * before and after the installation. + * irq_preinstall() and irq_postinstall() functions before and after the + * installation. + * + * This is the simplified helper interface provided for drivers with no special + * needs. Drivers which need to install interrupt handlers for multiple + * interrupts must instead set drm_device->irq_enabled to signal the DRM core + * that vblank interrupts are available. + * + * Returns: + * Zero on success or a negative error code on failure. */ -int drm_irq_install(struct drm_device *dev) +int drm_irq_install(struct drm_device *dev, int irq) { int ret; #ifdef __linux__ unsigned long sh_flags = 0; - char *irqname; #endif if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ)) return -EINVAL; - if (drm_dev_to_irq(dev) == 0) + if (irq == 0) return -EINVAL; - mutex_lock(&dev->struct_mutex); - /* Driver must have been initialized */ - if (!dev->dev_private) { - mutex_unlock(&dev->struct_mutex); + if (!dev->dev_private) return -EINVAL; - } - if (dev->irq_enabled) { - mutex_unlock(&dev->struct_mutex); + if (dev->irq_enabled) return -EBUSY; - } dev->irq_enabled = true; - mutex_unlock(&dev->struct_mutex); - DRM_DEBUG("irq=%d\n", drm_dev_to_irq(dev)); + DRM_DEBUG("irq=%d\n", irq); /* Before installing handler */ if (dev->driver->irq_preinstall) @@ -321,18 +520,11 @@ int drm_irq_install(struct drm_device *dev) if (drm_core_check_feature(dev, DRIVER_IRQ_SHARED)) sh_flags = IRQF_SHARED; - if (dev->devname) - irqname = dev->devname; - else - irqname = dev->driver->name; - - ret = request_irq(drm_dev_to_irq(dev), dev->driver->irq_handler, - sh_flags, irqname, dev); + ret = request_irq(irq, dev->driver->irq_handler, + sh_flags, dev->driver->name, dev); if (ret < 0) { - mutex_lock(&dev->struct_mutex); dev->irq_enabled = false; - mutex_unlock(&dev->struct_mutex); return ret; } @@ -345,14 +537,14 @@ int drm_irq_install(struct drm_device *dev) ret = dev->driver->irq_postinstall(dev); if (ret < 0) { - mutex_lock(&dev->struct_mutex); dev->irq_enabled = false; - mutex_unlock(&dev->struct_mutex); #ifdef __linux__ if (!drm_core_check_feature(dev, DRIVER_MODESET)) vga_client_register(dev->pdev, NULL, NULL, NULL); - free_irq(drm_dev_to_irq(dev), dev); + free_irq(irq, dev); #endif + } else { + dev->irq = irq; } return ret; @@ -360,11 +552,20 @@ int drm_irq_install(struct drm_device *dev) EXPORT_SYMBOL(drm_irq_install); /** - * Uninstall the IRQ handler. + * drm_irq_uninstall - uninstall the IRQ handler + * @dev: DRM device + * + * Calls the driver's irq_uninstall() function and unregisters the IRQ handler. + * This should only be called by drivers which used drm_irq_install() to set up + * their interrupt handler. Other drivers must only reset + * drm_device->irq_enabled to false. * - * \param dev DRM device. + * Note that for kernel modesetting drivers it is a bug if this function fails. + * The sanity checks are only to catch buggy user modesetting drivers which call + * the same function through an ioctl. * - * Calls the driver's \c irq_uninstall() function, and stops the irq. + * Returns: + * Zero on success or a negative error code on failure. */ int drm_irq_uninstall(struct drm_device *dev) { @@ -375,21 +576,27 @@ int drm_irq_uninstall(struct drm_device *dev) if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ)) return -EINVAL; - mutex_lock(&dev->struct_mutex); irq_enabled = dev->irq_enabled; dev->irq_enabled = false; - mutex_unlock(&dev->struct_mutex); /* - * Wake up any waiters so they don't hang. + * Wake up any waiters so they don't hang. This is just to paper over + * isssues for UMS drivers which aren't in full control of their + * vblank/irq handling. KMS drivers must ensure that vblanks are all + * disabled when uninstalling the irq handler. */ if (dev->num_crtcs) { spin_lock_irqsave(&dev->vbl_lock, irqflags); for (i = 0; i < dev->num_crtcs; i++) { - wake_up(&dev->vblank[i].queue); - dev->vblank[i].enabled = false; - dev->vblank[i].last = - dev->driver->get_vblank_counter(dev, i); + struct drm_vblank_crtc *vblank = &dev->vblank[i]; + + if (!vblank->enabled) + continue; + + WARN_ON(drm_core_check_feature(dev, DRIVER_MODESET)); + + vblank_disable_and_save(dev, i); + wake_up(&vblank->queue); } spin_unlock_irqrestore(&dev->vbl_lock, irqflags); } @@ -397,7 +604,7 @@ int drm_irq_uninstall(struct drm_device *dev) if (!irq_enabled) return -EINVAL; - DRM_DEBUG("irq=%d\n", drm_dev_to_irq(dev)); + DRM_DEBUG("irq=%d\n", dev->irq); #ifdef __linux__ if (!drm_core_check_feature(dev, DRIVER_MODESET)) @@ -408,15 +615,14 @@ int drm_irq_uninstall(struct drm_device *dev) dev->driver->irq_uninstall(dev); #ifdef __linux__ - free_irq(drm_dev_to_irq(dev), dev); + free_irq(dev->irq, dev); #endif return 0; } EXPORT_SYMBOL(drm_irq_uninstall); -#ifdef __linux__ -/** +/* * IRQ control ioctl. * * \param inode device inode. @@ -431,62 +637,78 @@ int drm_control(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_control *ctl = data; + int ret = 0, irq; /* if we haven't irq we fallback for compatibility reasons - * this used to be a separate function in drm_dma.h */ + if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ)) + return 0; + if (drm_core_check_feature(dev, DRIVER_MODESET)) + return 0; + /* UMS was only ever support on pci devices. */ + if (WARN_ON(!dev->pdev)) + return -EINVAL; switch (ctl->func) { case DRM_INST_HANDLER: - if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ)) - return 0; - if (drm_core_check_feature(dev, DRIVER_MODESET)) - return 0; + irq = dev->pdev->irq; + if (dev->if_version < DRM_IF_VERSION(1, 2) && - ctl->irq != drm_dev_to_irq(dev)) + ctl->irq != irq) return -EINVAL; - return drm_irq_install(dev); + mutex_lock(&dev->struct_mutex); + ret = drm_irq_install(dev, irq); + mutex_unlock(&dev->struct_mutex); + + return ret; case DRM_UNINST_HANDLER: - if (!drm_core_check_feature(dev, DRIVER_HAVE_IRQ)) - return 0; - if (drm_core_check_feature(dev, DRIVER_MODESET)) - return 0; - return drm_irq_uninstall(dev); + mutex_lock(&dev->struct_mutex); + ret = drm_irq_uninstall(dev); + mutex_unlock(&dev->struct_mutex); + + return ret; default: return -EINVAL; } } -#endif /** - * drm_calc_timestamping_constants - Calculate vblank timestamp constants - * - * @crtc drm_crtc whose timestamp constants should be updated. - * @mode display mode containing the scanout timings + * drm_calc_timestamping_constants - calculate vblank timestamp constants + * @crtc: drm_crtc whose timestamp constants should be updated. + * @mode: display mode containing the scanout timings * * Calculate and store various constants which are later * needed by vblank and swap-completion timestamping, e.g, * by drm_calc_vbltimestamp_from_scanoutpos(). They are - * derived from crtc's true scanout timing, so they take + * derived from CRTC's true scanout timing, so they take * things like panel scaling or other adjustments into account. */ void drm_calc_timestamping_constants(struct drm_crtc *crtc, const struct drm_display_mode *mode) { - int linedur_ns = 0, pixeldur_ns = 0, framedur_ns = 0; + struct drm_device *dev = crtc->dev; + unsigned int pipe = drm_crtc_index(crtc); + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + int linedur_ns = 0, framedur_ns = 0; int dotclock = mode->crtc_clock; + if (!dev->num_crtcs) + return; + + if (WARN_ON(pipe >= dev->num_crtcs)) + return; + /* Valid dotclock? */ if (dotclock > 0) { int frame_size = mode->crtc_htotal * mode->crtc_vtotal; /* * Convert scanline length in pixels and video - * dot clock to line duration, frame duration - * and pixel duration in nanoseconds: + * dot clock to line duration and frame duration + * in nanoseconds: */ - pixeldur_ns = 1000000 / dotclock; linedur_ns = div_u64((u64) mode->crtc_htotal * 1000000, dotclock); framedur_ns = div_u64((u64) frame_size * 1000000, dotclock); @@ -496,28 +718,36 @@ void drm_calc_timestamping_constants(struct drm_crtc *crtc, if (mode->flags & DRM_MODE_FLAG_INTERLACE) framedur_ns /= 2; } else - DRM_ERROR("crtc %d: Can't calculate constants, dotclock = 0!\n", + DRM_ERROR("crtc %u: Can't calculate constants, dotclock = 0!\n", crtc->base.id); - crtc->pixeldur_ns = pixeldur_ns; - crtc->linedur_ns = linedur_ns; - crtc->framedur_ns = framedur_ns; + vblank->linedur_ns = linedur_ns; + vblank->framedur_ns = framedur_ns; - DRM_DEBUG("crtc %d: hwmode: htotal %d, vtotal %d, vdisplay %d\n", + DRM_DEBUG("crtc %u: hwmode: htotal %d, vtotal %d, vdisplay %d\n", crtc->base.id, mode->crtc_htotal, mode->crtc_vtotal, mode->crtc_vdisplay); - DRM_DEBUG("crtc %d: clock %d kHz framedur %d linedur %d, pixeldur %d\n", - crtc->base.id, dotclock, framedur_ns, - linedur_ns, pixeldur_ns); + DRM_DEBUG("crtc %u: clock %d kHz framedur %d linedur %d\n", + crtc->base.id, dotclock, framedur_ns, linedur_ns); } EXPORT_SYMBOL(drm_calc_timestamping_constants); /** - * drm_calc_vbltimestamp_from_scanoutpos - helper routine for kms - * drivers. Implements calculation of exact vblank timestamps from - * given drm_display_mode timings and current video scanout position - * of a crtc. This can be called from within get_vblank_timestamp() - * implementation of a kms driver to implement the actual timestamping. + * drm_calc_vbltimestamp_from_scanoutpos - precise vblank timestamp helper + * @dev: DRM device + * @pipe: index of CRTC whose vblank timestamp to retrieve + * @max_error: Desired maximum allowable error in timestamps (nanosecs) + * On return contains true maximum error of timestamp + * @vblank_time: Pointer to struct timeval which should receive the timestamp + * @flags: Flags to pass to driver: + * 0 = Default, + * DRM_CALLED_FROM_VBLIRQ = If function is called from vbl IRQ handler + * @mode: mode which defines the scanout timings + * + * Implements calculation of exact vblank timestamps from given drm_display_mode + * timings and current video scanout position of a CRTC. This can be called from + * within get_vblank_timestamp() implementation of a kms driver to implement the + * actual timestamping. * * Should return timestamps conforming to the OML_sync_control OpenML * extension specification. The timestamp corresponds to the end of @@ -532,21 +762,11 @@ EXPORT_SYMBOL(drm_calc_timestamping_constants); * returns as no operation if a doublescan or interlaced video mode is * active. Higher level code is expected to handle this. * - * @dev: DRM device. - * @crtc: Which crtc's vblank timestamp to retrieve. - * @max_error: Desired maximum allowable error in timestamps (nanosecs). - * On return contains true maximum error of timestamp. - * @vblank_time: Pointer to struct timeval which should receive the timestamp. - * @flags: Flags to pass to driver: - * 0 = Default. - * DRM_CALLED_FROM_VBLIRQ = If function is called from vbl irq handler. - * @refcrtc: drm_crtc* of crtc which defines scanout timing. - * @mode: mode which defines the scanout timings - * - * Returns negative value on error, failure or if not supported in current + * Returns: + * Negative value on error, failure or if not supported in current * video mode: * - * -EINVAL - Invalid crtc. + * -EINVAL - Invalid CRTC. * -EAGAIN - Temporary unavailable, e.g., called before initial modeset. * -ENOTSUPP - Function not supported in current display mode. * -EIO - Failed, e.g., due to failed scanout position query. @@ -557,22 +777,22 @@ EXPORT_SYMBOL(drm_calc_timestamping_constants); * DRM_VBLANKTIME_INVBL - Timestamp taken while scanout was in vblank interval. * */ -int drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, int crtc, +int drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, + unsigned int pipe, int *max_error, struct timeval *vblank_time, unsigned flags, - const struct drm_crtc *refcrtc, const struct drm_display_mode *mode) { - ktime_t stime, etime, mono_time_offset; struct timeval tv_etime; - int vbl_status; + ktime_t stime, etime; + unsigned int vbl_status; + int ret = DRM_VBLANKTIME_SCANOUTPOS_METHOD; int vpos, hpos, i; - int framedur_ns, linedur_ns, pixeldur_ns, delta_ns, duration_ns; - bool invbl; + int delta_ns, duration_ns; - if (crtc < 0 || crtc >= dev->num_crtcs) { - DRM_ERROR("Invalid crtc %d\n", crtc); + if (pipe >= dev->num_crtcs) { + DRM_ERROR("Invalid crtc %u\n", pipe); return -EINVAL; } @@ -582,16 +802,11 @@ int drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, int crtc, return -EIO; } - /* Durations of frames, lines, pixels in nanoseconds. */ - framedur_ns = refcrtc->framedur_ns; - linedur_ns = refcrtc->linedur_ns; - pixeldur_ns = refcrtc->pixeldur_ns; - /* If mode timing undefined, just return as no-op: * Happens during initial modesetting of a crtc. */ - if (framedur_ns == 0) { - DRM_DEBUG("crtc %d: Noop due to uninitialized mode.\n", crtc); + if (mode->crtc_clock == 0) { + DRM_DEBUG("crtc %u: Noop due to uninitialized mode.\n", pipe); return -EAGAIN; } @@ -607,20 +822,15 @@ int drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, int crtc, * Get vertical and horizontal scanout position vpos, hpos, * and bounding timestamps stime, etime, pre/post query. */ - vbl_status = dev->driver->get_scanout_position(dev, crtc, flags, &vpos, - &hpos, &stime, &etime); - - /* - * Get correction for CLOCK_MONOTONIC -> CLOCK_REALTIME if - * CLOCK_REALTIME is requested. - */ - if (!drm_timestamp_monotonic) - mono_time_offset = ktime_get_monotonic_offset(); + vbl_status = dev->driver->get_scanout_position(dev, pipe, flags, + &vpos, &hpos, + &stime, &etime, + mode); /* Return as no-op if scanout query unsupported or failed. */ if (!(vbl_status & DRM_SCANOUTPOS_VALID)) { - DRM_DEBUG("crtc %d : scanoutpos query failed [%d].\n", - crtc, vbl_status); + DRM_DEBUG("crtc %u : scanoutpos query failed [0x%x].\n", + pipe, vbl_status); return -EIO; } @@ -634,8 +844,8 @@ int drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, int crtc, /* Noisy system timing? */ if (i == DRM_TIMESTAMP_MAXRETRIES) { - DRM_DEBUG("crtc %d: Noisy timestamp %d us > %d us [%d reps].\n", - crtc, duration_ns/1000, *max_error/1000, i); + DRM_DEBUG("crtc %u: Noisy timestamp %d us > %d us [%d reps].\n", + pipe, duration_ns/1000, *max_error/1000, i); } /* Return upper bound of timestamp precision error. */ @@ -646,16 +856,18 @@ int drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, int crtc, * within vblank area, counting down the number of lines until * start of scanout. */ - invbl = vbl_status & DRM_SCANOUTPOS_INVBL; + if (vbl_status & DRM_SCANOUTPOS_IN_VBLANK) + ret |= DRM_VBLANKTIME_IN_VBLANK; /* Convert scanout position into elapsed time at raw_time query * since start of scanout at first display scanline. delta_ns * can be negative if start of scanout hasn't happened yet. */ - delta_ns = vpos * linedur_ns + hpos * pixeldur_ns; + delta_ns = div_s64(1000000LL * (vpos * mode->crtc_htotal + hpos), + mode->crtc_clock); if (!drm_timestamp_monotonic) - etime = ktime_sub(etime, mono_time_offset); + etime = ktime_mono_to_real(etime); /* save this only for debugging purposes */ tv_etime = ktime_to_timeval(etime); @@ -668,17 +880,13 @@ int drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, int crtc, etime = ktime_sub_ns(etime, delta_ns); *vblank_time = ktime_to_timeval(etime); - VBL_DEBUG("crtc %d : v %d p(%d,%d)@ %ld.%ld -> %ld.%ld [e %d us, %d rep]\n", - crtc, (int)vbl_status, hpos, vpos, - (long)tv_etime.tv_sec, (long)tv_etime.tv_usec, - (long)vblank_time->tv_sec, (long)vblank_time->tv_usec, - duration_ns/1000, i); + DRM_DEBUG_VBL("crtc %u : v 0x%x p(%d,%d)@ %ld.%ld -> %ld.%ld [e %d us, %d rep]\n", + pipe, vbl_status, hpos, vpos, + (long)tv_etime.tv_sec, (long)tv_etime.tv_usec, + (long)vblank_time->tv_sec, (long)vblank_time->tv_usec, + duration_ns/1000, i); - vbl_status = DRM_VBLANKTIME_SCANOUTPOS_METHOD; - if (invbl) - vbl_status |= DRM_VBLANKTIME_INVBL; - - return vbl_status; + return ret; } EXPORT_SYMBOL(drm_calc_vbltimestamp_from_scanoutpos); @@ -686,35 +894,33 @@ static struct timeval get_drm_timestamp(void) { ktime_t now; - now = ktime_get(); - if (!drm_timestamp_monotonic) - now = ktime_sub(now, ktime_get_monotonic_offset()); - + now = drm_timestamp_monotonic ? ktime_get() : ktime_get_real(); return ktime_to_timeval(now); } /** * drm_get_last_vbltimestamp - retrieve raw timestamp for the most recent - * vblank interval. - * + * vblank interval * @dev: DRM device - * @crtc: which crtc's vblank timestamp to retrieve + * @pipe: index of CRTC whose vblank timestamp to retrieve * @tvblank: Pointer to target struct timeval which should receive the timestamp * @flags: Flags to pass to driver: - * 0 = Default. - * DRM_CALLED_FROM_VBLIRQ = If function is called from vbl irq handler. + * 0 = Default, + * DRM_CALLED_FROM_VBLIRQ = If function is called from vbl IRQ handler * * Fetches the system timestamp corresponding to the time of the most recent - * vblank interval on specified crtc. May call into kms-driver to + * vblank interval on specified CRTC. May call into kms-driver to * compute the timestamp with a high-precision GPU specific method. * * Returns zero if timestamp originates from uncorrected do_gettimeofday() * call, i.e., it isn't very precisely locked to the true vblank. * - * Returns non-zero if timestamp is considered to be very precise. + * Returns: + * True if timestamp is considered to be very precise, false otherwise. */ -u32 drm_get_last_vbltimestamp(struct drm_device *dev, int crtc, - struct timeval *tvblank, unsigned flags) +static bool +drm_get_last_vbltimestamp(struct drm_device *dev, unsigned int pipe, + struct timeval *tvblank, unsigned flags) { int ret; @@ -723,10 +929,10 @@ u32 drm_get_last_vbltimestamp(struct drm_device *dev, int crtc, /* Query driver if possible and precision timestamping enabled. */ if (dev->driver->get_vblank_timestamp && (max_error > 0)) { - ret = dev->driver->get_vblank_timestamp(dev, crtc, &max_error, + ret = dev->driver->get_vblank_timestamp(dev, pipe, &max_error, tvblank, flags); if (ret > 0) - return (u32) ret; + return true; } /* GPU high precision timestamp query unsupported or failed. @@ -734,64 +940,120 @@ u32 drm_get_last_vbltimestamp(struct drm_device *dev, int crtc, */ *tvblank = get_drm_timestamp(); - return 0; + return false; } -EXPORT_SYMBOL(drm_get_last_vbltimestamp); /** * drm_vblank_count - retrieve "cooked" vblank counter value * @dev: DRM device - * @crtc: which counter to retrieve + * @pipe: index of CRTC for which to retrieve the counter * * Fetches the "cooked" vblank count value that represents the number of * vblank events since the system was booted, including lost events due to * modesetting activity. + * + * This is the legacy version of drm_crtc_vblank_count(). + * + * Returns: + * The software vblank counter. */ -u32 drm_vblank_count(struct drm_device *dev, int crtc) +u32 drm_vblank_count(struct drm_device *dev, unsigned int pipe) { - return atomic_read(&dev->vblank[crtc].count); + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + + if (WARN_ON(pipe >= dev->num_crtcs)) + return 0; + + return vblank->count; } EXPORT_SYMBOL(drm_vblank_count); /** - * drm_vblank_count_and_time - retrieve "cooked" vblank counter value - * and the system timestamp corresponding to that vblank counter value. + * drm_crtc_vblank_count - retrieve "cooked" vblank counter value + * @crtc: which counter to retrieve + * + * Fetches the "cooked" vblank count value that represents the number of + * vblank events since the system was booted, including lost events due to + * modesetting activity. + * + * This is the native KMS version of drm_vblank_count(). * + * Returns: + * The software vblank counter. + */ +u32 drm_crtc_vblank_count(struct drm_crtc *crtc) +{ + return drm_vblank_count(crtc->dev, drm_crtc_index(crtc)); +} +EXPORT_SYMBOL(drm_crtc_vblank_count); + +/** + * drm_vblank_count_and_time - retrieve "cooked" vblank counter value and the + * system timestamp corresponding to that vblank counter value. * @dev: DRM device - * @crtc: which counter to retrieve + * @pipe: index of CRTC whose counter to retrieve * @vblanktime: Pointer to struct timeval to receive the vblank timestamp. * * Fetches the "cooked" vblank count value that represents the number of * vblank events since the system was booted, including lost events due to * modesetting activity. Returns corresponding system timestamp of the time - * of the vblank interval that corresponds to the current value vblank counter - * value. + * of the vblank interval that corresponds to the current vblank counter value. + * + * This is the legacy version of drm_crtc_vblank_count_and_time(). */ -u32 drm_vblank_count_and_time(struct drm_device *dev, int crtc, +u32 drm_vblank_count_and_time(struct drm_device *dev, unsigned int pipe, struct timeval *vblanktime) { + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + int count = DRM_TIMESTAMP_MAXRETRIES; u32 cur_vblank; - /* Read timestamp from slot of _vblank_time ringbuffer - * that corresponds to current vblank count. Retry if - * count has incremented during readout. This works like - * a seqlock. + if (WARN_ON(pipe >= dev->num_crtcs)) + return 0; + + /* + * Vblank timestamps are read lockless. To ensure consistency the vblank + * counter is rechecked and ordering is ensured using memory barriers. + * This works like a seqlock. The write-side barriers are in store_vblank. */ do { - cur_vblank = atomic_read(&dev->vblank[crtc].count); - *vblanktime = vblanktimestamp(dev, crtc, cur_vblank); + cur_vblank = vblank->count; + smp_rmb(); + *vblanktime = vblanktimestamp(dev, pipe, cur_vblank); smp_rmb(); - } while (cur_vblank != atomic_read(&dev->vblank[crtc].count)); + } while (cur_vblank != vblank->count && --count > 0); return cur_vblank; } EXPORT_SYMBOL(drm_vblank_count_and_time); +/** + * drm_crtc_vblank_count_and_time - retrieve "cooked" vblank counter value + * and the system timestamp corresponding to that vblank counter value + * @crtc: which counter to retrieve + * @vblanktime: Pointer to struct timeval to receive the vblank timestamp. + * + * Fetches the "cooked" vblank count value that represents the number of + * vblank events since the system was booted, including lost events due to + * modesetting activity. Returns corresponding system timestamp of the time + * of the vblank interval that corresponds to the current vblank counter value. + * + * This is the native KMS version of drm_vblank_count_and_time(). + */ +u32 drm_crtc_vblank_count_and_time(struct drm_crtc *crtc, + struct timeval *vblanktime) +{ + return drm_vblank_count_and_time(crtc->dev, drm_crtc_index(crtc), + vblanktime); +} +EXPORT_SYMBOL(drm_crtc_vblank_count_and_time); + static void send_vblank_event(struct drm_device *dev, struct drm_pending_vblank_event *e, unsigned long seq, struct timeval *now) { - MUTEX_ASSERT_LOCKED(&dev->event_lock); + assert_spin_locked(&dev->event_lock); + e->event.sequence = seq; e->event.tv_sec = now->tv_sec; e->event.tv_usec = now->tv_usec; @@ -799,142 +1061,182 @@ static void send_vblank_event(struct drm_device *dev, list_add_tail(&e->base.link, &e->base.file_priv->event_list); wake_up_interruptible(&e->base.file_priv->event_wait); +#ifdef __OpenBSD__ selwakeup(&e->base.file_priv->rsel); +#endif trace_drm_vblank_event_delivered(e->base.pid, e->pipe, e->event.sequence); } +/** + * drm_arm_vblank_event - arm vblank event after pageflip + * @dev: DRM device + * @pipe: CRTC index + * @e: the event to prepare to send + * + * A lot of drivers need to generate vblank events for the very next vblank + * interrupt. For example when the page flip interrupt happens when the page + * flip gets armed, but not when it actually executes within the next vblank + * period. This helper function implements exactly the required vblank arming + * behaviour. + * + * Caller must hold event lock. Caller must also hold a vblank reference for + * the event @e, which will be dropped when the next vblank arrives. + * + * This is the legacy version of drm_crtc_arm_vblank_event(). + */ +void drm_arm_vblank_event(struct drm_device *dev, unsigned int pipe, + struct drm_pending_vblank_event *e) +{ + assert_spin_locked(&dev->event_lock); + + e->pipe = pipe; + e->event.sequence = drm_vblank_count(dev, pipe); + list_add_tail(&e->base.link, &dev->vblank_event_list); +} +EXPORT_SYMBOL(drm_arm_vblank_event); + +/** + * drm_crtc_arm_vblank_event - arm vblank event after pageflip + * @crtc: the source CRTC of the vblank event + * @e: the event to send + * + * A lot of drivers need to generate vblank events for the very next vblank + * interrupt. For example when the page flip interrupt happens when the page + * flip gets armed, but not when it actually executes within the next vblank + * period. This helper function implements exactly the required vblank arming + * behaviour. + * + * Caller must hold event lock. Caller must also hold a vblank reference for + * the event @e, which will be dropped when the next vblank arrives. + * + * This is the native KMS version of drm_arm_vblank_event(). + */ +void drm_crtc_arm_vblank_event(struct drm_crtc *crtc, + struct drm_pending_vblank_event *e) +{ + drm_arm_vblank_event(crtc->dev, drm_crtc_index(crtc), e); +} +EXPORT_SYMBOL(drm_crtc_arm_vblank_event); + /** * drm_send_vblank_event - helper to send vblank event after pageflip * @dev: DRM device - * @crtc: CRTC in question + * @pipe: CRTC index * @e: the event to send * * Updates sequence # and timestamp on event, and sends it to userspace. * Caller must hold event lock. + * + * This is the legacy version of drm_crtc_send_vblank_event(). */ -void drm_send_vblank_event(struct drm_device *dev, int crtc, - struct drm_pending_vblank_event *e) +void drm_send_vblank_event(struct drm_device *dev, unsigned int pipe, + struct drm_pending_vblank_event *e) { struct timeval now; unsigned int seq; - if (crtc >= 0) { - seq = drm_vblank_count_and_time(dev, crtc, &now); + + if (dev->num_crtcs > 0) { + seq = drm_vblank_count_and_time(dev, pipe, &now); } else { seq = 0; now = get_drm_timestamp(); } - e->pipe = crtc; + e->pipe = pipe; send_vblank_event(dev, e, seq, &now); } EXPORT_SYMBOL(drm_send_vblank_event); /** - * drm_update_vblank_count - update the master vblank counter - * @dev: DRM device - * @crtc: counter to update + * drm_crtc_send_vblank_event - helper to send vblank event after pageflip + * @crtc: the source CRTC of the vblank event + * @e: the event to send * - * Call back into the driver to update the appropriate vblank counter - * (specified by @crtc). Deal with wraparound, if it occurred, and - * update the last read value so we can deal with wraparound on the next - * call if necessary. + * Updates sequence # and timestamp on event, and sends it to userspace. + * Caller must hold event lock. * - * Only necessary when going from off->on, to account for frames we - * didn't get an interrupt for. + * This is the native KMS version of drm_send_vblank_event(). + */ +void drm_crtc_send_vblank_event(struct drm_crtc *crtc, + struct drm_pending_vblank_event *e) +{ + drm_send_vblank_event(crtc->dev, drm_crtc_index(crtc), e); +} +EXPORT_SYMBOL(drm_crtc_send_vblank_event); + +/** + * drm_vblank_enable - enable the vblank interrupt on a CRTC + * @dev: DRM device + * @pipe: CRTC index * - * Note: caller must hold dev->vbl_lock since this reads & writes - * device vblank fields. + * Returns: + * Zero on success or a negative error code on failure. */ -static void drm_update_vblank_count(struct drm_device *dev, int crtc) +static int drm_vblank_enable(struct drm_device *dev, unsigned int pipe) { - u32 cur_vblank, diff, tslot, rc; - struct timeval t_vblank; + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + int ret = 0; - /* - * Interrupts were disabled prior to this call, so deal with counter - * wrap if needed. - * NOTE! It's possible we lost a full dev->max_vblank_count events - * here if the register is small or we had vblank interrupts off for - * a long time. - * - * We repeat the hardware vblank counter & timestamp query until - * we get consistent results. This to prevent races between gpu - * updating its hardware counter while we are retrieving the - * corresponding vblank timestamp. - */ - do { - cur_vblank = dev->driver->get_vblank_counter(dev, crtc); - rc = drm_get_last_vbltimestamp(dev, crtc, &t_vblank, 0); - } while (cur_vblank != dev->driver->get_vblank_counter(dev, crtc)); + assert_spin_locked(&dev->vbl_lock); - /* Deal with counter wrap */ - diff = cur_vblank - dev->vblank[crtc].last; - if (cur_vblank < dev->vblank[crtc].last) { - diff += dev->max_vblank_count; + spin_lock(&dev->vblank_time_lock); - DRM_DEBUG("last_vblank[%d]=0x%x, cur_vblank=0x%x => diff=0x%x\n", - crtc, dev->vblank[crtc].last, cur_vblank, diff); + if (!vblank->enabled) { + /* + * Enable vblank irqs under vblank_time_lock protection. + * All vblank count & timestamp updates are held off + * until we are done reinitializing master counter and + * timestamps. Filtercode in drm_handle_vblank() will + * prevent double-accounting of same vblank interval. + */ + ret = dev->driver->enable_vblank(dev, pipe); + DRM_DEBUG("enabling vblank on crtc %u, ret: %d\n", pipe, ret); + if (ret) + atomic_dec(&vblank->refcount); + else { + vblank->enabled = true; + drm_update_vblank_count(dev, pipe, 0); + } } - VBL_DEBUG("enabling vblank interrupts on crtc %d, missed %d\n", - crtc, diff); + spin_unlock(&dev->vblank_time_lock); - /* Reinitialize corresponding vblank timestamp if high-precision query - * available. Skip this step if query unsupported or failed. Will - * reinitialize delayed at next vblank interrupt in that case. - */ - if (rc) { - tslot = atomic_read(&dev->vblank[crtc].count) + diff; - vblanktimestamp(dev, crtc, tslot) = t_vblank; - } - - smp_mb__before_atomic_inc(); - atomic_add(diff, &dev->vblank[crtc].count); - smp_mb__after_atomic_inc(); + return ret; } /** * drm_vblank_get - get a reference count on vblank events * @dev: DRM device - * @crtc: which CRTC to own + * @pipe: index of CRTC to own * * Acquire a reference count on vblank events to avoid having them disabled * while in use. * - * RETURNS - * Zero on success, nonzero on failure. + * This is the legacy version of drm_crtc_vblank_get(). + * + * Returns: + * Zero on success or a negative error code on failure. */ -int drm_vblank_get(struct drm_device *dev, int crtc) +int drm_vblank_get(struct drm_device *dev, unsigned int pipe) { - unsigned long irqflags, irqflags2; + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + unsigned long irqflags; int ret = 0; + if (!dev->num_crtcs) + return -EINVAL; + + if (WARN_ON(pipe >= dev->num_crtcs)) + return -EINVAL; + spin_lock_irqsave(&dev->vbl_lock, irqflags); /* Going from 0->1 means we have to enable interrupts again */ - if (atomic_add_return(1, &dev->vblank[crtc].refcount) == 1) { - spin_lock_irqsave(&dev->vblank_time_lock, irqflags2); - if (!dev->vblank[crtc].enabled) { - /* Enable vblank irqs under vblank_time_lock protection. - * All vblank count & timestamp updates are held off - * until we are done reinitializing master counter and - * timestamps. Filtercode in drm_handle_vblank() will - * prevent double-accounting of same vblank interval. - */ - ret = dev->driver->enable_vblank(dev, crtc); - VBL_DEBUG("enabling vblank on crtc %d, ret: %d\n", - crtc, ret); - if (ret) - atomic_dec(&dev->vblank[crtc].refcount); - else { - dev->vblank[crtc].enabled = true; - drm_update_vblank_count(dev, crtc); - } - } - spin_unlock_irqrestore(&dev->vblank_time_lock, irqflags2); + if (atomic_add_return(1, &vblank->refcount) == 1) { + ret = drm_vblank_enable(dev, pipe); } else { - if (!dev->vblank[crtc].enabled) { - atomic_dec(&dev->vblank[crtc].refcount); + if (!vblank->enabled) { + atomic_dec(&vblank->refcount); ret = -EINVAL; } } @@ -945,76 +1247,327 @@ int drm_vblank_get(struct drm_device *dev, int crtc) EXPORT_SYMBOL(drm_vblank_get); /** - * drm_vblank_put - give up ownership of vblank events + * drm_crtc_vblank_get - get a reference count on vblank events + * @crtc: which CRTC to own + * + * Acquire a reference count on vblank events to avoid having them disabled + * while in use. + * + * This is the native kms version of drm_vblank_get(). + * + * Returns: + * Zero on success or a negative error code on failure. + */ +int drm_crtc_vblank_get(struct drm_crtc *crtc) +{ + return drm_vblank_get(crtc->dev, drm_crtc_index(crtc)); +} +EXPORT_SYMBOL(drm_crtc_vblank_get); + +/** + * drm_vblank_put - release ownership of vblank events * @dev: DRM device - * @crtc: which counter to give up + * @pipe: index of CRTC to release * * Release ownership of a given vblank counter, turning off interrupts * if possible. Disable interrupts after drm_vblank_offdelay milliseconds. + * + * This is the legacy version of drm_crtc_vblank_put(). */ -void drm_vblank_put(struct drm_device *dev, int crtc) +void drm_vblank_put(struct drm_device *dev, unsigned int pipe) { - BUG_ON(atomic_read(&dev->vblank[crtc].refcount) == 0); + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + + if (WARN_ON(pipe >= dev->num_crtcs)) + return; + + if (WARN_ON(atomic_read(&vblank->refcount) == 0)) + return; /* Last user schedules interrupt disable */ - if (atomic_dec_and_test(&dev->vblank[crtc].refcount) && - (drm_vblank_offdelay > 0)) - mod_timer(&dev->vblank_disable_timer, - jiffies + ((drm_vblank_offdelay * HZ)/1000)); + if (atomic_dec_and_test(&vblank->refcount)) { + if (drm_vblank_offdelay == 0) + return; + else if (dev->vblank_disable_immediate || drm_vblank_offdelay < 0) + vblank_disable_fn((unsigned long)vblank); + else + mod_timer(&vblank->disable_timer, + jiffies + ((drm_vblank_offdelay * HZ)/1000)); + } } EXPORT_SYMBOL(drm_vblank_put); +/** + * drm_crtc_vblank_put - give up ownership of vblank events + * @crtc: which counter to give up + * + * Release ownership of a given vblank counter, turning off interrupts + * if possible. Disable interrupts after drm_vblank_offdelay milliseconds. + * + * This is the native kms version of drm_vblank_put(). + */ +void drm_crtc_vblank_put(struct drm_crtc *crtc) +{ + drm_vblank_put(crtc->dev, drm_crtc_index(crtc)); +} +EXPORT_SYMBOL(drm_crtc_vblank_put); + +/** + * drm_wait_one_vblank - wait for one vblank + * @dev: DRM device + * @pipe: CRTC index + * + * This waits for one vblank to pass on @pipe, using the irq driver interfaces. + * It is a failure to call this when the vblank irq for @pipe is disabled, e.g. + * due to lack of driver support or because the crtc is off. + */ +void drm_wait_one_vblank(struct drm_device *dev, unsigned int pipe) +{ + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + int ret; + u32 last; + + if (WARN_ON(pipe >= dev->num_crtcs) || cold) + return; + + ret = drm_vblank_get(dev, pipe); + if (WARN(ret, "vblank not available on crtc %i, ret=%i\n", pipe, ret)) + return; + + last = drm_vblank_count(dev, pipe); + + ret = wait_event_timeout(vblank->queue, + last != drm_vblank_count(dev, pipe), + msecs_to_jiffies(100)); + + WARN(ret == 0, "vblank wait timed out on crtc %i\n", pipe); + + drm_vblank_put(dev, pipe); +} +EXPORT_SYMBOL(drm_wait_one_vblank); + +/** + * drm_crtc_wait_one_vblank - wait for one vblank + * @crtc: DRM crtc + * + * This waits for one vblank to pass on @crtc, using the irq driver interfaces. + * It is a failure to call this when the vblank irq for @crtc is disabled, e.g. + * due to lack of driver support or because the crtc is off. + */ +void drm_crtc_wait_one_vblank(struct drm_crtc *crtc) +{ + drm_wait_one_vblank(crtc->dev, drm_crtc_index(crtc)); +} +EXPORT_SYMBOL(drm_crtc_wait_one_vblank); + /** * drm_vblank_off - disable vblank events on a CRTC * @dev: DRM device - * @crtc: CRTC in question + * @pipe: CRTC index * - * Caller must hold event lock. + * Drivers can use this function to shut down the vblank interrupt handling when + * disabling a crtc. This function ensures that the latest vblank frame count is + * stored so that drm_vblank_on() can restore it again. + * + * Drivers must use this function when the hardware vblank counter can get + * reset, e.g. when suspending. + * + * This is the legacy version of drm_crtc_vblank_off(). */ -void drm_vblank_off(struct drm_device *dev, int crtc) +void drm_vblank_off(struct drm_device *dev, unsigned int pipe) { + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; struct drm_pending_vblank_event *e, *t; struct timeval now; unsigned long irqflags; unsigned int seq; - spin_lock_irqsave(&dev->vbl_lock, irqflags); - vblank_disable_and_save(dev, crtc); - wake_up(&dev->vblank[crtc].queue); + if (WARN_ON(pipe >= dev->num_crtcs)) + return; + + spin_lock_irqsave(&dev->event_lock, irqflags); + + spin_lock(&dev->vbl_lock); + DRM_DEBUG_VBL("crtc %d, vblank enabled %d, inmodeset %d\n", + pipe, vblank->enabled, vblank->inmodeset); + + /* Avoid redundant vblank disables without previous drm_vblank_on(). */ + if (drm_core_check_feature(dev, DRIVER_ATOMIC) || !vblank->inmodeset) + vblank_disable_and_save(dev, pipe); + + wake_up(&vblank->queue); + + /* + * Prevent subsequent drm_vblank_get() from re-enabling + * the vblank interrupt by bumping the refcount. + */ + if (!vblank->inmodeset) { + atomic_inc(&vblank->refcount); + vblank->inmodeset = 1; + } + spin_unlock(&dev->vbl_lock); /* Send any queued vblank events, lest the natives grow disquiet */ - seq = drm_vblank_count_and_time(dev, crtc, &now); + seq = drm_vblank_count_and_time(dev, pipe, &now); - spin_lock(&dev->event_lock); list_for_each_entry_safe(e, t, &dev->vblank_event_list, base.link) { - if (e->pipe != crtc) + if (e->pipe != pipe) continue; - DRM_DEBUG("Sending premature vblank event on disable: \ - wanted %d, current %d\n", + DRM_DEBUG("Sending premature vblank event on disable: " + "wanted %d, current %d\n", e->event.sequence, seq); list_del(&e->base.link); - drm_vblank_put(dev, e->pipe); + drm_vblank_put(dev, pipe); send_vblank_event(dev, e, seq, &now); } - spin_unlock(&dev->event_lock); + spin_unlock_irqrestore(&dev->event_lock, irqflags); +} +EXPORT_SYMBOL(drm_vblank_off); +/** + * drm_crtc_vblank_off - disable vblank events on a CRTC + * @crtc: CRTC in question + * + * Drivers can use this function to shut down the vblank interrupt handling when + * disabling a crtc. This function ensures that the latest vblank frame count is + * stored so that drm_vblank_on can restore it again. + * + * Drivers must use this function when the hardware vblank counter can get + * reset, e.g. when suspending. + * + * This is the native kms version of drm_vblank_off(). + */ +void drm_crtc_vblank_off(struct drm_crtc *crtc) +{ + drm_vblank_off(crtc->dev, drm_crtc_index(crtc)); +} +EXPORT_SYMBOL(drm_crtc_vblank_off); + +/** + * drm_crtc_vblank_reset - reset vblank state to off on a CRTC + * @crtc: CRTC in question + * + * Drivers can use this function to reset the vblank state to off at load time. + * Drivers should use this together with the drm_crtc_vblank_off() and + * drm_crtc_vblank_on() functions. The difference compared to + * drm_crtc_vblank_off() is that this function doesn't save the vblank counter + * and hence doesn't need to call any driver hooks. + */ +void drm_crtc_vblank_reset(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + unsigned long irqflags; + unsigned int pipe = drm_crtc_index(crtc); + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + + spin_lock_irqsave(&dev->vbl_lock, irqflags); + /* + * Prevent subsequent drm_vblank_get() from enabling the vblank + * interrupt by bumping the refcount. + */ + if (!vblank->inmodeset) { + atomic_inc(&vblank->refcount); + vblank->inmodeset = 1; + } spin_unlock_irqrestore(&dev->vbl_lock, irqflags); + + WARN_ON(!list_empty(&dev->vblank_event_list)); } -EXPORT_SYMBOL(drm_vblank_off); +EXPORT_SYMBOL(drm_crtc_vblank_reset); /** - * drm_vblank_pre_modeset - account for vblanks across mode sets + * drm_vblank_on - enable vblank events on a CRTC * @dev: DRM device + * @pipe: CRTC index + * + * This functions restores the vblank interrupt state captured with + * drm_vblank_off() again. Note that calls to drm_vblank_on() and + * drm_vblank_off() can be unbalanced and so can also be unconditionally called + * in driver load code to reflect the current hardware state of the crtc. + * + * This is the legacy version of drm_crtc_vblank_on(). + */ +void drm_vblank_on(struct drm_device *dev, unsigned int pipe) +{ + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + unsigned long irqflags; + + if (WARN_ON(pipe >= dev->num_crtcs)) + return; + + spin_lock_irqsave(&dev->vbl_lock, irqflags); + DRM_DEBUG_VBL("crtc %d, vblank enabled %d, inmodeset %d\n", + pipe, vblank->enabled, vblank->inmodeset); + + /* Drop our private "prevent drm_vblank_get" refcount */ + if (vblank->inmodeset) { + atomic_dec(&vblank->refcount); + vblank->inmodeset = 0; + } + + drm_reset_vblank_timestamp(dev, pipe); + + /* + * re-enable interrupts if there are users left, or the + * user wishes vblank interrupts to be enabled all the time. + */ + if (atomic_read(&vblank->refcount) != 0 || drm_vblank_offdelay == 0) + WARN_ON(drm_vblank_enable(dev, pipe)); + spin_unlock_irqrestore(&dev->vbl_lock, irqflags); +} +EXPORT_SYMBOL(drm_vblank_on); + +/** + * drm_crtc_vblank_on - enable vblank events on a CRTC * @crtc: CRTC in question * + * This functions restores the vblank interrupt state captured with + * drm_vblank_off() again. Note that calls to drm_vblank_on() and + * drm_vblank_off() can be unbalanced and so can also be unconditionally called + * in driver load code to reflect the current hardware state of the crtc. + * + * This is the native kms version of drm_vblank_on(). + */ +void drm_crtc_vblank_on(struct drm_crtc *crtc) +{ + drm_vblank_on(crtc->dev, drm_crtc_index(crtc)); +} +EXPORT_SYMBOL(drm_crtc_vblank_on); + +/** + * drm_vblank_pre_modeset - account for vblanks across mode sets + * @dev: DRM device + * @pipe: CRTC index + * * Account for vblank events across mode setting events, which will likely * reset the hardware frame counter. + * + * This is done by grabbing a temporary vblank reference to ensure that the + * vblank interrupt keeps running across the modeset sequence. With this the + * software-side vblank frame counting will ensure that there are no jumps or + * discontinuities. + * + * Unfortunately this approach is racy and also doesn't work when the vblank + * interrupt stops running, e.g. across system suspend resume. It is therefore + * highly recommended that drivers use the newer drm_vblank_off() and + * drm_vblank_on() instead. drm_vblank_pre_modeset() only works correctly when + * using "cooked" software vblank frame counters and not relying on any hardware + * counters. + * + * Drivers must call drm_vblank_post_modeset() when re-enabling the same crtc + * again. */ -void drm_vblank_pre_modeset(struct drm_device *dev, int crtc) +void drm_vblank_pre_modeset(struct drm_device *dev, unsigned int pipe) { + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; + /* vblank is not initialized (IRQ not installed ?), or has been freed */ if (!dev->num_crtcs) return; + + if (WARN_ON(pipe >= dev->num_crtcs)) + return; + /* * To avoid all the problems that might happen if interrupts * were enabled/disabled around or between these calls, we just @@ -1022,36 +1575,49 @@ void drm_vblank_pre_modeset(struct drm_device *dev, int crtc) * to avoid corrupting the count if multiple, mismatch calls occur), * so that interrupts remain enabled in the interim. */ - if (!dev->vblank[crtc].inmodeset) { - dev->vblank[crtc].inmodeset = 0x1; - if (drm_vblank_get(dev, crtc) == 0) - dev->vblank[crtc].inmodeset |= 0x2; + if (!vblank->inmodeset) { + vblank->inmodeset = 0x1; + if (drm_vblank_get(dev, pipe) == 0) + vblank->inmodeset |= 0x2; } } EXPORT_SYMBOL(drm_vblank_pre_modeset); -void drm_vblank_post_modeset(struct drm_device *dev, int crtc) +/** + * drm_vblank_post_modeset - undo drm_vblank_pre_modeset changes + * @dev: DRM device + * @pipe: CRTC index + * + * This function again drops the temporary vblank reference acquired in + * drm_vblank_pre_modeset. + */ +void drm_vblank_post_modeset(struct drm_device *dev, unsigned int pipe) { + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; unsigned long irqflags; /* vblank is not initialized (IRQ not installed ?), or has been freed */ if (!dev->num_crtcs) return; - if (dev->vblank[crtc].inmodeset) { + if (WARN_ON(pipe >= dev->num_crtcs)) + return; + + if (vblank->inmodeset) { spin_lock_irqsave(&dev->vbl_lock, irqflags); dev->vblank_disable_allowed = true; + drm_reset_vblank_timestamp(dev, pipe); spin_unlock_irqrestore(&dev->vbl_lock, irqflags); - if (dev->vblank[crtc].inmodeset & 0x2) - drm_vblank_put(dev, crtc); + if (vblank->inmodeset & 0x2) + drm_vblank_put(dev, pipe); - dev->vblank[crtc].inmodeset = 0; + vblank->inmodeset = 0; } } EXPORT_SYMBOL(drm_vblank_post_modeset); -/** +/* * drm_modeset_ctl - handle vblank event counter changes across mode switch * @DRM_IOCTL_ARGS: standard ioctl arguments * @@ -1066,7 +1632,7 @@ int drm_modeset_ctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { struct drm_modeset_ctl *modeset = data; - unsigned int crtc; + unsigned int pipe; /* If drm_vblank_init() hasn't been called yet, just no-op */ if (!dev->num_crtcs) @@ -1076,16 +1642,16 @@ int drm_modeset_ctl(struct drm_device *dev, void *data, if (drm_core_check_feature(dev, DRIVER_MODESET)) return 0; - crtc = modeset->crtc; - if (crtc >= dev->num_crtcs) + pipe = modeset->crtc; + if (pipe >= dev->num_crtcs) return -EINVAL; switch (modeset->cmd) { case _DRM_PRE_MODESET: - drm_vblank_pre_modeset(dev, crtc); + drm_vblank_pre_modeset(dev, pipe); break; case _DRM_POST_MODESET: - drm_vblank_post_modeset(dev, crtc); + drm_vblank_post_modeset(dev, pipe); break; default: return -EINVAL; @@ -1094,26 +1660,31 @@ int drm_modeset_ctl(struct drm_device *dev, void *data, return 0; } -static int drm_queue_vblank_event(struct drm_device *dev, int pipe, +static int drm_queue_vblank_event(struct drm_device *dev, unsigned int pipe, union drm_wait_vblank *vblwait, struct drm_file *file_priv) { + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; struct drm_pending_vblank_event *e; struct timeval now; unsigned long flags; unsigned int seq; int ret; - e = kzalloc(sizeof *e, GFP_KERNEL); + e = kzalloc(sizeof(*e), GFP_KERNEL); if (e == NULL) { ret = -ENOMEM; goto err_put; } e->pipe = pipe; - e->base.pid = curproc->p_tid; /* XXX */ +#ifdef __linux__ + e->base.pid = current->pid; +#else + e->base.pid = curproc->p_p->ps_pid; +#endif e->event.base.type = DRM_EVENT_VBLANK; - e->event.base.length = sizeof e->event; + e->event.base.length = sizeof(e->event); e->event.user_data = vblwait->request.signal; e->base.event = &e->event.base; e->base.file_priv = file_priv; @@ -1121,12 +1692,24 @@ static int drm_queue_vblank_event(struct drm_device *dev, int pipe, spin_lock_irqsave(&dev->event_lock, flags); - if (file_priv->event_space < sizeof e->event) { + /* + * drm_vblank_off() might have been called after we called + * drm_vblank_get(). drm_vblank_off() holds event_lock + * around the vblank disable, so no need for further locking. + * The reference from drm_vblank_get() protects against + * vblank disable from another source. + */ + if (!vblank->enabled) { + ret = -EINVAL; + goto err_unlock; + } + + if (file_priv->event_space < sizeof(e->event)) { ret = -EBUSY; goto err_unlock; } - file_priv->event_space -= sizeof e->event; + file_priv->event_space -= sizeof(e->event); seq = drm_vblank_count_and_time(dev, pipe, &now); if ((vblwait->request.type & _DRM_VBLANK_NEXTONMISS) && @@ -1135,11 +1718,16 @@ static int drm_queue_vblank_event(struct drm_device *dev, int pipe, vblwait->reply.sequence = vblwait->request.sequence; } - DRM_DEBUG("event on vblank count %d, current %d, crtc %d\n", + DRM_DEBUG("event on vblank count %d, current %d, crtc %u\n", vblwait->request.sequence, seq, pipe); - trace_drm_vblank_event_queued(curproc->p_tid, pipe, +#ifdef __linux__ + trace_drm_vblank_event_queued(current->pid, pipe, + vblwait->request.sequence); +#else + trace_drm_vblank_event_queued(curproc->p_p->ps_pid, pipe, vblwait->request.sequence); +#endif e->event.sequence = vblwait->request.sequence; if ((seq - vblwait->request.sequence) <= (1 << 23)) { @@ -1164,7 +1752,7 @@ err_put: return ret; } -/** +/* * Wait for VBLANK. * * \param inode device inode. @@ -1175,19 +1763,19 @@ err_put: * * This function enables the vblank interrupt on the pipe requested, then * sleeps waiting for the requested sequence number to occur, and drops - * the vblank interrupt refcount afterwards. (vblank irq disable follows that + * the vblank interrupt refcount afterwards. (vblank IRQ disable follows that * after a timeout with no further vblank waits scheduled). */ int drm_wait_vblank(struct drm_device *dev, void *data, struct drm_file *file_priv) { + struct drm_vblank_crtc *vblank; union drm_wait_vblank *vblwait = data; int ret; - unsigned int flags, seq, crtc, high_crtc; + unsigned int flags, seq, pipe, high_pipe; - if (drm_core_check_feature(dev, DRIVER_HAVE_IRQ)) - if ((!drm_dev_to_irq(dev)) || (!dev->irq_enabled)) - return -EINVAL; + if (!dev->irq_enabled) + return -EINVAL; if (vblwait->request.type & _DRM_VBLANK_SIGNAL) return -EINVAL; @@ -1203,20 +1791,22 @@ int drm_wait_vblank(struct drm_device *dev, void *data, } flags = vblwait->request.type & _DRM_VBLANK_FLAGS_MASK; - high_crtc = (vblwait->request.type & _DRM_VBLANK_HIGH_CRTC_MASK); - if (high_crtc) - crtc = high_crtc >> _DRM_VBLANK_HIGH_CRTC_SHIFT; + high_pipe = (vblwait->request.type & _DRM_VBLANK_HIGH_CRTC_MASK); + if (high_pipe) + pipe = high_pipe >> _DRM_VBLANK_HIGH_CRTC_SHIFT; else - crtc = flags & _DRM_VBLANK_SECONDARY ? 1 : 0; - if (crtc >= dev->num_crtcs) + pipe = flags & _DRM_VBLANK_SECONDARY ? 1 : 0; + if (pipe >= dev->num_crtcs) return -EINVAL; - ret = drm_vblank_get(dev, crtc); + vblank = &dev->vblank[pipe]; + + ret = drm_vblank_get(dev, pipe); if (ret) { DRM_DEBUG("failed to acquire vblank counter, %d\n", ret); return ret; } - seq = drm_vblank_count(dev, crtc); + seq = drm_vblank_count(dev, pipe); switch (vblwait->request.type & _DRM_VBLANK_TYPES_MASK) { case _DRM_VBLANK_RELATIVE: @@ -1233,7 +1823,7 @@ int drm_wait_vblank(struct drm_device *dev, void *data, /* must hold on to the vblank ref until the event fires * drm_vblank_put will be called asynchronously */ - return drm_queue_vblank_event(dev, crtc, vblwait, file_priv); + return drm_queue_vblank_event(dev, pipe, vblwait, file_priv); } if ((flags & _DRM_VBLANK_NEXTONMISS) && @@ -1241,18 +1831,19 @@ int drm_wait_vblank(struct drm_device *dev, void *data, vblwait->request.sequence = seq + 1; } - DRM_DEBUG("waiting on vblank count %d, crtc %d\n", - vblwait->request.sequence, crtc); - dev->vblank[crtc].last_wait = vblwait->request.sequence; - DRM_WAIT_ON(ret, dev->vblank[crtc].queue, 3 * HZ, - (((drm_vblank_count(dev, crtc) - + DRM_DEBUG("waiting on vblank count %d, crtc %u\n", + vblwait->request.sequence, pipe); + vblank->last_wait = vblwait->request.sequence; + DRM_WAIT_ON(ret, vblank->queue, 3 * HZ, + (((drm_vblank_count(dev, pipe) - vblwait->request.sequence) <= (1 << 23)) || + !vblank->enabled || !dev->irq_enabled)); if (ret != -EINTR) { struct timeval now; - vblwait->reply.sequence = drm_vblank_count_and_time(dev, crtc, &now); + vblwait->reply.sequence = drm_vblank_count_and_time(dev, pipe, &now); vblwait->reply.tval_sec = now.tv_sec; vblwait->reply.tval_usec = now.tv_usec; @@ -1263,110 +1854,117 @@ int drm_wait_vblank(struct drm_device *dev, void *data, } done: - drm_vblank_put(dev, crtc); + drm_vblank_put(dev, pipe); return ret; } -static void drm_handle_vblank_events(struct drm_device *dev, int crtc) +static void drm_handle_vblank_events(struct drm_device *dev, unsigned int pipe) { struct drm_pending_vblank_event *e, *t; struct timeval now; - unsigned long flags; unsigned int seq; - seq = drm_vblank_count_and_time(dev, crtc, &now); + assert_spin_locked(&dev->event_lock); - spin_lock_irqsave(&dev->event_lock, flags); + seq = drm_vblank_count_and_time(dev, pipe, &now); list_for_each_entry_safe(e, t, &dev->vblank_event_list, base.link) { - if (e->pipe != crtc) + if (e->pipe != pipe) continue; if ((seq - e->event.sequence) > (1<<23)) continue; - VBL_DEBUG("vblank event on %d, current %d\n", + DRM_DEBUG("vblank event on %d, current %d\n", e->event.sequence, seq); list_del(&e->base.link); - drm_vblank_put(dev, e->pipe); + drm_vblank_put(dev, pipe); send_vblank_event(dev, e, seq, &now); } - spin_unlock_irqrestore(&dev->event_lock, flags); - - trace_drm_vblank_event(crtc, seq); + trace_drm_vblank_event(pipe, seq); } /** * drm_handle_vblank - handle a vblank event * @dev: DRM device - * @crtc: where this event occurred + * @pipe: index of CRTC where this event occurred * * Drivers should call this routine in their vblank interrupt handlers to * update the vblank counter and send any signals that may be pending. + * + * This is the legacy version of drm_crtc_handle_vblank(). */ -bool drm_handle_vblank(struct drm_device *dev, int crtc) +bool drm_handle_vblank(struct drm_device *dev, unsigned int pipe) { - u32 vblcount; - s64 diff_ns; - struct timeval tvblank; + struct drm_vblank_crtc *vblank = &dev->vblank[pipe]; unsigned long irqflags; - if (!dev->num_crtcs) + if (WARN_ON_ONCE(!dev->num_crtcs)) + return false; + + if (WARN_ON(pipe >= dev->num_crtcs)) return false; + spin_lock_irqsave(&dev->event_lock, irqflags); + /* Need timestamp lock to prevent concurrent execution with * vblank enable/disable, as this would cause inconsistent * or corrupted timestamps and vblank counts. */ - spin_lock_irqsave(&dev->vblank_time_lock, irqflags); + spin_lock(&dev->vblank_time_lock); /* Vblank irq handling disabled. Nothing to do. */ - if (!dev->vblank[crtc].enabled) { - spin_unlock_irqrestore(&dev->vblank_time_lock, irqflags); + if (!vblank->enabled) { + spin_unlock(&dev->vblank_time_lock); + spin_unlock_irqrestore(&dev->event_lock, irqflags); return false; } - /* Fetch corresponding timestamp for this vblank interval from - * driver and store it in proper slot of timestamp ringbuffer. - */ + drm_update_vblank_count(dev, pipe, DRM_CALLED_FROM_VBLIRQ); - /* Get current timestamp and count. */ - vblcount = atomic_read(&dev->vblank[crtc].count); - drm_get_last_vbltimestamp(dev, crtc, &tvblank, DRM_CALLED_FROM_VBLIRQ); - - /* Compute time difference to timestamp of last vblank */ - diff_ns = timeval_to_ns(&tvblank) - - timeval_to_ns(&vblanktimestamp(dev, crtc, vblcount)); - - /* Update vblank timestamp and count if at least - * DRM_REDUNDANT_VBLIRQ_THRESH_NS nanoseconds - * difference between last stored timestamp and current - * timestamp. A smaller difference means basically - * identical timestamps. Happens if this vblank has - * been already processed and this is a redundant call, - * e.g., due to spurious vblank interrupts. We need to - * ignore those for accounting. - */ - if (abs64(diff_ns) > DRM_REDUNDANT_VBLIRQ_THRESH_NS) { - /* Store new timestamp in ringbuffer. */ - vblanktimestamp(dev, crtc, vblcount + 1) = tvblank; + spin_unlock(&dev->vblank_time_lock); - /* Increment cooked vblank count. This also atomically commits - * the timestamp computed above. - */ - smp_mb__before_atomic_inc(); - atomic_inc(&dev->vblank[crtc].count); - smp_mb__after_atomic_inc(); - } else { - DRM_DEBUG("crtc %d: Redundant vblirq ignored. diff_ns = %d\n", - crtc, (int) diff_ns); - } + wake_up(&vblank->queue); + drm_handle_vblank_events(dev, pipe); - wake_up(&dev->vblank[crtc].queue); - drm_handle_vblank_events(dev, crtc); + spin_unlock_irqrestore(&dev->event_lock, irqflags); - spin_unlock_irqrestore(&dev->vblank_time_lock, irqflags); return true; } EXPORT_SYMBOL(drm_handle_vblank); + +/** + * drm_crtc_handle_vblank - handle a vblank event + * @crtc: where this event occurred + * + * Drivers should call this routine in their vblank interrupt handlers to + * update the vblank counter and send any signals that may be pending. + * + * This is the native KMS version of drm_handle_vblank(). + * + * Returns: + * True if the event was successfully handled, false on failure. + */ +bool drm_crtc_handle_vblank(struct drm_crtc *crtc) +{ + return drm_handle_vblank(crtc->dev, drm_crtc_index(crtc)); +} +EXPORT_SYMBOL(drm_crtc_handle_vblank); + +/** + * drm_vblank_no_hw_counter - "No hw counter" implementation of .get_vblank_counter() + * @dev: DRM device + * @pipe: CRTC for which to read the counter + * + * Drivers can plug this into the .get_vblank_counter() function if + * there is no useable hardware frame counter available. + * + * Returns: + * 0 + */ +u32 drm_vblank_no_hw_counter(struct drm_device *dev, unsigned int pipe) +{ + return 0; +} +EXPORT_SYMBOL(drm_vblank_no_hw_counter); diff --git a/sys/dev/pci/drm/drm_linux.c b/sys/dev/pci/drm/drm_linux.c index c01495da3d2..1c713a60f70 100644 --- a/sys/dev/pci/drm/drm_linux.c +++ b/sys/dev/pci/drm/drm_linux.c @@ -1,4 +1,4 @@ -/* $OpenBSD: drm_linux.c,v 1.12 2016/09/15 02:00:17 dlg Exp $ */ +/* $OpenBSD: drm_linux.c,v 1.13 2017/07/01 16:14:10 kettenis Exp $ */ /* * Copyright (c) 2013 Jonathan Gray * Copyright (c) 2015, 2016 Mark Kettenis @@ -19,6 +19,72 @@ #include #include +void +flush_barrier(void *arg) +{ + int *barrier = arg; + + *barrier = 1; + wakeup(barrier); +} + +void +flush_workqueue(struct workqueue_struct *wq) +{ + struct sleep_state sls; + struct task task; + int barrier = 0; + + if (cold) + return; + + task_set(&task, flush_barrier, &barrier); + task_add((struct taskq *)wq, &task); + while (!barrier) { + sleep_setup(&sls, &barrier, PWAIT, "flwqbar"); + sleep_finish(&sls, !barrier); + } +} + +void +flush_work(struct work_struct *work) +{ + struct sleep_state sls; + struct task task; + int barrier = 0; + + if (cold) + return; + + task_set(&task, flush_barrier, &barrier); + task_add(work->tq, &task); + while (!barrier) { + sleep_setup(&sls, &barrier, PWAIT, "flwkbar"); + sleep_finish(&sls, !barrier); + } +} + +void +flush_delayed_work(struct delayed_work *dwork) +{ + struct sleep_state sls; + struct task task; + int barrier = 0; + + if (cold) + return; + + while (timeout_pending(&dwork->to)) + tsleep(&barrier, PWAIT, "fldwto", 1); + + task_set(&task, flush_barrier, &barrier); + task_add(dwork->tq, &task); + while (!barrier) { + sleep_setup(&sls, &barrier, PWAIT, "fldwbar"); + sleep_finish(&sls, !barrier); + } +} + struct timespec ns_to_timespec(const int64_t nsec) { @@ -70,6 +136,12 @@ ns_to_timeval(const int64_t nsec) return (tv); } +int64_t +timeval_to_us(const struct timeval *tv) +{ + return ((int64_t)tv->tv_sec * 1000000) + tv->tv_usec; +} + extern char *hw_vendor, *hw_prod; static bool @@ -128,6 +200,8 @@ alloc_pages(unsigned int gfp_mask, unsigned int order) if (gfp_mask & M_CANFAIL) flags |= UVM_PLA_FAILOK; + if (gfp_mask & M_ZERO) + flags |= UVM_PLA_ZERO; TAILQ_INIT(&mlist); if (uvm_pglistalloc(PAGE_SIZE << order, 0, -1, PAGE_SIZE, 0, @@ -209,6 +283,38 @@ vunmap(void *addr, size_t size) uvm_km_free(kernel_map, va, size); } +void +print_hex_dump(const char *level, const char *prefix_str, int prefix_type, + int rowsize, int groupsize, const void *buf, size_t len, bool ascii) +{ + const uint8_t *cbuf = buf; + int i; + + for (i = 0; i < len; i++) { + if ((i % rowsize) == 0) + printf("%s", prefix_str); + printf("%02x", cbuf[i]); + if ((i % rowsize) == (rowsize - 1)) + printf("\n"); + else + printf(" "); + } +} + +void * +memchr_inv(const void *s, int c, size_t n) +{ + if (n != 0) { + const unsigned char *p = s; + + do { + if (*p++ != (unsigned char)c) + return ((void *)(p - 1)); + }while (--n != 0); + } + return (NULL); +} + int panic_cmp(struct rb_node *a, struct rb_node *b) { @@ -291,7 +397,11 @@ idr_alloc(struct idr *idr, void *ptr, int start, int end, if (end <= 0) end = INT_MAX; +#ifdef notyet id->id = begin = start + arc4random_uniform(end - start); +#else + id->id = begin = start; +#endif while (SPLAY_INSERT(idr_tree, &idr->tree, id)) { if (++id->id == end) id->id = start; @@ -304,6 +414,21 @@ idr_alloc(struct idr *idr, void *ptr, int start, int end, return id->id; } +void * +idr_replace(struct idr *idr, void *ptr, int id) +{ + struct idr_entry find, *res; + void *old; + + find.id = id; + res = SPLAY_FIND(idr_tree, &idr->tree, &find); + if (res == NULL) + return ERR_PTR(-ENOENT); + old = res->ptr; + res->ptr = ptr; + return old; +} + void idr_remove(struct idr *idr, int id) { @@ -329,6 +454,22 @@ idr_find(struct idr *idr, int id) return res->ptr; } +void * +idr_get_next(struct idr *idr, int *id) +{ + struct idr_entry *res; + + res = idr_find(idr, *id); + if (res == NULL) + res = SPLAY_MIN(idr_tree, &idr->tree); + else + res = SPLAY_NEXT(idr_tree, &idr->tree, res); + if (res == NULL) + return NULL; + *id = res->id; + return res->ptr; +} + int idr_for_each(struct idr *idr, int (*func)(int, void *, void *), void *data) { @@ -352,6 +493,112 @@ idr_cmp(struct idr_entry *a, struct idr_entry *b) SPLAY_GENERATE(idr_tree, idr_entry, entry, idr_cmp); +void +ida_init(struct ida *ida) +{ + ida->counter = 0; +} + +void +ida_destroy(struct ida *ida) +{ +} + +void +ida_remove(struct ida *ida, int id) +{ +} + +int +ida_simple_get(struct ida *ida, unsigned int start, unsigned int end, + int flags) +{ + if (end <= 0) + end = INT_MAX; + + if (start > ida->counter) + ida->counter = start; + + if (ida->counter >= end) + return -ENOSPC; + + return ida->counter++; +} + +int +sg_alloc_table(struct sg_table *table, unsigned int nents, gfp_t gfp_mask) +{ + table->sgl = mallocarray(nents, sizeof(struct scatterlist), + M_DRM, gfp_mask); + if (table->sgl == NULL) + return -ENOMEM; + table->nents = table->orig_nents = nents; + return 0; +} + +void +sg_free_table(struct sg_table *table) +{ + free(table->sgl, M_DRM, + table->orig_nents * sizeof(struct scatterlist)); +} + +size_t +sg_copy_from_buffer(struct scatterlist *sgl, unsigned int nents, + const void *buf, size_t buflen) +{ + panic("%s", __func__); +} + +int +i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) +{ + void *cmd = NULL; + int cmdlen = 0; + int err, ret = 0; + int op; + + if (adap->algo) + return adap->algo->master_xfer(adap, msgs, num); + + iic_acquire_bus(&adap->ic, 0); + + while (num > 2) { + op = (msgs->flags & I2C_M_RD) ? I2C_OP_READ : I2C_OP_WRITE; + err = iic_exec(&adap->ic, op, msgs->addr, NULL, 0, + msgs->buf, msgs->len, 0); + if (err) { + ret = -err; + goto fail; + } + msgs++; + num--; + ret++; + } + + if (num > 1) { + cmd = msgs->buf; + cmdlen = msgs->len; + msgs++; + num--; + ret++; + } + + op = (msgs->flags & I2C_M_RD) ? I2C_OP_READ_WITH_STOP : I2C_OP_WRITE_WITH_STOP; + err = iic_exec(&adap->ic, op, msgs->addr, cmd, cmdlen, msgs->buf, msgs->len, 0); + if (err) { + ret = -err; + goto fail; + } + msgs++; + ret++; + +fail: + iic_release_bus(&adap->ic, 0); + + return ret; +} + #if defined(__amd64__) || defined(__i386__) /* diff --git a/sys/dev/pci/drm/drm_linux.h b/sys/dev/pci/drm/drm_linux.h index 637cd04e265..2c289e38d88 100644 --- a/sys/dev/pci/drm/drm_linux.h +++ b/sys/dev/pci/drm/drm_linux.h @@ -1,4 +1,4 @@ -/* $OpenBSD: drm_linux.h,v 1.50 2017/05/26 14:26:33 kettenis Exp $ */ +/* $OpenBSD: drm_linux.h,v 1.51 2017/07/01 16:14:10 kettenis Exp $ */ /* * Copyright (c) 2013, 2014, 2015 Mark Kettenis * @@ -15,8 +15,31 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#ifndef _DRM_LINUX_H_ +#define _DRM_LINUX_H_ + +#include #include +#include +#include +#include +#include +#include #include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include +#include /* The Linux code doesn't meet our usual standards! */ #ifdef __clang__ @@ -27,19 +50,68 @@ #pragma clang diagnostic ignored "-Wunused-const-variable" #endif -typedef int irqreturn_t; -#define IRQ_NONE 0 -#define IRQ_HANDLED 1 +struct i2c_algorithm; + +struct i2c_adapter { + struct i2c_controller ic; + + char name[48]; + const struct i2c_algorithm *algo; + void *algo_data; + int retries; + + void *data; +}; + +#define I2C_NAME_SIZE 20 + +struct i2c_msg { + uint16_t addr; + uint16_t flags; + uint16_t len; + uint8_t *buf; +}; + +#define I2C_M_RD 0x0001 +#define I2C_M_NOSTART 0x0002 + +struct i2c_algorithm { + int (*master_xfer)(struct i2c_adapter *, struct i2c_msg *, int); +}; + +int i2c_transfer(struct i2c_adapter *, struct i2c_msg *, int); +#define i2c_add_adapter(x) 0 +#define i2c_del_adapter(x) + +static inline void * +i2c_get_adapdata(struct i2c_adapter *adap) +{ + return adap->data; +} + +static inline void +i2c_set_adapdata(struct i2c_adapter *adap, void *data) +{ + adap->data = data; +} -typedef u_int64_t u64; -typedef u_int32_t u32; -typedef u_int16_t u16; -typedef u_int8_t u8; +typedef int irqreturn_t; +enum irqreturn { + IRQ_NONE = 0, + IRQ_HANDLED = 1 +}; -typedef int32_t s32; -typedef int64_t s64; +typedef int8_t s8; +typedef uint8_t u8; +typedef int16_t s16; +typedef uint16_t u16; +typedef int32_t s32; +typedef uint32_t u32; +typedef int64_t s64; +typedef uint64_t u64; -typedef uint64_t __u64; +#define U64_C(x) UINT64_C(x) +#define U64_MAX UINT64_MAX typedef uint16_t __le16; typedef uint16_t __be16; @@ -49,6 +121,8 @@ typedef uint32_t __be32; typedef bus_addr_t dma_addr_t; typedef bus_addr_t phys_addr_t; +typedef bus_addr_t resource_size_t; + typedef off_t loff_t; #define __force @@ -57,6 +131,9 @@ typedef off_t loff_t; #define __iomem #define __must_check #define __init +#define __exit + +#define __printf(x, y) #define barrier() __asm __volatile("" : : : "memory"); @@ -75,21 +152,143 @@ typedef off_t loff_t; #define be32_to_cpup(x) betoh32(*x) +static inline uint8_t +hweight8(uint32_t x) +{ + x = (x & 0x55) + ((x & 0xaa) >> 1); + x = (x & 0x33) + ((x & 0xcc) >> 2); + x = (x + (x >> 4)) & 0x0f; + return (x); +} + +static inline uint16_t +hweight16(uint32_t x) +{ + x = (x & 0x5555) + ((x & 0xaaaa) >> 1); + x = (x & 0x3333) + ((x & 0xcccc) >> 2); + x = (x + (x >> 4)) & 0x0f0f; + x = (x + (x >> 8)) & 0x00ff; + return (x); +} + +static inline uint32_t +hweight32(uint32_t x) +{ + x = (x & 0x55555555) + ((x & 0xaaaaaaaa) >> 1); + x = (x & 0x33333333) + ((x & 0xcccccccc) >> 2); + x = (x + (x >> 4)) & 0x0f0f0f0f; + x = (x + (x >> 8)); + x = (x + (x >> 16)) & 0x000000ff; + return x; +} + +static inline uint32_t +hweight64(uint64_t x) +{ + x = (x & 0x5555555555555555ULL) + ((x & 0xaaaaaaaaaaaaaaaaULL) >> 1); + x = (x & 0x3333333333333333ULL) + ((x & 0xccccccccccccccccULL) >> 2); + x = (x + (x >> 4)) & 0x0f0f0f0f0f0f0f0fULL; + x = (x + (x >> 8)); + x = (x + (x >> 16)); + x = (x + (x >> 32)) & 0x000000ff; + return x; +} + #define lower_32_bits(n) ((u32)(n)) #define upper_32_bits(_val) ((u32)(((_val) >> 16) >> 16)) #define DMA_BIT_MASK(n) (((n) == 64) ? ~0ULL : (1ULL<<(n)) -1) -#define BIT(x) (1 << x) +#define BIT(x) (1UL << x) #define BITS_TO_LONGS(x) howmany((x), 8 * sizeof(long)) +#define DECLARE_BITMAP(x, y) unsigned long x[BITS_TO_LONGS(y)]; +#define bitmap_empty(p, n) (find_first_bit(p, n) == n) +#define GENMASK(h, l) ((~0U >> (32 - h -1)) & (~0U << l)) + +static inline void +bitmap_set(void *p, int b, u_int n) +{ + u_int end = b + n; + + for (; b < end; b++) + __set_bit(b, p); +} + +static inline void +bitmap_zero(void *p, u_int n) +{ + u_int *ptr = p; + u_int b; + + for (b = 0; b < n; b += 32) + ptr[b >> 5] = 0; +} + +static inline void +bitmap_or(void *d, void *s1, void *s2, u_int n) +{ + u_int *dst = d; + u_int *src1 = s1; + u_int *src2 = s2; + u_int b; + + for (b = 0; b < n; b += 32) + dst[b >> 5] = src1[b >> 5] | src2[b >> 5]; +} + +static inline int +bitmap_weight(void *p, u_int n) +{ + u_int *ptr = p; + u_int b; + int sum = 0; + + for (b = 0; b < n; b += 32) + sum += hweight32(ptr[b >> 5]); + return sum; +} + +#define DECLARE_HASHTABLE(x, y) struct hlist_head x; + +#define hash_init(x) INIT_HLIST_HEAD(&(x)) +#define hash_add(x, y, z) hlist_add_head(y, &(x)) +#define hash_del(x) hlist_del_init(x) +#define hash_empty(x) hlist_empty(&(x)) +#define hash_for_each_possible(a, b, c, d) \ + hlist_for_each_entry(b, &(a), c) +#define hash_for_each_safe(a, b, c, d, e) (void)(b); \ + hlist_for_each_entry_safe(d, c, &(a), e) + #define ACCESS_ONCE(x) (x) #define EXPORT_SYMBOL(x) #define IS_ENABLED(x) x - 0 +#define IS_BUILTIN(x) 1 + +struct device_driver { + struct device *dev; +}; + +#define dev_get_drvdata(x) NULL +#define dev_set_drvdata(x, y) +#define dev_name(dev) "" + +#define devm_kzalloc(x, y, z) kzalloc(y, z) + +struct module; + +#define MODULE_AUTHOR(x) +#define MODULE_DESCRIPTION(x) +#define MODULE_LICENSE(x) #define MODULE_FIRMWARE(x) +#define MODULE_DEVICE_TABLE(x, y) #define MODULE_PARM_DESC(parm, desc) #define module_param_named(name, value, type, perm) +#define module_param_named_unsafe(name, value, type, perm) +#define module_param_unsafe(name, type, perm) + +#define THIS_MODULE NULL #define ARRAY_SIZE nitems @@ -98,16 +297,22 @@ typedef off_t loff_t; #define EREMOTEIO EIO #define EPROTO EIO #define ENOTSUPP ENOTSUP +#define ENODATA ENOTSUP +#define ECHRNG EINVAL -#define KERN_INFO -#define KERN_WARNING -#define KERN_NOTICE -#define KERN_DEBUG -#define KERN_CRIT -#define KERN_ERR +#define KERN_INFO "" +#define KERN_WARNING "" +#define KERN_NOTICE "" +#define KERN_DEBUG "" +#define KERN_CRIT "" +#define KERN_ERR "" #define KBUILD_MODNAME "drm" +#define UTS_RELEASE "" + +#define TASK_COMM_LEN (MAXCOMLEN + 1) + #ifndef pr_fmt #define pr_fmt(fmt) fmt #endif @@ -163,6 +368,17 @@ typedef off_t loff_t; do { } while(0) #endif +enum { + DUMP_PREFIX_NONE, + DUMP_PREFIX_ADDRESS, + DUMP_PREFIX_OFFSET +}; + +void print_hex_dump(const char *, const char *, int, int, int, + const void *, size_t, bool); + +#define scnprintf(str, size, fmt, arg...) snprintf(str, size, fmt, ## arg) + #define unlikely(x) __builtin_expect(!!(x), 0) #define likely(x) __builtin_expect(!!(x), 1) @@ -177,8 +393,10 @@ do { \ #define BUG_ON(x) KASSERT(!(x)) #endif +#define BUILD_BUG() #define BUILD_BUG_ON(x) CTASSERT(!(x)) #define BUILD_BUG_ON_NOT_POWER_OF_2(x) +#define BUILD_BUG_ON_MSG(x, y) #define WARN(condition, fmt...) ({ \ int __ret = !!(condition); \ @@ -223,6 +441,9 @@ do { \ #define DEFINE_EVENT(template, name, proto, args) \ static inline void trace_##name(proto) {} +#define DEFINE_EVENT_PRINT(template, name, proto, args, print) \ +static inline void trace_##name(proto) {} + #define TRACE_EVENT(name, proto, args, tstruct, assign, print) \ static inline void trace_##name(proto) {} @@ -258,6 +479,21 @@ IS_ERR_OR_NULL(const void *ptr) return !ptr || IS_ERR_VALUE((unsigned long)ptr); } +static inline void * +ERR_CAST(const void *ptr) +{ + return (void *)ptr; +} + +static inline int +PTR_ERR_OR_ZERO(const void *ptr) +{ + return IS_ERR(ptr)? PTR_ERR(ptr) : 0; +} + +#define swap(a, b) \ + do { __typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while(0) + #define container_of(ptr, type, member) ({ \ __typeof( ((type *)0)->member ) *__mptr = (ptr); \ (type *)( (char *)__mptr - offsetof(type,member) );}) @@ -300,9 +536,17 @@ spin_unlock_irqrestore(struct mutex *mtxp, __unused unsigned long flags) #define write_lock(rwl) rw_enter_write(rwl) #define write_unlock(rwl) rw_exit_write(rwl) +#define might_lock(lock) +#define lockdep_assert_held(lock) do { (void)(lock); } while(0) + #define local_irq_save(x) (x) = splhigh() #define local_irq_restore(x) splx((x)) +#define synchronize_irq(x) + +#define fence_wait(x, y) +#define fence_put(x) + struct wait_queue_head { struct mutex lock; unsigned int count; @@ -320,6 +564,7 @@ init_waitqueue_head(wait_queue_head_t *wq) do { \ struct sleep_state sls; \ \ + KASSERT(!cold); \ if (condition) \ break; \ atomic_inc_int(&(wq).count); \ @@ -333,6 +578,7 @@ do { \ struct sleep_state sls; \ int deadline, __error; \ \ + KASSERT(!cold); \ atomic_inc_int(&(wq).count); \ sleep_setup(&sls, &wq, 0, "drmwet"); \ sleep_setup_timeout(&sls, ret); \ @@ -360,8 +606,9 @@ do { \ #define __wait_event_interruptible_timeout(wq, condition, ret) \ do { \ struct sleep_state sls; \ - int deadline, __error, __error1; \ + int deadline, __error, __error1; \ \ + KASSERT(!cold); \ atomic_inc_int(&(wq).count); \ sleep_setup(&sls, &wq, PCATCH, "drmweti"); \ sleep_setup_timeout(&sls, ret); \ @@ -374,9 +621,9 @@ do { \ atomic_dec_int(&(wq).count); \ if (ret < 0 || __error1 == EWOULDBLOCK) \ ret = 0; \ - if (__error == ERESTART) \ + if (__error == ERESTART) \ ret = -ERESTARTSYS; \ - else if (__error) \ + else if (__error) \ ret = -__error; \ if (ret == 0 && (condition)) { \ ret = 1; \ @@ -441,6 +688,9 @@ complete_all(struct completion *x) struct workqueue_struct; +#define system_wq (struct workqueue_struct *)systq +#define system_long_wq (struct workqueue_struct *)systq + static inline struct workqueue_struct * alloc_ordered_workqueue(const char *name, int flags) { @@ -468,6 +718,8 @@ INIT_WORK(struct work_struct *work, work_func_t func) task_set(&work->task, (void (*)(void *))func, work); } +#define INIT_WORK_ONSTACK(x, y) INIT_WORK((x), (y)) + static inline bool queue_work(struct workqueue_struct *wq, struct work_struct *work) { @@ -553,20 +805,42 @@ cancel_delayed_work_sync(struct delayed_work *dwork) return task_del(dwork->tq, &dwork->work.task); } -#define flush_workqueue(x) -#define flush_scheduled_work(x) -#define flush_delayed_work(x) (void)(x) +void flush_workqueue(struct workqueue_struct *); +void flush_work(struct work_struct *); +void flush_delayed_work(struct delayed_work *); +#define flush_scheduled_work() flush_workqueue(system_wq) + +#define destroy_work_on_stack(x) + +typedef void *async_cookie_t; +#define async_schedule(func, data) (func)((data), NULL) + +#define local_irq_disable() disable_intr() +#define local_irq_enable() enable_intr() #define setup_timer(x, y, z) timeout_set((x), (void (*)(void *))(y), (void *)(z)) #define mod_timer(x, y) timeout_add((x), (y - jiffies)) +#define mod_timer_pinned(x, y) timeout_add((x), (y - jiffies)) #define del_timer_sync(x) timeout_del((x)) +#define timer_pending(x) timeout_pending((x)) + +#define cond_resched() sched_pause(yield) +#define drm_need_resched() \ + (curcpu()->ci_schedstate.spc_schedflags & SPCF_SHOULDYIELD) + +#define TASK_UNINTERRUPTIBLE 0 +#define TASK_INTERRUPTIBLE PCATCH + +#define signal_pending_state(x, y) CURSIG(curproc) #define NSEC_PER_USEC 1000L +#define NSEC_PER_MSEC 1000000L #define NSEC_PER_SEC 1000000000L #define KHZ2PICOS(a) (1000000000UL/(a)) extern struct timespec ns_to_timespec(const int64_t); extern int64_t timeval_to_ns(const struct timeval *); +extern int64_t timeval_to_us(const struct timeval *); extern struct timeval ns_to_timeval(const int64_t); static inline struct timespec @@ -600,7 +874,10 @@ round_jiffies_up_relative(unsigned long j) } #define jiffies_to_msecs(x) (((int64_t)(x)) * 1000 / hz) +#define jiffies_to_usecs(x) (((int64_t)(x)) * 1000000 / hz) #define msecs_to_jiffies(x) (((int64_t)(x)) * hz / 1000) +#define nsecs_to_jiffies64(x) (((int64_t)(x)) * hz / 1000000000) +#define get_jiffies_64() jiffies #define time_after(a,b) ((long)(b) - (long)(a) < 0) #define time_after_eq(a,b) ((long)(b) - (long)(a) <= 0) #define get_seconds() time_second @@ -663,12 +940,24 @@ ktime_get_monotonic_offset(void) return tv; } +static inline int64_t +ktime_to_us(struct timeval tv) +{ + return timeval_to_us(&tv); +} + static inline int64_t ktime_to_ns(struct timeval tv) { return timeval_to_ns(&tv); } +static inline int64_t +ktime_get_raw_ns(void) +{ + return ktime_to_ns(ktime_get()); +} + #define ktime_to_timeval(tv) (tv) static inline struct timeval @@ -691,12 +980,26 @@ ktime_sub_ns(struct timeval tv, int64_t ns) return ns_to_timeval(timeval_to_ns(&tv) - ns); } +static inline int64_t +ktime_us_delta(struct timeval a, struct timeval b) +{ + return ktime_to_us(ktime_sub(a, b)); +} + +#define ktime_mono_to_real(x) (x) +#define ktime_get_real() ktime_get() + +#define do_gettimeofday(tv) getmicrouptime(tv) + #define GFP_ATOMIC M_NOWAIT #define GFP_NOWAIT M_NOWAIT #define GFP_KERNEL (M_WAITOK | M_CANFAIL) #define GFP_TEMPORARY (M_WAITOK | M_CANFAIL) +#define GFP_HIGHUSER 0 +#define GFP_DMA32 0 #define __GFP_NOWARN 0 #define __GFP_NORETRY 0 +#define __GFP_ZERO M_ZERO static inline void * kmalloc(size_t size, int flags) @@ -727,9 +1030,9 @@ kzalloc(size_t size, int flags) } static inline void -kfree(void *objp) +kfree(const void *objp) { - free(objp, M_DRM, 0); + free((void *)objp, M_DRM, 0); } static inline void * @@ -741,6 +1044,27 @@ kmemdup(const void *src, size_t len, int flags) return (p); } +static inline char * +kasprintf(int flags, const char *fmt, ...) +{ + char *buf; + size_t len; + va_list ap; + + va_start(ap, fmt); + len = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + + buf = kmalloc(len, flags); + if (buf) { + va_start(ap, fmt); + vsnprintf(buf, len, fmt, ap); + va_end(ap); + } + + return buf; +} + static inline void * vzalloc(unsigned long size) { @@ -794,6 +1118,23 @@ kref_sub(struct kref *ref, unsigned int v, void (*release)(struct kref *ref)) release(ref); } +static inline int +kref_put_mutex(struct kref *kref, void (*release)(struct kref *kref), + struct rwlock *lock) +{ + if (!atomic_add_unless(&kref->refcount, -1, 1)) { + rw_enter_write(lock); + if (likely(atomic_dec_and_test(&kref->refcount))) { + release(kref); + return 1; + } + rw_exit_write(lock); + return 0; + } + + return 0; +} + struct kobject { struct kref kref; struct kobj_type *type; @@ -861,9 +1202,33 @@ void idr_preload(unsigned int); int idr_alloc(struct idr *, void *, int, int, unsigned int); #define idr_preload_end() void *idr_find(struct idr *, int); +void *idr_replace(struct idr *, void *ptr, int); void idr_remove(struct idr *, int); void idr_destroy(struct idr *); int idr_for_each(struct idr *, int (*)(int, void *, void *), void *); +void *idr_get_next(struct idr *, int *); + +#define idr_for_each_entry(idp, entry, id) \ + for (id = 0; ((entry) = idr_get_next(idp, &(id))) != NULL; id++) + + +struct ida { + int counter; +}; + +void ida_init(struct ida *); +void ida_destroy(struct ida *); +int ida_simple_get(struct ida *, unsigned int, unsigned nt, int); +void ida_remove(struct ida *, int); + +struct notifier_block { + void *notifier_call; +}; + +#define register_reboot_notifier(x) +#define unregister_reboot_notifier(x) + +#define SYS_RESTART 0 #define min_t(t, a, b) ({ \ t __min_a = (a); \ @@ -876,9 +1241,16 @@ int idr_for_each(struct idr *, int (*)(int, void *, void *), void *); __max_a > __max_b ? __max_a : __max_b; }) #define clamp_t(t, x, a, b) min_t(t, max_t(t, x, a), b) +#define clamp(x, a, b) clamp_t(__typeof(x), x, a, b) -#define do_div(n, base) \ - n = n / base +#define min3(x, y, z) MIN(x, MIN(y, z)) + +#define do_div(n, base) ({ \ + uint32_t __base = (base); \ + uint32_t __rem = ((uint64_t)(n)) % __base; \ + (n) = ((uint64_t)(n)) / __base; \ + __rem; \ +}) static inline uint64_t div_u64(uint64_t x, uint32_t y) @@ -886,12 +1258,25 @@ div_u64(uint64_t x, uint32_t y) return (x / y); } +static inline int64_t +div_s64(int64_t x, int64_t y) +{ + return (x / y); +} + static inline uint64_t div64_u64(uint64_t x, uint64_t y) { return (x / y); } +static inline uint64_t +div64_u64_rem(uint64_t x, uint64_t y, uint64_t *rem) +{ + *rem = x % y; + return (x / y); +} + static inline int64_t div64_s64(int64_t x, int64_t y) { @@ -937,28 +1322,8 @@ copy_from_user(void *to, const void *from, unsigned len) #define get_user(x, ptr) -copyin(ptr, &(x), sizeof(x)) #define put_user(x, ptr) -copyout(&(x), ptr, sizeof(x)) -static __inline uint16_t -hweight16(uint32_t x) -{ - x = (x & 0x5555) + ((x & 0xaaaa) >> 1); - x = (x & 0x3333) + ((x & 0xcccc) >> 2); - x = (x + (x >> 4)) & 0x0f0f; - x = (x + (x >> 8)) & 0x00ff; - return (x); -} - -static inline uint32_t -hweight32(uint32_t x) -{ - x = (x & 0x55555555) + ((x & 0xaaaaaaaa) >> 1); - x = (x & 0x33333333) + ((x & 0xcccccccc) >> 2); - x = (x + (x >> 4)) & 0x0f0f0f0f; - x = (x + (x >> 8)); - x = (x + (x >> 16)) & 0x000000ff; - return x; -} - #define console_lock() +#define console_trylock() 1 #define console_unlock() #ifndef PCI_MEM_START @@ -1011,6 +1376,7 @@ struct resource { }; struct pci_bus { + pci_chipset_tag_t pc; unsigned char number; }; @@ -1023,10 +1389,14 @@ struct pci_dev { uint16_t device; uint16_t subsystem_vendor; uint16_t subsystem_device; + uint8_t revision; pci_chipset_tag_t pc; pcitag_t tag; struct pci_softc *pci; + + int irq; + int msi_enabled; }; #define PCI_ANY_ID (uint16_t) (~0U) @@ -1045,37 +1415,44 @@ struct pci_dev { #define PCI_SLOT(devfn) ((devfn) >> 3) #define PCI_FUNC(devfn) ((devfn) & 0x7) -static inline void +#define pci_dev_put(x) + + +static inline int pci_read_config_dword(struct pci_dev *pdev, int reg, u32 *val) { *val = pci_conf_read(pdev->pc, pdev->tag, reg); + return 0; } -static inline void +static inline int pci_read_config_word(struct pci_dev *pdev, int reg, u16 *val) { uint32_t v; v = pci_conf_read(pdev->pc, pdev->tag, (reg & ~0x2)); *val = (v >> ((reg & 0x2) * 8)); + return 0; } -static inline void +static inline int pci_read_config_byte(struct pci_dev *pdev, int reg, u8 *val) { uint32_t v; v = pci_conf_read(pdev->pc, pdev->tag, (reg & ~0x3)); *val = (v >> ((reg & 0x3) * 8)); + return 0; } -static inline void +static inline int pci_write_config_dword(struct pci_dev *pdev, int reg, u32 val) { pci_conf_write(pdev->pc, pdev->tag, reg, val); + return 0; } -static inline void +static inline int pci_write_config_word(struct pci_dev *pdev, int reg, u16 val) { uint32_t v; @@ -1084,9 +1461,10 @@ pci_write_config_word(struct pci_dev *pdev, int reg, u16 val) v &= ~(0xffff << ((reg & 0x2) * 8)); v |= (val << ((reg & 0x2) * 8)); pci_conf_write(pdev->pc, pdev->tag, (reg & ~0x2), v); + return 0; } -static inline void +static inline int pci_write_config_byte(struct pci_dev *pdev, int reg, u8 val) { uint32_t v; @@ -1095,8 +1473,40 @@ pci_write_config_byte(struct pci_dev *pdev, int reg, u8 val) v &= ~(0xff << ((reg & 0x3) * 8)); v |= (val << ((reg & 0x3) * 8)); pci_conf_write(pdev->pc, pdev->tag, (reg & ~0x3), v); + return 0; +} + +static inline int +pci_bus_read_config_word(struct pci_bus *bus, unsigned int devfn, + int reg, u16 *val) +{ + pcitag_t tag = pci_make_tag(bus->pc, bus->number, + PCI_SLOT(devfn), PCI_FUNC(devfn)); + uint32_t v; + + v = pci_conf_read(bus->pc, tag, (reg & ~0x2)); + *val = (v >> ((reg & 0x2) * 8)); + return 0; +} + +static inline int +pci_bus_read_config_byte(struct pci_bus *bus, unsigned int devfn, + int reg, u8 *val) +{ + pcitag_t tag = pci_make_tag(bus->pc, bus->number, + PCI_SLOT(devfn), PCI_FUNC(devfn)); + uint32_t v; + + v = pci_conf_read(bus->pc, tag, (reg & ~0x3)); + *val = (v >> ((reg & 0x3) * 8)); + return 0; } +#define pci_set_master(x) + +#define pci_enable_msi(x) +#define pci_disable_msi(x) + typedef enum { PCI_D0, PCI_D1, @@ -1105,8 +1515,15 @@ typedef enum { PCI_D3cold } pci_power_t; +#define pci_save_state(x) +#define pci_enable_device(x) 0 +#define pci_disable_device(x) + #if defined(__amd64__) || defined(__i386__) +#define AGP_USER_MEMORY 0 +#define AGP_USER_CACHED_MEMORY BUS_DMA_COHERENT + #define PCI_DMA_BIDIRECTIONAL 0 static inline dma_addr_t @@ -1126,11 +1543,23 @@ pci_dma_mapping_error(struct pci_dev *pdev, dma_addr_t dma_addr) return 0; } +#define dma_set_coherent_mask(x, y) + #define VGA_RSRC_LEGACY_IO 0x01 void vga_get_uninterruptible(struct pci_dev *, int); void vga_put(struct pci_dev *, int); +static inline int +vga_client_register(struct pci_dev *a, void *b, void *c, void *d) +{ + return -ENODEV; +} + +#define vga_switcheroo_register_client(a, b, c) 0 +#define vga_switcheroo_unregister_client(a) +#define vga_switcheroo_process_delayed_switch() + #endif #define memcpy_toio(d, s, n) memcpy(d, s, n) @@ -1195,6 +1624,7 @@ void vunmap(void *, size_t); #define DIV_ROUND_UP(x, y) (((x) + ((y) - 1)) / (y)) #define DIV_ROUND_UP_ULL(x, y) DIV_ROUND_UP(x, y) #define DIV_ROUND_CLOSEST(x, y) (((x) + ((y) / 2)) / (y)) +#define DIV_ROUND_CLOSEST_ULL(x, y) DIV_ROUND_CLOSEST(x, y) static inline unsigned long roundup_pow_of_two(unsigned long x) @@ -1233,6 +1663,20 @@ mdelay(unsigned long msecs) DELAY(1000); } +static __inline void +cpu_relax(void) +{ + CPU_BUSY_CYCLE(); + if (cold) { + delay(tick); + jiffies++; + } +} + +#define cpu_relax_lowlatency() CPU_BUSY_CYCLE() +#define cpu_has_pat 1 +#define cpu_has_clflush 1 + static inline uint32_t ror32(uint32_t word, unsigned int shift) { return (word >> shift) | (word << (32 - shift)); @@ -1262,6 +1706,9 @@ power_supply_is_system_supplied(void) return (1); } +#define pm_qos_update_request(x, y) +#define pm_qos_remove_request(x) + #define _U 0x01 #define _L 0x02 #define _N 0x04 @@ -1296,6 +1743,8 @@ of_machine_is_compatible(const char *model) } #endif +typedef unsigned int gfp_t; + struct vm_page *alloc_pages(unsigned int, unsigned int); void __free_pages(struct vm_page *, unsigned int); @@ -1319,6 +1768,12 @@ get_order(size_t size) #if defined(__i386__) || defined(__amd64__) +#define _PAGE_PRESENT PG_V +#define _PAGE_RW PG_RW +#define _PAGE_PAT PG_PAT +#define _PAGE_PWT PG_WT +#define _PAGE_PCD PG_N + static inline void pagefault_disable(void) { @@ -1334,7 +1789,7 @@ pagefault_enable(void) } static inline int -in_atomic(void) +pagefault_disabled(void) { return curcpu()->ci_inatomic; } @@ -1409,6 +1864,15 @@ struct fb_info { void *par; }; +#define FB_BLANK_UNBLANK 0 +#define FB_BLANK_NORMAL 1 +#define FB_BLANK_HSYNC_SUSPEND 2 +#define FB_BLANK_VSYNC_SUSPEND 3 +#define FB_BLANK_POWERDOWN 4 + +#define FBINFO_STATE_RUNNING 0 +#define FBINFO_STATE_SUSPENDED 1 + #define framebuffer_alloc(flags, device) \ kzalloc(sizeof(struct fb_info), GFP_KERNEL) @@ -1429,3 +1893,150 @@ struct acpi_table_header; #define AE_NOT_FOUND 0x0005 acpi_status acpi_get_table_with_size(const char *, int, struct acpi_table_header **, acpi_size *); + +#define acpi_video_register() +#define acpi_video_unregister() + +#define MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM 0x03 +#define MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM 0x13 +#define MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM 0x23 +#define MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM 0x04 +#define MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM 0x14 +#define MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM 0x24 +#define MIPI_DSI_DCS_SHORT_WRITE 0x05 +#define MIPI_DSI_DCS_SHORT_WRITE_PARAM 0x15 +#define MIPI_DSI_DCS_READ 0x06 +#define MIPI_DSI_GENERIC_LONG_WRITE 0x29 +#define MIPI_DSI_DCS_LONG_WRITE 0x39 + +struct pwm_device; + +static inline struct pwm_device * +pwm_get(struct device *dev, const char *consumer) +{ + return ERR_PTR(-ENODEV); +} + +static inline void +pwm_put(struct pwm_device *pwm) +{ +} + +static inline unsigned int +pwm_get_duty_cycle(const struct pwm_device *pwm) +{ + return 0; +} + +static inline int +pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) +{ + return -EINVAL; +} + +static inline int +pwm_enable(struct pwm_device *pwm) +{ + return -EINVAL; +} + +static inline void +pwm_disable(struct pwm_device *pwm) +{ +} + +struct scatterlist { + dma_addr_t dma_address; + unsigned int offset; + unsigned int length; +}; + +struct sg_table { + struct scatterlist *sgl; + unsigned int nents; + unsigned int orig_nents; +}; + +struct sg_page_iter { + struct scatterlist *sg; + unsigned int sg_pgoffset; + unsigned int __nents; +}; + +int sg_alloc_table(struct sg_table *, unsigned int, gfp_t); +void sg_free_table(struct sg_table *); + +#define sg_mark_end(x) + +static __inline void +__sg_page_iter_start(struct sg_page_iter *iter, struct scatterlist *sgl, + unsigned int nents, unsigned long pgoffset) +{ + iter->sg = sgl; + iter->sg_pgoffset = pgoffset - 1; + iter->__nents = nents; +} + +static inline bool +__sg_page_iter_next(struct sg_page_iter *iter) +{ + iter->sg_pgoffset++; + while (iter->__nents > 0 && + iter->sg_pgoffset >= (iter->sg->length / PAGE_SIZE)) { + iter->sg_pgoffset -= (iter->sg->length / PAGE_SIZE); + iter->sg++; + iter->__nents--; + } + + return (iter->__nents > 0); +} + +static inline paddr_t +sg_page_iter_dma_address(struct sg_page_iter *iter) +{ + return iter->sg->dma_address + (iter->sg_pgoffset << PAGE_SHIFT); +} + +static inline struct vm_page * +sg_page_iter_page(struct sg_page_iter *iter) +{ + return PHYS_TO_VM_PAGE(sg_page_iter_dma_address(iter)); +} + +static inline struct vm_page * +sg_page(struct scatterlist *sgl) +{ + return PHYS_TO_VM_PAGE(sgl->dma_address); +} + +#define sg_dma_address(sg) ((sg)->dma_address) +#define sg_dma_len(sg) ((sg)->length) + +#define for_each_sg_page(sgl, iter, nents, pgoffset) \ + __sg_page_iter_start((iter), (sgl), (nents), (pgoffset)); \ + while (__sg_page_iter_next(iter)) + +size_t sg_copy_from_buffer(struct scatterlist *, unsigned int, + const void *, size_t); + +struct firmware { + const u8 *data; +}; + +static inline int +request_firmware(const struct firmware **fw, const char *name, + struct device *device) +{ + return -EINVAL; +} + +#define request_firmware_nowait(a, b, c, d, e, f, g) -EINVAL + +static inline void +release_firmware(const struct firmware *fw) +{ +} + +void *memchr_inv(const void *, int, size_t); + +#endif diff --git a/sys/dev/pci/drm/drm_linux_atomic.h b/sys/dev/pci/drm/drm_linux_atomic.h new file mode 100644 index 00000000000..8f23b37b2f6 --- /dev/null +++ b/sys/dev/pci/drm/drm_linux_atomic.h @@ -0,0 +1,293 @@ +/* $OpenBSD: drm_linux_atomic.h,v 1.1 2017/07/01 16:14:10 kettenis Exp $ */ +/** + * \file drm_atomic.h + * Atomic operations used in the DRM which may or may not be provided by the OS. + * + * \author Eric Anholt + */ + +/*- + * Copyright 2004 Eric Anholt + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * VA LINUX SYSTEMS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef _DRM_LINUX_ATOMIC_H_ +#define _DRM_LINUX_ATOMIC_H_ + +#include + +typedef uint32_t atomic_t; + +#define atomic_set(p, v) (*(p) = (v)) +#define atomic_read(p) (*(p)) +#define atomic_inc(p) __sync_fetch_and_add(p, 1) +#define atomic_dec(p) __sync_fetch_and_sub(p, 1) +#define atomic_add(n, p) __sync_fetch_and_add(p, n) +#define atomic_sub(n, p) __sync_fetch_and_sub(p, n) +#define atomic_add_return(n, p) __sync_add_and_fetch(p, n) +#define atomic_sub_return(n, p) __sync_sub_and_fetch(p, n) +#define atomic_inc_return(v) atomic_add_return(1, (v)) +#define atomic_dec_return(v) atomic_sub_return(1, (v)) +#define atomic_dec_and_test(v) (atomic_dec_return(v) == 0) +#define atomic_inc_and_test(v) (atomic_inc_return(v) == 0) +#define atomic_or(n, p) atomic_setbits_int(p, n) + +static __inline int +atomic_xchg(volatile int *v, int n) +{ + __sync_synchronize(); + return __sync_lock_test_and_set(v, n); +} + +static __inline int +atomic_add_unless(volatile int *v, int n, int u) +{ + int o = *v; + + do { + o = *v; + if (o == u) + return 0; + } while (__sync_val_compare_and_swap(v, o, o +n) != o); + + return 1; +} + +#define atomic_long_read(p) (*(p)) + +#ifdef __LP64__ +typedef uint64_t atomic64_t; + +#define atomic64_set(p, v) (*(p) = (v)) +#define atomic64_read(p) (*(p)) + +static __inline int64_t +atomic64_xchg(volatile int64_t *v, int64_t n) +{ + __sync_synchronize(); + return __sync_lock_test_and_set(v, n); +} + +#else + +typedef struct { + volatile uint64_t val; + struct mutex lock; +} atomic64_t; + +static __inline void +atomic64_set(atomic64_t *v, int64_t i) +{ + mtx_init(&v->lock, IPL_HIGH); + v->val = i; +} + +static __inline int64_t +atomic64_read(atomic64_t *v) +{ + int64_t val; + + mtx_enter(&v->lock); + val = v->val; + mtx_leave(&v->lock); + + return val; +} + +static __inline int64_t +atomic64_xchg(atomic64_t *v, int64_t n) +{ + int64_t val; + + mtx_enter(&v->lock); + val = v->val; + v->val = n; + mtx_leave(&v->lock); + + return val; +} +#endif + +static inline int +atomic_inc_not_zero(atomic_t *p) +{ + if (*p == 0) + return (0); + + *(p) += 1; + return (*p); +} + +/* FIXME */ +#define atomic_set_int(p, bits) atomic_setbits_int(p,bits) +#define atomic_set_mask(bits, p) atomic_setbits_int(p,bits) +#define atomic_clear_int(p, bits) atomic_clearbits_int(p,bits) +#define atomic_clear_mask(bits, p) atomic_clearbits_int(p,bits) +#define atomic_fetchadd_int(p, n) __sync_fetch_and_add(p, n) +#define atomic_fetchsub_int(p, n) __sync_fetch_and_sub(p, n) + +#if defined(__i386__) || defined(__amd64__) +static __inline int +atomic_cmpset_int(volatile u_int *dst, u_int exp, u_int src) +{ + int res = exp; + + __asm __volatile ( + " lock ; " + " cmpxchgl %1,%2 ; " + " setz %%al ; " + " movzbl %%al,%0 ; " + "1: " + "# atomic_cmpset_int" + : "+a" (res) /* 0 (result) */ + : "r" (src), /* 1 */ + "m" (*(dst)) /* 2 */ + : "memory"); + + return (res); +} +#else /* __i386__ */ +static __inline int +atomic_cmpset_int(__volatile__ u_int *dst, u_int old, u_int new) +{ + int s = splhigh(); + if (*dst==old) { + *dst = new; + splx(s); + return 1; + } + splx(s); + return 0; +} +#endif /* !__i386__ */ + +static __inline atomic_t +test_and_set_bit(u_int b, volatile void *p) +{ + int s = splhigh(); + unsigned int m = 1<> 5), 1 << (b & 0x1f)); +} + +static __inline void +set_bit(u_int b, volatile void *p) +{ + atomic_set_int(((volatile u_int *)p) + (b >> 5), 1 << (b & 0x1f)); +} + +static __inline void +__clear_bit(u_int b, volatile void *p) +{ + volatile u_int *ptr = (volatile u_int *)p; + ptr[b >> 5] &= ~(1 << (b & 0x1f)); +} + +static __inline void +__set_bit(u_int b, volatile void *p) +{ + volatile u_int *ptr = (volatile u_int *)p; + ptr[b >> 5] |= (1 << (b & 0x1f)); +} + +static __inline int +test_bit(u_int b, volatile void *p) +{ + return !!(((volatile u_int *)p)[b >> 5] & (1 << (b & 0x1f))); +} + +static __inline int +__test_and_clear_bit(u_int b, volatile void *p) +{ + volatile u_int *ptr = (volatile u_int *)p; + int rv = !!(ptr[b >> 5] & (1 << (b & 0x1f))); + ptr[b >> 5] &= ~(1 << (b & 0x1f)); + return rv; +} + +static __inline int +find_first_zero_bit(volatile void *p, int max) +{ + int b; + volatile u_int *ptr = (volatile u_int *)p; + + for (b = 0; b < max; b += 32) { + if (ptr[b >> 5] != ~0) { + for (;;) { + if ((ptr[b >> 5] & (1 << (b & 0x1f))) == 0) + return b; + b++; + } + } + } + return max; +} + +static __inline int +find_first_bit(volatile void *p, int max) +{ + int b; + volatile u_int *ptr = (volatile u_int *)p; + + for (b = 0; b < max; b += 32) { + if (ptr[b >> 5] != 0) { + for (;;) { + if (ptr[b >> 5] & (1 << (b & 0x1f))) + return b; + b++; + } + } + } + return max; +} + +static __inline int +find_next_bit(volatile void *p, int max, int b) +{ + volatile u_int *ptr = (volatile u_int *)p; + + for (; b < max; b+= 32) { + if (ptr[b >> 5] != 0) { + for (;;) { + if (ptr[b >> 5] & (1 << (b & 0x1f))) + return b; + b++; + } + } + } + return max; +} + +#define for_each_set_bit(b, p, max) \ + for ((b) = find_first_bit((p), (max)); \ + (b) < (max); \ + (b) = find_next_bit((p), (max), (b) + 1)) + +#endif diff --git a/sys/dev/pci/drm/drm_linux_list.h b/sys/dev/pci/drm/drm_linux_list.h index 3b0c359e89e..7c5af7f707a 100644 --- a/sys/dev/pci/drm/drm_linux_list.h +++ b/sys/dev/pci/drm/drm_linux_list.h @@ -1,4 +1,4 @@ -/* $OpenBSD: drm_linux_list.h,v 1.6 2015/09/23 23:12:11 kettenis Exp $ */ +/* $OpenBSD: drm_linux_list.h,v 1.7 2017/07/01 16:14:10 kettenis Exp $ */ /* drm_linux_list.h -- linux list functions for the BSDs. * Created: Mon Apr 7 14:30:16 1999 by anholt@FreeBSD.org */ @@ -37,7 +37,7 @@ struct list_head { struct list_head *next, *prev; }; -#define list_entry(ptr, type, member) container_of(ptr,type,member) +#define list_entry(ptr, type, member) container_of(ptr, type, member) static inline void INIT_LIST_HEAD(struct list_head *head) { @@ -91,6 +91,13 @@ static inline void list_replace(struct list_head *old, new->prev->next = new; } +static inline void list_replace_init(struct list_head *old, + struct list_head *new) +{ + list_replace(old, new); + INIT_LIST_HEAD(old); +} + static inline void list_move(struct list_head *list, struct list_head *head) { list_del(list); @@ -111,6 +118,9 @@ list_del_init(struct list_head *entry) { INIT_LIST_HEAD(entry); } +#define list_next_entry(pos, member) \ + list_entry(((pos)->member.next), typeof(*(pos)), member) + #define list_for_each(entry, head) \ for (entry = (head)->next; entry != head; entry = (entry)->next) @@ -133,6 +143,11 @@ list_del_init(struct list_head *entry) { &pos->member != (head); \ pos = list_entry(pos->member.next, __typeof(*pos), member)) +#define list_for_each_entry_continue(pos, head, member) \ + for (pos = list_entry((pos)->member.next, __typeof(*pos), member); \ + &pos->member != (head); \ + pos = list_entry(pos->member.next, __typeof(*pos), member)) + #define list_for_each_entry_continue_reverse(pos, head, member) \ for (pos = list_entry(pos->member.prev, __typeof(*pos), member); \ &pos->member != (head); \ @@ -154,6 +169,11 @@ list_del_init(struct list_head *entry) { #define list_first_entry(ptr, type, member) \ list_entry((ptr)->next, type, member) +#define list_first_entry_or_null(ptr, type, member) \ + (list_empty(ptr) ? NULL : list_first_entry(ptr, type, member)) + +#define list_last_entry(ptr, type, member) \ + list_entry((ptr)->prev, type, member) static inline void __list_splice(const struct list_head *list, struct list_head *prev, @@ -187,18 +207,60 @@ list_splice_tail(const struct list_head *list, struct list_head *head) __list_splice(list, head->prev, head); } -void drm_list_sort(void *priv, struct list_head *head, int (*cmp)(void *priv, - struct list_head *a, struct list_head *b)); +void list_sort(void *, struct list_head *, + int (*)(void *, struct list_head *, struct list_head *)); struct hlist_node { - LIST_ENTRY(hlist_node) link; + struct hlist_node *next, **prev; +}; + +struct hlist_head { + struct hlist_node *first; }; -LIST_HEAD(hlist_head, hlist_node); +#define hlist_entry(ptr, type, member) \ + ((ptr) ? container_of(ptr, type, member) : NULL) + +static inline void +INIT_HLIST_HEAD(struct hlist_head *head) { + head->first = NULL; +} + +static inline int +hlist_empty(const struct hlist_head *head) { + return head->first == NULL; +} + +static inline void +hlist_add_head(struct hlist_node *new, struct hlist_head *head) +{ + if ((new->next = head->first) != NULL) + head->first->prev = &new->next; + head->first = new; + new->prev = &head->first; +} + +static inline void +hlist_del_init(struct hlist_node *node) +{ + if (node->next != NULL) + node->next->prev = node->prev; + *(node->prev) = node->next; + node->next = NULL; + node->prev = NULL; +} + +#define hlist_for_each(pos, head) \ + for (pos = (head)->first; pos != NULL; pos = pos->next) -#define hlist_entry(ptr, type, member) container_of(ptr,type,member) +#define hlist_for_each_entry(pos, head, member) \ + for (pos = hlist_entry((head)->first, __typeof(*pos), member); \ + pos != NULL; \ + pos = hlist_entry((pos)->member.next, __typeof(*pos), member)) -#define hlist_add_head(elm, head) LIST_INSERT_HEAD(head, elm, link) -#define hlist_for_each(var, head) LIST_FOREACH(var, head, link) +#define hlist_for_each_entry_safe(pos, n, head, member) \ + for (pos = hlist_entry((head)->first, __typeof(*pos), member); \ + pos != NULL && (n = pos->member.next, 1); \ + pos = hlist_entry(n, __typeof(*pos), member)) #endif /* _DRM_LINUX_LIST_H_ */ diff --git a/sys/dev/pci/drm/drm_mipi_dsi.h b/sys/dev/pci/drm/drm_mipi_dsi.h new file mode 100644 index 00000000000..f1d8d0dbb4f --- /dev/null +++ b/sys/dev/pci/drm/drm_mipi_dsi.h @@ -0,0 +1,259 @@ +/* + * MIPI DSI Bus + * + * Copyright (C) 2012-2013, Samsung Electronics, Co., Ltd. + * Andrzej Hajda + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef __DRM_MIPI_DSI_H__ +#define __DRM_MIPI_DSI_H__ + +#include + +struct mipi_dsi_host; +struct mipi_dsi_device; + +/* request ACK from peripheral */ +#define MIPI_DSI_MSG_REQ_ACK BIT(0) +/* use Low Power Mode to transmit message */ +#define MIPI_DSI_MSG_USE_LPM BIT(1) + +/** + * struct mipi_dsi_msg - read/write DSI buffer + * @channel: virtual channel id + * @type: payload data type + * @flags: flags controlling this message transmission + * @tx_len: length of @tx_buf + * @tx_buf: data to be written + * @rx_len: length of @rx_buf + * @rx_buf: data to be read, or NULL + */ +struct mipi_dsi_msg { + u8 channel; + u8 type; + u16 flags; + + size_t tx_len; + const void *tx_buf; + + size_t rx_len; + void *rx_buf; +}; + +bool mipi_dsi_packet_format_is_short(u8 type); +bool mipi_dsi_packet_format_is_long(u8 type); + +/** + * struct mipi_dsi_packet - represents a MIPI DSI packet in protocol format + * @size: size (in bytes) of the packet + * @header: the four bytes that make up the header (Data ID, Word Count or + * Packet Data, and ECC) + * @payload_length: number of bytes in the payload + * @payload: a pointer to a buffer containing the payload, if any + */ +struct mipi_dsi_packet { + size_t size; + u8 header[4]; + size_t payload_length; + const u8 *payload; +}; + +int mipi_dsi_create_packet(struct mipi_dsi_packet *packet, + const struct mipi_dsi_msg *msg); + +/** + * struct mipi_dsi_host_ops - DSI bus operations + * @attach: attach DSI device to DSI host + * @detach: detach DSI device from DSI host + * @transfer: transmit a DSI packet + * + * DSI packets transmitted by .transfer() are passed in as mipi_dsi_msg + * structures. This structure contains information about the type of packet + * being transmitted as well as the transmit and receive buffers. When an + * error is encountered during transmission, this function will return a + * negative error code. On success it shall return the number of bytes + * transmitted for write packets or the number of bytes received for read + * packets. + * + * Note that typically DSI packet transmission is atomic, so the .transfer() + * function will seldomly return anything other than the number of bytes + * contained in the transmit buffer on success. + */ +struct mipi_dsi_host_ops { + int (*attach)(struct mipi_dsi_host *host, + struct mipi_dsi_device *dsi); + int (*detach)(struct mipi_dsi_host *host, + struct mipi_dsi_device *dsi); + ssize_t (*transfer)(struct mipi_dsi_host *host, + const struct mipi_dsi_msg *msg); +}; + +/** + * struct mipi_dsi_host - DSI host device + * @dev: driver model device node for this DSI host + * @ops: DSI host operations + */ +struct mipi_dsi_host { + struct device *dev; + const struct mipi_dsi_host_ops *ops; +}; + +int mipi_dsi_host_register(struct mipi_dsi_host *host); +void mipi_dsi_host_unregister(struct mipi_dsi_host *host); + +/* DSI mode flags */ + +/* video mode */ +#define MIPI_DSI_MODE_VIDEO BIT(0) +/* video burst mode */ +#define MIPI_DSI_MODE_VIDEO_BURST BIT(1) +/* video pulse mode */ +#define MIPI_DSI_MODE_VIDEO_SYNC_PULSE BIT(2) +/* enable auto vertical count mode */ +#define MIPI_DSI_MODE_VIDEO_AUTO_VERT BIT(3) +/* enable hsync-end packets in vsync-pulse and v-porch area */ +#define MIPI_DSI_MODE_VIDEO_HSE BIT(4) +/* disable hfront-porch area */ +#define MIPI_DSI_MODE_VIDEO_HFP BIT(5) +/* disable hback-porch area */ +#define MIPI_DSI_MODE_VIDEO_HBP BIT(6) +/* disable hsync-active area */ +#define MIPI_DSI_MODE_VIDEO_HSA BIT(7) +/* flush display FIFO on vsync pulse */ +#define MIPI_DSI_MODE_VSYNC_FLUSH BIT(8) +/* disable EoT packets in HS mode */ +#define MIPI_DSI_MODE_EOT_PACKET BIT(9) +/* device supports non-continuous clock behavior (DSI spec 5.6.1) */ +#define MIPI_DSI_CLOCK_NON_CONTINUOUS BIT(10) +/* transmit data in low power */ +#define MIPI_DSI_MODE_LPM BIT(11) + +enum mipi_dsi_pixel_format { + MIPI_DSI_FMT_RGB888, + MIPI_DSI_FMT_RGB666, + MIPI_DSI_FMT_RGB666_PACKED, + MIPI_DSI_FMT_RGB565, +}; + +/** + * struct mipi_dsi_device - DSI peripheral device + * @host: DSI host for this peripheral + * @dev: driver model device node for this peripheral + * @channel: virtual channel assigned to the peripheral + * @format: pixel format for video mode + * @lanes: number of active data lanes + * @mode_flags: DSI operation mode related flags + */ +struct mipi_dsi_device { + struct mipi_dsi_host *host; + struct device dev; + + unsigned int channel; + unsigned int lanes; + enum mipi_dsi_pixel_format format; + unsigned long mode_flags; +}; + +static inline struct mipi_dsi_device *to_mipi_dsi_device(struct device *dev) +{ + return container_of(dev, struct mipi_dsi_device, dev); +} + +struct mipi_dsi_device *of_find_mipi_dsi_device_by_node(struct device_node *np); +int mipi_dsi_attach(struct mipi_dsi_device *dsi); +int mipi_dsi_detach(struct mipi_dsi_device *dsi); +int mipi_dsi_set_maximum_return_packet_size(struct mipi_dsi_device *dsi, + u16 value); + +ssize_t mipi_dsi_generic_write(struct mipi_dsi_device *dsi, const void *payload, + size_t size); +ssize_t mipi_dsi_generic_read(struct mipi_dsi_device *dsi, const void *params, + size_t num_params, void *data, size_t size); + +/** + * enum mipi_dsi_dcs_tear_mode - Tearing Effect Output Line mode + * @MIPI_DSI_DCS_TEAR_MODE_VBLANK: the TE output line consists of V-Blanking + * information only + * @MIPI_DSI_DCS_TEAR_MODE_VHBLANK : the TE output line consists of both + * V-Blanking and H-Blanking information + */ +enum mipi_dsi_dcs_tear_mode { + MIPI_DSI_DCS_TEAR_MODE_VBLANK, + MIPI_DSI_DCS_TEAR_MODE_VHBLANK, +}; + +#define MIPI_DSI_DCS_POWER_MODE_DISPLAY (1 << 2) +#define MIPI_DSI_DCS_POWER_MODE_NORMAL (1 << 3) +#define MIPI_DSI_DCS_POWER_MODE_SLEEP (1 << 4) +#define MIPI_DSI_DCS_POWER_MODE_PARTIAL (1 << 5) +#define MIPI_DSI_DCS_POWER_MODE_IDLE (1 << 6) + +ssize_t mipi_dsi_dcs_write_buffer(struct mipi_dsi_device *dsi, + const void *data, size_t len); +ssize_t mipi_dsi_dcs_write(struct mipi_dsi_device *dsi, u8 cmd, + const void *data, size_t len); +ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dsi, u8 cmd, void *data, + size_t len); +int mipi_dsi_dcs_nop(struct mipi_dsi_device *dsi); +int mipi_dsi_dcs_soft_reset(struct mipi_dsi_device *dsi); +int mipi_dsi_dcs_get_power_mode(struct mipi_dsi_device *dsi, u8 *mode); +int mipi_dsi_dcs_get_pixel_format(struct mipi_dsi_device *dsi, u8 *format); +int mipi_dsi_dcs_enter_sleep_mode(struct mipi_dsi_device *dsi); +int mipi_dsi_dcs_exit_sleep_mode(struct mipi_dsi_device *dsi); +int mipi_dsi_dcs_set_display_off(struct mipi_dsi_device *dsi); +int mipi_dsi_dcs_set_display_on(struct mipi_dsi_device *dsi); +int mipi_dsi_dcs_set_column_address(struct mipi_dsi_device *dsi, u16 start, + u16 end); +int mipi_dsi_dcs_set_page_address(struct mipi_dsi_device *dsi, u16 start, + u16 end); +int mipi_dsi_dcs_set_tear_off(struct mipi_dsi_device *dsi); +int mipi_dsi_dcs_set_tear_on(struct mipi_dsi_device *dsi, + enum mipi_dsi_dcs_tear_mode mode); +int mipi_dsi_dcs_set_pixel_format(struct mipi_dsi_device *dsi, u8 format); + +/** + * struct mipi_dsi_driver - DSI driver + * @driver: device driver model driver + * @probe: callback for device binding + * @remove: callback for device unbinding + * @shutdown: called at shutdown time to quiesce the device + */ +struct mipi_dsi_driver { + struct device_driver driver; + int(*probe)(struct mipi_dsi_device *dsi); + int(*remove)(struct mipi_dsi_device *dsi); + void (*shutdown)(struct mipi_dsi_device *dsi); +}; + +static inline struct mipi_dsi_driver * +to_mipi_dsi_driver(struct device_driver *driver) +{ + return container_of(driver, struct mipi_dsi_driver, driver); +} + +static inline void *mipi_dsi_get_drvdata(const struct mipi_dsi_device *dsi) +{ + return dev_get_drvdata(&dsi->dev); +} + +static inline void mipi_dsi_set_drvdata(struct mipi_dsi_device *dsi, void *data) +{ + dev_set_drvdata(&dsi->dev, data); +} + +int mipi_dsi_driver_register_full(struct mipi_dsi_driver *driver, + struct module *owner); +void mipi_dsi_driver_unregister(struct mipi_dsi_driver *driver); + +#define mipi_dsi_driver_register(driver) \ + mipi_dsi_driver_register_full(driver, THIS_MODULE) + +#define module_mipi_dsi_driver(__mipi_dsi_driver) \ + module_driver(__mipi_dsi_driver, mipi_dsi_driver_register, \ + mipi_dsi_driver_unregister) + +#endif /* __DRM_MIPI_DSI__ */ diff --git a/sys/dev/pci/drm/drm_mm.c b/sys/dev/pci/drm/drm_mm.c index 5cff9e248c6..7bf268a967d 100644 --- a/sys/dev/pci/drm/drm_mm.c +++ b/sys/dev/pci/drm/drm_mm.c @@ -1,4 +1,3 @@ -/* $OpenBSD: drm_mm.c,v 1.7 2015/09/23 23:12:11 kettenis Exp $ */ /************************************************************************** * * Copyright 2006 Tungsten Graphics, Inc., Bismarck, ND., USA. @@ -42,86 +41,106 @@ * Thomas Hellström */ -#include "drmP.h" -#include "drm_mm.h" - -#define MM_UNUSED_TARGET 4 - -static struct drm_mm_node *drm_mm_kmalloc(struct drm_mm *mm, int atomic) -{ - struct drm_mm_node *child; - - if (atomic) - child = kzalloc(sizeof(*child), GFP_ATOMIC); - else - child = kzalloc(sizeof(*child), GFP_KERNEL); - - if (unlikely(child == NULL)) { - spin_lock(&mm->unused_lock); - if (list_empty(&mm->unused_nodes)) - child = NULL; - else { - child = - list_entry(mm->unused_nodes.next, - struct drm_mm_node, node_list); - list_del(&child->node_list); - --mm->num_unused; - } - spin_unlock(&mm->unused_lock); - } - return child; -} +#include +#include +#ifdef __linux__ +#include +#include +#include +#endif -/* drm_mm_pre_get() - pre allocate drm_mm_node structure - * drm_mm: memory manager struct we are pre-allocating for +/** + * DOC: Overview + * + * drm_mm provides a simple range allocator. The drivers are free to use the + * resource allocator from the linux core if it suits them, the upside of drm_mm + * is that it's in the DRM core. Which means that it's easier to extend for + * some of the crazier special purpose needs of gpus. + * + * The main data struct is &drm_mm, allocations are tracked in &drm_mm_node. + * Drivers are free to embed either of them into their own suitable + * datastructures. drm_mm itself will not do any allocations of its own, so if + * drivers choose not to embed nodes they need to still allocate them + * themselves. + * + * The range allocator also supports reservation of preallocated blocks. This is + * useful for taking over initial mode setting configurations from the firmware, + * where an object needs to be created which exactly matches the firmware's + * scanout target. As long as the range is still free it can be inserted anytime + * after the allocator is initialized, which helps with avoiding looped + * depencies in the driver load sequence. + * + * drm_mm maintains a stack of most recently freed holes, which of all + * simplistic datastructures seems to be a fairly decent approach to clustering + * allocations and avoiding too much fragmentation. This means free space + * searches are O(num_holes). Given that all the fancy features drm_mm supports + * something better would be fairly complex and since gfx thrashing is a fairly + * steep cliff not a real concern. Removing a node again is O(1). + * + * drm_mm supports a few features: Alignment and range restrictions can be + * supplied. Further more every &drm_mm_node has a color value (which is just an + * opaqua unsigned long) which in conjunction with a driver callback can be used + * to implement sophisticated placement restrictions. The i915 DRM driver uses + * this to implement guard pages between incompatible caching domains in the + * graphics TT. * - * Returns 0 on success or -ENOMEM if allocation fails. + * Two behaviors are supported for searching and allocating: bottom-up and top-down. + * The default is bottom-up. Top-down allocation can be used if the memory area + * has different restrictions, or just to reduce fragmentation. + * + * Finally iteration helpers to walk all nodes and all holes are provided as are + * some basic allocator dumpers for debugging. */ -int drm_mm_pre_get(struct drm_mm *mm) -{ - struct drm_mm_node *node; - - spin_lock(&mm->unused_lock); - while (mm->num_unused < MM_UNUSED_TARGET) { - spin_unlock(&mm->unused_lock); - node = kzalloc(sizeof(*node), GFP_KERNEL); - spin_lock(&mm->unused_lock); - - if (unlikely(node == NULL)) { - int ret = (mm->num_unused < 2) ? -ENOMEM : 0; - spin_unlock(&mm->unused_lock); - return ret; - } - ++mm->num_unused; - list_add_tail(&node->node_list, &mm->unused_nodes); - } - spin_unlock(&mm->unused_lock); - return 0; -} -EXPORT_SYMBOL(drm_mm_pre_get); + +static struct drm_mm_node *drm_mm_search_free_generic(const struct drm_mm *mm, + u64 size, + unsigned alignment, + unsigned long color, + enum drm_mm_search_flags flags); +static struct drm_mm_node *drm_mm_search_free_in_range_generic(const struct drm_mm *mm, + u64 size, + unsigned alignment, + unsigned long color, + u64 start, + u64 end, + enum drm_mm_search_flags flags); static void drm_mm_insert_helper(struct drm_mm_node *hole_node, struct drm_mm_node *node, - unsigned long size, unsigned alignment, - unsigned long color) + u64 size, unsigned alignment, + unsigned long color, + enum drm_mm_allocator_flags flags) { struct drm_mm *mm = hole_node->mm; - unsigned long hole_start = drm_mm_hole_node_start(hole_node); - unsigned long hole_end = drm_mm_hole_node_end(hole_node); - unsigned long adj_start = hole_start; - unsigned long adj_end = hole_end; + u64 hole_start = drm_mm_hole_node_start(hole_node); + u64 hole_end = drm_mm_hole_node_end(hole_node); + u64 adj_start = hole_start; + u64 adj_end = hole_end; BUG_ON(node->allocated); if (mm->color_adjust) mm->color_adjust(hole_node, color, &adj_start, &adj_end); + if (flags & DRM_MM_CREATE_TOP) + adj_start = adj_end - size; + if (alignment) { - unsigned tmp = adj_start % alignment; - if (tmp) - adj_start += alignment - tmp; + u64 tmp = adj_start; + unsigned rem; + + rem = do_div(tmp, alignment); + if (rem) { + if (flags & DRM_MM_CREATE_TOP) + adj_start -= rem; + else + adj_start += alignment - rem; + } } + BUG_ON(adj_start < hole_start); + BUG_ON(adj_end > hole_end); + if (adj_start == hole_start) { hole_node->hole_follows = 0; list_del(&hole_node->hole_stack); @@ -145,15 +164,31 @@ static void drm_mm_insert_helper(struct drm_mm_node *hole_node, } } +/** + * drm_mm_reserve_node - insert an pre-initialized node + * @mm: drm_mm allocator to insert @node into + * @node: drm_mm_node to insert + * + * This functions inserts an already set-up drm_mm_node into the allocator, + * meaning that start, size and color must be set by the caller. This is useful + * to initialize the allocator with preallocated objects which must be set-up + * before the range allocator can be set-up, e.g. when taking over a firmware + * framebuffer. + * + * Returns: + * 0 on success, -ENOSPC if there's no hole where @node is. + */ int drm_mm_reserve_node(struct drm_mm *mm, struct drm_mm_node *node) { struct drm_mm_node *hole; - unsigned long end = node->start + node->size; - unsigned long hole_start; - unsigned long hole_end; + u64 end; + u64 hole_start; + u64 hole_end; BUG_ON(node == NULL); + end = node->start + node->size; + /* Find the relevant hole to add our node to */ drm_mm_for_each_hole(hole, mm, hole_start, hole_end) { if (hole_start > node->start || hole_end < end) @@ -179,63 +214,55 @@ int drm_mm_reserve_node(struct drm_mm *mm, struct drm_mm_node *node) return 0; } - WARN(1, "no hole found for node 0x%lx + 0x%lx\n", - node->start, node->size); return -ENOSPC; } EXPORT_SYMBOL(drm_mm_reserve_node); -struct drm_mm_node *drm_mm_get_block_generic(struct drm_mm_node *hole_node, - unsigned long size, - unsigned alignment, - unsigned long color, - int atomic) -{ - struct drm_mm_node *node; - - node = drm_mm_kmalloc(hole_node->mm, atomic); - if (unlikely(node == NULL)) - return NULL; - - drm_mm_insert_helper(hole_node, node, size, alignment, color); - - return node; -} -EXPORT_SYMBOL(drm_mm_get_block_generic); - /** - * Search for free space and insert a preallocated memory node. Returns - * -ENOSPC if no suitable free area is available. The preallocated memory node - * must be cleared. + * drm_mm_insert_node_generic - search for space and insert @node + * @mm: drm_mm to allocate from + * @node: preallocate node to insert + * @size: size of the allocation + * @alignment: alignment of the allocation + * @color: opaque tag value to use for this node + * @sflags: flags to fine-tune the allocation search + * @aflags: flags to fine-tune the allocation behavior + * + * The preallocated node must be cleared to 0. + * + * Returns: + * 0 on success, -ENOSPC if there's no suitable hole. */ int drm_mm_insert_node_generic(struct drm_mm *mm, struct drm_mm_node *node, - unsigned long size, unsigned alignment, + u64 size, unsigned alignment, unsigned long color, - enum drm_mm_search_flags flags) + enum drm_mm_search_flags sflags, + enum drm_mm_allocator_flags aflags) { struct drm_mm_node *hole_node; hole_node = drm_mm_search_free_generic(mm, size, alignment, - color, flags); + color, sflags); if (!hole_node) return -ENOSPC; - drm_mm_insert_helper(hole_node, node, size, alignment, color); + drm_mm_insert_helper(hole_node, node, size, alignment, color, aflags); return 0; } EXPORT_SYMBOL(drm_mm_insert_node_generic); static void drm_mm_insert_helper_range(struct drm_mm_node *hole_node, struct drm_mm_node *node, - unsigned long size, unsigned alignment, + u64 size, unsigned alignment, unsigned long color, - unsigned long start, unsigned long end) + u64 start, u64 end, + enum drm_mm_allocator_flags flags) { struct drm_mm *mm = hole_node->mm; - unsigned long hole_start = drm_mm_hole_node_start(hole_node); - unsigned long hole_end = drm_mm_hole_node_end(hole_node); - unsigned long adj_start = hole_start; - unsigned long adj_end = hole_end; + u64 hole_start = drm_mm_hole_node_start(hole_node); + u64 hole_end = drm_mm_hole_node_end(hole_node); + u64 adj_start = hole_start; + u64 adj_end = hole_end; BUG_ON(!hole_node->hole_follows || node->allocated); @@ -247,10 +274,20 @@ static void drm_mm_insert_helper_range(struct drm_mm_node *hole_node, if (mm->color_adjust) mm->color_adjust(hole_node, color, &adj_start, &adj_end); + if (flags & DRM_MM_CREATE_TOP) + adj_start = adj_end - size; + if (alignment) { - unsigned tmp = adj_start % alignment; - if (tmp) - adj_start += alignment - tmp; + u64 tmp = adj_start; + unsigned rem; + + rem = do_div(tmp, alignment); + if (rem) { + if (flags & DRM_MM_CREATE_TOP) + adj_start -= rem; + else + adj_start += alignment - rem; + } } if (adj_start == hole_start) { @@ -267,6 +304,8 @@ static void drm_mm_insert_helper_range(struct drm_mm_node *hole_node, INIT_LIST_HEAD(&node->hole_stack); list_add(&node->node_list, &hole_node->node_list); + BUG_ON(node->start < start); + BUG_ON(node->start < adj_start); BUG_ON(node->start + node->size > adj_end); BUG_ON(node->start + node->size > end); @@ -277,54 +316,52 @@ static void drm_mm_insert_helper_range(struct drm_mm_node *hole_node, } } -struct drm_mm_node *drm_mm_get_block_range_generic(struct drm_mm_node *hole_node, - unsigned long size, - unsigned alignment, - unsigned long color, - unsigned long start, - unsigned long end, - int atomic) -{ - struct drm_mm_node *node; - - node = drm_mm_kmalloc(hole_node->mm, atomic); - if (unlikely(node == NULL)) - return NULL; - - drm_mm_insert_helper_range(hole_node, node, size, alignment, color, - start, end); - - return node; -} -EXPORT_SYMBOL(drm_mm_get_block_range_generic); - /** - * Search for free space and insert a preallocated memory node. Returns - * -ENOSPC if no suitable free area is available. This is for range - * restricted allocations. The preallocated memory node must be cleared. + * drm_mm_insert_node_in_range_generic - ranged search for space and insert @node + * @mm: drm_mm to allocate from + * @node: preallocate node to insert + * @size: size of the allocation + * @alignment: alignment of the allocation + * @color: opaque tag value to use for this node + * @start: start of the allowed range for this node + * @end: end of the allowed range for this node + * @sflags: flags to fine-tune the allocation search + * @aflags: flags to fine-tune the allocation behavior + * + * The preallocated node must be cleared to 0. + * + * Returns: + * 0 on success, -ENOSPC if there's no suitable hole. */ int drm_mm_insert_node_in_range_generic(struct drm_mm *mm, struct drm_mm_node *node, - unsigned long size, unsigned alignment, unsigned long color, - unsigned long start, unsigned long end, - enum drm_mm_search_flags flags) + u64 size, unsigned alignment, + unsigned long color, + u64 start, u64 end, + enum drm_mm_search_flags sflags, + enum drm_mm_allocator_flags aflags) { struct drm_mm_node *hole_node; hole_node = drm_mm_search_free_in_range_generic(mm, size, alignment, color, - start, end, flags); + start, end, sflags); if (!hole_node) return -ENOSPC; drm_mm_insert_helper_range(hole_node, node, size, alignment, color, - start, end); + start, end, aflags); return 0; } EXPORT_SYMBOL(drm_mm_insert_node_in_range_generic); /** - * Remove a memory node from the allocator. + * drm_mm_remove_node - Remove a memory node from the allocator. + * @node: drm_mm_node to remove + * + * This just removes a node from its drm_mm allocator. The node does not need to + * be cleared again before it can be re-inserted into this or any other drm_mm + * allocator. It is a bug to call this function on a un-allocated node. */ void drm_mm_remove_node(struct drm_mm_node *node) { @@ -360,61 +397,44 @@ void drm_mm_remove_node(struct drm_mm_node *node) } EXPORT_SYMBOL(drm_mm_remove_node); -/* - * Remove a memory node from the allocator and free the allocated struct - * drm_mm_node. Only to be used on a struct drm_mm_node obtained by one of the - * drm_mm_get_block functions. - */ -void drm_mm_put_block(struct drm_mm_node *node) -{ - - struct drm_mm *mm = node->mm; - - drm_mm_remove_node(node); - - spin_lock(&mm->unused_lock); - if (mm->num_unused < MM_UNUSED_TARGET) { - list_add(&node->node_list, &mm->unused_nodes); - ++mm->num_unused; - } else - kfree(node); - spin_unlock(&mm->unused_lock); -} -EXPORT_SYMBOL(drm_mm_put_block); - -static int check_free_hole(unsigned long start, unsigned long end, - unsigned long size, unsigned alignment) +static int check_free_hole(u64 start, u64 end, u64 size, unsigned alignment) { if (end - start < size) return 0; if (alignment) { - unsigned tmp = start % alignment; - if (tmp) - start += alignment - tmp; + u64 tmp = start; + unsigned rem; + + rem = do_div(tmp, alignment); + if (rem) + start += alignment - rem; } return end >= start + size; } -struct drm_mm_node *drm_mm_search_free_generic(const struct drm_mm *mm, - unsigned long size, - unsigned alignment, - unsigned long color, - enum drm_mm_search_flags flags) +static struct drm_mm_node *drm_mm_search_free_generic(const struct drm_mm *mm, + u64 size, + unsigned alignment, + unsigned long color, + enum drm_mm_search_flags flags) { struct drm_mm_node *entry; struct drm_mm_node *best; - unsigned long adj_start; - unsigned long adj_end; - unsigned long best_size; + u64 adj_start; + u64 adj_end; + u64 best_size; BUG_ON(mm->scanned_blocks); best = NULL; best_size = ~0UL; - drm_mm_for_each_hole(entry, mm, adj_start, adj_end) { + __drm_mm_for_each_hole(entry, mm, adj_start, adj_end, + flags & DRM_MM_SEARCH_BELOW) { + u64 hole_size = adj_end - adj_start; + if (mm->color_adjust) { mm->color_adjust(entry, color, &adj_start, &adj_end); if (adj_end <= adj_start) @@ -427,36 +447,38 @@ struct drm_mm_node *drm_mm_search_free_generic(const struct drm_mm *mm, if (!(flags & DRM_MM_SEARCH_BEST)) return entry; - if (entry->size < best_size) { + if (hole_size < best_size) { best = entry; - best_size = entry->size; + best_size = hole_size; } } return best; } -EXPORT_SYMBOL(drm_mm_search_free_generic); -struct drm_mm_node *drm_mm_search_free_in_range_generic(const struct drm_mm *mm, - unsigned long size, +static struct drm_mm_node *drm_mm_search_free_in_range_generic(const struct drm_mm *mm, + u64 size, unsigned alignment, unsigned long color, - unsigned long start, - unsigned long end, + u64 start, + u64 end, enum drm_mm_search_flags flags) { struct drm_mm_node *entry; struct drm_mm_node *best; - unsigned long adj_start; - unsigned long adj_end; - unsigned long best_size; + u64 adj_start; + u64 adj_end; + u64 best_size; BUG_ON(mm->scanned_blocks); best = NULL; best_size = ~0UL; - drm_mm_for_each_hole(entry, mm, adj_start, adj_end) { + __drm_mm_for_each_hole(entry, mm, adj_start, adj_end, + flags & DRM_MM_SEARCH_BELOW) { + u64 hole_size = adj_end - adj_start; + if (adj_start < start) adj_start = start; if (adj_end > end) @@ -474,18 +496,23 @@ struct drm_mm_node *drm_mm_search_free_in_range_generic(const struct drm_mm *mm, if (!(flags & DRM_MM_SEARCH_BEST)) return entry; - if (entry->size < best_size) { + if (hole_size < best_size) { best = entry; - best_size = entry->size; + best_size = hole_size; } } return best; } -EXPORT_SYMBOL(drm_mm_search_free_in_range_generic); /** - * Moves an allocation. To be used with embedded struct drm_mm_node. + * drm_mm_replace_node - move an allocation from @old to @new + * @old: drm_mm_node to remove from the allocator + * @new: drm_mm_node which should inherit @old's allocation + * + * This is useful for when drivers embed the drm_mm_node structure and hence + * can't move allocations by reassigning pointers. It's a combination of remove + * and insert with the guarantee that the allocation start will match. */ void drm_mm_replace_node(struct drm_mm_node *old, struct drm_mm_node *new) { @@ -503,16 +530,50 @@ void drm_mm_replace_node(struct drm_mm_node *old, struct drm_mm_node *new) EXPORT_SYMBOL(drm_mm_replace_node); /** - * Initializa lru scanning. + * DOC: lru scan roaster + * + * Very often GPUs need to have continuous allocations for a given object. When + * evicting objects to make space for a new one it is therefore not most + * efficient when we simply start to select all objects from the tail of an LRU + * until there's a suitable hole: Especially for big objects or nodes that + * otherwise have special allocation constraints there's a good chance we evict + * lots of (smaller) objects unecessarily. + * + * The DRM range allocator supports this use-case through the scanning + * interfaces. First a scan operation needs to be initialized with + * drm_mm_init_scan() or drm_mm_init_scan_with_range(). The the driver adds + * objects to the roaster (probably by walking an LRU list, but this can be + * freely implemented) until a suitable hole is found or there's no further + * evitable object. + * + * The the driver must walk through all objects again in exactly the reverse + * order to restore the allocator state. Note that while the allocator is used + * in the scan mode no other operation is allowed. + * + * Finally the driver evicts all objects selected in the scan. Adding and + * removing an object is O(1), and since freeing a node is also O(1) the overall + * complexity is O(scanned_objects). So like the free stack which needs to be + * walked before a scan operation even begins this is linear in the number of + * objects. It doesn't seem to hurt badly. + */ + +/** + * drm_mm_init_scan - initialize lru scanning + * @mm: drm_mm to scan + * @size: size of the allocation + * @alignment: alignment of the allocation + * @color: opaque tag value to use for the allocation * * This simply sets up the scanning routines with the parameters for the desired - * hole. + * hole. Note that there's no need to specify allocation flags, since they only + * change the place a node is allocated from within a suitable hole. * - * Warning: As long as the scan list is non-empty, no other operations than + * Warning: + * As long as the scan list is non-empty, no other operations than * adding/removing nodes to/from the scan list are allowed. */ void drm_mm_init_scan(struct drm_mm *mm, - unsigned long size, + u64 size, unsigned alignment, unsigned long color) { @@ -528,20 +589,28 @@ void drm_mm_init_scan(struct drm_mm *mm, EXPORT_SYMBOL(drm_mm_init_scan); /** - * Initializa lru scanning. + * drm_mm_init_scan - initialize range-restricted lru scanning + * @mm: drm_mm to scan + * @size: size of the allocation + * @alignment: alignment of the allocation + * @color: opaque tag value to use for the allocation + * @start: start of the allowed range for the allocation + * @end: end of the allowed range for the allocation * * This simply sets up the scanning routines with the parameters for the desired - * hole. This version is for range-restricted scans. + * hole. Note that there's no need to specify allocation flags, since they only + * change the place a node is allocated from within a suitable hole. * - * Warning: As long as the scan list is non-empty, no other operations than + * Warning: + * As long as the scan list is non-empty, no other operations than * adding/removing nodes to/from the scan list are allowed. */ void drm_mm_init_scan_with_range(struct drm_mm *mm, - unsigned long size, + u64 size, unsigned alignment, unsigned long color, - unsigned long start, - unsigned long end) + u64 start, + u64 end) { mm->scan_color = color; mm->scan_alignment = alignment; @@ -557,17 +626,21 @@ void drm_mm_init_scan_with_range(struct drm_mm *mm, EXPORT_SYMBOL(drm_mm_init_scan_with_range); /** + * drm_mm_scan_add_block - add a node to the scan list + * @node: drm_mm_node to add + * * Add a node to the scan list that might be freed to make space for the desired * hole. * - * Returns non-zero, if a hole has been found, zero otherwise. + * Returns: + * True if a hole has been found, false otherwise. */ -int drm_mm_scan_add_block(struct drm_mm_node *node) +bool drm_mm_scan_add_block(struct drm_mm_node *node) { struct drm_mm *mm = node->mm; struct drm_mm_node *prev_node; - unsigned long hole_start, hole_end; - unsigned long adj_start, adj_end; + u64 hole_start, hole_end; + u64 adj_start, adj_end; mm->scanned_blocks++; @@ -602,15 +675,16 @@ int drm_mm_scan_add_block(struct drm_mm_node *node) mm->scan_size, mm->scan_alignment)) { mm->scan_hit_start = hole_start; mm->scan_hit_end = hole_end; - return 1; + return true; } - return 0; + return false; } EXPORT_SYMBOL(drm_mm_scan_add_block); /** - * Remove a node from the scan list. + * drm_mm_scan_remove_block - remove a node from the scan list + * @node: drm_mm_node to remove * * Nodes _must_ be removed in the exact same order from the scan list as they * have been added, otherwise the internal state of the memory manager will be @@ -620,10 +694,11 @@ EXPORT_SYMBOL(drm_mm_scan_add_block); * immediately following drm_mm_search_free with !DRM_MM_SEARCH_BEST will then * return the just freed block (because its at the top of the free_stack list). * - * Returns one if this block should be evicted, zero otherwise. Will always - * return zero when no hole has been found. + * Returns: + * True if this block should be evicted, false otherwise. Will always + * return false when no hole has been found. */ -int drm_mm_scan_remove_block(struct drm_mm_node *node) +bool drm_mm_scan_remove_block(struct drm_mm_node *node) { struct drm_mm *mm = node->mm; struct drm_mm_node *prev_node; @@ -644,7 +719,15 @@ int drm_mm_scan_remove_block(struct drm_mm_node *node) } EXPORT_SYMBOL(drm_mm_scan_remove_block); -int drm_mm_clean(struct drm_mm * mm) +/** + * drm_mm_clean - checks whether an allocator is clean + * @mm: drm_mm allocator to check + * + * Returns: + * True if the allocator is completely free, false if there's still a node + * allocated in it. + */ +bool drm_mm_clean(struct drm_mm * mm) { struct list_head *head = &mm->head_node.node_list; @@ -652,13 +735,18 @@ int drm_mm_clean(struct drm_mm * mm) } EXPORT_SYMBOL(drm_mm_clean); -void drm_mm_init(struct drm_mm * mm, unsigned long start, unsigned long size) +/** + * drm_mm_init - initialize a drm-mm allocator + * @mm: the drm_mm structure to initialize + * @start: start of the range managed by @mm + * @size: end of the range managed by @mm + * + * Note that @mm must be cleared to 0 before calling this function. + */ +void drm_mm_init(struct drm_mm * mm, u64 start, u64 size) { INIT_LIST_HEAD(&mm->hole_stack); - INIT_LIST_HEAD(&mm->unused_nodes); - mm->num_unused = 0; mm->scanned_blocks = 0; - mtx_init(&mm->unused_lock, IPL_NONE); /* Clever trick to avoid a special case in the free hole tracking. */ INIT_LIST_HEAD(&mm->head_node.node_list); @@ -676,100 +764,101 @@ void drm_mm_init(struct drm_mm * mm, unsigned long start, unsigned long size) } EXPORT_SYMBOL(drm_mm_init); +/** + * drm_mm_takedown - clean up a drm_mm allocator + * @mm: drm_mm allocator to clean up + * + * Note that it is a bug to call this function on an allocator which is not + * clean. + */ void drm_mm_takedown(struct drm_mm * mm) { - struct drm_mm_node *entry, *next; - - if (WARN(!list_empty(&mm->head_node.node_list), - "Memory manager not clean. Delaying takedown\n")) { - return; - } - - spin_lock(&mm->unused_lock); - list_for_each_entry_safe(entry, next, &mm->unused_nodes, node_list) { - list_del(&entry->node_list); - kfree(entry); - --mm->num_unused; - } - spin_unlock(&mm->unused_lock); - - BUG_ON(mm->num_unused != 0); + WARN(!list_empty(&mm->head_node.node_list), + "Memory manager not clean during takedown.\n"); } EXPORT_SYMBOL(drm_mm_takedown); -static unsigned long drm_mm_debug_hole(struct drm_mm_node *entry, - const char *prefix) +static u64 drm_mm_debug_hole(struct drm_mm_node *entry, + const char *prefix) { - unsigned long hole_start, hole_end, hole_size; + u64 hole_start, hole_end, hole_size; if (entry->hole_follows) { hole_start = drm_mm_hole_node_start(entry); hole_end = drm_mm_hole_node_end(entry); hole_size = hole_end - hole_start; - printk(KERN_DEBUG "%s 0x%08lx-0x%08lx: %8lu: free\n", - prefix, hole_start, hole_end, - hole_size); + pr_debug("%s %#llx-%#llx: %llu: free\n", prefix, hole_start, + hole_end, hole_size); return hole_size; } return 0; } +/** + * drm_mm_debug_table - dump allocator state to dmesg + * @mm: drm_mm allocator to dump + * @prefix: prefix to use for dumping to dmesg + */ void drm_mm_debug_table(struct drm_mm *mm, const char *prefix) { struct drm_mm_node *entry; - unsigned long total_used = 0, total_free = 0, total = 0; + u64 total_used = 0, total_free = 0, total = 0; total_free += drm_mm_debug_hole(&mm->head_node, prefix); drm_mm_for_each_node(entry, mm) { - printk(KERN_DEBUG "%s 0x%08lx-0x%08lx: %8lu: used\n", - prefix, entry->start, entry->start + entry->size, - entry->size); + pr_debug("%s %#llx-%#llx: %llu: used\n", prefix, entry->start, + entry->start + entry->size, entry->size); total_used += entry->size; total_free += drm_mm_debug_hole(entry, prefix); } total = total_free + total_used; - printk(KERN_DEBUG "%s total: %lu, used %lu free %lu\n", prefix, total, - total_used, total_free); + pr_debug("%s total: %llu, used %llu free %llu\n", prefix, total, + total_used, total_free); } EXPORT_SYMBOL(drm_mm_debug_table); #if defined(CONFIG_DEBUG_FS) -static unsigned long drm_mm_dump_hole(struct seq_file *m, struct drm_mm_node *entry) +static u64 drm_mm_dump_hole(struct seq_file *m, struct drm_mm_node *entry) { - unsigned long hole_start, hole_end, hole_size; + u64 hole_start, hole_end, hole_size; if (entry->hole_follows) { hole_start = drm_mm_hole_node_start(entry); hole_end = drm_mm_hole_node_end(entry); hole_size = hole_end - hole_start; - seq_printf(m, "0x%08lx-0x%08lx: 0x%08lx: free\n", - hole_start, hole_end, hole_size); + seq_printf(m, "%#018llx-%#018llx: %llu: free\n", hole_start, + hole_end, hole_size); return hole_size; } return 0; } +/** + * drm_mm_dump_table - dump allocator state to a seq_file + * @m: seq_file to dump to + * @mm: drm_mm allocator to dump + */ int drm_mm_dump_table(struct seq_file *m, struct drm_mm *mm) { struct drm_mm_node *entry; - unsigned long total_used = 0, total_free = 0, total = 0; + u64 total_used = 0, total_free = 0, total = 0; total_free += drm_mm_dump_hole(m, &mm->head_node); drm_mm_for_each_node(entry, mm) { - seq_printf(m, "0x%08lx-0x%08lx: 0x%08lx: used\n", - entry->start, entry->start + entry->size, - entry->size); + seq_printf(m, "%#018llx-%#018llx: %llu: used\n", entry->start, + entry->start + entry->size, entry->size); total_used += entry->size; total_free += drm_mm_dump_hole(m, entry); } total = total_free + total_used; - seq_printf(m, "total: %lu, used %lu free %lu\n", total, total_used, total_free); + seq_printf(m, "total: %llu, used %llu free %llu\n", total, + total_used, total_free); return 0; } EXPORT_SYMBOL(drm_mm_dump_table); diff --git a/sys/dev/pci/drm/drm_mm.h b/sys/dev/pci/drm/drm_mm.h index 87bcb4ffadd..f37314a6404 100644 --- a/sys/dev/pci/drm/drm_mm.h +++ b/sys/dev/pci/drm/drm_mm.h @@ -1,4 +1,3 @@ -/* $OpenBSD: drm_mm.h,v 1.4 2015/09/23 23:12:11 kettenis Exp $ */ /************************************************************************** * * Copyright 2006-2008 Tungsten Graphics, Inc., Cedar Park, TX. USA. @@ -37,7 +36,14 @@ /* * Generic range manager structs */ +#ifdef __linux__ +#include +#include +#include +#include +#else #include +#endif #ifdef CONFIG_DEBUG_FS #include #endif @@ -45,8 +51,17 @@ enum drm_mm_search_flags { DRM_MM_SEARCH_DEFAULT = 0, DRM_MM_SEARCH_BEST = 1 << 0, + DRM_MM_SEARCH_BELOW = 1 << 1, +}; + +enum drm_mm_allocator_flags { + DRM_MM_CREATE_DEFAULT = 0, + DRM_MM_CREATE_TOP = 1 << 0, }; +#define DRM_MM_BOTTOMUP DRM_MM_SEARCH_DEFAULT, DRM_MM_CREATE_DEFAULT +#define DRM_MM_TOPDOWN DRM_MM_SEARCH_BELOW, DRM_MM_CREATE_TOP + struct drm_mm_node { struct list_head node_list; struct list_head hole_stack; @@ -57,8 +72,8 @@ struct drm_mm_node { unsigned scanned_preceeds_hole : 1; unsigned allocated : 1; unsigned long color; - unsigned long start; - unsigned long size; + u64 start; + u64 size; struct drm_mm *mm; }; @@ -68,226 +83,236 @@ struct drm_mm { /* head_node.node_list is the list of all memory nodes, ordered * according to the (increasing) start address of the memory node. */ struct drm_mm_node head_node; - struct list_head unused_nodes; - int num_unused; - spinlock_t unused_lock; unsigned int scan_check_range : 1; unsigned scan_alignment; unsigned long scan_color; - unsigned long scan_size; - unsigned long scan_hit_start; - unsigned long scan_hit_end; + u64 scan_size; + u64 scan_hit_start; + u64 scan_hit_end; unsigned scanned_blocks; - unsigned long scan_start; - unsigned long scan_end; + u64 scan_start; + u64 scan_end; struct drm_mm_node *prev_scanned_node; void (*color_adjust)(struct drm_mm_node *node, unsigned long color, - unsigned long *start, unsigned long *end); + u64 *start, u64 *end); }; +/** + * drm_mm_node_allocated - checks whether a node is allocated + * @node: drm_mm_node to check + * + * Drivers should use this helpers for proper encapusulation of drm_mm + * internals. + * + * Returns: + * True if the @node is allocated. + */ static inline bool drm_mm_node_allocated(struct drm_mm_node *node) { return node->allocated; } +/** + * drm_mm_initialized - checks whether an allocator is initialized + * @mm: drm_mm to check + * + * Drivers should use this helpers for proper encapusulation of drm_mm + * internals. + * + * Returns: + * True if the @mm is initialized. + */ static inline bool drm_mm_initialized(struct drm_mm *mm) { return mm->hole_stack.next; } -static inline unsigned long __drm_mm_hole_node_start(struct drm_mm_node *hole_node) +static inline u64 __drm_mm_hole_node_start(struct drm_mm_node *hole_node) { return hole_node->start + hole_node->size; } -static inline unsigned long drm_mm_hole_node_start(struct drm_mm_node *hole_node) +/** + * drm_mm_hole_node_start - computes the start of the hole following @node + * @hole_node: drm_mm_node which implicitly tracks the following hole + * + * This is useful for driver-sepific debug dumpers. Otherwise drivers should not + * inspect holes themselves. Drivers must check first whether a hole indeed + * follows by looking at node->hole_follows. + * + * Returns: + * Start of the subsequent hole. + */ +static inline u64 drm_mm_hole_node_start(struct drm_mm_node *hole_node) { BUG_ON(!hole_node->hole_follows); return __drm_mm_hole_node_start(hole_node); } -static inline unsigned long __drm_mm_hole_node_end(struct drm_mm_node *hole_node) +static inline u64 __drm_mm_hole_node_end(struct drm_mm_node *hole_node) { - return list_entry(hole_node->node_list.next, - struct drm_mm_node, node_list)->start; + return list_next_entry(hole_node, node_list)->start; } -static inline unsigned long drm_mm_hole_node_end(struct drm_mm_node *hole_node) +/** + * drm_mm_hole_node_end - computes the end of the hole following @node + * @hole_node: drm_mm_node which implicitly tracks the following hole + * + * This is useful for driver-sepific debug dumpers. Otherwise drivers should not + * inspect holes themselves. Drivers must check first whether a hole indeed + * follows by looking at node->hole_follows. + * + * Returns: + * End of the subsequent hole. + */ +static inline u64 drm_mm_hole_node_end(struct drm_mm_node *hole_node) { return __drm_mm_hole_node_end(hole_node); } +/** + * drm_mm_for_each_node - iterator to walk over all allocated nodes + * @entry: drm_mm_node structure to assign to in each iteration step + * @mm: drm_mm allocator to walk + * + * This iterator walks over all nodes in the range allocator. It is implemented + * with list_for_each, so not save against removal of elements. + */ #define drm_mm_for_each_node(entry, mm) list_for_each_entry(entry, \ &(mm)->head_node.node_list, \ node_list) -#define drm_mm_for_each_scanned_node_reverse(entry, n, mm) \ - for (entry = (mm)->prev_scanned_node, \ - next = entry ? list_entry(entry->node_list.next, \ - struct drm_mm_node, node_list) : NULL; \ - entry != NULL; entry = next, \ - next = entry ? list_entry(entry->node_list.next, \ - struct drm_mm_node, node_list) : NULL) \ -/* Note that we need to unroll list_for_each_entry in order to inline - * setting hole_start and hole_end on each iteration and keep the - * macro sane. - */ -#define drm_mm_for_each_hole(entry, mm, hole_start, hole_end) \ - for (entry = list_entry((mm)->hole_stack.next, struct drm_mm_node, hole_stack); \ +#define __drm_mm_for_each_hole(entry, mm, hole_start, hole_end, backwards) \ + for (entry = list_entry((backwards) ? (mm)->hole_stack.prev : (mm)->hole_stack.next, struct drm_mm_node, hole_stack); \ &entry->hole_stack != &(mm)->hole_stack ? \ hole_start = drm_mm_hole_node_start(entry), \ hole_end = drm_mm_hole_node_end(entry), \ 1 : 0; \ - entry = list_entry(entry->hole_stack.next, struct drm_mm_node, hole_stack)) + entry = list_entry((backwards) ? entry->hole_stack.prev : entry->hole_stack.next, struct drm_mm_node, hole_stack)) + +/** + * drm_mm_for_each_hole - iterator to walk over all holes + * @entry: drm_mm_node used internally to track progress + * @mm: drm_mm allocator to walk + * @hole_start: ulong variable to assign the hole start to on each iteration + * @hole_end: ulong variable to assign the hole end to on each iteration + * + * This iterator walks over all holes in the range allocator. It is implemented + * with list_for_each, so not save against removal of elements. @entry is used + * internally and will not reflect a real drm_mm_node for the very first hole. + * Hence users of this iterator may not access it. + * + * Implementation Note: + * We need to inline list_for_each_entry in order to be able to set hole_start + * and hole_end on each iteration while keeping the macro sane. + * + * The __drm_mm_for_each_hole version is similar, but with added support for + * going backwards. + */ +#define drm_mm_for_each_hole(entry, mm, hole_start, hole_end) \ + __drm_mm_for_each_hole(entry, mm, hole_start, hole_end, 0) /* * Basic range manager support (drm_mm.c) */ -extern int drm_mm_reserve_node(struct drm_mm *mm, struct drm_mm_node *node); -extern struct drm_mm_node *drm_mm_get_block_generic(struct drm_mm_node *node, - unsigned long size, - unsigned alignment, - unsigned long color, - int atomic); -extern struct drm_mm_node *drm_mm_get_block_range_generic( - struct drm_mm_node *node, - unsigned long size, - unsigned alignment, - unsigned long color, - unsigned long start, - unsigned long end, - int atomic); +int drm_mm_reserve_node(struct drm_mm *mm, struct drm_mm_node *node); -static inline struct drm_mm_node *drm_mm_get_block(struct drm_mm_node *parent, - unsigned long size, - unsigned alignment) -{ - return drm_mm_get_block_generic(parent, size, alignment, 0, 0); -} -static inline struct drm_mm_node *drm_mm_get_block_atomic(struct drm_mm_node *parent, - unsigned long size, - unsigned alignment) -{ - return drm_mm_get_block_generic(parent, size, alignment, 0, 1); -} -static inline struct drm_mm_node *drm_mm_get_block_range( - struct drm_mm_node *parent, - unsigned long size, - unsigned alignment, - unsigned long start, - unsigned long end) -{ - return drm_mm_get_block_range_generic(parent, size, alignment, 0, - start, end, 0); -} -static inline struct drm_mm_node *drm_mm_get_block_atomic_range( - struct drm_mm_node *parent, - unsigned long size, - unsigned alignment, - unsigned long start, - unsigned long end) -{ - return drm_mm_get_block_range_generic(parent, size, alignment, 0, - start, end, 1); -} - -extern int drm_mm_insert_node_generic(struct drm_mm *mm, - struct drm_mm_node *node, - unsigned long size, - unsigned alignment, - unsigned long color, - enum drm_mm_search_flags flags); +int drm_mm_insert_node_generic(struct drm_mm *mm, + struct drm_mm_node *node, + u64 size, + unsigned alignment, + unsigned long color, + enum drm_mm_search_flags sflags, + enum drm_mm_allocator_flags aflags); +/** + * drm_mm_insert_node - search for space and insert @node + * @mm: drm_mm to allocate from + * @node: preallocate node to insert + * @size: size of the allocation + * @alignment: alignment of the allocation + * @flags: flags to fine-tune the allocation + * + * This is a simplified version of drm_mm_insert_node_generic() with @color set + * to 0. + * + * The preallocated node must be cleared to 0. + * + * Returns: + * 0 on success, -ENOSPC if there's no suitable hole. + */ static inline int drm_mm_insert_node(struct drm_mm *mm, struct drm_mm_node *node, - unsigned long size, + u64 size, unsigned alignment, enum drm_mm_search_flags flags) { - return drm_mm_insert_node_generic(mm, node, size, alignment, 0, flags); + return drm_mm_insert_node_generic(mm, node, size, alignment, 0, flags, + DRM_MM_CREATE_DEFAULT); } -extern int drm_mm_insert_node_in_range_generic(struct drm_mm *mm, - struct drm_mm_node *node, - unsigned long size, - unsigned alignment, - unsigned long color, - unsigned long start, - unsigned long end, - enum drm_mm_search_flags flags); +int drm_mm_insert_node_in_range_generic(struct drm_mm *mm, + struct drm_mm_node *node, + u64 size, + unsigned alignment, + unsigned long color, + u64 start, + u64 end, + enum drm_mm_search_flags sflags, + enum drm_mm_allocator_flags aflags); +/** + * drm_mm_insert_node_in_range - ranged search for space and insert @node + * @mm: drm_mm to allocate from + * @node: preallocate node to insert + * @size: size of the allocation + * @alignment: alignment of the allocation + * @start: start of the allowed range for this node + * @end: end of the allowed range for this node + * @flags: flags to fine-tune the allocation + * + * This is a simplified version of drm_mm_insert_node_in_range_generic() with + * @color set to 0. + * + * The preallocated node must be cleared to 0. + * + * Returns: + * 0 on success, -ENOSPC if there's no suitable hole. + */ static inline int drm_mm_insert_node_in_range(struct drm_mm *mm, struct drm_mm_node *node, - unsigned long size, + u64 size, unsigned alignment, - unsigned long start, - unsigned long end, + u64 start, + u64 end, enum drm_mm_search_flags flags) { return drm_mm_insert_node_in_range_generic(mm, node, size, alignment, - 0, start, end, flags); -} - -extern void drm_mm_put_block(struct drm_mm_node *cur); -extern void drm_mm_remove_node(struct drm_mm_node *node); -extern void drm_mm_replace_node(struct drm_mm_node *old, struct drm_mm_node *new); -extern struct drm_mm_node *drm_mm_search_free_generic(const struct drm_mm *mm, - unsigned long size, - unsigned alignment, - unsigned long color, - enum drm_mm_search_flags flags); -extern struct drm_mm_node *drm_mm_search_free_in_range_generic( - const struct drm_mm *mm, - unsigned long size, - unsigned alignment, - unsigned long color, - unsigned long start, - unsigned long end, - enum drm_mm_search_flags flags); -static inline struct drm_mm_node *drm_mm_search_free(const struct drm_mm *mm, - unsigned long size, - unsigned alignment, - enum drm_mm_search_flags flags) -{ - return drm_mm_search_free_generic(mm,size, alignment, 0, flags); -} -static inline struct drm_mm_node *drm_mm_search_free_in_range( - const struct drm_mm *mm, - unsigned long size, - unsigned alignment, - unsigned long start, - unsigned long end, - enum drm_mm_search_flags flags) -{ - return drm_mm_search_free_in_range_generic(mm, size, alignment, 0, - start, end, flags); + 0, start, end, flags, + DRM_MM_CREATE_DEFAULT); } -extern void drm_mm_init(struct drm_mm *mm, - unsigned long start, - unsigned long size); -extern void drm_mm_takedown(struct drm_mm *mm); -extern int drm_mm_clean(struct drm_mm *mm); -extern int drm_mm_pre_get(struct drm_mm *mm); - -static inline struct drm_mm *drm_get_mm(struct drm_mm_node *block) -{ - return block->mm; -} +void drm_mm_remove_node(struct drm_mm_node *node); +void drm_mm_replace_node(struct drm_mm_node *old, struct drm_mm_node *new); +void drm_mm_init(struct drm_mm *mm, + u64 start, + u64 size); +void drm_mm_takedown(struct drm_mm *mm); +bool drm_mm_clean(struct drm_mm *mm); void drm_mm_init_scan(struct drm_mm *mm, - unsigned long size, + u64 size, unsigned alignment, unsigned long color); void drm_mm_init_scan_with_range(struct drm_mm *mm, - unsigned long size, + u64 size, unsigned alignment, unsigned long color, - unsigned long start, - unsigned long end); -int drm_mm_scan_add_block(struct drm_mm_node *node); -int drm_mm_scan_remove_block(struct drm_mm_node *node); + u64 start, + u64 end); +bool drm_mm_scan_add_block(struct drm_mm_node *node); +bool drm_mm_scan_remove_block(struct drm_mm_node *node); -extern void drm_mm_debug_table(struct drm_mm *mm, const char *prefix); +void drm_mm_debug_table(struct drm_mm *mm, const char *prefix); #ifdef CONFIG_DEBUG_FS int drm_mm_dump_table(struct seq_file *m, struct drm_mm *mm); #endif diff --git a/sys/dev/pci/drm/drm_mode.h b/sys/dev/pci/drm/drm_mode.h index 0a229d92f1d..85f91fa1c1c 100644 --- a/sys/dev/pci/drm/drm_mode.h +++ b/sys/dev/pci/drm/drm_mode.h @@ -1,4 +1,3 @@ -/* $OpenBSD: drm_mode.h,v 1.7 2015/07/15 22:39:20 jsg Exp $ */ /* * Copyright (c) 2007 Dave Airlie * Copyright (c) 2007 Jakob Bornecrantz @@ -28,6 +27,8 @@ #ifndef _DRM_MODE_H #define _DRM_MODE_H +#include + #define DRM_DISPLAY_INFO_LEN 32 #define DRM_CONNECTOR_NAME_LEN 32 #define DRM_DISPLAY_MODE_LEN 32 @@ -57,6 +58,10 @@ #define DRM_MODE_FLAG_PIXMUX (1<<11) #define DRM_MODE_FLAG_DBLCLK (1<<12) #define DRM_MODE_FLAG_CLKDIV2 (1<<13) + /* + * When adding a new stereo mode don't forget to adjust DRM_MODE_FLAGS_3D_MAX + * (define not exposed to user space). + */ #define DRM_MODE_FLAG_3D_MASK (0x1f<<14) #define DRM_MODE_FLAG_3D_NONE (0<<14) #define DRM_MODE_FLAG_3D_FRAME_PACKING (1<<14) @@ -83,6 +88,11 @@ #define DRM_MODE_SCALE_CENTER 2 /* Centered, no scaling */ #define DRM_MODE_SCALE_ASPECT 3 /* Full screen, preserve aspect */ +/* Picture aspect ratio options */ +#define DRM_MODE_PICTURE_ASPECT_NONE 0 +#define DRM_MODE_PICTURE_ASPECT_4_3 1 +#define DRM_MODE_PICTURE_ASPECT_16_9 2 + /* Dithering mode options */ #define DRM_MODE_DITHERING_OFF 0 #define DRM_MODE_DITHERING_ON 1 @@ -94,41 +104,52 @@ #define DRM_MODE_DIRTY_ANNOTATE 2 struct drm_mode_modeinfo { - uint32_t clock; - uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew; - uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan; - - uint32_t vrefresh; - - uint32_t flags; - uint32_t type; + __u32 clock; + __u16 hdisplay; + __u16 hsync_start; + __u16 hsync_end; + __u16 htotal; + __u16 hskew; + __u16 vdisplay; + __u16 vsync_start; + __u16 vsync_end; + __u16 vtotal; + __u16 vscan; + + __u32 vrefresh; + + __u32 flags; + __u32 type; char name[DRM_DISPLAY_MODE_LEN]; }; struct drm_mode_card_res { - uint64_t fb_id_ptr; - uint64_t crtc_id_ptr; - uint64_t connector_id_ptr; - uint64_t encoder_id_ptr; - uint32_t count_fbs; - uint32_t count_crtcs; - uint32_t count_connectors; - uint32_t count_encoders; - uint32_t min_width, max_width; - uint32_t min_height, max_height; + __u64 fb_id_ptr; + __u64 crtc_id_ptr; + __u64 connector_id_ptr; + __u64 encoder_id_ptr; + __u32 count_fbs; + __u32 count_crtcs; + __u32 count_connectors; + __u32 count_encoders; + __u32 min_width; + __u32 max_width; + __u32 min_height; + __u32 max_height; }; struct drm_mode_crtc { - uint64_t set_connectors_ptr; - uint32_t count_connectors; + __u64 set_connectors_ptr; + __u32 count_connectors; - uint32_t crtc_id; /**< Id */ - uint32_t fb_id; /**< Id of framebuffer */ + __u32 crtc_id; /**< Id */ + __u32 fb_id; /**< Id of framebuffer */ - uint32_t x, y; /**< Position on the frameuffer */ + __u32 x; /**< x Position on the framebuffer */ + __u32 y; /**< y Position on the framebuffer */ - uint32_t gamma_size; - uint32_t mode_valid; + __u32 gamma_size; + __u32 mode_valid; struct drm_mode_modeinfo mode; }; @@ -137,36 +158,40 @@ struct drm_mode_crtc { /* Planes blend with or override other bits on the CRTC */ struct drm_mode_set_plane { - uint32_t plane_id; - uint32_t crtc_id; - uint32_t fb_id; /* fb object contains surface format type */ - uint32_t flags; /* see above flags */ + __u32 plane_id; + __u32 crtc_id; + __u32 fb_id; /* fb object contains surface format type */ + __u32 flags; /* see above flags */ /* Signed dest location allows it to be partially off screen */ - int32_t crtc_x, crtc_y; - uint32_t crtc_w, crtc_h; + __s32 crtc_x; + __s32 crtc_y; + __u32 crtc_w; + __u32 crtc_h; /* Source values are 16.16 fixed point */ - uint32_t src_x, src_y; - uint32_t src_h, src_w; + __u32 src_x; + __u32 src_y; + __u32 src_h; + __u32 src_w; }; struct drm_mode_get_plane { - uint32_t plane_id; + __u32 plane_id; - uint32_t crtc_id; - uint32_t fb_id; + __u32 crtc_id; + __u32 fb_id; - uint32_t possible_crtcs; - uint32_t gamma_size; + __u32 possible_crtcs; + __u32 gamma_size; - uint32_t count_format_types; - uint64_t format_type_ptr; + __u32 count_format_types; + __u64 format_type_ptr; }; struct drm_mode_get_plane_res { - uint64_t plane_id_ptr; - uint32_t count_planes; + __u64 plane_id_ptr; + __u32 count_planes; }; #define DRM_MODE_ENCODER_NONE 0 @@ -179,13 +204,13 @@ struct drm_mode_get_plane_res { #define DRM_MODE_ENCODER_DPMST 7 struct drm_mode_get_encoder { - uint32_t encoder_id; - uint32_t encoder_type; + __u32 encoder_id; + __u32 encoder_type; - uint32_t crtc_id; /**< Id of crtc */ + __u32 crtc_id; /**< Id of crtc */ - uint32_t possible_crtcs; - uint32_t possible_clones; + __u32 possible_crtcs; + __u32 possible_clones; }; /* This is for connectors with multiple signal types. */ @@ -219,23 +244,26 @@ struct drm_mode_get_encoder { struct drm_mode_get_connector { - uint64_t encoders_ptr; - uint64_t modes_ptr; - uint64_t props_ptr; - uint64_t prop_values_ptr; + __u64 encoders_ptr; + __u64 modes_ptr; + __u64 props_ptr; + __u64 prop_values_ptr; + + __u32 count_modes; + __u32 count_props; + __u32 count_encoders; - uint32_t count_modes; - uint32_t count_props; - uint32_t count_encoders; + __u32 encoder_id; /**< Current Encoder */ + __u32 connector_id; /**< Id */ + __u32 connector_type; + __u32 connector_type_id; - uint32_t encoder_id; /**< Current Encoder */ - uint32_t connector_id; /**< Id */ - uint32_t connector_type; - uint32_t connector_type_id; + __u32 connection; + __u32 mm_width; /**< width in millimeters */ + __u32 mm_height; /**< height in millimeters */ + __u32 subpixel; - uint32_t connection; - uint32_t mm_width, mm_height; /**< HxW in millimeters */ - uint32_t subpixel; + __u32 pad; }; #define DRM_MODE_PROP_PENDING (1<<0) @@ -260,71 +288,83 @@ struct drm_mode_get_connector { #define DRM_MODE_PROP_OBJECT DRM_MODE_PROP_TYPE(1) #define DRM_MODE_PROP_SIGNED_RANGE DRM_MODE_PROP_TYPE(2) +/* the PROP_ATOMIC flag is used to hide properties from userspace that + * is not aware of atomic properties. This is mostly to work around + * older userspace (DDX drivers) that read/write each prop they find, + * witout being aware that this could be triggering a lengthy modeset. + */ +#define DRM_MODE_PROP_ATOMIC 0x80000000 + struct drm_mode_property_enum { - uint64_t value; + __u64 value; char name[DRM_PROP_NAME_LEN]; }; struct drm_mode_get_property { - uint64_t values_ptr; /* values and blob lengths */ - uint64_t enum_blob_ptr; /* enum and blob id ptrs */ + __u64 values_ptr; /* values and blob lengths */ + __u64 enum_blob_ptr; /* enum and blob id ptrs */ - uint32_t prop_id; - uint32_t flags; + __u32 prop_id; + __u32 flags; char name[DRM_PROP_NAME_LEN]; - uint32_t count_values; - uint32_t count_enum_blobs; + __u32 count_values; + /* This is only used to count enum values, not blobs. The _blobs is + * simply because of a historical reason, i.e. backwards compat. */ + __u32 count_enum_blobs; }; struct drm_mode_connector_set_property { - uint64_t value; - uint32_t prop_id; - uint32_t connector_id; + __u64 value; + __u32 prop_id; + __u32 connector_id; }; struct drm_mode_obj_get_properties { - uint64_t props_ptr; - uint64_t prop_values_ptr; - uint32_t count_props; - uint32_t obj_id; - uint32_t obj_type; + __u64 props_ptr; + __u64 prop_values_ptr; + __u32 count_props; + __u32 obj_id; + __u32 obj_type; }; struct drm_mode_obj_set_property { - uint64_t value; - uint32_t prop_id; - uint32_t obj_id; - uint32_t obj_type; + __u64 value; + __u32 prop_id; + __u32 obj_id; + __u32 obj_type; }; struct drm_mode_get_blob { - uint32_t blob_id; - uint32_t length; - uint64_t data; + __u32 blob_id; + __u32 length; + __u64 data; }; struct drm_mode_fb_cmd { - uint32_t fb_id; - uint32_t width, height; - uint32_t pitch; - uint32_t bpp; - uint32_t depth; + __u32 fb_id; + __u32 width; + __u32 height; + __u32 pitch; + __u32 bpp; + __u32 depth; /* driver specific handle */ - uint32_t handle; + __u32 handle; }; #define DRM_MODE_FB_INTERLACED (1<<0) /* for interlaced framebuffers */ +#define DRM_MODE_FB_MODIFIERS (1<<1) /* enables ->modifer[] */ struct drm_mode_fb_cmd2 { - uint32_t fb_id; - uint32_t width, height; - uint32_t pixel_format; /* fourcc code from drm_fourcc.h */ - uint32_t flags; /* see above flags */ + __u32 fb_id; + __u32 width; + __u32 height; + __u32 pixel_format; /* fourcc code from drm_fourcc.h */ + __u32 flags; /* see above flags */ /* * In case of planar formats, this ioctl allows up to 4 - * buffer objects with offets and pitches per plane. + * buffer objects with offsets and pitches per plane. * The pitch and offset order is dictated by the fourcc, * e.g. NV12 (http://fourcc.org/yuv.php#NV12) is described as: * @@ -332,13 +372,21 @@ struct drm_mode_fb_cmd2 { * followed by an interleaved U/V plane containing * 8 bit 2x2 subsampled colour difference samples. * - * So it would consist of Y as offset[0] and UV as - * offeset[1]. Note that offset[0] will generally - * be 0. + * So it would consist of Y as offsets[0] and UV as + * offsets[1]. Note that offsets[0] will generally + * be 0 (but this is not required). + * + * To accommodate tiled, compressed, etc formats, a per-plane + * modifier can be specified. The default value of zero + * indicates "native" format as specified by the fourcc. + * Vendor specific modifier token. This allows, for example, + * different tiling/swizzling pattern on different planes. + * See discussion above of DRM_FORMAT_MOD_xxx. */ - uint32_t handles[4]; - uint32_t pitches[4]; /* pitch for each plane */ - uint32_t offsets[4]; /* offset of each plane */ + __u32 handles[4]; + __u32 pitches[4]; /* pitch for each plane */ + __u32 offsets[4]; /* offset of each plane */ + __u64 modifier[4]; /* ie, tiling, compressed (per plane) */ }; #define DRM_MODE_FB_DIRTY_ANNOTATE_COPY 0x01 @@ -375,15 +423,15 @@ struct drm_mode_fb_cmd2 { */ struct drm_mode_fb_dirty_cmd { - uint32_t fb_id; - uint32_t flags; - uint32_t color; - uint32_t num_clips; - uint64_t clips_ptr; + __u32 fb_id; + __u32 flags; + __u32 color; + __u32 num_clips; + __u64 clips_ptr; }; struct drm_mode_mode_cmd { - uint32_t connector_id; + __u32 connector_id; struct drm_mode_modeinfo mode; }; @@ -395,48 +443,48 @@ struct drm_mode_mode_cmd { * depending on the value in flags different members are used. * * CURSOR_BO uses - * crtc + * crtc_id * width * height - * handle - if 0 turns the cursor of + * handle - if 0 turns the cursor off * * CURSOR_MOVE uses - * crtc + * crtc_id * x * y */ struct drm_mode_cursor { - uint32_t flags; - uint32_t crtc_id; - int32_t x; - int32_t y; - uint32_t width; - uint32_t height; + __u32 flags; + __u32 crtc_id; + __s32 x; + __s32 y; + __u32 width; + __u32 height; /* driver specific handle */ - uint32_t handle; + __u32 handle; }; struct drm_mode_cursor2 { - uint32_t flags; - uint32_t crtc_id; - int32_t x; - int32_t y; - uint32_t width; - uint32_t height; + __u32 flags; + __u32 crtc_id; + __s32 x; + __s32 y; + __u32 width; + __u32 height; /* driver specific handle */ - uint32_t handle; - int32_t hot_x; - int32_t hot_y; + __u32 handle; + __s32 hot_x; + __s32 hot_y; }; struct drm_mode_crtc_lut { - uint32_t crtc_id; - uint32_t gamma_size; + __u32 crtc_id; + __u32 gamma_size; /* pointers to arrays */ - uint64_t red; - uint64_t green; - uint64_t blue; + __u64 red; + __u64 green; + __u64 blue; }; #define DRM_MODE_PAGE_FLIP_EVENT 0x01 @@ -455,22 +503,25 @@ struct drm_mode_crtc_lut { * flip is already pending as the ioctl is called, EBUSY will be * returned. * - * The ioctl supports one flag, DRM_MODE_PAGE_FLIP_EVENT, which will - * request that drm sends back a vblank event (see drm.h: struct - * drm_event_vblank) when the page flip is done. The user_data field - * passed in with this ioctl will be returned as the user_data field - * in the vblank event struct. + * Flag DRM_MODE_PAGE_FLIP_EVENT requests that drm sends back a vblank + * event (see drm.h: struct drm_event_vblank) when the page flip is + * done. The user_data field passed in with this ioctl will be + * returned as the user_data field in the vblank event struct. + * + * Flag DRM_MODE_PAGE_FLIP_ASYNC requests that the flip happen + * 'as soon as possible', meaning that it not delay waiting for vblank. + * This may cause tearing on the screen. * * The reserved field must be zero until we figure out something * clever to use it for. */ struct drm_mode_crtc_page_flip { - uint32_t crtc_id; - uint32_t fb_id; - uint32_t flags; - uint32_t reserved; - uint64_t user_data; + __u32 crtc_id; + __u32 fb_id; + __u32 flags; + __u32 reserved; + __u64 user_data; }; /* create a dumb scanout buffer */ @@ -488,14 +539,14 @@ struct drm_mode_create_dumb { /* set up for mmap of a dumb scanout buffer */ struct drm_mode_map_dumb { /** Handle for the object being mapped. */ - uint32_t handle; - uint32_t pad; + __u32 handle; + __u32 pad; /** * Fake offset to use for subsequent mmap call * * This is a fixed-size type for 32/64 compatibility. */ - uint64_t offset; + __u64 offset; }; struct drm_mode_destroy_dumb { @@ -503,19 +554,26 @@ struct drm_mode_destroy_dumb { }; /* page-flip flags are valid, plus: */ -#define DRM_MODE_ATOMIC_TEST_ONLY 0x0100 -#define DRM_MODE_ATOMIC_NONBLOCK 0x0200 -#define DRM_MODE_ATOMIC_ALLOW_MODESET 0x0400 +#define DRM_MODE_ATOMIC_TEST_ONLY 0x0100 +#define DRM_MODE_ATOMIC_NONBLOCK 0x0200 +#define DRM_MODE_ATOMIC_ALLOW_MODESET 0x0400 + +#define DRM_MODE_ATOMIC_FLAGS (\ + DRM_MODE_PAGE_FLIP_EVENT |\ + DRM_MODE_PAGE_FLIP_ASYNC |\ + DRM_MODE_ATOMIC_TEST_ONLY |\ + DRM_MODE_ATOMIC_NONBLOCK |\ + DRM_MODE_ATOMIC_ALLOW_MODESET) struct drm_mode_atomic { - uint32_t flags; - uint32_t count_objs; - uint64_t objs_ptr; - uint64_t count_props_ptr; - uint64_t props_ptr; - uint64_t prop_values_ptr; - uint64_t reserved; - uint64_t user_data; + __u32 flags; + __u32 count_objs; + __u64 objs_ptr; + __u64 count_props_ptr; + __u64 props_ptr; + __u64 prop_values_ptr; + __u64 reserved; + __u64 user_data; }; /** @@ -524,19 +582,18 @@ struct drm_mode_atomic { */ struct drm_mode_create_blob { /** Pointer to data to copy. */ - uint64_t data; + __u64 data; /** Length of data to copy. */ - uint32_t length; + __u32 length; /** Return: new property ID. */ - uint32_t blob_id; + __u32 blob_id; }; /** * Destroy a user-created blob property. */ struct drm_mode_destroy_blob { - uint32_t blob_id; + __u32 blob_id; }; - #endif diff --git a/sys/dev/pci/drm/drm_modes.c b/sys/dev/pci/drm/drm_modes.c index f1e5ae5e770..fe4f82a01cf 100644 --- a/sys/dev/pci/drm/drm_modes.c +++ b/sys/dev/pci/drm/drm_modes.c @@ -1,4 +1,3 @@ -/* $OpenBSD: drm_modes.c,v 1.7 2016/04/05 08:22:50 kettenis Exp $ */ /* * Copyright © 1997-2003 by The XFree86 Project, Inc. * Copyright © 2007 Dave Airlie @@ -31,22 +30,25 @@ * authorization from the copyright holder(s) and author(s). */ -#include "drmP.h" -#include "drm_crtc.h" - -#undef RB_ROOT -#define RB_ROOT(head) (head)->rbh_root +#ifdef __linux__ +#include +#include +#include +#endif +#include +#include +#ifdef __linux__ +#include