#include "cairoint.h"
#include "cairo-drm-private.h"
#include "cairo-device-private.h"
#include "cairo-error-private.h"
#define LIBUDEV_I_KNOW_THE_API_IS_SUBJECT_TO_CHANGE
#include <libudev.h>
#include <fcntl.h>
#include <unistd.h>
static cairo_drm_device_t *_cairo_drm_known_devices;
static cairo_drm_device_t *_cairo_drm_default_device;
static const char *
get_udev_property(struct udev_device *device, const char *name)
{
struct udev_list_entry *entry;
udev_list_entry_foreach (entry,
udev_device_get_properties_list_entry (device))
{
if (strcmp (udev_list_entry_get_name (entry), name) == 0)
return udev_list_entry_get_value (entry);
}
return NULL;
}
static void
_device_flush (void *abstract_device)
{
cairo_drm_device_t *device = abstract_device;
device->device.flush (device);
}
static void
_device_finish (void *abstract_device)
{
cairo_drm_device_t *device = abstract_device;
CAIRO_MUTEX_LOCK (_cairo_drm_device_mutex);
if (device->prev != NULL)
device->prev->next = device->next;
else
_cairo_drm_known_devices = device->next;
if (device->next != NULL)
device->next->prev = device->prev;
CAIRO_MUTEX_UNLOCK (_cairo_drm_device_mutex);
if (_cairo_atomic_ptr_cmpxchg (&_cairo_drm_default_device,
device, NULL))
{
cairo_device_destroy (&device->base);
}
}
static void
_device_destroy (void *abstract_device)
{
cairo_drm_device_t *device = abstract_device;
device->device.destroy (device);
}
static const cairo_device_backend_t _cairo_drm_device_backend = {
CAIRO_DEVICE_TYPE_DRM,
NULL, NULL,
_device_flush,
_device_finish,
_device_destroy,
};
cairo_drm_device_t *
_cairo_drm_device_init (cairo_drm_device_t *dev,
int fd,
dev_t devid,
int vendor_id,
int chip_id,
int max_surface_size)
{
assert (CAIRO_MUTEX_IS_LOCKED (_cairo_drm_device_mutex));
_cairo_device_init (&dev->base, &_cairo_drm_device_backend);
dev->id = devid;
dev->vendor_id = vendor_id;
dev->chip_id = chip_id;
dev->fd = fd;
dev->max_surface_size = max_surface_size;
dev->prev = NULL;
dev->next = _cairo_drm_known_devices;
if (_cairo_drm_known_devices != NULL)
_cairo_drm_known_devices->prev = dev;
_cairo_drm_known_devices = dev;
if (_cairo_drm_default_device == NULL)
_cairo_drm_default_device = (cairo_drm_device_t *) cairo_device_reference (&dev->base);
return dev;
}
cairo_device_t *
cairo_drm_device_get (struct udev_device *device)
{
static const struct dri_driver_entry {
uint32_t vendor_id;
uint32_t chip_id;
cairo_drm_device_create_func_t create_func;
} driver_map[] = {
{ 0x8086, 0x29a2, _cairo_drm_i965_device_create },
{ 0x8086, 0x2982, _cairo_drm_i965_device_create },
{ 0x8086, 0x2992, _cairo_drm_i965_device_create },
{ 0x8086, 0x2972, _cairo_drm_i965_device_create },
{ 0x8086, 0x2a02, _cairo_drm_i965_device_create },
{ 0x8086, 0x2a12, _cairo_drm_i965_device_create },
{ 0x8086, 0x2e02, _cairo_drm_i965_device_create },
{ 0x8086, 0x2e22, _cairo_drm_i965_device_create },
{ 0x8086, 0x2e12, _cairo_drm_i965_device_create },
{ 0x8086, 0x2e32, _cairo_drm_i965_device_create },
{ 0x8086, 0x2a42, _cairo_drm_i965_device_create },
{ 0x8086, 0x2582, _cairo_drm_i915_device_create },
{ 0x8086, 0x2592, _cairo_drm_i915_device_create },
{ 0x8086, 0x258a, _cairo_drm_i915_device_create },
{ 0x8086, 0x2772, _cairo_drm_i915_device_create },
{ 0x8086, 0x27a2, _cairo_drm_i915_device_create },
{ 0x8086, 0x27ae, _cairo_drm_i915_device_create },
{ 0x8086, 0x29c2, _cairo_drm_i915_device_create },
{ 0x8086, 0x29b2, _cairo_drm_i915_device_create },
{ 0x8086, 0x29d2, _cairo_drm_i915_device_create },
{ 0x8086, 0xa011, _cairo_drm_i915_device_create },
{ 0x8086, 0xa001, _cairo_drm_i915_device_create },
{ 0x8086, ~0, _cairo_drm_intel_device_create },
{ 0x1002, ~0, _cairo_drm_radeon_device_create },
#if CAIRO_HAS_GALLIUM_SURFACE
{ ~0, ~0, _cairo_drm_gallium_device_create },
#endif
};
cairo_drm_device_t *dev;
dev_t devid;
struct udev_device *parent;
const char *pci_id;
uint32_t vendor_id, chip_id;
const char *path;
int i, fd;
devid = udev_device_get_devnum (device);
CAIRO_MUTEX_LOCK (_cairo_drm_device_mutex);
for (dev = _cairo_drm_known_devices; dev != NULL; dev = dev->next) {
if (dev->id == devid) {
dev = (cairo_drm_device_t *) cairo_device_reference (&dev->base);
goto DONE;
}
}
parent = udev_device_get_parent (device);
pci_id = get_udev_property (parent, "PCI_ID");
if (pci_id == NULL || sscanf (pci_id, "%x:%x", &vendor_id, &chip_id) != 2) {
dev = (cairo_drm_device_t *)
_cairo_device_create_in_error (CAIRO_STATUS_DEVICE_ERROR);
goto DONE;
}
#if CAIRO_HAS_GALLIUM_SURFACE
if (getenv ("CAIRO_GALLIUM_FORCE"))
{
i = ARRAY_LENGTH (driver_map) - 1;
}
else
#endif
{
for (i = 0; i < ARRAY_LENGTH (driver_map); i++) {
if (driver_map[i].vendor_id == ~0U)
break;
if (driver_map[i].vendor_id == vendor_id &&
(driver_map[i].chip_id == ~0U || driver_map[i].chip_id == chip_id))
break;
}
if (i == ARRAY_LENGTH (driver_map)) {
dev = (cairo_drm_device_t *)
_cairo_device_create_in_error (CAIRO_STATUS_DEVICE_ERROR);
goto DONE;
}
}
path = udev_device_get_devnode (device);
if (path == NULL)
path = "/dev/dri/card0";
fd = open (path, O_RDWR);
if (fd == -1) {
_cairo_error_throw (CAIRO_STATUS_FILE_NOT_FOUND);
goto DONE;
}
dev = driver_map[i].create_func (fd, devid, vendor_id, chip_id);
if (dev == NULL)
close (fd);
DONE:
CAIRO_MUTEX_UNLOCK (_cairo_drm_device_mutex);
return &dev->base;
}
slim_hidden_def (cairo_drm_device_get);
cairo_device_t *
cairo_drm_device_get_for_fd (int fd)
{
struct stat st;
struct udev *udev;
struct udev_device *device;
cairo_device_t *dev = NULL;
if (fstat (fd, &st) < 0 || ! S_ISCHR (st.st_mode)) {
return _cairo_device_create_in_error (CAIRO_STATUS_NO_MEMORY);
}
udev = udev_new ();
device = udev_device_new_from_devnum (udev, 'c', st.st_rdev);
if (device != NULL) {
dev = cairo_drm_device_get (device);
udev_device_unref (device);
}
udev_unref (udev);
return dev;
}
slim_hidden_def (cairo_drm_device_get_for_fd);
cairo_device_t *
cairo_drm_device_default (void)
{
struct udev *udev;
struct udev_enumerate *e;
struct udev_list_entry *entry;
cairo_device_t *dev;
dev = &_cairo_drm_default_device->base;
if (dev != NULL)
return dev;
udev = udev_new();
if (udev == NULL)
return _cairo_device_create_in_error (CAIRO_STATUS_NO_MEMORY);
e = udev_enumerate_new (udev);
udev_enumerate_add_match_subsystem (e, "drm");
udev_enumerate_scan_devices (e);
udev_list_entry_foreach (entry, udev_enumerate_get_list_entry (e)) {
struct udev_device *device;
device =
udev_device_new_from_syspath (udev,
udev_list_entry_get_name (entry));
dev = cairo_drm_device_get (device);
udev_device_unref (device);
if (dev != NULL) {
if (((cairo_drm_device_t *) dev)->fd == -1) {
cairo_device_destroy (dev);
dev = NULL;
} else
break;
}
}
udev_enumerate_unref (e);
udev_unref (udev);
cairo_device_destroy (dev);
return dev;
}
slim_hidden_def (cairo_drm_device_default);
void
_cairo_drm_device_reset_static_data (void)
{
if (_cairo_drm_default_device != NULL) {
cairo_device_t *device = &_cairo_drm_default_device->base;
_cairo_drm_default_device = NULL;
cairo_device_destroy (device);
}
}
int
cairo_drm_device_get_fd (cairo_device_t *abstract_device)
{
cairo_drm_device_t *device = (cairo_drm_device_t *) abstract_device;
if (device->base.status)
return -1;
return device->fd;
}
void
_cairo_drm_device_fini (cairo_drm_device_t *device)
{
if (device->fd != -1)
close (device->fd);
}
void
cairo_drm_device_throttle (cairo_device_t *abstract_device)
{
cairo_drm_device_t *device = (cairo_drm_device_t *) abstract_device;
cairo_status_t status;
if (unlikely (device->base.status))
return;
if (device->device.throttle == NULL)
return;
status = device->device.throttle (device);
if (unlikely (status))
_cairo_status_set_error (&device->base.status, status);
}
cairo_bool_t
_cairo_drm_size_is_valid (cairo_device_t *abstract_device,
int width, int height)
{
cairo_drm_device_t *device = (cairo_drm_device_t *) abstract_device;
if (unlikely (device->base.status))
return FALSE;
return width <= device->max_surface_size &&
height <= device->max_surface_size;
}