1/*
2 * Copyright © 2015 Tobias Nygren
3 * Copyright © 2013 Keith Packard
4 *
5 * Permission to use, copy, modify, distribute, and sell this software and its
6 * documentation for any purpose is hereby granted without fee, provided that
7 * the above copyright notice appear in all copies and that both that copyright
8 * notice and this permission notice appear in supporting documentation, and
9 * that the name of the copyright holders not be used in advertising or
10 * publicity pertaining to distribution of the software without specific,
11 * written prior permission.  The copyright holders make no representations
12 * about the suitability of this software for any purpose.  It is provided "as
13 * is" without express or implied warranty.
14 *
15 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
16 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
17 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
18 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
19 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
20 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
21 * OF THIS SOFTWARE.
22 */
23
24#if HAVE_CONFIG_H
25#include "config.h"
26#endif
27
28#include <err.h>
29#include <errno.h>
30#include <fcntl.h>
31#include <stdio.h>
32#include <unistd.h>
33
34#include "xshmfenceint.h"
35
36static sem_t *mksemtemp(char *, size_t, const char *);
37
38#define COND_WAIT_W() do {} while (sem_wait(f->cond_w) != 0)
39#define COND_SIGNAL_W() do { sem_post(f->cond_w); } while (0)
40#define COND_WAIT_T() do {} while (sem_wait(f->cond_t) != 0)
41#define COND_SIGNAL_T() do { sem_post(f->cond_t); } while (0)
42
43/**
44 * xshmfence_trigger:
45 * @f: An X fence
46 *
47 * Set @f to triggered, waking all waiters.
48 *
49 * Return value: 0 on success and -1 on error (in which case, errno
50 * will be set as appropriate).
51 **/
52int
53xshmfence_trigger(struct xshmfence *f) {
54	int v, waiting;
55	v = __sync_bool_compare_and_swap(&f->triggered, 0, 1);
56	if (v == 0) {
57		/* already triggered */
58		return 0;
59	}
60
61	while ((waiting = __sync_fetch_and_add(&f->waiting, 0)) > 0) {
62		if (__sync_bool_compare_and_swap(&f->waiting, waiting, 0)) {
63			__sync_fetch_and_add(&f->wakeups, waiting);
64			while (waiting--) {
65				COND_SIGNAL_W();
66			}
67			COND_WAIT_T();
68			return 0;
69		}
70	}
71
72	return 0;
73}
74
75/**
76 * xshmfence_await:
77 * @f: An X fence
78 *
79 * Wait for @f to be triggered. If @f is already triggered, this
80 * function returns immediately.
81 *
82 * Return value: 0 on success and -1 on error (in which case, errno
83 * will be set as appropriate).
84 **/
85int
86xshmfence_await(struct xshmfence *f) {
87
88	if (__sync_fetch_and_add(&f->triggered, 0) == 1) {
89		return 0;
90	}
91	do {
92		__sync_fetch_and_add(&f->waiting, 1);
93		COND_WAIT_W();
94	} while (__sync_fetch_and_add(&f->triggered, 0) == 0);
95
96	if (__sync_sub_and_fetch(&f->wakeups, 1) == 0) {
97		COND_SIGNAL_T();
98	}
99
100	return 0;
101}
102
103/**
104 * xshmfence_query:
105 * @f: An X fence
106 *
107 * Return value: 1 if @f is triggered, else returns 0.
108 **/
109int
110xshmfence_query(struct xshmfence *f) {
111	return __sync_fetch_and_add(&f->triggered, 0);
112}
113
114/**
115 * xshmfence_reset:
116 * @f: An X fence
117 *
118 * Reset @f to untriggered. If @f is already untriggered,
119 * this function has no effect.
120 **/
121void
122xshmfence_reset(struct xshmfence *f) {
123	__sync_bool_compare_and_swap(&f->triggered, 1, 0);
124}
125
126/**
127 * xshmfence_init:
128 * @fd: An fd for an X fence
129 *
130 * Initialize the fence when first allocated
131 **/
132void
133xshmfence_init(int fd)
134{
135	sem_t *cond_w, *cond_t;
136	struct xshmfence f;
137
138	__sync_fetch_and_and(&f.refcnt, 0);
139	__sync_fetch_and_and(&f.triggered, 0);
140	__sync_fetch_and_and(&f.waiting, 0);
141	__sync_fetch_and_and(&f.wakeups, 0);
142
143	cond_w = mksemtemp(f.condname_w, sizeof(f.condname_w), "w");
144	if (cond_w == SEM_FAILED) {
145		err(EXIT_FAILURE, "xshmfence_init: sem_open");
146	}
147	cond_t = mksemtemp(f.condname_t, sizeof(f.condname_t), "t");
148	if (cond_t == SEM_FAILED) {
149		err(EXIT_FAILURE, "xshmfence_init: sem_open");
150	}
151
152	sem_close(cond_w);
153	sem_close(cond_t);
154
155	pwrite(fd, &f, sizeof(f), 0);
156}
157
158/**
159 * xshmfence_open_semaphore:
160 * @f: An X fence
161 *
162 * Open the semaphore after mapping the fence
163 **/
164void
165xshmfence_open_semaphore(struct xshmfence *f)
166{
167	/*
168	 * map process local memory to page 2
169	 */
170	if (mmap ((void*)&f->cond_w,
171		  LIBXSHM_PAGESIZE,
172		  PROT_READ|PROT_WRITE,
173		  MAP_FIXED|MAP_ANON,
174		  -1, 0) == MAP_FAILED) {
175		errx(EXIT_FAILURE, "xshmfence_open_semaphore: mmap failed");
176	}
177
178	if ((f->cond_w = sem_open(f->condname_w, 0)) == SEM_FAILED) {
179		errx(EXIT_FAILURE, "xshmfence_open_semaphore: sem_open failed");
180	}
181
182	if ((f->cond_t = sem_open(f->condname_t, 0)) == SEM_FAILED) {
183		errx(EXIT_FAILURE, "xshmfence_open_semaphore: sem_open failed");
184	}
185	__sync_fetch_and_add(&f->refcnt, 1);
186}
187
188/**
189 * xshmfence_close_semaphore:
190 * @f: An X fence
191 *
192 * Close the semaphore before unmapping the fence
193 **/
194void
195xshmfence_close_semaphore(struct xshmfence *f)
196{
197	sem_close(f->cond_w);
198	sem_close(f->cond_t);
199	if (__sync_sub_and_fetch(&f->refcnt, 1) == 0) {
200		sem_unlink(f->condname_w);
201		sem_unlink(f->condname_t);
202	}
203}
204
205static sem_t *
206mksemtemp(char *name, size_t namelen, const char *suffix)
207{
208	sem_t *ret;
209	pid_t p;
210	p = getpid();
211	for(;;) {
212		if (snprintf(name, namelen, "/xshmf%s-%d", suffix, p) >= namelen)
213			return SEM_FAILED;
214		ret = sem_open(name, O_CREAT|O_EXCL, 0600, 0);
215		if (ret == SEM_FAILED) {
216			if (errno == EEXIST) {
217				p++;
218				continue;
219			}
220		}
221		return ret;
222	}
223}
224