blob: 4ca2d35ea33a551b67ff9c3723d81b74f4e82d40 [file] [log] [blame]
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include "pipe/p_context.h"
#include "pipe/p_state.h"
#include "util/u_format.h"
#include "util/u_memory.h"
#include "util/u_inlines.h"
#include "util/u_hash_table.h"
#include "os/os_thread.h"
#include "nouveau_drm_public.h"
#include "nouveau/nouveau_winsys.h"
#include "nouveau/nouveau_screen.h"
#include <nvif/class.h>
#include <nvif/cl0080.h>
static struct util_hash_table *fd_tab = NULL;
static mtx_t nouveau_screen_mutex = _MTX_INITIALIZER_NP;
bool nouveau_drm_screen_unref(struct nouveau_screen *screen)
{
int ret;
if (screen->refcount == -1)
return true;
mtx_lock(&nouveau_screen_mutex);
ret = --screen->refcount;
assert(ret >= 0);
if (ret == 0)
util_hash_table_remove(fd_tab, intptr_to_pointer(screen->drm->fd));
mtx_unlock(&nouveau_screen_mutex);
return ret == 0;
}
static unsigned hash_fd(void *key)
{
int fd = pointer_to_intptr(key);
struct stat stat;
fstat(fd, &stat);
return stat.st_dev ^ stat.st_ino ^ stat.st_rdev;
}
static int compare_fd(void *key1, void *key2)
{
int fd1 = pointer_to_intptr(key1);
int fd2 = pointer_to_intptr(key2);
struct stat stat1, stat2;
fstat(fd1, &stat1);
fstat(fd2, &stat2);
return stat1.st_dev != stat2.st_dev ||
stat1.st_ino != stat2.st_ino ||
stat1.st_rdev != stat2.st_rdev;
}
PUBLIC struct pipe_screen *
nouveau_drm_screen_create(int fd)
{
struct nouveau_drm *drm = NULL;
struct nouveau_device *dev = NULL;
struct nouveau_screen *(*init)(struct nouveau_device *);
struct nouveau_screen *screen = NULL;
int ret, dupfd;
mtx_lock(&nouveau_screen_mutex);
if (!fd_tab) {
fd_tab = util_hash_table_create(hash_fd, compare_fd);
if (!fd_tab) {
mtx_unlock(&nouveau_screen_mutex);
return NULL;
}
}
screen = util_hash_table_get(fd_tab, intptr_to_pointer(fd));
if (screen) {
screen->refcount++;
mtx_unlock(&nouveau_screen_mutex);
return &screen->base;
}
/* Since the screen re-use is based on the device node and not the fd,
* create a copy of the fd to be owned by the device. Otherwise a
* scenario could occur where two screens are created, and the first
* one is shut down, along with the fd being closed. The second
* (identical) screen would now have a reference to the closed fd. We
* avoid this by duplicating the original fd. Note that
* nouveau_device_wrap does not close the fd in case of a device
* creation error.
*/
dupfd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
ret = nouveau_drm_new(dupfd, &drm);
if (ret)
goto err;
ret = nouveau_device_new(&drm->client, NV_DEVICE,
&(struct nv_device_v0) {
.device = ~0ULL,
}, sizeof(struct nv_device_v0), &dev);
if (ret)
goto err;
switch (dev->chipset & ~0xf) {
case 0x30:
case 0x40:
case 0x60:
init = nv30_screen_create;
break;
case 0x50:
case 0x80:
case 0x90:
case 0xa0:
init = nv50_screen_create;
break;
case 0xc0:
case 0xd0:
case 0xe0:
case 0xf0:
case 0x100:
case 0x110:
case 0x120:
case 0x130:
init = nvc0_screen_create;
break;
default:
debug_printf("%s: unknown chipset nv%02x\n", __func__,
dev->chipset);
goto err;
}
screen = init(dev);
if (!screen || !screen->base.context_create)
goto err;
/* Use dupfd in hash table, to avoid errors if the original fd gets
* closed by its owner. The hash key needs to live at least as long as
* the screen.
*/
util_hash_table_set(fd_tab, intptr_to_pointer(dupfd), screen);
screen->refcount = 1;
mtx_unlock(&nouveau_screen_mutex);
return &screen->base;
err:
if (screen) {
screen->base.destroy(&screen->base);
} else {
nouveau_device_del(&dev);
nouveau_drm_del(&drm);
close(dupfd);
}
mtx_unlock(&nouveau_screen_mutex);
return NULL;
}