Эх сурвалжийг харах

Merge git://git.kernel.org/pub/scm/linux/kernel/git/cmetcalf/linux-tile

* git://git.kernel.org/pub/scm/linux/kernel/git/cmetcalf/linux-tile:
  arch/tile/mm/init.c: trivial: use BUG_ON
  arch/tile: remove useless set_fixmap_nocache() macro
  arch/tile: add hypervisor-based character driver for SPI flash ROM
  ioctl-number.txt: add the tile hardwall ioctl range
  tile: use generic-y format for one-line asm-generic headers
  clocksource: tile: convert to use clocksource_register_hz
Linus Torvalds 14 жил өмнө
parent
commit
1850536b93
46 өөрчлөгдсөн 576 нэмэгдсэн , 49 устгасан
  1. 1 0
      Documentation/ioctl/ioctl-number.txt
  2. 38 0
      arch/tile/include/asm/Kbuild
  3. 0 1
      arch/tile/include/asm/bug.h
  4. 0 1
      arch/tile/include/asm/bugs.h
  5. 0 1
      arch/tile/include/asm/cputime.h
  6. 0 1
      arch/tile/include/asm/device.h
  7. 0 1
      arch/tile/include/asm/div64.h
  8. 0 1
      arch/tile/include/asm/emergency-restart.h
  9. 0 1
      arch/tile/include/asm/errno.h
  10. 0 1
      arch/tile/include/asm/fb.h
  11. 0 1
      arch/tile/include/asm/fcntl.h
  12. 0 6
      arch/tile/include/asm/fixmap.h
  13. 0 1
      arch/tile/include/asm/ioctl.h
  14. 0 1
      arch/tile/include/asm/ioctls.h
  15. 0 1
      arch/tile/include/asm/ipc.h
  16. 0 1
      arch/tile/include/asm/ipcbuf.h
  17. 0 1
      arch/tile/include/asm/irq_regs.h
  18. 0 1
      arch/tile/include/asm/kdebug.h
  19. 0 1
      arch/tile/include/asm/local.h
  20. 0 1
      arch/tile/include/asm/module.h
  21. 0 1
      arch/tile/include/asm/msgbuf.h
  22. 0 1
      arch/tile/include/asm/mutex.h
  23. 0 1
      arch/tile/include/asm/param.h
  24. 0 1
      arch/tile/include/asm/parport.h
  25. 0 1
      arch/tile/include/asm/poll.h
  26. 0 1
      arch/tile/include/asm/posix_types.h
  27. 0 1
      arch/tile/include/asm/resource.h
  28. 0 1
      arch/tile/include/asm/scatterlist.h
  29. 0 1
      arch/tile/include/asm/sembuf.h
  30. 0 1
      arch/tile/include/asm/serial.h
  31. 0 1
      arch/tile/include/asm/shmbuf.h
  32. 0 1
      arch/tile/include/asm/shmparam.h
  33. 0 1
      arch/tile/include/asm/socket.h
  34. 0 1
      arch/tile/include/asm/sockios.h
  35. 0 1
      arch/tile/include/asm/statfs.h
  36. 0 1
      arch/tile/include/asm/termbits.h
  37. 0 1
      arch/tile/include/asm/termios.h
  38. 0 1
      arch/tile/include/asm/types.h
  39. 0 1
      arch/tile/include/asm/ucontext.h
  40. 0 1
      arch/tile/include/asm/xor.h
  41. 41 0
      arch/tile/include/hv/drv_srom_intf.h
  42. 1 4
      arch/tile/kernel/time.c
  43. 1 2
      arch/tile/mm/init.c
  44. 11 0
      drivers/char/Kconfig
  45. 2 0
      drivers/char/Makefile
  46. 481 0
      drivers/char/tile-srom.c

+ 1 - 0
Documentation/ioctl/ioctl-number.txt

@@ -292,6 +292,7 @@ Code  Seq#(hex)	Include File		Comments
 					<mailto:buk@buks.ipn.de>
 					<mailto:buk@buks.ipn.de>
 0xA0	all	linux/sdp/sdp.h		Industrial Device Project
 0xA0	all	linux/sdp/sdp.h		Industrial Device Project
 					<mailto:kenji@bitgate.com>
 					<mailto:kenji@bitgate.com>
+0xA2	00-0F	arch/tile/include/asm/hardwall.h
 0xA3	80-8F	Port ACL		in development:
 0xA3	80-8F	Port ACL		in development:
 					<mailto:tlewis@mindspring.com>
 					<mailto:tlewis@mindspring.com>
 0xA3	90-9F	linux/dtlk.h
 0xA3	90-9F	linux/dtlk.h

+ 38 - 0
arch/tile/include/asm/Kbuild

@@ -2,3 +2,41 @@ include include/asm-generic/Kbuild.asm
 
 
 header-y += ucontext.h
 header-y += ucontext.h
 header-y += hardwall.h
 header-y += hardwall.h
+
+generic-y += bug.h
+generic-y += bugs.h
+generic-y += cputime.h
+generic-y += device.h
+generic-y += div64.h
+generic-y += emergency-restart.h
+generic-y += errno.h
+generic-y += fb.h
+generic-y += fcntl.h
+generic-y += ioctl.h
+generic-y += ioctls.h
+generic-y += ipc.h
+generic-y += ipcbuf.h
+generic-y += irq_regs.h
+generic-y += kdebug.h
+generic-y += local.h
+generic-y += module.h
+generic-y += msgbuf.h
+generic-y += mutex.h
+generic-y += param.h
+generic-y += parport.h
+generic-y += poll.h
+generic-y += posix_types.h
+generic-y += resource.h
+generic-y += scatterlist.h
+generic-y += sembuf.h
+generic-y += serial.h
+generic-y += shmbuf.h
+generic-y += shmparam.h
+generic-y += socket.h
+generic-y += sockios.h
+generic-y += statfs.h
+generic-y += termbits.h
+generic-y += termios.h
+generic-y += types.h
+generic-y += ucontext.h
+generic-y += xor.h

+ 0 - 1
arch/tile/include/asm/bug.h

@@ -1 +0,0 @@
-#include <asm-generic/bug.h>

+ 0 - 1
arch/tile/include/asm/bugs.h

@@ -1 +0,0 @@
-#include <asm-generic/bugs.h>

+ 0 - 1
arch/tile/include/asm/cputime.h

@@ -1 +0,0 @@
-#include <asm-generic/cputime.h>

+ 0 - 1
arch/tile/include/asm/device.h

@@ -1 +0,0 @@
-#include <asm-generic/device.h>

+ 0 - 1
arch/tile/include/asm/div64.h

@@ -1 +0,0 @@
-#include <asm-generic/div64.h>

+ 0 - 1
arch/tile/include/asm/emergency-restart.h

@@ -1 +0,0 @@
-#include <asm-generic/emergency-restart.h>

+ 0 - 1
arch/tile/include/asm/errno.h

@@ -1 +0,0 @@
-#include <asm-generic/errno.h>

+ 0 - 1
arch/tile/include/asm/fb.h

@@ -1 +0,0 @@
-#include <asm-generic/fb.h>

+ 0 - 1
arch/tile/include/asm/fcntl.h

@@ -1 +0,0 @@
-#include <asm-generic/fcntl.h>

+ 0 - 6
arch/tile/include/asm/fixmap.h

@@ -75,12 +75,6 @@ extern void __set_fixmap(enum fixed_addresses idx,
 
 
 #define set_fixmap(idx, phys) \
 #define set_fixmap(idx, phys) \
 		__set_fixmap(idx, phys, PAGE_KERNEL)
 		__set_fixmap(idx, phys, PAGE_KERNEL)
-/*
- * Some hardware wants to get fixmapped without caching.
- */
-#define set_fixmap_nocache(idx, phys) \
-		__set_fixmap(idx, phys, PAGE_KERNEL_NOCACHE)
-
 #define clear_fixmap(idx) \
 #define clear_fixmap(idx) \
 		__set_fixmap(idx, 0, __pgprot(0))
 		__set_fixmap(idx, 0, __pgprot(0))
 
 

+ 0 - 1
arch/tile/include/asm/ioctl.h

@@ -1 +0,0 @@
-#include <asm-generic/ioctl.h>

+ 0 - 1
arch/tile/include/asm/ioctls.h

@@ -1 +0,0 @@
-#include <asm-generic/ioctls.h>

+ 0 - 1
arch/tile/include/asm/ipc.h

@@ -1 +0,0 @@
-#include <asm-generic/ipc.h>

+ 0 - 1
arch/tile/include/asm/ipcbuf.h

@@ -1 +0,0 @@
-#include <asm-generic/ipcbuf.h>

+ 0 - 1
arch/tile/include/asm/irq_regs.h

@@ -1 +0,0 @@
-#include <asm-generic/irq_regs.h>

+ 0 - 1
arch/tile/include/asm/kdebug.h

@@ -1 +0,0 @@
-#include <asm-generic/kdebug.h>

+ 0 - 1
arch/tile/include/asm/local.h

@@ -1 +0,0 @@
-#include <asm-generic/local.h>

+ 0 - 1
arch/tile/include/asm/module.h

@@ -1 +0,0 @@
-#include <asm-generic/module.h>

+ 0 - 1
arch/tile/include/asm/msgbuf.h

@@ -1 +0,0 @@
-#include <asm-generic/msgbuf.h>

+ 0 - 1
arch/tile/include/asm/mutex.h

@@ -1 +0,0 @@
-#include <asm-generic/mutex-dec.h>

+ 0 - 1
arch/tile/include/asm/param.h

@@ -1 +0,0 @@
-#include <asm-generic/param.h>

+ 0 - 1
arch/tile/include/asm/parport.h

@@ -1 +0,0 @@
-#include <asm-generic/parport.h>

+ 0 - 1
arch/tile/include/asm/poll.h

@@ -1 +0,0 @@
-#include <asm-generic/poll.h>

+ 0 - 1
arch/tile/include/asm/posix_types.h

@@ -1 +0,0 @@
-#include <asm-generic/posix_types.h>

+ 0 - 1
arch/tile/include/asm/resource.h

@@ -1 +0,0 @@
-#include <asm-generic/resource.h>

+ 0 - 1
arch/tile/include/asm/scatterlist.h

@@ -1 +0,0 @@
-#include <asm-generic/scatterlist.h>

+ 0 - 1
arch/tile/include/asm/sembuf.h

@@ -1 +0,0 @@
-#include <asm-generic/sembuf.h>

+ 0 - 1
arch/tile/include/asm/serial.h

@@ -1 +0,0 @@
-#include <asm-generic/serial.h>

+ 0 - 1
arch/tile/include/asm/shmbuf.h

@@ -1 +0,0 @@
-#include <asm-generic/shmbuf.h>

+ 0 - 1
arch/tile/include/asm/shmparam.h

@@ -1 +0,0 @@
-#include <asm-generic/shmparam.h>

+ 0 - 1
arch/tile/include/asm/socket.h

@@ -1 +0,0 @@
-#include <asm-generic/socket.h>

+ 0 - 1
arch/tile/include/asm/sockios.h

@@ -1 +0,0 @@
-#include <asm-generic/sockios.h>

+ 0 - 1
arch/tile/include/asm/statfs.h

@@ -1 +0,0 @@
-#include <asm-generic/statfs.h>

+ 0 - 1
arch/tile/include/asm/termbits.h

@@ -1 +0,0 @@
-#include <asm-generic/termbits.h>

+ 0 - 1
arch/tile/include/asm/termios.h

@@ -1 +0,0 @@
-#include <asm-generic/termios.h>

+ 0 - 1
arch/tile/include/asm/types.h

@@ -1 +0,0 @@
-#include <asm-generic/types.h>

+ 0 - 1
arch/tile/include/asm/ucontext.h

@@ -1 +0,0 @@
-#include <asm-generic/ucontext.h>

+ 0 - 1
arch/tile/include/asm/xor.h

@@ -1 +0,0 @@
-#include <asm-generic/xor.h>

+ 41 - 0
arch/tile/include/hv/drv_srom_intf.h

@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 Tilera Corporation. All Rights Reserved.
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU General Public License
+ *   as published by the Free Software Foundation, version 2.
+ *
+ *   This program is distributed in the hope that it will be useful, but
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ *   NON INFRINGEMENT.  See the GNU General Public License for
+ *   more details.
+ */
+
+/**
+ * @file drv_srom_intf.h
+ * Interface definitions for the SPI Flash ROM driver.
+ */
+
+#ifndef _SYS_HV_INCLUDE_DRV_SROM_INTF_H
+#define _SYS_HV_INCLUDE_DRV_SROM_INTF_H
+
+/** Read this offset to get the total device size. */
+#define SROM_TOTAL_SIZE_OFF   0xF0000000
+
+/** Read this offset to get the device sector size. */
+#define SROM_SECTOR_SIZE_OFF  0xF0000004
+
+/** Read this offset to get the device page size. */
+#define SROM_PAGE_SIZE_OFF    0xF0000008
+
+/** Write this offset to flush any pending writes. */
+#define SROM_FLUSH_OFF        0xF1000000
+
+/** Write this offset, plus the byte offset of the start of a sector, to
+ *  erase a sector.  Any write data is ignored, but there must be at least
+ *  one byte of write data.  Only applies when the driver is in MTD mode.
+ */
+#define SROM_ERASE_OFF        0xF2000000
+
+#endif /* _SYS_HV_INCLUDE_DRV_SROM_INTF_H */

+ 1 - 4
arch/tile/kernel/time.c

@@ -78,7 +78,6 @@ static struct clocksource cycle_counter_cs = {
 	.rating = 300,
 	.rating = 300,
 	.read = clocksource_get_cycles,
 	.read = clocksource_get_cycles,
 	.mask = CLOCKSOURCE_MASK(64),
 	.mask = CLOCKSOURCE_MASK(64),
-	.shift = 22,   /* typical value, e.g. x86 tsc uses this */
 	.flags = CLOCK_SOURCE_IS_CONTINUOUS,
 	.flags = CLOCK_SOURCE_IS_CONTINUOUS,
 };
 };
 
 
@@ -91,8 +90,6 @@ void __init setup_clock(void)
 	cycles_per_sec = hv_sysconf(HV_SYSCONF_CPU_SPEED);
 	cycles_per_sec = hv_sysconf(HV_SYSCONF_CPU_SPEED);
 	sched_clock_mult =
 	sched_clock_mult =
 		clocksource_hz2mult(cycles_per_sec, SCHED_CLOCK_SHIFT);
 		clocksource_hz2mult(cycles_per_sec, SCHED_CLOCK_SHIFT);
-	cycle_counter_cs.mult =
-		clocksource_hz2mult(cycles_per_sec, cycle_counter_cs.shift);
 }
 }
 
 
 void __init calibrate_delay(void)
 void __init calibrate_delay(void)
@@ -107,7 +104,7 @@ void __init calibrate_delay(void)
 void __init time_init(void)
 void __init time_init(void)
 {
 {
 	/* Initialize and register the clock source. */
 	/* Initialize and register the clock source. */
-	clocksource_register(&cycle_counter_cs);
+	clocksource_register_hz(&cycle_counter_cs, cycles_per_sec);
 
 
 	/* Start up the tile-timer interrupt source on the boot cpu. */
 	/* Start up the tile-timer interrupt source on the boot cpu. */
 	setup_tile_timer();
 	setup_tile_timer();

+ 1 - 2
arch/tile/mm/init.c

@@ -836,8 +836,7 @@ void __init mem_init(void)
 #endif
 #endif
 
 
 #ifdef CONFIG_FLATMEM
 #ifdef CONFIG_FLATMEM
-	if (!mem_map)
-		BUG();
+	BUG_ON(!mem_map);
 #endif
 #endif
 
 
 #ifdef CONFIG_HIGHMEM
 #ifdef CONFIG_HIGHMEM

+ 11 - 0
drivers/char/Kconfig

@@ -616,5 +616,16 @@ config MSM_SMD_PKT
 	  Enables userspace clients to read and write to some packet SMD
 	  Enables userspace clients to read and write to some packet SMD
 	  ports via device interface for MSM chipset.
 	  ports via device interface for MSM chipset.
 
 
+config TILE_SROM
+	bool "Character-device access via hypervisor to the Tilera SPI ROM"
+	depends on TILE
+	default y
+	---help---
+	  This device provides character-level read-write access
+	  to the SROM, typically via the "0", "1", and "2" devices
+	  in /dev/srom/.  The Tilera hypervisor makes the flash
+	  device appear much like a simple EEPROM, and knows
+	  how to partition a single ROM for multiple purposes.
+
 endmenu
 endmenu
 
 

+ 2 - 0
drivers/char/Makefile

@@ -63,3 +63,5 @@ obj-$(CONFIG_RAMOOPS)		+= ramoops.o
 
 
 obj-$(CONFIG_JS_RTC)		+= js-rtc.o
 obj-$(CONFIG_JS_RTC)		+= js-rtc.o
 js-rtc-y = rtc.o
 js-rtc-y = rtc.o
+
+obj-$(CONFIG_TILE_SROM)		+= tile-srom.o

+ 481 - 0
drivers/char/tile-srom.c

@@ -0,0 +1,481 @@
+/*
+ * Copyright 2011 Tilera Corporation. All Rights Reserved.
+ *
+ *   This program is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU General Public License
+ *   as published by the Free Software Foundation, version 2.
+ *
+ *   This program is distributed in the hope that it will be useful, but
+ *   WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ *   NON INFRINGEMENT.  See the GNU General Public License for
+ *   more details.
+ *
+ * SPI Flash ROM driver
+ *
+ * This source code is derived from code provided in "Linux Device
+ * Drivers, Third Edition", by Jonathan Corbet, Alessandro Rubini, and
+ * Greg Kroah-Hartman, published by O'Reilly Media, Inc.
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/kernel.h>	/* printk() */
+#include <linux/slab.h>		/* kmalloc() */
+#include <linux/fs.h>		/* everything... */
+#include <linux/errno.h>	/* error codes */
+#include <linux/types.h>	/* size_t */
+#include <linux/proc_fs.h>
+#include <linux/fcntl.h>	/* O_ACCMODE */
+#include <linux/aio.h>
+#include <linux/pagemap.h>
+#include <linux/hugetlb.h>
+#include <linux/uaccess.h>
+#include <linux/platform_device.h>
+#include <hv/hypervisor.h>
+#include <linux/ioctl.h>
+#include <linux/cdev.h>
+#include <linux/delay.h>
+#include <hv/drv_srom_intf.h>
+
+/*
+ * Size of our hypervisor I/O requests.  We break up large transfers
+ * so that we don't spend large uninterrupted spans of time in the
+ * hypervisor.  Erasing an SROM sector takes a significant fraction of
+ * a second, so if we allowed the user to, say, do one I/O to write the
+ * entire ROM, we'd get soft lockup timeouts, or worse.
+ */
+#define SROM_CHUNK_SIZE ((size_t)4096)
+
+/*
+ * When hypervisor is busy (e.g. erasing), poll the status periodically.
+ */
+
+/*
+ * Interval to poll the state in msec
+ */
+#define SROM_WAIT_TRY_INTERVAL 20
+
+/*
+ * Maximum times to poll the state
+ */
+#define SROM_MAX_WAIT_TRY_TIMES 1000
+
+struct srom_dev {
+	int hv_devhdl;			/* Handle for hypervisor device */
+	u32 total_size;			/* Size of this device */
+	u32 sector_size;		/* Size of a sector */
+	u32 page_size;			/* Size of a page */
+	struct mutex lock;		/* Allow only one accessor at a time */
+};
+
+static int srom_major;			/* Dynamic major by default */
+module_param(srom_major, int, 0);
+MODULE_AUTHOR("Tilera Corporation");
+MODULE_LICENSE("GPL");
+
+static int srom_devs;			/* Number of SROM partitions */
+static struct cdev srom_cdev;
+static struct class *srom_class;
+static struct srom_dev *srom_devices;
+
+/*
+ * Handle calling the hypervisor and managing EAGAIN/EBUSY.
+ */
+
+static ssize_t _srom_read(int hv_devhdl, void *buf,
+			  loff_t off, size_t count)
+{
+	int retval, retries = SROM_MAX_WAIT_TRY_TIMES;
+	for (;;) {
+		retval = hv_dev_pread(hv_devhdl, 0, (HV_VirtAddr)buf,
+				      count, off);
+		if (retval >= 0)
+			return retval;
+		if (retval == HV_EAGAIN)
+			continue;
+		if (retval == HV_EBUSY && --retries > 0) {
+			msleep(SROM_WAIT_TRY_INTERVAL);
+			continue;
+		}
+		pr_err("_srom_read: error %d\n", retval);
+		return -EIO;
+	}
+}
+
+static ssize_t _srom_write(int hv_devhdl, const void *buf,
+			   loff_t off, size_t count)
+{
+	int retval, retries = SROM_MAX_WAIT_TRY_TIMES;
+	for (;;) {
+		retval = hv_dev_pwrite(hv_devhdl, 0, (HV_VirtAddr)buf,
+				       count, off);
+		if (retval >= 0)
+			return retval;
+		if (retval == HV_EAGAIN)
+			continue;
+		if (retval == HV_EBUSY && --retries > 0) {
+			msleep(SROM_WAIT_TRY_INTERVAL);
+			continue;
+		}
+		pr_err("_srom_write: error %d\n", retval);
+		return -EIO;
+	}
+}
+
+/**
+ * srom_open() - Device open routine.
+ * @inode: Inode for this device.
+ * @filp: File for this specific open of the device.
+ *
+ * Returns zero, or an error code.
+ */
+static int srom_open(struct inode *inode, struct file *filp)
+{
+	filp->private_data = &srom_devices[iminor(inode)];
+	return 0;
+}
+
+
+/**
+ * srom_release() - Device release routine.
+ * @inode: Inode for this device.
+ * @filp: File for this specific open of the device.
+ *
+ * Returns zero, or an error code.
+ */
+static int srom_release(struct inode *inode, struct file *filp)
+{
+	struct srom_dev *srom = filp->private_data;
+	char dummy;
+
+	/* Make sure we've flushed anything written to the ROM. */
+	mutex_lock(&srom->lock);
+	if (srom->hv_devhdl >= 0)
+		_srom_write(srom->hv_devhdl, &dummy, SROM_FLUSH_OFF, 1);
+	mutex_unlock(&srom->lock);
+
+	filp->private_data = NULL;
+
+	return 0;
+}
+
+
+/**
+ * srom_read() - Read data from the device.
+ * @filp: File for this specific open of the device.
+ * @buf: User's data buffer.
+ * @count: Number of bytes requested.
+ * @f_pos: File position.
+ *
+ * Returns number of bytes read, or an error code.
+ */
+static ssize_t srom_read(struct file *filp, char __user *buf,
+			 size_t count, loff_t *f_pos)
+{
+	int retval = 0;
+	void *kernbuf;
+	struct srom_dev *srom = filp->private_data;
+
+	kernbuf = kmalloc(SROM_CHUNK_SIZE, GFP_KERNEL);
+	if (!kernbuf)
+		return -ENOMEM;
+
+	if (mutex_lock_interruptible(&srom->lock)) {
+		retval = -ERESTARTSYS;
+		kfree(kernbuf);
+		return retval;
+	}
+
+	while (count) {
+		int hv_retval;
+		int bytes_this_pass = min(count, SROM_CHUNK_SIZE);
+
+		hv_retval = _srom_read(srom->hv_devhdl, kernbuf,
+				       *f_pos, bytes_this_pass);
+		if (hv_retval > 0) {
+			if (copy_to_user(buf, kernbuf, hv_retval) != 0) {
+				retval = -EFAULT;
+				break;
+			}
+		} else if (hv_retval <= 0) {
+			if (retval == 0)
+				retval = hv_retval;
+			break;
+		}
+
+		retval += hv_retval;
+		*f_pos += hv_retval;
+		buf += hv_retval;
+		count -= hv_retval;
+	}
+
+	mutex_unlock(&srom->lock);
+	kfree(kernbuf);
+
+	return retval;
+}
+
+/**
+ * srom_write() - Write data to the device.
+ * @filp: File for this specific open of the device.
+ * @buf: User's data buffer.
+ * @count: Number of bytes requested.
+ * @f_pos: File position.
+ *
+ * Returns number of bytes written, or an error code.
+ */
+static ssize_t srom_write(struct file *filp, const char __user *buf,
+			  size_t count, loff_t *f_pos)
+{
+	int retval = 0;
+	void *kernbuf;
+	struct srom_dev *srom = filp->private_data;
+
+	kernbuf = kmalloc(SROM_CHUNK_SIZE, GFP_KERNEL);
+	if (!kernbuf)
+		return -ENOMEM;
+
+	if (mutex_lock_interruptible(&srom->lock)) {
+		retval = -ERESTARTSYS;
+		kfree(kernbuf);
+		return retval;
+	}
+
+	while (count) {
+		int hv_retval;
+		int bytes_this_pass = min(count, SROM_CHUNK_SIZE);
+
+		if (copy_from_user(kernbuf, buf, bytes_this_pass) != 0) {
+			retval = -EFAULT;
+			break;
+		}
+
+		hv_retval = _srom_write(srom->hv_devhdl, kernbuf,
+					*f_pos, bytes_this_pass);
+		if (hv_retval <= 0) {
+			if (retval == 0)
+				retval = hv_retval;
+			break;
+		}
+
+		retval += hv_retval;
+		*f_pos += hv_retval;
+		buf += hv_retval;
+		count -= hv_retval;
+	}
+
+	mutex_unlock(&srom->lock);
+	kfree(kernbuf);
+
+	return retval;
+}
+
+/* Provide our own implementation so we can use srom->total_size. */
+loff_t srom_llseek(struct file *filp, loff_t offset, int origin)
+{
+	struct srom_dev *srom = filp->private_data;
+
+	if (mutex_lock_interruptible(&srom->lock))
+		return -ERESTARTSYS;
+
+	switch (origin) {
+	case SEEK_END:
+		offset += srom->total_size;
+		break;
+	case SEEK_CUR:
+		offset += filp->f_pos;
+		break;
+	}
+
+	if (offset < 0 || offset > srom->total_size) {
+		offset = -EINVAL;
+	} else {
+		filp->f_pos = offset;
+		filp->f_version = 0;
+	}
+
+	mutex_unlock(&srom->lock);
+
+	return offset;
+}
+
+static ssize_t total_show(struct device *dev,
+			  struct device_attribute *attr, char *buf)
+{
+	struct srom_dev *srom = dev_get_drvdata(dev);
+	return sprintf(buf, "%u\n", srom->total_size);
+}
+
+static ssize_t sector_show(struct device *dev,
+			   struct device_attribute *attr, char *buf)
+{
+	struct srom_dev *srom = dev_get_drvdata(dev);
+	return sprintf(buf, "%u\n", srom->sector_size);
+}
+
+static ssize_t page_show(struct device *dev,
+			 struct device_attribute *attr, char *buf)
+{
+	struct srom_dev *srom = dev_get_drvdata(dev);
+	return sprintf(buf, "%u\n", srom->page_size);
+}
+
+static struct device_attribute srom_dev_attrs[] = {
+	__ATTR(total_size, S_IRUGO, total_show, NULL),
+	__ATTR(sector_size, S_IRUGO, sector_show, NULL),
+	__ATTR(page_size, S_IRUGO, page_show, NULL),
+	__ATTR_NULL
+};
+
+static char *srom_devnode(struct device *dev, mode_t *mode)
+{
+	*mode = S_IRUGO | S_IWUSR;
+	return kasprintf(GFP_KERNEL, "srom/%s", dev_name(dev));
+}
+
+/*
+ * The fops
+ */
+static const struct file_operations srom_fops = {
+	.owner =     THIS_MODULE,
+	.llseek =    srom_llseek,
+	.read =	     srom_read,
+	.write =     srom_write,
+	.open =	     srom_open,
+	.release =   srom_release,
+};
+
+/**
+ * srom_setup_minor() - Initialize per-minor information.
+ * @srom: Per-device SROM state.
+ * @index: Device to set up.
+ */
+static int srom_setup_minor(struct srom_dev *srom, int index)
+{
+	struct device *dev;
+	int devhdl = srom->hv_devhdl;
+
+	mutex_init(&srom->lock);
+
+	if (_srom_read(devhdl, &srom->total_size,
+		       SROM_TOTAL_SIZE_OFF, sizeof(srom->total_size)) < 0)
+		return -EIO;
+	if (_srom_read(devhdl, &srom->sector_size,
+		       SROM_SECTOR_SIZE_OFF, sizeof(srom->sector_size)) < 0)
+		return -EIO;
+	if (_srom_read(devhdl, &srom->page_size,
+		       SROM_PAGE_SIZE_OFF, sizeof(srom->page_size)) < 0)
+		return -EIO;
+
+	dev = device_create(srom_class, &platform_bus,
+			    MKDEV(srom_major, index), srom, "%d", index);
+	return IS_ERR(dev) ? PTR_ERR(dev) : 0;
+}
+
+/** srom_init() - Initialize the driver's module. */
+static int srom_init(void)
+{
+	int result, i;
+	dev_t dev = MKDEV(srom_major, 0);
+
+	/*
+	 * Start with a plausible number of partitions; the krealloc() call
+	 * below will yield about log(srom_devs) additional allocations.
+	 */
+	srom_devices = kzalloc(4 * sizeof(struct srom_dev), GFP_KERNEL);
+
+	/* Discover the number of srom partitions. */
+	for (i = 0; ; i++) {
+		int devhdl;
+		char buf[20];
+		struct srom_dev *new_srom_devices =
+			krealloc(srom_devices, (i+1) * sizeof(struct srom_dev),
+				 GFP_KERNEL | __GFP_ZERO);
+		if (!new_srom_devices) {
+			result = -ENOMEM;
+			goto fail_mem;
+		}
+		srom_devices = new_srom_devices;
+		sprintf(buf, "srom/0/%d", i);
+		devhdl = hv_dev_open((HV_VirtAddr)buf, 0);
+		if (devhdl < 0) {
+			if (devhdl != HV_ENODEV)
+				pr_notice("srom/%d: hv_dev_open failed: %d.\n",
+					  i, devhdl);
+			break;
+		}
+		srom_devices[i].hv_devhdl = devhdl;
+	}
+	srom_devs = i;
+
+	/* Bail out early if we have no partitions at all. */
+	if (srom_devs == 0) {
+		result = -ENODEV;
+		goto fail_mem;
+	}
+
+	/* Register our major, and accept a dynamic number. */
+	if (srom_major)
+		result = register_chrdev_region(dev, srom_devs, "srom");
+	else {
+		result = alloc_chrdev_region(&dev, 0, srom_devs, "srom");
+		srom_major = MAJOR(dev);
+	}
+	if (result < 0)
+		goto fail_mem;
+
+	/* Register a character device. */
+	cdev_init(&srom_cdev, &srom_fops);
+	srom_cdev.owner = THIS_MODULE;
+	srom_cdev.ops = &srom_fops;
+	result = cdev_add(&srom_cdev, dev, srom_devs);
+	if (result < 0)
+		goto fail_chrdev;
+
+	/* Create a sysfs class. */
+	srom_class = class_create(THIS_MODULE, "srom");
+	if (IS_ERR(srom_class)) {
+		result = PTR_ERR(srom_class);
+		goto fail_cdev;
+	}
+	srom_class->dev_attrs = srom_dev_attrs;
+	srom_class->devnode = srom_devnode;
+
+	/* Do per-partition initialization */
+	for (i = 0; i < srom_devs; i++) {
+		result = srom_setup_minor(srom_devices + i, i);
+		if (result < 0)
+			goto fail_class;
+	}
+
+	return 0;
+
+fail_class:
+	for (i = 0; i < srom_devs; i++)
+		device_destroy(srom_class, MKDEV(srom_major, i));
+	class_destroy(srom_class);
+fail_cdev:
+	cdev_del(&srom_cdev);
+fail_chrdev:
+	unregister_chrdev_region(dev, srom_devs);
+fail_mem:
+	kfree(srom_devices);
+	return result;
+}
+
+/** srom_cleanup() - Clean up the driver's module. */
+static void srom_cleanup(void)
+{
+	int i;
+	for (i = 0; i < srom_devs; i++)
+		device_destroy(srom_class, MKDEV(srom_major, i));
+	class_destroy(srom_class);
+	cdev_del(&srom_cdev);
+	unregister_chrdev_region(MKDEV(srom_major, 0), srom_devs);
+	kfree(srom_devices);
+}
+
+module_init(srom_init);
+module_exit(srom_cleanup);