add pthread user driver
[archipelago] / xseg / drivers / user / xseg_pthread.c
diff --git a/xseg/drivers/user/xseg_pthread.c b/xseg/drivers/user/xseg_pthread.c
new file mode 100644 (file)
index 0000000..1cc25aa
--- /dev/null
@@ -0,0 +1,301 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/util.h>
+#include <xseg/xseg.h>
+#include <pthread.h>
+
+#define ERRSIZE 512
+char errbuf[ERRSIZE];
+
+static long pthread_allocate(const char *name, uint64_t size)
+{
+       int fd, r;
+       fd = shm_open(name, O_RDWR | O_CREAT, 0770);
+       if (fd < 0) {
+               XSEGLOG("Cannot create shared segment: %s\n",
+                       strerror_r(errno, errbuf, ERRSIZE));
+               return fd;
+       }
+
+       r = lseek(fd, size -1, SEEK_SET);
+       if (r < 0) {
+               close(fd);
+               XSEGLOG("Cannot seek into segment file: %s\n",
+                       strerror_r(errno, errbuf, ERRSIZE));
+               return r;
+       }
+
+       errbuf[0] = 0;
+       r = write(fd, errbuf, 1);
+       if (r != 1) {
+               close(fd);
+               XSEGLOG("Failed to set segment size: %s\n",
+                       strerror_r(errno, errbuf, ERRSIZE));
+               return r;
+       }
+
+       close(fd);
+       return 0;
+}
+
+static long pthread_deallocate(const char *name)
+{
+       return shm_unlink(name);
+}
+
+static void *pthread_map(const char *name, uint64_t size, struct xseg *seg)
+{
+       struct xseg *xseg;
+       int fd;
+
+       if (seg)
+               XSEGLOG("struct xseg * is not NULL. Ignoring...\n");
+
+       fd = shm_open(name, O_RDWR, 0000);
+       if (fd < 0) {
+               XSEGLOG("Failed to open '%s' for mapping: %s\n",
+                       name, strerror_r(errno, errbuf, ERRSIZE));
+               return NULL;
+       }
+
+       xseg = mmap (   XSEG_BASE_AS_PTR,
+                       size,
+                       PROT_READ | PROT_WRITE,
+                       MAP_SHARED | MAP_FIXED /* | MAP_LOCKED */,
+                       fd, 0   );
+
+       if (xseg == MAP_FAILED) {
+               XSEGLOG("Could not map segment: %s\n",
+                       strerror_r(errno, errbuf, ERRSIZE));
+               return NULL;
+       }
+
+       close(fd);
+       return xseg;
+}
+
+static void pthread_unmap(void *ptr, uint64_t size)
+{
+       struct xseg *xseg = ptr;
+       (void)munmap(xseg, xseg->segment_size);
+}
+
+
+static void handler(int signum)
+{
+       static unsigned long counter;
+       printf("%lu: signal %d: this shouldn't have happened.\n", counter, signum);
+       counter ++;
+}
+
+static sigset_t savedset, set;
+static pthread_key_t key;
+static pthread_once_t once_init = PTHREAD_ONCE_INIT;
+static pthread_once_t once_quit = PTHREAD_ONCE_INIT;
+static int isInit;
+
+static void signal_init(void) 
+{
+       void (*h)(int);
+       int r;
+       h = signal(SIGIO, handler);
+       if (h == SIG_ERR){
+               isInit = 0;
+               return;
+       }
+
+       sigemptyset(&set);
+       sigaddset(&set, SIGIO);
+
+       r = pthread_sigmask(SIG_BLOCK, &set, &savedset);
+       if (r < 0) {
+               isInit = 0;
+               return;
+       }
+       
+       r = pthread_key_create(&key, NULL);
+       if (r < 0) {
+               isInit = 0;
+               return;
+       }
+       isInit = 1;
+       once_quit = PTHREAD_ONCE_INIT;
+}
+
+static void signal_quit(void) 
+{
+       pthread_key_delete(key);
+       signal(SIGIO, SIG_DFL);
+       pthread_sigmask(SIG_SETMASK, &savedset, NULL);
+       isInit = 0;
+       once_init = PTHREAD_ONCE_INIT;
+}
+
+#define INT_TO_POINTER(__myptr, __myint) \
+       do {\
+               unsigned long __foo__ = (unsigned long) __myint; \
+               __myptr = (void *) __foo__ ; \
+       } while (0)
+
+#define POINTER_TO_INT(__myint, __myptr)\
+       do { \
+               unsigned long __foo__ = (unsigned long) __myptr; \
+               __myint = (int) __foo__ ; \
+       } while (0)
+
+/* must be called by each thread */
+static int pthread_signal_init(void)
+{
+       int r;
+       pid_t pid;
+       void *tmp;
+       pthread_once(&once_init, signal_init);
+       if (!isInit)
+               return -1;
+       pid = syscall(SYS_gettid);
+       INT_TO_POINTER(tmp, pid);
+       r = pthread_setspecific(key, tmp);
+       return r;
+}
+
+/* can be called by each thread */
+static void pthread_signal_quit(void)
+{
+       pthread_once(&once_quit, signal_quit);
+}
+
+static int pthread_prepare_wait(struct xseg *xseg, uint32_t portno)
+{
+       void * tmp;
+       pid_t pid;
+       struct xseg_port *port = xseg_get_port(xseg, portno);
+       if (!port) 
+               return -1;
+
+       tmp = pthread_getspecific(key);
+       POINTER_TO_INT(pid, tmp);
+       if (!pid)
+               return -1;
+
+       xpool_add(&port->waiters, (xpool_index) pid, portno); 
+       return 0;
+}
+
+static int pthread_cancel_wait(struct xseg *xseg, uint32_t portno)
+{
+       void * tmp;
+       pid_t pid;
+       xpool_data data;
+       struct xseg_port *port = xseg_get_port(xseg, portno);
+       if (!port) 
+               return -1;
+       
+       tmp = pthread_getspecific(key);
+       POINTER_TO_INT(pid, tmp);
+       if (!pid)
+               return -1;
+
+       xpool_remove(&port->waiters, (xpool_index) pid, &data, portno);
+       return 0;
+}
+
+static int pthread_wait_signal(struct xseg *xseg, uint32_t usec_timeout)
+{
+       int r;
+       siginfo_t siginfo;
+       struct timespec ts;
+
+       ts.tv_sec = usec_timeout / 1000000;
+       ts.tv_nsec = 1000 * (usec_timeout - ts.tv_sec * 1000000);
+
+       /* FIXME: Now that pthread signaling is fixed, we could get rid of the timeout
+        * and use a NULL timespec linux-specific)
+        */
+       r = sigtimedwait(&set, &siginfo, &ts);
+       if (r < 0)
+               return r;
+
+       return siginfo.si_signo;
+}
+
+static int pthread_signal(struct xseg *xseg, uint32_t portno)
+{
+       xpool_data data;
+       xpool_index idx;
+
+       struct xseg_port *port = xseg_get_port(xseg, portno);
+       if (!port) 
+               return -1;
+
+       idx = xpool_peek(&port->waiters, &data, portno); //FIXME portno is not the caller but the callee
+       if (idx == NoIndex) 
+               return -1;
+
+       pid_t cue = (pid_t) data;
+       if (!cue)
+               return 0;
+
+       return syscall(SYS_tkill, cue, SIGIO);
+}
+
+static void *pthread_malloc(uint64_t size)
+{
+       return malloc((size_t)size);
+}
+
+static void *pthread_realloc(void *mem, uint64_t size)
+{
+       return realloc(mem, (size_t)size);
+}
+
+static void pthread_mfree(void *mem)
+{
+       free(mem);
+}
+
+static struct xseg_type xseg_pthread = {
+       /* xseg_operations */
+       {
+               .mfree          = pthread_mfree,
+               .allocate       = pthread_allocate,
+               .deallocate     = pthread_deallocate,
+               .map            = pthread_map,
+               .unmap          = pthread_unmap,
+       },
+       /* name */
+       "pthread"
+};
+
+static struct xseg_peer xseg_peer_pthread = {
+       /* xseg_peer_operations */
+       {
+               .signal_init    = pthread_signal_init,
+               .signal_quit    = pthread_signal_quit,
+               .prepare_wait   = pthread_prepare_wait,
+               .cancel_wait    = pthread_cancel_wait,
+               .wait_signal    = pthread_wait_signal,
+               .signal         = pthread_signal,
+               .malloc         = pthread_malloc,
+               .realloc        = pthread_realloc,
+               .mfree          = pthread_mfree,
+       },
+       /* name */
+       "pthread"
+};
+
+void xseg_pthread_init(void)
+{
+       xseg_register_type(&xseg_pthread);
+       xseg_register_peer(&xseg_peer_pthread);
+}
+