watchdog: Add a simple mmap() stub for shwdt.
authorPaul Mundt <lethal@linux-sh.org>
Wed, 27 Sep 2006 08:53:59 +0000 (17:53 +0900)
committerPaul Mundt <lethal@linux-sh.org>
Wed, 27 Sep 2006 08:53:59 +0000 (17:53 +0900)
In some applications people have expressed a need for an mmap() method,
so we implement a simple stub for this that maps back a page with the
counter in it.

Signed-off-by: Paul Mundt <lethal@linux-sh.org>
drivers/char/watchdog/Kconfig
drivers/char/watchdog/shwdt.c

index fff89c2..f114d7b 100644 (file)
@@ -510,6 +510,14 @@ config SH_WDT
          To compile this driver as a module, choose M here: the
          module will be called shwdt.
 
+config SH_WDT_MMAP
+       bool "Allow mmap of SH WDT"
+       default n
+       depends on SH_WDT
+       help
+         If you say Y here, user applications will be able to mmap the
+         WDT/CPG registers.
+#
 # SPARC64 Architecture
 
 config WATCHDOG_CP1XXX
index 7c39f39..e5b8c64 100644 (file)
@@ -27,7 +27,7 @@
 #include <linux/notifier.h>
 #include <linux/ioport.h>
 #include <linux/fs.h>
-
+#include <linux/mm.h>
 #include <asm/io.h>
 #include <asm/uaccess.h>
 #include <asm/watchdog.h>
@@ -257,6 +257,55 @@ static ssize_t sh_wdt_write(struct file *file, const char *buf,
        return count;
 }
 
+/**
+ *     sh_wdt_mmap - map WDT/CPG registers into userspace
+ *     @file: file structure for the device
+ *     @vma: VMA to map the registers into
+ *
+ *     A simple mmap() implementation for the corner cases where the counter
+ *     needs to be mapped in userspace directly. Due to the relatively small
+ *     size of the area, neighbouring registers not necessarily tied to the
+ *     CPG will also be accessible through the register page, so this remains
+ *     configurable for users that really know what they're doing.
+ *
+ *     Additionaly, the register page maps in the CPG register base relative
+ *     to the nearest page-aligned boundary, which requires that userspace do
+ *     the appropriate CPU subtype math for calculating the page offset for
+ *     the counter value.
+ */
+static int sh_wdt_mmap(struct file *file, struct vm_area_struct *vma)
+{
+       int ret = -ENOSYS;
+
+#ifdef CONFIG_SH_WDT_MMAP
+       unsigned long addr;
+
+       /* Only support the simple cases where we map in a register page. */
+       if (((vma->vm_end - vma->vm_start) != PAGE_SIZE) || vma->vm_pgoff)
+               return -EINVAL;
+
+       /*
+        * Pick WTCNT as the start, it's usually the first register after the
+        * FRQCR, and neither one are generally page-aligned out of the box.
+        */
+       addr = WTCNT & ~(PAGE_SIZE - 1);
+
+       vma->vm_flags |= VM_IO;
+       vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+       if (io_remap_pfn_range(vma, vma->vm_start, addr >> PAGE_SHIFT,
+                              PAGE_SIZE, vma->vm_page_prot)) {
+               printk(KERN_ERR PFX "%s: io_remap_pfn_range failed\n",
+                      __FUNCTION__);
+               return -EAGAIN;
+       }
+
+       ret = 0;
+#endif
+
+       return ret;
+}
+
 /**
  *     sh_wdt_ioctl - Query Device
  *     @inode: inode of device
@@ -342,6 +391,7 @@ static const struct file_operations sh_wdt_fops = {
        .ioctl          = sh_wdt_ioctl,
        .open           = sh_wdt_open,
        .release        = sh_wdt_close,
+       .mmap           = sh_wdt_mmap,
 };
 
 static struct watchdog_info sh_wdt_info = {