Artifact Content
Not logged in

Artifact 7509a7cc4952bf016fbe6cf9b550371ffb0eb6a5:


/*
 * This file contains the code for directly accessing the GPIO pins.
 */

#include "tcl.h"
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>

#define BCM2708_PERI_BASE_DEFAULT	0x20000000
#define BCM2709_PERI_BASE_DEFAULT	0x3f000000
#define GPIO_BASE_OFFSET		0x200000
#define PAGE_SIZE	(4*1024)
#define BLOCK_SIZE	(4*1024)
#define GPSET0		7
#define GPSET1		8
#define GPCLR0		10
#define GPCLR1		11
#define GPLEV0		13
#define GPLEV1		14
#define GPPUD		37
#define GPPUDCLK0	38
#define GPPUDCLK1	39

TCL_DECLARE_MUTEX(setupMutex);

static volatile unsigned *gpio = NULL;

static const char *edgestr[] = {
   "none", "falling", "rising", "both"
};
enum {
   GPIO_INT_NONE, GPIO_INT_FALLING, GPIO_INT_RISING, GPIO_INT_BOTH
};

typedef struct {
    Tcl_Interp *interp;
    int fd;
    int state;
    Tcl_Obj *rising, *falling;
} GpioEvent;

typedef struct {
    Tcl_Event event;
    Tcl_Interp *interp;
    Tcl_Obj *script;
    int num, edge;
} Tcl_GpioEvent;

static GpioEvent *gpio_events[54];

static void GpioExitHandler(ClientData);

static int setup_io(Tcl_Interp *interp)
{
    int mem_fd;
    char *gpio_mem, *gpio_map;
    unsigned peri_base, gpio_base;
    unsigned char buf[4];
    FILE *fp;
    char line [120];

    /* First check for gpiomem - this will always give us the correct
     * memory location and does not need root access
     */
    if ((mem_fd = open("/dev/gpiomem", O_RDWR|O_SYNC)) > 0) {
	gpio_map = (char *)mmap(NULL, BLOCK_SIZE,
		PROT_READ|PROT_WRITE, MAP_SHARED, mem_fd, 0);
	if (gpio_map == MAP_FAILED) {
	    Tcl_AppendResult(interp, "couldn't map memory: ",
			     Tcl_PosixError(interp), NULL);
	    close(mem_fd);
	    return TCL_ERROR;
	} else {
	    /* Always use volatile pointer! */
	    gpio = (volatile unsigned *)gpio_map;
	    return TCL_OK;
	}
    }

    /* Fall back to /dev/mem and requiring root */

    /* Determine the peri base */
    if ((fp = fopen("/proc/device-tree/soc/ranges", "rb")) != NULL) {
	/* get peri base from device tree */
        fseek(fp, 4, SEEK_SET);
        if (fread(buf, 1, sizeof buf, fp) == sizeof buf) {
            peri_base = buf[0] << 24 | buf[1] << 16 | buf[2] << 8 | buf[3];
	    fclose(fp);
	} else {
	    fclose(fp);
	    Tcl_AppendResult(interp, "couldn't read device tree", NULL);
	    return TCL_ERROR;
	}
    } else {
	/* guess peri base based on /proc/cpuinfo hardware field */
	if ((fp = fopen("/proc/cpuinfo", "r")) == NULL) {
	    Tcl_AppendResult(interp, "couldn't open /proc/cpuinfo: ",
			     Tcl_PosixError(interp), NULL);
	    return TCL_ERROR;
	}

	while (fgets(line, 120, fp) != NULL)
	  if (strncmp(line, "Hardware", 8) == 0) break;
	fclose(fp);

	if (strstr(line, "BCM2708") != NULL)
	  /* Model 1 */
	  peri_base = BCM2708_PERI_BASE_DEFAULT;
	else if (strstr(line, "BCM2709") != NULL)
	  peri_base = BCM2709_PERI_BASE_DEFAULT;
	else {
	    Tcl_AppendResult(interp, "couldn't determine peri base", NULL);
	    return TCL_ERROR;
	}
    }

    gpio_base = peri_base + GPIO_BASE_OFFSET;

    /* mmap the GPIO memory registers */
    if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) {
	Tcl_AppendResult(interp, "couldn't open /dev/mem: ",
			 Tcl_PosixError(interp), NULL);
	return TCL_ERROR;
    }

    /* Allocate MAP block */
    if ((gpio_mem = ckalloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) {
	close(mem_fd);
	return TCL_ERROR;
    }

    if ((unsigned long)gpio_mem % PAGE_SIZE)
      gpio_mem += PAGE_SIZE - ((unsigned long)gpio_mem % PAGE_SIZE);

    /* Now map it */
    gpio_map = (char *)mmap((void *)gpio_mem, BLOCK_SIZE,
	PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, mem_fd, gpio_base);

    if ((long)gpio_map < 0) {
	close(mem_fd);
	ckfree(gpio_mem);
	Tcl_AppendResult(interp, "couldn't map memory: ",
			 Tcl_PosixError(interp), NULL);
	return TCL_ERROR;
    }

    /* Always use volatile pointer! */
    gpio = (volatile unsigned *)gpio_map;

    Tcl_CreateExitHandler(GpioExitHandler, NULL);

    return TCL_OK;
}

#define GPIO_INIT if (gpio == NULL && setup_io(interp) != TCL_OK) return TCL_ERROR;

int GpioFunctionCmd(ClientData dummy, Tcl_Interp *interp,
		    int objc, Tcl_Obj *const objv[])
{
    int i, j, val, num;
    Tcl_Obj *result, *func[8];
    static const char *funcstr[] = {
	"input", "output", "altfunc5", "altfunc4",
	"altfunc0", "altfunc1", "altfunc2", "altfunc3", NULL
    };

    if (objc > 3) {
	Tcl_WrongNumArgs(interp, 1, objv, "?gpiopin? ?function?");
	return TCL_ERROR;
    }

    GPIO_INIT;

    if (objc == 1) {
	for (i = 0; funcstr[i] != NULL; i++) {
	    func[i] = Tcl_NewStringObj(funcstr[i], -1);
	}

	result = Tcl_NewObj();

	/* Read GPFSELn values */
	for (i = 0; i < 6; i++) {
	    val = *(gpio + i);
	    for (j = 0; j < 10 && 10 * i + j < 54; j++) {
		Tcl_ListObjAppendElement(NULL, result, func[val & 7]);
		val >>= 3;
	    }
	}
	Tcl_SetObjResult(interp, result);
	return TCL_OK;
    }

    if (Tcl_GetIntFromObj(interp, objv[1], &num) != TCL_OK)
      return TCL_ERROR;
    if (num < 0 || num > 53) {
	Tcl_AppendResult(interp, "pin number must be from 0 to 53", NULL);
	return TCL_ERROR;
    }

    if (objc == 2) {
	val = (*(gpio + num / 10) >> ((num % 10) * 3)) & 7;
	Tcl_SetObjResult(interp, Tcl_NewStringObj(funcstr[val], -1));
	return TCL_OK;
    }

    if (Tcl_GetIndexFromObj(interp, objv[2], funcstr, "function", 0, &val) != TCL_OK)
      return TCL_ERROR;
    val = *(gpio + num / 10) ^ (val << ((num % 10) * 3));
    *(gpio + num / 10) ^= val & (7 << ((num % 10) * 3));
    return TCL_OK;
}

int GpioOutputCmd(ClientData dummy, Tcl_Interp *interp,
		  int objc, Tcl_Obj *const objv[])
{
    int num, state, offset;

    if (objc != 3) {
	Tcl_WrongNumArgs(interp, 1, objv, "gpiopin state");
	return TCL_ERROR;
    }

    if (Tcl_GetIntFromObj(interp, objv[1], &num) != TCL_OK)
      return TCL_ERROR;
    if (num < 0 || num > 53) {
	Tcl_AppendResult(interp, "pin number must be from 0 to 53", NULL);
	return TCL_ERROR;
    }

    if (Tcl_GetIntFromObj(interp, objv[2], &state) != TCL_OK)
      return TCL_ERROR;

    GPIO_INIT;

    offset = (state != 0 ? GPSET0 : GPCLR0) + num / 32;
    *(gpio + offset) = 1 << (num % 32);
    return TCL_OK;
}

int GpioInputCmd(ClientData dummy, Tcl_Interp *interp,
		 int objc, Tcl_Obj *const objv[])
{
    int num, state, offset;

    if (objc != 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "gpiopin");
	return TCL_ERROR;
    }

    if (Tcl_GetIntFromObj(interp, objv[1], &num) != TCL_OK)
      return TCL_ERROR;
    if (num < 0 || num > 53) {
	Tcl_AppendResult(interp, "pin number must be from 0 to 53", NULL);
	return TCL_ERROR;
    }

    GPIO_INIT;

    offset = GPLEV0 + num / 32;
    state = (*(gpio + offset) >> (num % 32)) & 1;
    Tcl_SetObjResult(interp, Tcl_NewBooleanObj(state));
    return TCL_OK;
}

int GpioUSleepCmd(ClientData dummy, Tcl_Interp *interp,
		 int objc, Tcl_Obj *const objv[])
{
    int usec;

    if (objc != 2) {
	Tcl_WrongNumArgs(interp, 1, objv, "usec");
	return TCL_ERROR;
    }

    if (Tcl_GetIntFromObj(interp, objv[1], &usec) != TCL_OK)
      return TCL_ERROR;
    if (usec < 0) {
	Tcl_AppendResult(interp, "usec must be a positive integer", NULL);
	return TCL_ERROR;
    }

    usleep(usec);
    return TCL_OK;
}

int GpioPullCmd(ClientData dummy, Tcl_Interp *interp,
		      int objc, Tcl_Obj *const objv[])
{
    unsigned int mask[2] = {0, 0};
    int pull, num, x;

    static const char *pullstr[] = {
	"none", "down", "up", NULL
    };
    enum {
	GPIO_PULL_NONE = 0,
	GPIO_PULL_DOWN = 1,
	GPIO_PULL_UP = 2
    };

    if (objc < 3) {
	Tcl_WrongNumArgs(interp, 1, objv, "level gpiopin ?...?");
	return TCL_ERROR;
    }
    if (Tcl_GetIndexFromObj(interp, objv[1], pullstr,
			    "level", 0, &pull) != TCL_OK) {
	return TCL_ERROR;
    }
    for (x = 2; x < objc; x++) {
	if (Tcl_GetIntFromObj(interp, objv[x], &num) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (num < 0 || num > 53) {
	    Tcl_AppendResult(interp, "pin number must be from 0 to 53", NULL);
	    return TCL_ERROR;
	}
	mask[num / 32] |= 1 << (num % 32);
    }

    GPIO_INIT;

    *(gpio + GPPUD) = pull;
    usleep(10);
    *(gpio + GPPUDCLK0) = mask[0];
    *(gpio + GPPUDCLK1) = mask[1];
    usleep(10);
    *(gpio + GPPUD) = GPIO_PULL_NONE;
    *(gpio + GPPUDCLK0) = 0;
    *(gpio + GPPUDCLK1) = 0;
    return TCL_OK;
}

static int GpioExport(int num)
{
    int fd, n;
    char line[32];

    sprintf(line, "/sys/class/gpio/gpio%d", num);
    if (access(line, R_OK | W_OK | X_OK) != 0) {
	/* Pin is not yet exported */
	fd = open("/sys/class/gpio/export", O_WRONLY);
	if (fd < 0) {
	    return TCL_ERROR;
	}
	n = sprintf(line, "%d\n", num);
	if (write(fd, line, n) != n) {
	    close(fd);
	    return TCL_ERROR;
	}
	close(fd);
    }
    return TCL_OK;
}

static int GpioEdge(int num, const char *str)
{
   int fd, n, cnt = 0;
   char line[32];

   sprintf(line, "/sys/class/gpio/gpio%d/edge", num);
   while (access(line, R_OK | W_OK) != 0) {
      /* Allow some time for the file to spring into existence */
      /* This is normally accomplished within around 20 ms, */
      /* But on older hardware it can take around 100 ms */
      if (cnt++ > 25)
	return TCL_ERROR;
      usleep(5000);
   }
   fd = open(line, O_WRONLY);
   if (fd < 0)
     return TCL_ERROR;
   n = sprintf(line, "%s\n", str);
   if (write(fd, line, n) != n) {
      close(fd);
      return TCL_ERROR;
   }
   close(fd);
   return TCL_OK;
}

static void GpioRelease(int num) {
   GpioEvent *evPtr;

   Tcl_MutexLock(&setupMutex);
   evPtr = gpio_events[num];
   gpio_events[num] = NULL;
   Tcl_MutexUnlock(&setupMutex);

   if (evPtr != NULL) {
      if (evPtr->fd >= 0) {
	 GpioEdge(num, "none");
	 Tcl_DeleteFileHandler(evPtr->fd);
	 close(evPtr->fd);
      }
      if (evPtr->rising != NULL)
	Tcl_DecrRefCount(evPtr->rising);
      if (evPtr->falling != NULL)
	Tcl_DecrRefCount(evPtr->falling);

      ckfree((void *)evPtr);
   }
}

static void GpioUnbind(Tcl_Interp *interp, int num, int edge)
{
   int current = GPIO_INT_NONE;
   GpioEvent *evPtr;

   evPtr = gpio_events[num];
   /* In theory the pin may already have been released and */
   /* posibly even reassigned to another interpreter */
   if (evPtr != NULL && evPtr->interp == interp) {
      if (evPtr->rising != NULL)
	current |= GPIO_INT_RISING;
      if (evPtr->falling != NULL)
	current |= GPIO_INT_FALLING;
      if ((current & edge) == current) {
	 GpioRelease(num);
      } else if ((current & edge) != 0) {
	 if ((current & edge & GPIO_INT_RISING) != 0) {
	    Tcl_DecrRefCount(evPtr->rising);
	    evPtr->rising = NULL;
	 }
	 if ((current & edge & GPIO_INT_FALLING) != 0) {
	    Tcl_DecrRefCount(evPtr->falling);
	    evPtr->falling = NULL;
	 }
	 GpioEdge(num, edgestr[current & ~edge]);
      }
   }
}

static int GpioCallBackHandler(Tcl_Event *evPtr, int flags)
{
   Tcl_GpioEvent *ev = (Tcl_GpioEvent *) evPtr;

   Tcl_Preserve(ev->interp);
   if (Tcl_EvalObjEx(ev->interp, ev->script, TCL_EVAL_GLOBAL) != TCL_OK) {
      Tcl_AddErrorInfo(ev->interp, "\n    (\"gpio event\")");
      Tcl_BackgroundError(ev->interp);
      GpioUnbind(ev->interp, ev->num, ev->edge);
   }
   Tcl_Release(ev->interp);
   return 1;
}

static void GpioEventHandler(ClientData clientData, int mask)
{
   int n, edge, num = (int)(intptr_t)clientData;
   Tcl_Obj *script;
   Tcl_GpioEvent *cbPtr;
   GpioEvent *evPtr;
   char line[32];

   if (!(mask & TCL_EXCEPTION))
     return;

   evPtr = gpio_events[num];
   lseek(evPtr->fd, 0, SEEK_SET);
   n = read(evPtr->fd, line, sizeof line);
   if (n > 0) {
      if (evPtr->state != '1') {
	 script = evPtr->rising;
	 edge = GPIO_INT_RISING;
      } else {
	 script = evPtr->falling;
	 edge = GPIO_INT_FALLING;
      }
      if (script != NULL) {
	 cbPtr = (Tcl_GpioEvent *) ckalloc(sizeof(Tcl_GpioEvent));
	 cbPtr->event.proc = GpioCallBackHandler;
	 cbPtr->interp = evPtr->interp;
	 cbPtr->script = script;
	 cbPtr->num = num;
	 cbPtr->edge = edge;
	 Tcl_IncrRefCount(cbPtr->script);
	 Tcl_QueueEvent((Tcl_Event *)cbPtr, TCL_QUEUE_TAIL);
      }
      if (line[0] == evPtr->state) {
	 /* Must have been a short pulse */
	 if (line[0] == '1') {
	    script = evPtr->rising;
	    edge = GPIO_INT_RISING;
	 } else {
	    script = evPtr->falling;
	    edge = GPIO_INT_FALLING;
	 }
	 if (script != NULL) {
	    cbPtr = (Tcl_GpioEvent *) ckalloc(sizeof(Tcl_GpioEvent));
	    cbPtr->event.proc = GpioCallBackHandler;
	    cbPtr->interp = evPtr->interp;
	    cbPtr->script = script;
	    cbPtr->num = num;
	    cbPtr->edge = edge;
	    Tcl_IncrRefCount(cbPtr->script);
	    Tcl_QueueEvent((Tcl_Event *)cbPtr, TCL_QUEUE_TAIL);
	 }
      }
      evPtr->state = line[0];
   }
}

static void GpioEventCleanup(ClientData dummy, Tcl_Interp *interp)
{
    int i;

    for (i = 0; i <= 53; i++)
      if (gpio_events[i] != NULL && gpio_events[i]->interp == interp)
	GpioRelease(i);
}

static void GpioExitHandler(ClientData dummy)
{
    int i;

    for (i = 0; i <= 53; i++)
      if (gpio_events[i] != NULL) GpioRelease(i);
}

int GpioEventCmd(ClientData dummy, Tcl_Interp *interp,
		 int objc, Tcl_Obj *const objv[])
{
   int edge, num, fd, owner;
   GpioEvent *evPtr;
   char line[32];
   Tcl_Obj *command = NULL;

   static const char *edgeopt[] = {
      "falling", "rising", NULL
   };
   enum {
      GPIO_OPT_FALLING, GPIO_OPT_RISING
   };

   if (objc < 3 || objc > 4) {
      Tcl_WrongNumArgs(interp, 1, objv, "gpiopin edge ?command?");
      return TCL_ERROR;
   }

   if (Tcl_GetIntFromObj(interp, objv[1], &num) != TCL_OK) {
      return TCL_ERROR;
   }
   if (num < 0 || num > 53) {
      Tcl_AppendResult(interp, "pin number must be from 0 to 53", NULL);
      return TCL_ERROR;
   }

   if (Tcl_GetIndexFromObj(interp, objv[2], edgeopt,
			   "edge", 0, &edge) != TCL_OK) {
      return TCL_ERROR;
   }

   GPIO_INIT;

   Tcl_MutexLock(&setupMutex);
   evPtr = gpio_events[num];
   owner = (evPtr != NULL && evPtr->interp == interp);
   if (evPtr == NULL && objc > 3) {
      /* Claim the pin */
      evPtr = (GpioEvent *) ckalloc(sizeof(GpioEvent));
      evPtr->interp = interp;
      evPtr->fd = -1;
      evPtr->rising = NULL;
      evPtr->falling = NULL;
      gpio_events[num] = evPtr;
      owner = 1;
   }
   Tcl_MutexUnlock(&setupMutex);

   if (objc < 4) {
      if (!owner)
	return TCL_OK;
      /* This interp owns the pin, so no other interp should be */
      /* touching it and we don't need mutex protection */
      if (edge == GPIO_OPT_RISING) {
	 if (evPtr->rising != NULL) {
	    Tcl_SetObjResult(interp, evPtr->rising);
	 }
      } else {
	 if (evPtr->falling != NULL) {
	    Tcl_SetObjResult(interp, evPtr->falling);
	 }
      }
      return TCL_OK;
   }

   if (!owner) {
      Tcl_AppendResult(interp, "gpio pin already in use by another ",
		       "interpreter", NULL);
      return TCL_ERROR;
   }

   if (GpioExport(num) != TCL_OK) {
      GpioRelease(num);
      Tcl_AppendResult(interp, "couldn't export gpio pin: ",
		       Tcl_PosixError(interp), NULL);
      return TCL_ERROR;
   }

   if (Tcl_GetString(objv[3])[0] != '\0') {
      command = objv[3];
      Tcl_IncrRefCount(command);
   }

   if (edge == GPIO_OPT_RISING) {
      if (evPtr->rising != NULL)
	Tcl_DecrRefCount(evPtr->rising);
      evPtr->rising = command;
   } else {
      if (evPtr->falling != NULL)
	Tcl_DecrRefCount(evPtr->falling);
      evPtr->falling = command;
   }

   edge = GPIO_INT_NONE;
   if (evPtr->falling != NULL) edge |= GPIO_INT_FALLING;
   if (evPtr->rising != NULL) edge |= GPIO_INT_RISING;

   if (GpioEdge(num, edgestr[edge]) != TCL_OK) {
      GpioRelease(num);
      Tcl_AppendResult(interp, "couldn't change trigger mode on gpio pin: ",
		       Tcl_PosixError(interp), NULL);
      return TCL_ERROR;
   }

   if (edge == GPIO_INT_NONE) {
      GpioRelease(num);
      return TCL_OK;
   }

   if (evPtr->fd < 0) {
      sprintf(line, "/sys/class/gpio/gpio%d/value", num);
      fd = open(line, O_RDONLY | O_NONBLOCK);
      if (fd < 0) {
	 GpioRelease(num);
	 return TCL_ERROR;
      }
      read(fd, line, sizeof line);
      evPtr->state = line[0];
      evPtr->fd = fd;
      Tcl_CreateFileHandler(evPtr->fd, TCL_EXCEPTION,
			    GpioEventHandler, (void *)(intptr_t)num);
      Tcl_DontCallWhenDeleted(interp, GpioEventCleanup, NULL);
      Tcl_CallWhenDeleted(interp, GpioEventCleanup, NULL);
   }
   return TCL_OK;
}