Maarten Lankhorst
2015-Feb-24 09:01 UTC
[Nouveau] [PATCH 1/2] nouveau: make nouveau importing global buffers completely thread-safe, with tests
While I've closed off most races in a previous patch, a small race still existed where importing then unreffing cound cause an invalid bo. Add a test for this case. Racing sequence fixed: - thread 1 releases bo, refcount drops to zero, blocks on acquiring nvdev->lock. - thread 2 increases refcount to 1. - thread 2 decreases refcount to zero, blocks on acquiring nvdev->lock. At this point the 2 threads will clean up the same bo. How is this fixed? When thread 2 increases refcount to 1 it removes the bo from the list, and creates a new bo. The original thread will notice refcount was increased to 1 and skip deletion from list and closing the handle. Signed-off-by: Maarten Lankhorst <maarten.lankhorst at ubuntu.com> --- configure.ac | 1 + nouveau/nouveau.c | 69 +++++++++++------------ tests/Makefile.am | 4 ++ tests/nouveau/.gitignore | 1 + tests/nouveau/Makefile.am | 15 +++++ tests/nouveau/threaded.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++ xf86atomic.h | 6 +- 7 files changed, 198 insertions(+), 38 deletions(-) create mode 100644 tests/nouveau/.gitignore create mode 100644 tests/nouveau/Makefile.am create mode 100644 tests/nouveau/threaded.c diff --git a/configure.ac b/configure.ac index 8afee83..6dc5044 100644 --- a/configure.ac +++ b/configure.ac @@ -441,6 +441,7 @@ AC_CONFIG_FILES([ tests/vbltest/Makefile tests/exynos/Makefile tests/tegra/Makefile + tests/nouveau/Makefile man/Makefile libdrm.pc]) AC_OUTPUT diff --git a/nouveau/nouveau.c b/nouveau/nouveau.c index c6c153a..1c723b9 100644 --- a/nouveau/nouveau.c +++ b/nouveau/nouveau.c @@ -351,29 +351,18 @@ nouveau_bo_del(struct nouveau_bo *bo) pthread_mutex_lock(&nvdev->lock); if (nvbo->name) { - if (atomic_read(&nvbo->refcnt)) { + if (atomic_read(&nvbo->refcnt) == 0) { + DRMLISTDEL(&nvbo->head); /* - * bo has been revived by a race with - * nouveau_bo_prime_handle_ref, or nouveau_bo_name_ref. - * - * In theory there's still a race possible with - * nouveau_bo_wrap, but when using this function - * the lifetime of the handle is probably already - * handled in another way. If there are races - * you're probably using nouveau_bo_wrap wrong. + * This bo has to be closed with the lock held because + * gem handles are not refcounted. If a shared bo is + * closed and re-opened in another thread a race + * against DRM_IOCTL_GEM_OPEN or drmPrimeFDToHandle + * might cause the bo to be closed accidentally while + * re-importing. */ - pthread_mutex_unlock(&nvdev->lock); - return; + drmIoctl(bo->device->fd, DRM_IOCTL_GEM_CLOSE, &req); } - DRMLISTDEL(&nvbo->head); - /* - * This bo has to be closed with the lock held because gem - * handles are not refcounted. If a shared bo is closed and - * re-opened in another thread a race against - * DRM_IOCTL_GEM_OPEN or drmPrimeFDToHandle might cause the - * bo to be closed accidentally while re-importing. - */ - drmIoctl(bo->device->fd, DRM_IOCTL_GEM_CLOSE, &req); pthread_mutex_unlock(&nvdev->lock); } else { DRMLISTDEL(&nvbo->head); @@ -418,7 +407,7 @@ nouveau_bo_new(struct nouveau_device *dev, uint32_t flags, uint32_t align, static int nouveau_bo_wrap_locked(struct nouveau_device *dev, uint32_t handle, - struct nouveau_bo **pbo) + struct nouveau_bo **pbo, int name) { struct nouveau_device_priv *nvdev = nouveau_device(dev); struct drm_nouveau_gem_info req = { .handle = handle }; @@ -427,8 +416,24 @@ nouveau_bo_wrap_locked(struct nouveau_device *dev, uint32_t handle, DRMLISTFOREACHENTRY(nvbo, &nvdev->bo_list, head) { if (nvbo->base.handle == handle) { - *pbo = NULL; - nouveau_bo_ref(&nvbo->base, pbo); + if (atomic_inc_return(&nvbo->refcnt) == 1) { + /* + * Uh oh, this bo is dead and someone else + * will free it, but because refcnt is + * now non-zero fortunately they won't + * call the ioctl to close the bo. + * + * Remove this bo from the list so other + * calls to nouveau_bo_wrap_locked will + * see our replacement nvbo. + */ + DRMLISTDEL(&nvbo->head); + if (!name) + name = nvbo->name; + break; + } + + *pbo = &nvbo->base; return 0; } } @@ -443,6 +448,7 @@ nouveau_bo_wrap_locked(struct nouveau_device *dev, uint32_t handle, atomic_set(&nvbo->refcnt, 1); nvbo->base.device = dev; abi16_bo_info(&nvbo->base, &req); + nvbo->name = name; DRMLISTADD(&nvbo->head, &nvdev->bo_list); *pbo = &nvbo->base; return 0; @@ -458,7 +464,7 @@ nouveau_bo_wrap(struct nouveau_device *dev, uint32_t handle, struct nouveau_device_priv *nvdev = nouveau_device(dev); int ret; pthread_mutex_lock(&nvdev->lock); - ret = nouveau_bo_wrap_locked(dev, handle, pbo); + ret = nouveau_bo_wrap_locked(dev, handle, pbo, 0); pthread_mutex_unlock(&nvdev->lock); return ret; } @@ -468,24 +474,13 @@ nouveau_bo_name_ref(struct nouveau_device *dev, uint32_t name, struct nouveau_bo **pbo) { struct nouveau_device_priv *nvdev = nouveau_device(dev); - struct nouveau_bo_priv *nvbo; struct drm_gem_open req = { .name = name }; int ret; pthread_mutex_lock(&nvdev->lock); - DRMLISTFOREACHENTRY(nvbo, &nvdev->bo_list, head) { - if (nvbo->name == name) { - *pbo = NULL; - nouveau_bo_ref(&nvbo->base, pbo); - pthread_mutex_unlock(&nvdev->lock); - return 0; - } - } - ret = drmIoctl(dev->fd, DRM_IOCTL_GEM_OPEN, &req); if (ret == 0) { - ret = nouveau_bo_wrap_locked(dev, req.handle, pbo); - nouveau_bo((*pbo))->name = name; + ret = nouveau_bo_wrap_locked(dev, req.handle, pbo, name); } pthread_mutex_unlock(&nvdev->lock); @@ -537,7 +532,7 @@ nouveau_bo_prime_handle_ref(struct nouveau_device *dev, int prime_fd, pthread_mutex_lock(&nvdev->lock); ret = drmPrimeFDToHandle(dev->fd, prime_fd, &handle); if (ret == 0) { - ret = nouveau_bo_wrap_locked(dev, handle, bo); + ret = nouveau_bo_wrap_locked(dev, handle, bo, 0); if (!ret) { struct nouveau_bo_priv *nvbo = nouveau_bo(*bo); if (!nvbo->name) { diff --git a/tests/Makefile.am b/tests/Makefile.am index 37b8d3a..38e4094 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -28,6 +28,10 @@ if HAVE_TEGRA SUBDIRS += tegra endif +if HAVE_NOUVEAU +SUBDIRS += nouveau +endif + if HAVE_LIBUDEV check_LTLIBRARIES = libdrmtest.la diff --git a/tests/nouveau/.gitignore b/tests/nouveau/.gitignore new file mode 100644 index 0000000..837bfb9 --- /dev/null +++ b/tests/nouveau/.gitignore @@ -0,0 +1 @@ +threaded diff --git a/tests/nouveau/Makefile.am b/tests/nouveau/Makefile.am new file mode 100644 index 0000000..8e3392e --- /dev/null +++ b/tests/nouveau/Makefile.am @@ -0,0 +1,15 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/include/drm \ + -I$(top_srcdir)/nouveau \ + -I$(top_srcdir) + +AM_CFLAGS = -Wall -Werror +AM_LDFLAGS = -pthread + +LDADD = -ldl \ + ../../nouveau/libdrm_nouveau.la \ + ../../libdrm.la + +noinst_PROGRAMS = \ + threaded + diff --git a/tests/nouveau/threaded.c b/tests/nouveau/threaded.c new file mode 100644 index 0000000..490a7fa --- /dev/null +++ b/tests/nouveau/threaded.c @@ -0,0 +1,140 @@ +/* + * Copyright © 2015 Canonical Ltd. (Maarten Lankhorst) + * + * 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 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 + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) 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. + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <sys/ioctl.h> +#include <dlfcn.h> +#include <fcntl.h> +#include <stdio.h> +#include <unistd.h> +#include <errno.h> +#include <pthread.h> + +#include "xf86drm.h" +#include "nouveau.h" + +static const char default_device[] = "/dev/dri/renderD128"; + +static typeof(ioctl) *old_ioctl; +static int failed; + +static int import_fd; + +int ioctl(int fd, unsigned long request, ...) +{ + va_list va; + int ret; + void *arg; + + va_start(va, request); + arg = va_arg(va, void *); + ret = old_ioctl(fd, request, arg); + va_end(va); + + if (ret < 0 && request == DRM_IOCTL_GEM_CLOSE && errno == EINVAL) + failed = 1; + + return ret; +} + +static void * +openclose(void *dev) +{ + struct nouveau_device *nvdev = dev; + struct nouveau_bo *bo = NULL; + int i; + + for (i = 0; i < 100000; ++i) { + if (!nouveau_bo_prime_handle_ref(nvdev, import_fd, &bo)) + nouveau_bo_ref(NULL, &bo); + } + return NULL; +} + +int main(int argc, char *argv[]) +{ + drmVersionPtr version; + const char *device; + int err, fd, fd2; + struct nouveau_device *nvdev, *nvdev2; + struct nouveau_bo *bo; + + old_ioctl = dlsym(RTLD_NEXT, "ioctl"); + + pthread_t t1, t2; + + if (argc < 2) + device = default_device; + else + device = argv[1]; + + fd = open(device, O_RDWR); + fd2 = open(device, O_RDWR); + if (fd < 0 || fd2 < 0) + return 1; + + version = drmGetVersion(fd); + if (version) { + printf("Version: %d.%d.%d\n", version->version_major, + version->version_minor, version->version_patchlevel); + printf(" Name: %s\n", version->name); + printf(" Date: %s\n", version->date); + printf(" Description: %s\n", version->desc); + + drmFreeVersion(version); + } + + err = nouveau_device_wrap(fd, 0, &nvdev); + if (!err) + err = nouveau_device_wrap(fd2, 0, &nvdev2); + if (err < 0) + return 1; + + err = nouveau_bo_new(nvdev2, NOUVEAU_BO_GART, 0, 4096, NULL, &bo); + if (!err) + err = nouveau_bo_set_prime(bo, &import_fd); + + if (!err) { + pthread_create(&t1, NULL, openclose, nvdev); + pthread_create(&t2, NULL, openclose, nvdev); + } + + pthread_join(t1, NULL); + pthread_join(t2, NULL); + + close(import_fd); + nouveau_bo_ref(NULL, &bo); + + nouveau_device_del(&nvdev2); + nouveau_device_del(&nvdev); + close(fd2); + close(fd); + + if (failed) + fprintf(stderr, "DRM_IOCTL_GEM_CLOSE failed with EINVAL,\n" + "race in opening/closing bo is likely.\n"); + + return failed; +} diff --git a/xf86atomic.h b/xf86atomic.h index 8c4b696..66a8d9a 100644 --- a/xf86atomic.h +++ b/xf86atomic.h @@ -49,7 +49,8 @@ typedef struct { # define atomic_read(x) ((x)->atomic) # define atomic_set(x, val) ((x)->atomic = (val)) # define atomic_inc(x) ((void) __sync_fetch_and_add (&(x)->atomic, 1)) -# define atomic_dec_and_test(x) (__sync_fetch_and_add (&(x)->atomic, -1) == 1) +# define atomic_inc_return(x) (__sync_add_and_fetch (&(x)->atomic, 1)) +# define atomic_dec_and_test(x) (__sync_add_and_fetch (&(x)->atomic, -1) == 0) # define atomic_add(x, v) ((void) __sync_add_and_fetch(&(x)->atomic, (v))) # define atomic_dec(x, v) ((void) __sync_sub_and_fetch(&(x)->atomic, (v))) # define atomic_cmpxchg(x, oldv, newv) __sync_val_compare_and_swap (&(x)->atomic, oldv, newv) @@ -68,6 +69,7 @@ typedef struct { # define atomic_read(x) AO_load_full(&(x)->atomic) # define atomic_set(x, val) AO_store_full(&(x)->atomic, (val)) # define atomic_inc(x) ((void) AO_fetch_and_add1_full(&(x)->atomic)) +# define atomic_inc_return(x) (AO_fetch_and_add1_full(&(x)->atomic) + 1) # define atomic_add(x, v) ((void) AO_fetch_and_add_full(&(x)->atomic, (v))) # define atomic_dec(x, v) ((void) AO_fetch_and_add_full(&(x)->atomic, -(v))) # define atomic_dec_and_test(x) (AO_fetch_and_sub1_full(&(x)->atomic) == 1) @@ -91,6 +93,7 @@ typedef struct { LIBDRM_ATOMIC_TYPE atomic; } atomic_t; # define atomic_read(x) (int) ((x)->atomic) # define atomic_set(x, val) ((x)->atomic = (LIBDRM_ATOMIC_TYPE)(val)) # define atomic_inc(x) (atomic_inc_uint (&(x)->atomic)) +# define atomic_inc_return (atomic_inc_uint_nv(&(x)->atomic)) # define atomic_dec_and_test(x) (atomic_dec_uint_nv(&(x)->atomic) == 0) # define atomic_add(x, v) (atomic_add_int(&(x)->atomic, (v))) # define atomic_dec(x, v) (atomic_add_int(&(x)->atomic, -(v))) @@ -112,3 +115,4 @@ static inline int atomic_add_unless(atomic_t *v, int add, int unless) } #endif + -- 2.3.0
Maarten Lankhorst
2015-Feb-24 09:01 UTC
[Nouveau] [PATCH 2/2] nouveau: Do not add most bo's to the global bo list.
Only add wrapped bo's and bo's that have been exported through flink or dma-buf. This avoids a lock in the common case, and decreases traversal needed for importing a dma-buf or flink. Signed-off-by: Maarten Lankhorst <maarten.lankhorst at ubuntu.com> --- nouveau/nouveau.c | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/nouveau/nouveau.c b/nouveau/nouveau.c index 1c723b9..d411523 100644 --- a/nouveau/nouveau.c +++ b/nouveau/nouveau.c @@ -349,8 +349,8 @@ nouveau_bo_del(struct nouveau_bo *bo) struct nouveau_bo_priv *nvbo = nouveau_bo(bo); struct drm_gem_close req = { bo->handle }; - pthread_mutex_lock(&nvdev->lock); - if (nvbo->name) { + if (nvbo->head.next) { + pthread_mutex_lock(&nvdev->lock); if (atomic_read(&nvbo->refcnt) == 0) { DRMLISTDEL(&nvbo->head); /* @@ -365,8 +365,6 @@ nouveau_bo_del(struct nouveau_bo *bo) } pthread_mutex_unlock(&nvdev->lock); } else { - DRMLISTDEL(&nvbo->head); - pthread_mutex_unlock(&nvdev->lock); drmIoctl(bo->device->fd, DRM_IOCTL_GEM_CLOSE, &req); } if (bo->map) @@ -379,7 +377,6 @@ nouveau_bo_new(struct nouveau_device *dev, uint32_t flags, uint32_t align, uint64_t size, union nouveau_bo_config *config, struct nouveau_bo **pbo) { - struct nouveau_device_priv *nvdev = nouveau_device(dev); struct nouveau_bo_priv *nvbo = calloc(1, sizeof(*nvbo)); struct nouveau_bo *bo = &nvbo->base; int ret; @@ -397,10 +394,6 @@ nouveau_bo_new(struct nouveau_device *dev, uint32_t flags, uint32_t align, return ret; } - pthread_mutex_lock(&nvdev->lock); - DRMLISTADD(&nvbo->head, &nvdev->bo_list); - pthread_mutex_unlock(&nvdev->lock); - *pbo = bo; return 0; } @@ -457,6 +450,18 @@ nouveau_bo_wrap_locked(struct nouveau_device *dev, uint32_t handle, return -ENOMEM; } +static void +nouveau_bo_make_global(struct nouveau_bo_priv *nvbo) +{ + if (!nvbo->head.next) { + struct nouveau_device_priv *nvdev = nouveau_device(nvbo->base.device); + pthread_mutex_lock(&nvdev->lock); + if (!nvbo->head.next) + DRMLISTADD(&nvbo->head, &nvdev->bo_list); + pthread_mutex_unlock(&nvdev->lock); + } +} + drm_public int nouveau_bo_wrap(struct nouveau_device *dev, uint32_t handle, struct nouveau_bo **pbo) @@ -494,13 +499,17 @@ nouveau_bo_name_get(struct nouveau_bo *bo, uint32_t *name) struct nouveau_bo_priv *nvbo = nouveau_bo(bo); *name = nvbo->name; - if (!*name || *name == ~0U) { + if (!*name) { int ret = drmIoctl(bo->device->fd, DRM_IOCTL_GEM_FLINK, &req); + struct nouveau_device_priv *nvdev = nouveau_device(bo->device); + if (ret) { *name = 0; return ret; } nvbo->name = *name = req.name; + + nouveau_bo_make_global(nvbo); } return 0; } @@ -533,16 +542,6 @@ nouveau_bo_prime_handle_ref(struct nouveau_device *dev, int prime_fd, ret = drmPrimeFDToHandle(dev->fd, prime_fd, &handle); if (ret == 0) { ret = nouveau_bo_wrap_locked(dev, handle, bo, 0); - if (!ret) { - struct nouveau_bo_priv *nvbo = nouveau_bo(*bo); - if (!nvbo->name) { - /* - * XXX: Force locked DRM_IOCTL_GEM_CLOSE - * to rule out race conditions - */ - nvbo->name = ~0; - } - } } pthread_mutex_unlock(&nvdev->lock); return ret; @@ -557,8 +556,8 @@ nouveau_bo_set_prime(struct nouveau_bo *bo, int *prime_fd) ret = drmPrimeHandleToFD(bo->device->fd, nvbo->base.handle, DRM_CLOEXEC, prime_fd); if (ret) return ret; - if (!nvbo->name) - nvbo->name = ~0; + + nouveau_bo_make_global(nvbo); return 0; } @@ -578,8 +577,8 @@ nouveau_bo_wait(struct nouveau_bo *bo, uint32_t access, if (push && push->channel) nouveau_pushbuf_kick(push, push->channel); - if (!nvbo->name && !(nvbo->access & NOUVEAU_BO_WR) && - !( access & NOUVEAU_BO_WR)) + if (!nvbo->head.next && !(nvbo->access & NOUVEAU_BO_WR) && + !(access & NOUVEAU_BO_WR)) return 0; req.handle = bo->handle; -- 2.3.0
Emil Velikov
2015-Feb-25 14:11 UTC
[Nouveau] [PATCH 2/2] nouveau: Do not add most bo's to the global bo list.
On 24 February 2015 at 09:01, Maarten Lankhorst <maarten.lankhorst at ubuntu.com> wrote:> Only add wrapped bo's and bo's that have been exported through flink or dma-buf. > This avoids a lock in the common case, and decreases traversal needed for importing > a dma-buf or flink. > > Signed-off-by: Maarten Lankhorst <maarten.lankhorst at ubuntu.com> > --- > nouveau/nouveau.c | 47 +++++++++++++++++++++++------------------------ > 1 file changed, 23 insertions(+), 24 deletions(-) > > diff --git a/nouveau/nouveau.c b/nouveau/nouveau.c > index 1c723b9..d411523 100644 > --- a/nouveau/nouveau.c > +++ b/nouveau/nouveau.c > @@ -349,8 +349,8 @@ nouveau_bo_del(struct nouveau_bo *bo) > struct nouveau_bo_priv *nvbo = nouveau_bo(bo); > struct drm_gem_close req = { bo->handle }; > > - pthread_mutex_lock(&nvdev->lock); > - if (nvbo->name) { > + if (nvbo->head.next) { > + pthread_mutex_lock(&nvdev->lock); > if (atomic_read(&nvbo->refcnt) == 0) { > DRMLISTDEL(&nvbo->head); > /* > @@ -365,8 +365,6 @@ nouveau_bo_del(struct nouveau_bo *bo) > } > pthread_mutex_unlock(&nvdev->lock); > } else { > - DRMLISTDEL(&nvbo->head); > - pthread_mutex_unlock(&nvdev->lock); > drmIoctl(bo->device->fd, DRM_IOCTL_GEM_CLOSE, &req); > } > if (bo->map) > @@ -379,7 +377,6 @@ nouveau_bo_new(struct nouveau_device *dev, uint32_t flags, uint32_t align, > uint64_t size, union nouveau_bo_config *config, > struct nouveau_bo **pbo) > { > - struct nouveau_device_priv *nvdev = nouveau_device(dev); > struct nouveau_bo_priv *nvbo = calloc(1, sizeof(*nvbo)); > struct nouveau_bo *bo = &nvbo->base; > int ret; > @@ -397,10 +394,6 @@ nouveau_bo_new(struct nouveau_device *dev, uint32_t flags, uint32_t align, > return ret; > } > > - pthread_mutex_lock(&nvdev->lock); > - DRMLISTADD(&nvbo->head, &nvdev->bo_list); > - pthread_mutex_unlock(&nvdev->lock); > - > *pbo = bo; > return 0; > } > @@ -457,6 +450,18 @@ nouveau_bo_wrap_locked(struct nouveau_device *dev, uint32_t handle, > return -ENOMEM; > } > > +static void > +nouveau_bo_make_global(struct nouveau_bo_priv *nvbo) > +{ > + if (!nvbo->head.next) { > + struct nouveau_device_priv *nvdev = nouveau_device(nvbo->base.device); > + pthread_mutex_lock(&nvdev->lock); > + if (!nvbo->head.next)Something looks a bit strange here. The list is modified under a lock, so there should be no need for the second if above. Did you have something else in mind ? -Emil
Emil Velikov
2015-Feb-25 14:14 UTC
[Nouveau] [PATCH 1/2] nouveau: make nouveau importing global buffers completely thread-safe, with tests
On 24 February 2015 at 09:01, Maarten Lankhorst <maarten.lankhorst at ubuntu.com> wrote:> While I've closed off most races in a previous patch, a small race still existed > where importing then unreffing cound cause an invalid bo. Add a test for this case. > > Racing sequence fixed: > > - thread 1 releases bo, refcount drops to zero, blocks on acquiring nvdev->lock. > - thread 2 increases refcount to 1. > - thread 2 decreases refcount to zero, blocks on acquiring nvdev->lock. > > At this point the 2 threads will clean up the same bo. > > How is this fixed? When thread 2 increases refcount to 1 it removes > the bo from the list, and creates a new bo. The original thread > will notice refcount was increased to 1 and skip deletion from list > and closing the handle. > > Signed-off-by: Maarten Lankhorst <maarten.lankhorst at ubuntu.com> > --- > configure.ac | 1 + > nouveau/nouveau.c | 69 +++++++++++------------ > tests/Makefile.am | 4 ++ > tests/nouveau/.gitignore | 1 + > tests/nouveau/Makefile.am | 15 +++++ > tests/nouveau/threaded.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++ > xf86atomic.h | 6 +- > 7 files changed, 198 insertions(+), 38 deletions(-) > create mode 100644 tests/nouveau/.gitignore > create mode 100644 tests/nouveau/Makefile.am > create mode 100644 tests/nouveau/threaded.c > > diff --git a/configure.ac b/configure.ac > index 8afee83..6dc5044 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -441,6 +441,7 @@ AC_CONFIG_FILES([ > tests/vbltest/Makefile > tests/exynos/Makefile > tests/tegra/Makefile > + tests/nouveau/Makefile > man/Makefile > libdrm.pc]) > AC_OUTPUT > diff --git a/nouveau/nouveau.c b/nouveau/nouveau.c > index c6c153a..1c723b9 100644 > --- a/nouveau/nouveau.c > +++ b/nouveau/nouveau.c > @@ -351,29 +351,18 @@ nouveau_bo_del(struct nouveau_bo *bo) > > pthread_mutex_lock(&nvdev->lock); > if (nvbo->name) { > - if (atomic_read(&nvbo->refcnt)) { > + if (atomic_read(&nvbo->refcnt) == 0) { > + DRMLISTDEL(&nvbo->head); > /* > - * bo has been revived by a race with > - * nouveau_bo_prime_handle_ref, or nouveau_bo_name_ref. > - * > - * In theory there's still a race possible with > - * nouveau_bo_wrap, but when using this function > - * the lifetime of the handle is probably already > - * handled in another way. If there are races > - * you're probably using nouveau_bo_wrap wrong. > + * This bo has to be closed with the lock held because > + * gem handles are not refcounted. If a shared bo is > + * closed and re-opened in another thread a race > + * against DRM_IOCTL_GEM_OPEN or drmPrimeFDToHandle > + * might cause the bo to be closed accidentally while > + * re-importing. > */ > - pthread_mutex_unlock(&nvdev->lock); > - return; > + drmIoctl(bo->device->fd, DRM_IOCTL_GEM_CLOSE, &req); > } > - DRMLISTDEL(&nvbo->head); > - /* > - * This bo has to be closed with the lock held because gem > - * handles are not refcounted. If a shared bo is closed and > - * re-opened in another thread a race against > - * DRM_IOCTL_GEM_OPEN or drmPrimeFDToHandle might cause the > - * bo to be closed accidentally while re-importing. > - */ > - drmIoctl(bo->device->fd, DRM_IOCTL_GEM_CLOSE, &req); > pthread_mutex_unlock(&nvdev->lock); > } else { > DRMLISTDEL(&nvbo->head); > @@ -418,7 +407,7 @@ nouveau_bo_new(struct nouveau_device *dev, uint32_t flags, uint32_t align, > > static int > nouveau_bo_wrap_locked(struct nouveau_device *dev, uint32_t handle, > - struct nouveau_bo **pbo) > + struct nouveau_bo **pbo, int name) > { > struct nouveau_device_priv *nvdev = nouveau_device(dev); > struct drm_nouveau_gem_info req = { .handle = handle }; > @@ -427,8 +416,24 @@ nouveau_bo_wrap_locked(struct nouveau_device *dev, uint32_t handle, > > DRMLISTFOREACHENTRY(nvbo, &nvdev->bo_list, head) { > if (nvbo->base.handle == handle) { > - *pbo = NULL; > - nouveau_bo_ref(&nvbo->base, pbo); > + if (atomic_inc_return(&nvbo->refcnt) == 1) { > + /* > + * Uh oh, this bo is dead and someone else > + * will free it, but because refcnt is > + * now non-zero fortunately they won't > + * call the ioctl to close the bo. > + * > + * Remove this bo from the list so other > + * calls to nouveau_bo_wrap_locked will > + * see our replacement nvbo. > + */ > + DRMLISTDEL(&nvbo->head); > + if (!name) > + name = nvbo->name; > + break; > + } > + > + *pbo = &nvbo->base; > return 0; > } > } > @@ -443,6 +448,7 @@ nouveau_bo_wrap_locked(struct nouveau_device *dev, uint32_t handle, > atomic_set(&nvbo->refcnt, 1); > nvbo->base.device = dev; > abi16_bo_info(&nvbo->base, &req); > + nvbo->name = name; > DRMLISTADD(&nvbo->head, &nvdev->bo_list); > *pbo = &nvbo->base; > return 0; > @@ -458,7 +464,7 @@ nouveau_bo_wrap(struct nouveau_device *dev, uint32_t handle, > struct nouveau_device_priv *nvdev = nouveau_device(dev); > int ret; > pthread_mutex_lock(&nvdev->lock); > - ret = nouveau_bo_wrap_locked(dev, handle, pbo); > + ret = nouveau_bo_wrap_locked(dev, handle, pbo, 0); > pthread_mutex_unlock(&nvdev->lock); > return ret; > } > @@ -468,24 +474,13 @@ nouveau_bo_name_ref(struct nouveau_device *dev, uint32_t name, > struct nouveau_bo **pbo) > { > struct nouveau_device_priv *nvdev = nouveau_device(dev); > - struct nouveau_bo_priv *nvbo; > struct drm_gem_open req = { .name = name }; > int ret; > > pthread_mutex_lock(&nvdev->lock); > - DRMLISTFOREACHENTRY(nvbo, &nvdev->bo_list, head) { > - if (nvbo->name == name) { > - *pbo = NULL; > - nouveau_bo_ref(&nvbo->base, pbo); > - pthread_mutex_unlock(&nvdev->lock); > - return 0; > - } > - } > - > ret = drmIoctl(dev->fd, DRM_IOCTL_GEM_OPEN, &req); > if (ret == 0) { > - ret = nouveau_bo_wrap_locked(dev, req.handle, pbo); > - nouveau_bo((*pbo))->name = name; > + ret = nouveau_bo_wrap_locked(dev, req.handle, pbo, name); > } > > pthread_mutex_unlock(&nvdev->lock); > @@ -537,7 +532,7 @@ nouveau_bo_prime_handle_ref(struct nouveau_device *dev, int prime_fd, > pthread_mutex_lock(&nvdev->lock); > ret = drmPrimeFDToHandle(dev->fd, prime_fd, &handle); > if (ret == 0) { > - ret = nouveau_bo_wrap_locked(dev, handle, bo); > + ret = nouveau_bo_wrap_locked(dev, handle, bo, 0); > if (!ret) { > struct nouveau_bo_priv *nvbo = nouveau_bo(*bo); > if (!nvbo->name) { > diff --git a/tests/Makefile.am b/tests/Makefile.am > index 37b8d3a..38e4094 100644 > --- a/tests/Makefile.am > +++ b/tests/Makefile.am > @@ -28,6 +28,10 @@ if HAVE_TEGRA > SUBDIRS += tegra > endif > > +if HAVE_NOUVEAU > +SUBDIRS += nouveau > +endif > + > if HAVE_LIBUDEV > > check_LTLIBRARIES = libdrmtest.la > diff --git a/tests/nouveau/.gitignore b/tests/nouveau/.gitignore > new file mode 100644 > index 0000000..837bfb9 > --- /dev/null > +++ b/tests/nouveau/.gitignore > @@ -0,0 +1 @@ > +threaded > diff --git a/tests/nouveau/Makefile.am b/tests/nouveau/Makefile.am > new file mode 100644 > index 0000000..8e3392e > --- /dev/null > +++ b/tests/nouveau/Makefile.am > @@ -0,0 +1,15 @@ > +AM_CPPFLAGS = \ > + -I$(top_srcdir)/include/drm \ > + -I$(top_srcdir)/nouveau \ > + -I$(top_srcdir) > + > +AM_CFLAGS = -Wall -WerrorPlease use $(WARN_CFLAGS)> +AM_LDFLAGS = -pthread > +I assume that adding -lpthread to the LDADD below will be better, but I'm not 100% sure.> +LDADD = -ldl \ > + ../../nouveau/libdrm_nouveau.la \ > + ../../libdrm.la > +Move -ldl at the bottom of the list so that it doesn't bite us in the future.> +noinst_PROGRAMS = \ > + threadedIf you want you can add this to the installed tests, and/or make check> + > diff --git a/tests/nouveau/threaded.c b/tests/nouveau/threaded.c > new file mode 100644 > index 0000000..490a7fa > --- /dev/null > +++ b/tests/nouveau/threaded.c > @@ -0,0 +1,140 @@ > +/* > + * Copyright © 2015 Canonical Ltd. (Maarten Lankhorst) > + * > + * 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 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 > + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) 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. > + */ > + > +#ifdef HAVE_CONFIG_H > +# include "config.h" > +#endif > + > +#include <sys/ioctl.h> > +#include <dlfcn.h> > +#include <fcntl.h> > +#include <stdio.h> > +#include <unistd.h> > +#include <errno.h> > +#include <pthread.h> > + > +#include "xf86drm.h" > +#include "nouveau.h" > + > +static const char default_device[] = "/dev/dri/renderD128"; > +Reuse the defines in xf86drm.h ?> +static typeof(ioctl) *old_ioctl; > +static int failed; > + > +static int import_fd; > + > +int ioctl(int fd, unsigned long request, ...) > +{ > + va_list va; > + int ret; > + void *arg; > + > + va_start(va, request); > + arg = va_arg(va, void *); > + ret = old_ioctl(fd, request, arg); > + va_end(va); > + > + if (ret < 0 && request == DRM_IOCTL_GEM_CLOSE && errno == EINVAL) > + failed = 1; > + > + return ret; > +} > + > +static void * > +openclose(void *dev) > +{ > + struct nouveau_device *nvdev = dev; > + struct nouveau_bo *bo = NULL; > + int i; > + > + for (i = 0; i < 100000; ++i) { > + if (!nouveau_bo_prime_handle_ref(nvdev, import_fd, &bo)) > + nouveau_bo_ref(NULL, &bo); > + } > + return NULL; > +} > + > +int main(int argc, char *argv[]) > +{ > + drmVersionPtr version; > + const char *device; > + int err, fd, fd2; > + struct nouveau_device *nvdev, *nvdev2; > + struct nouveau_bo *bo; > + > + old_ioctl = dlsym(RTLD_NEXT, "ioctl"); > + > + pthread_t t1, t2; > + > + if (argc < 2) > + device = default_device; > + else > + device = argv[1]; > + > + fd = open(device, O_RDWR); > + fd2 = open(device, O_RDWR); > + if (fd < 0 || fd2 < 0) > + return 1; > + > + version = drmGetVersion(fd); > + if (version) { > + printf("Version: %d.%d.%d\n", version->version_major, > + version->version_minor, version->version_patchlevel); > + printf(" Name: %s\n", version->name); > + printf(" Date: %s\n", version->date); > + printf(" Description: %s\n", version->desc); > + > + drmFreeVersion(version); > + } > + > + err = nouveau_device_wrap(fd, 0, &nvdev); > + if (!err) > + err = nouveau_device_wrap(fd2, 0, &nvdev2); > + if (err < 0) > + return 1; > + > + err = nouveau_bo_new(nvdev2, NOUVEAU_BO_GART, 0, 4096, NULL, &bo); > + if (!err) > + err = nouveau_bo_set_prime(bo, &import_fd); > + > + if (!err) { > + pthread_create(&t1, NULL, openclose, nvdev); > + pthread_create(&t2, NULL, openclose, nvdev); > + } > + > + pthread_join(t1, NULL); > + pthread_join(t2, NULL); > + > + close(import_fd); > + nouveau_bo_ref(NULL, &bo); > + > + nouveau_device_del(&nvdev2); > + nouveau_device_del(&nvdev); > + close(fd2); > + close(fd); > + > + if (failed) > + fprintf(stderr, "DRM_IOCTL_GEM_CLOSE failed with EINVAL,\n" > + "race in opening/closing bo is likely.\n"); > + > + return failed; > +} > diff --git a/xf86atomic.h b/xf86atomic.h > index 8c4b696..66a8d9a 100644 > --- a/xf86atomic.h > +++ b/xf86atomic.h > @@ -49,7 +49,8 @@ typedef struct { > # define atomic_read(x) ((x)->atomic) > # define atomic_set(x, val) ((x)->atomic = (val)) > # define atomic_inc(x) ((void) __sync_fetch_and_add (&(x)->atomic, 1)) > -# define atomic_dec_and_test(x) (__sync_fetch_and_add (&(x)->atomic, -1) == 1) > +# define atomic_inc_return(x) (__sync_add_and_fetch (&(x)->atomic, 1)) > +# define atomic_dec_and_test(x) (__sync_add_and_fetch (&(x)->atomic, -1) == 0)The atomic_dec_and_test change seems like unrelated bugfix. Split it out perhaps ? Introduction of atomic_inc_return could/should be a separate commit as well.> # define atomic_add(x, v) ((void) __sync_add_and_fetch(&(x)->atomic, (v))) > # define atomic_dec(x, v) ((void) __sync_sub_and_fetch(&(x)->atomic, (v))) > # define atomic_cmpxchg(x, oldv, newv) __sync_val_compare_and_swap (&(x)->atomic, oldv, newv) > @@ -68,6 +69,7 @@ typedef struct { > # define atomic_read(x) AO_load_full(&(x)->atomic) > # define atomic_set(x, val) AO_store_full(&(x)->atomic, (val)) > # define atomic_inc(x) ((void) AO_fetch_and_add1_full(&(x)->atomic)) > +# define atomic_inc_return(x) (AO_fetch_and_add1_full(&(x)->atomic) + 1) > # define atomic_add(x, v) ((void) AO_fetch_and_add_full(&(x)->atomic, (v))) > # define atomic_dec(x, v) ((void) AO_fetch_and_add_full(&(x)->atomic, -(v))) > # define atomic_dec_and_test(x) (AO_fetch_and_sub1_full(&(x)->atomic) == 1) > @@ -91,6 +93,7 @@ typedef struct { LIBDRM_ATOMIC_TYPE atomic; } atomic_t; > # define atomic_read(x) (int) ((x)->atomic) > # define atomic_set(x, val) ((x)->atomic = (LIBDRM_ATOMIC_TYPE)(val)) > # define atomic_inc(x) (atomic_inc_uint (&(x)->atomic)) > +# define atomic_inc_return (atomic_inc_uint_nv(&(x)->atomic)) > # define atomic_dec_and_test(x) (atomic_dec_uint_nv(&(x)->atomic) == 0) > # define atomic_add(x, v) (atomic_add_int(&(x)->atomic, (v))) > # define atomic_dec(x, v) (atomic_add_int(&(x)->atomic, -(v))) > @@ -112,3 +115,4 @@ static inline int atomic_add_unless(atomic_t *v, int add, int unless) > } > > #endif > +Extra white-space. IMHO sending the series for dri-devel might be nice, for at least the xf86atomic.h changes. Cheers Emil P.S. Bless you dude for going through the lovely experience to fix this.
Maarten Lankhorst
2015-Feb-25 21:59 UTC
[Nouveau] [PATCH 1/2] nouveau: make nouveau importing global buffers completely thread-safe, with tests
On 25-02-15 15:14, Emil Velikov wrote:> On 24 February 2015 at 09:01, Maarten Lankhorst > <maarten.lankhorst at ubuntu.com> wrote: >> While I've closed off most races in a previous patch, a small race still existed >> where importing then unreffing cound cause an invalid bo. Add a test for this case. >> >> Racing sequence fixed: >> >> - thread 1 releases bo, refcount drops to zero, blocks on acquiring nvdev->lock. >> - thread 2 increases refcount to 1. >> - thread 2 decreases refcount to zero, blocks on acquiring nvdev->lock. >> >> At this point the 2 threads will clean up the same bo. >> >> How is this fixed? When thread 2 increases refcount to 1 it removes >> the bo from the list, and creates a new bo. The original thread >> will notice refcount was increased to 1 and skip deletion from list >> and closing the handle. >> >> Signed-off-by: Maarten Lankhorst <maarten.lankhorst at ubuntu.com> >> --- >> configure.ac | 1 + >> nouveau/nouveau.c | 69 +++++++++++------------ >> tests/Makefile.am | 4 ++ >> tests/nouveau/.gitignore | 1 + >> tests/nouveau/Makefile.am | 15 +++++ >> tests/nouveau/threaded.c | 140 ++++++++++++++++++++++++++++++++++++++++++++++ >> xf86atomic.h | 6 +- >> 7 files changed, 198 insertions(+), 38 deletions(-) >> create mode 100644 tests/nouveau/.gitignore >> create mode 100644 tests/nouveau/Makefile.am >> create mode 100644 tests/nouveau/threaded.c >> >> diff --git a/configure.ac b/configure.ac >> index 8afee83..6dc5044 100644 >> --- a/configure.ac >> +++ b/configure.ac >> @@ -441,6 +441,7 @@ AC_CONFIG_FILES([ >> tests/vbltest/Makefile >> tests/exynos/Makefile >> tests/tegra/Makefile >> + tests/nouveau/Makefile >> man/Makefile >> libdrm.pc]) >> AC_OUTPUT >> diff --git a/nouveau/nouveau.c b/nouveau/nouveau.c >> index c6c153a..1c723b9 100644 >> --- a/nouveau/nouveau.c >> +++ b/nouveau/nouveau.c >> @@ -351,29 +351,18 @@ nouveau_bo_del(struct nouveau_bo *bo) >> >> pthread_mutex_lock(&nvdev->lock); >> if (nvbo->name) { >> - if (atomic_read(&nvbo->refcnt)) { >> + if (atomic_read(&nvbo->refcnt) == 0) { >> + DRMLISTDEL(&nvbo->head); >> /* >> - * bo has been revived by a race with >> - * nouveau_bo_prime_handle_ref, or nouveau_bo_name_ref. >> - * >> - * In theory there's still a race possible with >> - * nouveau_bo_wrap, but when using this function >> - * the lifetime of the handle is probably already >> - * handled in another way. If there are races >> - * you're probably using nouveau_bo_wrap wrong. >> + * This bo has to be closed with the lock held because >> + * gem handles are not refcounted. If a shared bo is >> + * closed and re-opened in another thread a race >> + * against DRM_IOCTL_GEM_OPEN or drmPrimeFDToHandle >> + * might cause the bo to be closed accidentally while >> + * re-importing. >> */ >> - pthread_mutex_unlock(&nvdev->lock); >> - return; >> + drmIoctl(bo->device->fd, DRM_IOCTL_GEM_CLOSE, &req); >> } >> - DRMLISTDEL(&nvbo->head); >> - /* >> - * This bo has to be closed with the lock held because gem >> - * handles are not refcounted. If a shared bo is closed and >> - * re-opened in another thread a race against >> - * DRM_IOCTL_GEM_OPEN or drmPrimeFDToHandle might cause the >> - * bo to be closed accidentally while re-importing. >> - */ >> - drmIoctl(bo->device->fd, DRM_IOCTL_GEM_CLOSE, &req); >> pthread_mutex_unlock(&nvdev->lock); >> } else { >> DRMLISTDEL(&nvbo->head); >> @@ -418,7 +407,7 @@ nouveau_bo_new(struct nouveau_device *dev, uint32_t flags, uint32_t align, >> >> static int >> nouveau_bo_wrap_locked(struct nouveau_device *dev, uint32_t handle, >> - struct nouveau_bo **pbo) >> + struct nouveau_bo **pbo, int name) >> { >> struct nouveau_device_priv *nvdev = nouveau_device(dev); >> struct drm_nouveau_gem_info req = { .handle = handle }; >> @@ -427,8 +416,24 @@ nouveau_bo_wrap_locked(struct nouveau_device *dev, uint32_t handle, >> >> DRMLISTFOREACHENTRY(nvbo, &nvdev->bo_list, head) { >> if (nvbo->base.handle == handle) { >> - *pbo = NULL; >> - nouveau_bo_ref(&nvbo->base, pbo); >> + if (atomic_inc_return(&nvbo->refcnt) == 1) { >> + /* >> + * Uh oh, this bo is dead and someone else >> + * will free it, but because refcnt is >> + * now non-zero fortunately they won't >> + * call the ioctl to close the bo. >> + * >> + * Remove this bo from the list so other >> + * calls to nouveau_bo_wrap_locked will >> + * see our replacement nvbo. >> + */ >> + DRMLISTDEL(&nvbo->head); >> + if (!name) >> + name = nvbo->name; >> + break; >> + } >> + >> + *pbo = &nvbo->base; >> return 0; >> } >> } >> @@ -443,6 +448,7 @@ nouveau_bo_wrap_locked(struct nouveau_device *dev, uint32_t handle, >> atomic_set(&nvbo->refcnt, 1); >> nvbo->base.device = dev; >> abi16_bo_info(&nvbo->base, &req); >> + nvbo->name = name; >> DRMLISTADD(&nvbo->head, &nvdev->bo_list); >> *pbo = &nvbo->base; >> return 0; >> @@ -458,7 +464,7 @@ nouveau_bo_wrap(struct nouveau_device *dev, uint32_t handle, >> struct nouveau_device_priv *nvdev = nouveau_device(dev); >> int ret; >> pthread_mutex_lock(&nvdev->lock); >> - ret = nouveau_bo_wrap_locked(dev, handle, pbo); >> + ret = nouveau_bo_wrap_locked(dev, handle, pbo, 0); >> pthread_mutex_unlock(&nvdev->lock); >> return ret; >> } >> @@ -468,24 +474,13 @@ nouveau_bo_name_ref(struct nouveau_device *dev, uint32_t name, >> struct nouveau_bo **pbo) >> { >> struct nouveau_device_priv *nvdev = nouveau_device(dev); >> - struct nouveau_bo_priv *nvbo; >> struct drm_gem_open req = { .name = name }; >> int ret; >> >> pthread_mutex_lock(&nvdev->lock); >> - DRMLISTFOREACHENTRY(nvbo, &nvdev->bo_list, head) { >> - if (nvbo->name == name) { >> - *pbo = NULL; >> - nouveau_bo_ref(&nvbo->base, pbo); >> - pthread_mutex_unlock(&nvdev->lock); >> - return 0; >> - } >> - } >> - >> ret = drmIoctl(dev->fd, DRM_IOCTL_GEM_OPEN, &req); >> if (ret == 0) { >> - ret = nouveau_bo_wrap_locked(dev, req.handle, pbo); >> - nouveau_bo((*pbo))->name = name; >> + ret = nouveau_bo_wrap_locked(dev, req.handle, pbo, name); >> } >> >> pthread_mutex_unlock(&nvdev->lock); >> @@ -537,7 +532,7 @@ nouveau_bo_prime_handle_ref(struct nouveau_device *dev, int prime_fd, >> pthread_mutex_lock(&nvdev->lock); >> ret = drmPrimeFDToHandle(dev->fd, prime_fd, &handle); >> if (ret == 0) { >> - ret = nouveau_bo_wrap_locked(dev, handle, bo); >> + ret = nouveau_bo_wrap_locked(dev, handle, bo, 0); >> if (!ret) { >> struct nouveau_bo_priv *nvbo = nouveau_bo(*bo); >> if (!nvbo->name) { >> diff --git a/tests/Makefile.am b/tests/Makefile.am >> index 37b8d3a..38e4094 100644 >> --- a/tests/Makefile.am >> +++ b/tests/Makefile.am >> @@ -28,6 +28,10 @@ if HAVE_TEGRA >> SUBDIRS += tegra >> endif >> >> +if HAVE_NOUVEAU >> +SUBDIRS += nouveau >> +endif >> + >> if HAVE_LIBUDEV >> >> check_LTLIBRARIES = libdrmtest.la >> diff --git a/tests/nouveau/.gitignore b/tests/nouveau/.gitignore >> new file mode 100644 >> index 0000000..837bfb9 >> --- /dev/null >> +++ b/tests/nouveau/.gitignore >> @@ -0,0 +1 @@ >> +threaded >> diff --git a/tests/nouveau/Makefile.am b/tests/nouveau/Makefile.am >> new file mode 100644 >> index 0000000..8e3392e >> --- /dev/null >> +++ b/tests/nouveau/Makefile.am >> @@ -0,0 +1,15 @@ >> +AM_CPPFLAGS = \ >> + -I$(top_srcdir)/include/drm \ >> + -I$(top_srcdir)/nouveau \ >> + -I$(top_srcdir) >> + >> +AM_CFLAGS = -Wall -Werror > Please use $(WARN_CFLAGS) > >> +AM_LDFLAGS = -pthread >> + > I assume that adding -lpthread to the LDADD below will be better, but > I'm not 100% sure. > >> +LDADD = -ldl \ >> + ../../nouveau/libdrm_nouveau.la \ >> + ../../libdrm.la >> + > Move -ldl at the bottom of the list so that it doesn't bite us in the future.Ok might be useful indeed.>> +noinst_PROGRAMS = \ >> + threaded > If you want you can add this to the installed tests, and/or make checkmake check might fail if nouveau is not found or no render nodes are available, I'd have to add a skip then.>> + >> diff --git a/tests/nouveau/threaded.c b/tests/nouveau/threaded.c >> new file mode 100644 >> index 0000000..490a7fa >> --- /dev/null >> +++ b/tests/nouveau/threaded.c >> @@ -0,0 +1,140 @@ >> +/* >> + * Copyright © 2015 Canonical Ltd. (Maarten Lankhorst) >> + * >> + * 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 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 >> + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) 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. >> + */ >> + >> +#ifdef HAVE_CONFIG_H >> +# include "config.h" >> +#endif >> + >> +#include <sys/ioctl.h> >> +#include <dlfcn.h> >> +#include <fcntl.h> >> +#include <stdio.h> >> +#include <unistd.h> >> +#include <errno.h> >> +#include <pthread.h> >> + >> +#include "xf86drm.h" >> +#include "nouveau.h" >> + >> +static const char default_device[] = "/dev/dri/renderD128"; >> + > Reuse the defines in xf86drm.h ?Hmm, switching to drmOpenWithType("nouveau", NULL, DRM_NODE_RENDER) might be better..>> +static typeof(ioctl) *old_ioctl; >> +static int failed; >> + >> +static int import_fd; >> + >> +int ioctl(int fd, unsigned long request, ...) >> +{ >> + va_list va; >> + int ret; >> + void *arg; >> + >> + va_start(va, request); >> + arg = va_arg(va, void *); >> + ret = old_ioctl(fd, request, arg); >> + va_end(va); >> + >> + if (ret < 0 && request == DRM_IOCTL_GEM_CLOSE && errno == EINVAL) >> + failed = 1; >> + >> + return ret; >> +} >> + >> +static void * >> +openclose(void *dev) >> +{ >> + struct nouveau_device *nvdev = dev; >> + struct nouveau_bo *bo = NULL; >> + int i; >> + >> + for (i = 0; i < 100000; ++i) { >> + if (!nouveau_bo_prime_handle_ref(nvdev, import_fd, &bo)) >> + nouveau_bo_ref(NULL, &bo); >> + } >> + return NULL; >> +} >> + >> +int main(int argc, char *argv[]) >> +{ >> + drmVersionPtr version; >> + const char *device; >> + int err, fd, fd2; >> + struct nouveau_device *nvdev, *nvdev2; >> + struct nouveau_bo *bo; >> + >> + old_ioctl = dlsym(RTLD_NEXT, "ioctl"); >> + >> + pthread_t t1, t2; >> + >> + if (argc < 2) >> + device = default_device; >> + else >> + device = argv[1]; >> + >> + fd = open(device, O_RDWR); >> + fd2 = open(device, O_RDWR); >> + if (fd < 0 || fd2 < 0) >> + return 1; >> + >> + version = drmGetVersion(fd); >> + if (version) { >> + printf("Version: %d.%d.%d\n", version->version_major, >> + version->version_minor, version->version_patchlevel); >> + printf(" Name: %s\n", version->name); >> + printf(" Date: %s\n", version->date); >> + printf(" Description: %s\n", version->desc); >> + >> + drmFreeVersion(version); >> + } >> + >> + err = nouveau_device_wrap(fd, 0, &nvdev); >> + if (!err) >> + err = nouveau_device_wrap(fd2, 0, &nvdev2); >> + if (err < 0) >> + return 1; >> + >> + err = nouveau_bo_new(nvdev2, NOUVEAU_BO_GART, 0, 4096, NULL, &bo); >> + if (!err) >> + err = nouveau_bo_set_prime(bo, &import_fd); >> + >> + if (!err) { >> + pthread_create(&t1, NULL, openclose, nvdev); >> + pthread_create(&t2, NULL, openclose, nvdev); >> + } >> + >> + pthread_join(t1, NULL); >> + pthread_join(t2, NULL); >> + >> + close(import_fd); >> + nouveau_bo_ref(NULL, &bo); >> + >> + nouveau_device_del(&nvdev2); >> + nouveau_device_del(&nvdev); >> + close(fd2); >> + close(fd); >> + >> + if (failed) >> + fprintf(stderr, "DRM_IOCTL_GEM_CLOSE failed with EINVAL,\n" >> + "race in opening/closing bo is likely.\n"); >> + >> + return failed; >> +} >> diff --git a/xf86atomic.h b/xf86atomic.h >> index 8c4b696..66a8d9a 100644 >> --- a/xf86atomic.h >> +++ b/xf86atomic.h >> @@ -49,7 +49,8 @@ typedef struct { >> # define atomic_read(x) ((x)->atomic) >> # define atomic_set(x, val) ((x)->atomic = (val)) >> # define atomic_inc(x) ((void) __sync_fetch_and_add (&(x)->atomic, 1)) >> -# define atomic_dec_and_test(x) (__sync_fetch_and_add (&(x)->atomic, -1) == 1) >> +# define atomic_inc_return(x) (__sync_add_and_fetch (&(x)->atomic, 1)) >> +# define atomic_dec_and_test(x) (__sync_add_and_fetch (&(x)->atomic, -1) == 0) > The atomic_dec_and_test change seems like unrelated bugfix. Split it > out perhaps ?Not a bug fix, just swapping the order.. - atomic_fetch_and_add(-1) == 1 + atomic_add_and_fetch(-1) == 0> Introduction of atomic_inc_return could/should be a separate commit as well.>> # define atomic_add(x, v) ((void) __sync_add_and_fetch(&(x)->atomic, (v))) >> # define atomic_dec(x, v) ((void) __sync_sub_and_fetch(&(x)->atomic, (v))) >> # define atomic_cmpxchg(x, oldv, newv) __sync_val_compare_and_swap (&(x)->atomic, oldv, newv) >> @@ -68,6 +69,7 @@ typedef struct { >> # define atomic_read(x) AO_load_full(&(x)->atomic) >> # define atomic_set(x, val) AO_store_full(&(x)->atomic, (val)) >> # define atomic_inc(x) ((void) AO_fetch_and_add1_full(&(x)->atomic)) >> +# define atomic_inc_return(x) (AO_fetch_and_add1_full(&(x)->atomic) + 1) >> # define atomic_add(x, v) ((void) AO_fetch_and_add_full(&(x)->atomic, (v))) >> # define atomic_dec(x, v) ((void) AO_fetch_and_add_full(&(x)->atomic, -(v))) >> # define atomic_dec_and_test(x) (AO_fetch_and_sub1_full(&(x)->atomic) == 1) >> @@ -91,6 +93,7 @@ typedef struct { LIBDRM_ATOMIC_TYPE atomic; } atomic_t; >> # define atomic_read(x) (int) ((x)->atomic) >> # define atomic_set(x, val) ((x)->atomic = (LIBDRM_ATOMIC_TYPE)(val)) >> # define atomic_inc(x) (atomic_inc_uint (&(x)->atomic)) >> +# define atomic_inc_return (atomic_inc_uint_nv(&(x)->atomic)) >> # define atomic_dec_and_test(x) (atomic_dec_uint_nv(&(x)->atomic) == 0) >> # define atomic_add(x, v) (atomic_add_int(&(x)->atomic, (v))) >> # define atomic_dec(x, v) (atomic_add_int(&(x)->atomic, -(v))) >> @@ -112,3 +115,4 @@ static inline int atomic_add_unless(atomic_t *v, int add, int unless) >> } >> >> #endif >> + > Extra white-space. > > IMHO sending the series for dri-devel might be nice, for at least the > xf86atomic.h changes.Probably, not sure anyone will review it though, so I think just adding in a separate commit is fine.> P.S. Bless you dude for going through the lovely experience to fix this.Np. When I find some time I'll try nouveau in mesa next. :) ~Maarten
Apparently Analagous Threads
- [PATCH 2/2] nouveau: Do not add most bo's to the global bo list.
- [PATCH 2/2] nouveau: Do not add most bo's to the global bo list.
- [PATCH 1/2] nouveau: make nouveau importing global buffers completely thread-safe, with tests
- [PATCH v2 1/4] Add atomic_inc_return to atomics.
- [PATCH] nouveau: safen up nouveau_device list usage against concurrent access