diff options
Diffstat (limited to 'driver')
-rw-r--r-- | driver/Makefile | 21 | ||||
-rw-r--r-- | driver/config.h | 26 | ||||
-rw-r--r-- | driver/fopdrv.c | 76 | ||||
-rw-r--r-- | driver/fopdrv.h | 11 | ||||
-rw-r--r-- | driver/main.c | 110 | ||||
-rw-r--r-- | driver/test/.gitignore | 1 | ||||
-rw-r--r-- | driver/test/main.c | 56 | ||||
-rw-r--r-- | driver/test/makefile | 2 | ||||
-rwxr-xr-x | driver/test/test.sh | 24 |
9 files changed, 327 insertions, 0 deletions
diff --git a/driver/Makefile b/driver/Makefile new file mode 100644 index 0000000..d968e31 --- /dev/null +++ b/driver/Makefile @@ -0,0 +1,21 @@ +RELEASE := $(shell uname -r) +KERNEL := /lib/modules/$(RELEASE)/build +# PWD doesn't have to be defined as it's inherited from the current environment +# MAKE shouldn't be defined as it's special in GNU make + +# the names of these variables are required by the kernel's (imo weird) build +# system, see [kbuild-obj-var] and [kbuild-module-makefile] in ../readme.md +obj-m += lork.o +lork-y += main.o +lork-y += fopdrv.o + +all: + $(MAKE) -C $(KERNEL) M=$(PWD) modules + +# the generated compile_commands.json needs to be edited to point to the kernel +# folder as working directory because the include paths are relative +compile_commands.json: + compiledb -o $@.tmp make -Bn + jq --arg dir $(KERNEL) '.[].directory = $$dir' $@.tmp > $@ + $(RM) $@.tmp + diff --git a/driver/config.h b/driver/config.h new file mode 100644 index 0000000..dbe3a86 --- /dev/null +++ b/driver/config.h @@ -0,0 +1,26 @@ +#pragma once + +#define DRV_NAME "lork" + +#include <linux/types.h> + +// values from <https://www.ti.com/lit/ug/spruh73q/spruh73q.pdf> + +typedef uint32_t ti_am335x_word_t; + +// p. 180: "Control Module" +#define TI_AM335X_CM_ADDR 0x44E10000 +// p. 182: "GPIO1" +#define TI_AM335X_GPIO1_ADDR 0x4804C000 +// p. 1459: "conf_gpmc_a3" +#define TI_AM335X_CM_GPMC_A3_ADDR (TI_AM335X_CM_ADDR + 0x84C) + +// p. 4990, variable names copied as-is +#define GPIO_OE (0x134 / (sizeof(ti_am335x_word_t))) +#define GPIO_DATAIN (0x138 / (sizeof(ti_am335x_word_t))) +#define GPIO_CLEARDATAOUT (0x190 / (sizeof(ti_am335x_word_t))) +#define GPIO_SETDATAOUT (0x194 / (sizeof(ti_am335x_word_t))) +// total size of GPIO registers from p. 4990 (in bytes) +#define GPIO_REG_SIZE 0x198 + +#define PIN 19 diff --git a/driver/fopdrv.c b/driver/fopdrv.c new file mode 100644 index 0000000..18a1d35 --- /dev/null +++ b/driver/fopdrv.c @@ -0,0 +1,76 @@ +#include <linux/io.h> + +#include "fopdrv.h" +#include "config.h" + +bool printed = false; + +// driver/char/mem.c read_null (/dev/null) +ssize_t fop_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { + printk("%s(<file>, <buf>, %u, <ppos>)\n", __PRETTY_FUNCTION__, count); + + if (count < 2) return 0; + if (printed) return 0; + + ti_am335x_word_t* gpio1 = ioremap(TI_AM335X_GPIO1_ADDR, GPIO_REG_SIZE); + barrier(); + + ti_am335x_word_t gpio = ioread32(gpio1 + GPIO_DATAIN); rmb(); + bool on = (gpio & (1<<PIN)) > 0; + + char output[10]; + snprintf(output, 10, "%d\n", on); + + if (copy_to_user(buf, output + *ppos, 2)) + return -EFAULT; + *ppos += 2; + printed = true; + return 2; +} + +ssize_t fop_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { + printk("%s(<file>, <buf>, %u, <ppos>)\n", __PRETTY_FUNCTION__, count); + + // only allow single character as input + if (count < 1) return count; + if (count > 2) return count; + + // copy buffer for reading (see [kernel-labs-chrdev] in ../readme.md) + char input_buf[10]; + if (copy_from_user(input_buf + *ppos, buf, count)) + return -EFAULT; + + ti_am335x_word_t* gpio1 = ioremap(TI_AM335X_GPIO1_ADDR, GPIO_REG_SIZE); + barrier(); + + if (input_buf[0] == '0') { + printk("TODO: TURN OFF OUTPUT\n"); + iowrite32((1<<PIN), gpio1 + GPIO_CLEARDATAOUT); wmb(); + } + if (input_buf[0] == '1') { + printk("TODO: TURN ON OUTPUT\n"); + iowrite32((1<<PIN), gpio1 + GPIO_SETDATAOUT); wmb(); + } + + iounmap(gpio1); + + return count; +} + +int fop_open(struct inode * inode, struct file * file) { + printk("%s(<inode>, <file>)\n", __PRETTY_FUNCTION__); + printed = false; + return 0; + // 0 seems to be a safe return value as it's used in driver/char/mem.c. The + // manual page for open(2) says that the system call returns a nonnegative + // integer representing the file descriptor on success, but it does not + // appears to be required. +} + +int fop_release(struct inode * inode, struct file * file) { + printk("%s(<inode>, <file>)\n", __PRETTY_FUNCTION__); + return 0; + // same as above, but found in driver/char/lp.c +} + + diff --git a/driver/fopdrv.h b/driver/fopdrv.h new file mode 100644 index 0000000..f2ca18c --- /dev/null +++ b/driver/fopdrv.h @@ -0,0 +1,11 @@ +#pragma once + +#include <linux/fs.h> +#include <linux/types.h> + +// declarations copied from linux/fs.h +ssize_t fop_read(struct file *, char __user *, size_t, loff_t *); +ssize_t fop_write(struct file *, const char __user *, size_t, loff_t *); +int fop_open(struct inode *, struct file *); +int fop_release(struct inode *, struct file *); + diff --git a/driver/main.c b/driver/main.c new file mode 100644 index 0000000..d8ea7fc --- /dev/null +++ b/driver/main.c @@ -0,0 +1,110 @@ +#include <linux/cdev.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/io.h> + +#include "fopdrv.h" +#include "config.h" + +int one = 0; +module_param(one, int, 0400); +MODULE_PARM_DESC(one, "first parameter, only printed on init"); + +int not_named_two = 0; +module_param_named(two, not_named_two, int, 0400); +MODULE_PARM_DESC(not_named_two, "second parameter, only printed on init"); + +struct cdev *cdev; +struct device *dev; +struct class *class; +dev_t node; +struct file_operations fops = { + .read = fop_read, + .write = fop_write, + .open = fop_open, + .release = fop_release, +}; + +static int mod_init(void) { + int err; + + err = alloc_chrdev_region(&node, 0, 1, DRV_NAME); + if (err < 0) { + printk(KERN_ERR "alloc_chrdev_region error %d\n", err); + goto return_err; + } + + class = class_create(DRV_NAME); + if (IS_ERR_OR_NULL(class)) { + err = PTR_ERR(class); + printk(KERN_ERR "class_create error %d\n", err); + goto free_class; + } + + cdev = cdev_alloc(); + if (!cdev) { + err = ENOMEM; + goto free_cdev; + } + + cdev->ops = &fops; + cdev_init(cdev, &fops); + err = cdev_add(cdev, node, 1); + if (err < 0) { + printk(KERN_ERR "cdev_add error %d\n", err); + goto free_cdev; + } + + dev = device_create(class, NULL, node, NULL, DRV_NAME); + if (IS_ERR_OR_NULL(dev)) { + err = PTR_ERR(dev); + printk(KERN_ERR "device_create error %d\n", err); + goto free_device; + } + + printk("%s() -> 0 (%d:%d)\n", __PRETTY_FUNCTION__, MAJOR(node), MINOR(node)); + printk("one = %d, two = %d\n", one, not_named_two); + + // configure gpio mux + uint32_t* conf_gpmc_a3 = ioremap(TI_AM335X_CM_GPMC_A3_ADDR, sizeof(ti_am335x_word_t)); + if (conf_gpmc_a3 == NULL) + return -EINVAL; + barrier(); + uint32_t val = ioread32(conf_gpmc_a3); rmb(); + val &= ~0x7; + val |= 0x7; + iowrite32(val, conf_gpmc_a3); wmb(); + iounmap(conf_gpmc_a3); + + // configure output pin + ti_am335x_word_t* gpio1 = ioremap(TI_AM335X_GPIO1_ADDR, GPIO_REG_SIZE); + barrier(); + ti_am335x_word_t oe_reg = ioread32(gpio1 + GPIO_OE); rmb(); + oe_reg &= ~(1<<PIN); // 0=output + iowrite32(oe_reg, gpio1 + GPIO_OE); wmb(); + iounmap(gpio1); + + return 0; + +free_device: + device_destroy(class, node); +free_cdev: + cdev_del(cdev); +free_class: + class_destroy(class); +return_err: + printk("%s() -> %d\n", __PRETTY_FUNCTION__, err); + return err; +} + +static void mod_exit(void) { + device_destroy(class, node); + cdev_del(cdev); + class_destroy(class); + printk("%s()\n", __PRETTY_FUNCTION__); +} + +module_init(mod_init); +module_exit(mod_exit); +MODULE_LICENSE("Dual MIT/GPL"); + diff --git a/driver/test/.gitignore b/driver/test/.gitignore new file mode 100644 index 0000000..ba2906d --- /dev/null +++ b/driver/test/.gitignore @@ -0,0 +1 @@ +main diff --git a/driver/test/main.c b/driver/test/main.c new file mode 100644 index 0000000..b87e56c --- /dev/null +++ b/driver/test/main.c @@ -0,0 +1,56 @@ +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> + +int main(int argc, char** argv) { + if (geteuid() != 0) { + fprintf(stderr, "run me as root!\n"); + return 1; + } + + argc--; // argv[0] is the program name + if (argc == 0) { + fprintf(stderr, "usage: %s /dev/lork\n", argv[0]); + return 1; + } + + char* dev = argv[1]; + const char* input = "test"; + int fd; + ssize_t bytes; + + fd = open(dev, O_RDWR); + if (-1 == fd) { + fprintf(stderr, "open() failed with code %d\n", errno); + return 1; + } + + bytes = write(fd, input, strlen(input)); + if (-1 == bytes) { + fprintf(stderr, "write() failed with code %d\n", errno); + return 1; + } + fprintf(stderr, "input \"%s\" to %s (%ld bytes succesful)\n", input, dev, bytes); + + char buf[80]; + bytes = read(fd, buf, 80); + if (-1 == bytes) { + fprintf(stderr, "read() failed with code %d\n", errno); + return 1; + } + fprintf(stderr, "output from %s: (%ld bytes)\n", dev, bytes); + if (bytes > 0) { + if (bytes < 80) buf[bytes] = '\0'; // null terminate string for printf + printf("%s\n", buf); + } + + if (-1 == close(fd)) { + fprintf(stderr, "close() failed with code %d\n", errno); + return 1; + } + + return 0; +} + diff --git a/driver/test/makefile b/driver/test/makefile new file mode 100644 index 0000000..3491973 --- /dev/null +++ b/driver/test/makefile @@ -0,0 +1,2 @@ +main: main.o + diff --git a/driver/test/test.sh b/driver/test/test.sh new file mode 100755 index 0000000..7dba005 --- /dev/null +++ b/driver/test/test.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +if [ $(id -u) -ne 0 ] ; then + echo "run me as root!" >&2 + exit 1 +fi + +if [ $# -eq 0 ] ; then + echo "usage: $0 /dev/lork" >&2 + exit 1 +fi + +DEV="$1" +INPUT="test" + +echo "input \"$INPUT\" to $DEV" >&2 +echo "$INPUT" > "$DEV" +echo "(EC $?)" >&2 + +echo "output from $DEV:" >&2 +cat "$DEV" +echo "(EC $?)" >&2 + +exit 0 |