All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Bruno Prémont" <bonbons@linux-vserver.org>
To: Jiri Kosina <jkosina@suse.cz>
Cc: linux-input@vger.kernel.org, linux-usb@vger.kernel.org,
	linux-fbdev@vger.kernel.org, linux-kernel@vger.kernel.org,
	"Rick L. Vinyard Jr." <rvinyard@cs.nmsu.edu>,
	Nicu Pavel <npavel@ituner.com>, Oliver Neukum <oliver@neukum.org>,
	Jaya Kumar <jayakumar.lkml@gmail.com>,
	Dmitry Torokhov <dmitry.torokhov@gmail.com>
Subject: [PATCH v3 6/6] hid: add experimental access to PicoLCD device's EEPROM and FLASH
Date: Wed, 24 Mar 2010 23:58:35 +0100	[thread overview]
Message-ID: <20100324235835.0e7e1bcb@neptune.home> (raw)
In-Reply-To: <20100324233707.7243b04d@neptune.home>

The PicoLCD device has a small amount of EEPROM and also provides access
to its FLASH where firmware and splash image are saved.
In flasher mode FLASH access is the only active feature.

Give read/write access to both via debugfs files.

NOTE: EEPROM and FLASH access should be switched to better suited API,
      until then they will reside in debugfs

Changes since v2:
 - Drop inline keyword on non-stub functions
 - Prevent interleaved flash writes via mutex
 - Add description of flash/eeprom access schematics

Signed-off-by: Bruno Prémont <bonbons@linux-vserver.org>
---
 drivers/hid/Kconfig       |    2 +-
 drivers/hid/hid-picolcd.c |  439 +++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 423 insertions(+), 18 deletions(-)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 34f6593..a2ecd83 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -281,9 +281,9 @@ config HID_PICOLCD
 	  - Backlight control         (needs CONFIG_BACKLIGHT_CLASS_DEVICE)
 	  - Contrast control          (needs CONFIG_LCD_CLASS_DEVICE)
 	  - General purpose outputs   (needs CONFIG_LEDS_CLASS)
+	  - EEProm / Flash access     (via debugfs)
 	  Features that are not (yet) supported:
 	  - IR
-	  - EEProm / Flash access
 
 config HID_QUANTA
 	tristate "Quanta Optical Touch"
diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c
index ff70aa2..6a88a72 100644
--- a/drivers/hid/hid-picolcd.c
+++ b/drivers/hid/hid-picolcd.c
@@ -169,6 +169,10 @@ struct picolcd_pending {
 struct picolcd_data {
 	struct hid_device *hdev;
 #ifdef CONFIG_DEBUG_FS
+	struct dentry *debug_reset;
+	struct dentry *debug_eeprom;
+	struct dentry *debug_flash;
+	struct mutex mutex_flash;
 	int addr_sz;
 #endif
 	u8 version[2];
@@ -1301,6 +1305,357 @@ static DEVICE_ATTR(operation_mode, 0644, picolcd_operation_mode_show,
 
 #ifdef CONFIG_DEBUG_FS
 /*
+ * The "reset" file
+ */
+static int picolcd_debug_reset_show(struct seq_file *f, void *p)
+{
+	if (picolcd_fbinfo((struct picolcd_data *)f->private))
+		seq_printf(f, "all fb\n");
+	else
+		seq_printf(f, "all\n");
+	return 0;
+}
+
+static ssize_t picolcd_debug_reset_open(struct inode *inode, struct file *f)
+{
+	return single_open(f, picolcd_debug_reset_show, inode->i_private);
+}
+
+static ssize_t picolcd_debug_reset_write(struct file *f, const char __user *user_buf,
+		size_t count, loff_t *ppos)
+{
+	struct picolcd_data *data = ((struct seq_file *)f->private_data)->private;
+	char buf[32];
+	size_t cnt = min(count, sizeof(buf)-1);
+	if (copy_from_user(buf, user_buf, cnt))
+		return -EFAULT;
+
+	while (cnt > 0 && (buf[cnt-1] == ' ' || buf[cnt-1] == '\n'))
+		cnt--;
+	buf[cnt] = '\0';
+	if (strcmp(buf, "all") == 0) {
+		picolcd_reset(data->hdev);
+		picolcd_fb_reset(data, 1);
+	} else if (strcmp(buf, "fb") == 0) {
+		picolcd_fb_reset(data, 1);
+	} else {
+		return -EINVAL;
+	}
+	return count;
+}
+
+static const struct file_operations picolcd_debug_reset_fops = {
+	.owner    = THIS_MODULE,
+	.open     = picolcd_debug_reset_open,
+	.read     = seq_read,
+	.llseek   = seq_lseek,
+	.write    = picolcd_debug_reset_write,
+	.release  = single_release,
+};
+
+/*
+ * The "eeprom" file
+ */
+static int picolcd_debug_eeprom_open(struct inode *i, struct file *f)
+{
+	f->private_data = i->i_private;
+	return 0;
+}
+
+static ssize_t picolcd_debug_eeprom_read(struct file *f, char __user *u,
+		size_t s, loff_t *off)
+{
+	struct picolcd_data *data = f->private_data;
+	struct picolcd_pending *resp;
+	u8 raw_data[3];
+	ssize_t ret = -EIO;
+
+	if (s == 0)
+		return -EINVAL;
+	if (*off > 0x0ff)
+		return 0;
+
+	/* prepare buffer with info about what we want to read (addr & len) */
+	raw_data[0] = *off & 0xff;
+	raw_data[1] = (*off >> 8) && 0xff;
+	raw_data[2] = s < 20 ? s : 20;
+	if (*off + raw_data[2] > 0xff)
+		raw_data[2] = 0x100 - *off;
+	resp = picolcd_send_and_wait(data->hdev, REPORT_EE_READ, raw_data,
+			sizeof(raw_data));
+	if (!resp)
+		return -EIO;
+
+	if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) {
+		/* successful read :) */
+		ret = resp->raw_data[2];
+		if (ret > s)
+			ret = s;
+		if (copy_to_user(u, resp->raw_data+3, ret))
+			ret = -EFAULT;
+		else
+			*off += ret;
+	} /* anything else is some kind of IO error */
+
+	kfree(resp);
+	return ret;
+}
+
+static ssize_t picolcd_debug_eeprom_write(struct file *f, const char __user *u,
+		size_t s, loff_t *off)
+{
+	struct picolcd_data *data = f->private_data;
+	struct picolcd_pending *resp;
+	ssize_t ret = -EIO;
+	u8 raw_data[23];
+
+	if (s == 0)
+		return -EINVAL;
+	if (*off > 0x0ff)
+		return -ENOSPC;
+
+	memset(raw_data, 0, sizeof(raw_data));
+	raw_data[0] = *off & 0xff;
+	raw_data[1] = (*off >> 8) && 0xff;
+	raw_data[2] = s < 20 ? s : 20;
+	if (*off + raw_data[2] > 0xff)
+		raw_data[2] = 0x100 - *off;
+
+	if (copy_from_user(raw_data+3, u, raw_data[2]))
+		return -EFAULT;
+	resp = picolcd_send_and_wait(data->hdev, REPORT_EE_WRITE, raw_data,
+			sizeof(raw_data));
+
+	if (!resp)
+		return -EIO;
+
+	if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) {
+		/* check if written data matches */
+		if (memcmp(raw_data, resp->raw_data, 3+raw_data[2]) == 0) {
+			*off += raw_data[2];
+			ret = raw_data[2];
+		}
+	}
+	kfree(resp);
+	return ret;
+}
+
+/*
+ * Notes:
+ * - read/write happens in chunks of at most 20 bytes, it's up to userspace
+ *   to loop in order to get more data.
+ * - on write errors on otherwise correct write request the bytes
+ *   that should have been written are in undefined state.
+ */
+static const struct file_operations picolcd_debug_eeprom_fops = {
+	.owner    = THIS_MODULE,
+	.open     = picolcd_debug_eeprom_open,
+	.read     = picolcd_debug_eeprom_read,
+	.write    = picolcd_debug_eeprom_write,
+	.llseek   = generic_file_llseek,
+};
+
+/*
+ * The "flash" file
+ */
+static int picolcd_debug_flash_open(struct inode *i, struct file *f)
+{
+	f->private_data = i->i_private;
+	return 0;
+}
+
+/* record a flash address to buf (bounds check to be done by caller) */
+static int _picolcd_flash_setaddr(struct picolcd_data *data, u8 *buf, long off)
+{
+	buf[0] = off & 0xff;
+	buf[1] = (off >> 8) & 0xff;
+	if (data->addr_sz == 3)
+		buf[2] = (off >> 16) & 0xff;
+	return data->addr_sz == 2 ? 2 : 3;
+}
+
+/* read a given size of data (bounds check to be done by caller) */
+static ssize_t _picolcd_flash_read(struct picolcd_data *data, int report_id,
+		char __user *u, size_t s, loff_t *off)
+{
+	struct picolcd_pending *resp;
+	u8 raw_data[4];
+	ssize_t ret = 0;
+	int len_off, err = -EIO;
+
+	while (s > 0) {
+		err = -EIO;
+		len_off = _picolcd_flash_setaddr(data, raw_data, *off);
+		raw_data[len_off] = s > 32 ? 32 : s;
+		resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off+1);
+		if (!resp || !resp->in_report)
+			goto skip;
+		if (resp->in_report->id == REPORT_MEMORY ||
+			resp->in_report->id == REPORT_BL_READ_MEMORY) {
+			if (memcmp(raw_data, resp->raw_data, len_off+1) != 0)
+				goto skip;
+			if (copy_to_user(u+ret, resp->raw_data+len_off+1, raw_data[len_off])) {
+				err = -EFAULT;
+				goto skip;
+			}
+			*off += raw_data[len_off];
+			s    -= raw_data[len_off];
+			ret  += raw_data[len_off];
+			err   = 0;
+		}
+skip:
+		kfree(resp);
+		if (err)
+			return ret > 0 ? ret : err;
+	}
+	return ret;
+}
+
+static ssize_t picolcd_debug_flash_read(struct file *f, char __user *u,
+		size_t s, loff_t *off)
+{
+	struct picolcd_data *data = f->private_data;
+
+	if (s == 0)
+		return -EINVAL;
+	if (*off > 0x05fff)
+		return 0;
+	if (*off + s > 0x05fff)
+		s = 0x06000 - *off;
+
+	if (data->status & PICOLCD_BOOTLOADER)
+		return _picolcd_flash_read(data, REPORT_BL_READ_MEMORY, u, s, off);
+	else
+		return _picolcd_flash_read(data, REPORT_READ_MEMORY, u, s, off);
+}
+
+/* erase block aligned to 64bytes boundary */
+static ssize_t _picolcd_flash_erase64(struct picolcd_data *data, int report_id,
+		loff_t *off)
+{
+	struct picolcd_pending *resp;
+	u8 raw_data[3];
+	int len_off;
+	ssize_t ret = -EIO;
+
+	if (*off & 0x3f)
+		return -EINVAL;
+
+	len_off = _picolcd_flash_setaddr(data, raw_data, *off);
+	resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off);
+	if (!resp || !resp->in_report)
+		goto skip;
+	if (resp->in_report->id == REPORT_MEMORY ||
+		resp->in_report->id == REPORT_BL_ERASE_MEMORY) {
+		if (memcmp(raw_data, resp->raw_data, len_off) != 0)
+			goto skip;
+		ret = 0;
+	}
+skip:
+	kfree(resp);
+	return ret;
+}
+
+/* write a given size of data (bounds check to be done by caller) */
+static ssize_t _picolcd_flash_write(struct picolcd_data *data, int report_id,
+		const char __user *u, size_t s, loff_t *off)
+{
+	struct picolcd_pending *resp;
+	u8 raw_data[36];
+	ssize_t ret = 0;
+	int len_off, err = -EIO;
+
+	while (s > 0) {
+		err = -EIO;
+		len_off = _picolcd_flash_setaddr(data, raw_data, *off);
+		raw_data[len_off] = s > 32 ? 32 : s;
+		if (copy_from_user(raw_data+len_off+1, u, raw_data[len_off])) {
+			err = -EFAULT;
+			goto skip;
+		}
+		resp = picolcd_send_and_wait(data->hdev, report_id, raw_data,
+				len_off+1+raw_data[len_off]);
+		if (!resp || !resp->in_report)
+			goto skip;
+		if (resp->in_report->id == REPORT_MEMORY ||
+			resp->in_report->id == REPORT_BL_WRITE_MEMORY) {
+			if (memcmp(raw_data, resp->raw_data, len_off+1+raw_data[len_off]) != 0)
+				goto skip;
+			*off += raw_data[len_off];
+			s    -= raw_data[len_off];
+			ret  += raw_data[len_off];
+			err   = 0;
+		}
+skip:
+		kfree(resp);
+		if (err)
+			break;
+	}
+	return ret > 0 ? ret : err;
+}
+
+static ssize_t picolcd_debug_flash_write(struct file *f, const char __user *u,
+		size_t s, loff_t *off)
+{
+	struct picolcd_data *data = f->private_data;
+	ssize_t err, ret = 0;
+	int report_erase, report_write;
+
+	if (s == 0)
+		return -EINVAL;
+	if (*off > 0x5fff)
+		return -ENOSPC;
+	if (s & 0x3f)
+		return -EINVAL;
+	if (*off & 0x3f)
+		return -EINVAL;
+
+	if (data->status & PICOLCD_BOOTLOADER) {
+		report_erase = REPORT_BL_ERASE_MEMORY;
+		report_write = REPORT_BL_WRITE_MEMORY;
+	} else {
+		report_erase = REPORT_ERASE_MEMORY;
+		report_write = REPORT_WRITE_MEMORY;
+	}
+	mutex_lock(&data->mutex_flash);
+	while (s > 0) {
+		err = _picolcd_flash_erase64(data, report_erase, off);
+		if (err)
+			break;
+		err = _picolcd_flash_write(data, report_write, u, 64, off);
+		if (err < 0)
+			break;
+		ret += err;
+		*off += err;
+		s -= err;
+		if (err != 64)
+			break;
+	}
+	mutex_unlock(&data->mutex_flash);
+	return ret > 0 ? ret : err;
+}
+
+/*
+ * Notes:
+ * - concurrent writing is prevented by mutex and all writes must be
+ *   n*64 bytes and 64-byte aligned, each write being preceeded by an
+ *   ERASE which erases a 64byte block.
+ *   If less than requested was written or an error is returned for an
+ *   otherwise correct write request the next 64-byte block which should
+ *   have been written is in undefined state (mostly: original, erased,
+ *   (half-)written with write error)
+ * - reading can happend without special restriction
+ */
+static const struct file_operations picolcd_debug_flash_fops = {
+	.owner    = THIS_MODULE,
+	.open     = picolcd_debug_flash_open,
+	.read     = picolcd_debug_flash_read,
+	.write    = picolcd_debug_flash_write,
+	.llseek   = generic_file_llseek,
+};
+
+
+/*
  * Helper code for HID report level dumping/debugging
  */
 static const char *error_codes[] = {
@@ -1775,9 +2130,66 @@ static void picolcd_debug_raw_event(struct picolcd_data *data,
 	wake_up_interruptible(&hdev->debug_wait);
 	kfree(buff);
 }
+
+static void picolcd_init_devfs(struct picolcd_data *data,
+		struct hid_report *eeprom_r, struct hid_report *eeprom_w,
+		struct hid_report *flash_r, struct hid_report *flash_w,
+		struct hid_report *reset)
+{
+	struct hid_device *hdev = data->hdev;
+
+	mutex_init(&data->mutex_flash);
+
+	/* reset */
+	if (reset)
+		data->debug_reset = debugfs_create_file("reset", 0600,
+				hdev->debug_dir, data, &picolcd_debug_reset_fops);
+
+	/* eeprom */
+	if (eeprom_r || eeprom_w)
+		data->debug_eeprom = debugfs_create_file("eeprom",
+			(eeprom_w ? S_IWUSR : 0) | (eeprom_r ? S_IRUSR : 0),
+			hdev->debug_dir, data, &picolcd_debug_eeprom_fops);
+
+	/* flash */
+	if (flash_r && flash_r->maxfield == 1 && flash_r->field[0]->report_size == 8)
+		data->addr_sz = flash_r->field[0]->report_count - 1;
+	else
+		data->addr_sz = -1;
+	if (data->addr_sz == 2 || data->addr_sz == 3) {
+		data->debug_flash = debugfs_create_file("flash",
+			(flash_w ? S_IWUSR : 0) | (flash_r ? S_IRUSR : 0),
+			hdev->debug_dir, data, &picolcd_debug_flash_fops);
+	} else if (flash_r || flash_w)
+		dev_warn(&hdev->dev, "Unexpected FLASH access reports, "
+				"please submit rdesc for review\n");
+}
+
+static void picolcd_exit_devfs(struct picolcd_data *data)
+{
+	struct dentry *dent;
+
+	dent = data->debug_reset;
+	data->debug_reset = NULL;
+	if (dent)
+		debugfs_remove(dent);
+	dent = data->debug_eeprom;
+	data->debug_eeprom = NULL;
+	if (dent)
+		debugfs_remove(dent);
+	dent = data->debug_flash;
+	data->debug_flash = NULL;
+	if (dent)
+		debugfs_remove(dent);
+	mutex_destroy(&data->mutex_flash);
+}
 #else
 #define picolcd_debug_raw_event(data, hdev, report, raw_data, size)
-#endif
+#define picolcd_init_devfs(data, eeprom_r, eeprom_w, flash_r, flash_w, reset)
+static void picolcd_exit_devfs(struct picolcd_data *data)
+{
+}
+#endif /* CONFIG_DEBUG_FS */
 
 /*
  * Handle raw report as sent by device
@@ -1927,7 +2339,6 @@ static inline void picolcd_exit_cir(struct picolcd_data *data)
 
 static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
 {
-	struct hid_report *report;
 	int error;
 
 	error = picolcd_check_version(hdev);
@@ -1969,13 +2380,11 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
 	if (error)
 		goto err;
 
-#ifdef CONFIG_DEBUG_FS
-	report = picolcd_out_report(REPORT_READ_MEMORY, hdev);
-	if (report && report->maxfield == 1 && report->field[0]->report_size == 8)
-		data->addr_sz = report->field[0]->report_count - 1;
-	else
-		data->addr_sz = -1;
-#endif
+	picolcd_init_devfs(data, picolcd_out_report(REPORT_EE_READ, hdev),
+			picolcd_out_report(REPORT_EE_WRITE, hdev),
+			picolcd_out_report(REPORT_READ_MEMORY, hdev),
+			picolcd_out_report(REPORT_WRITE_MEMORY, hdev),
+			picolcd_out_report(REPORT_RESET, hdev));
 	return 0;
 err:
 	picolcd_exit_leds(data);
@@ -1989,7 +2398,6 @@ err:
 
 static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data)
 {
-	struct hid_report *report;
 	int error;
 
 	error = picolcd_check_version(hdev);
@@ -2001,13 +2409,9 @@ static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data
 				"please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n",
 				dev_name(&hdev->dev));
 
-#ifdef CONFIG_DEBUG_FS
-	report = picolcd_out_report(REPORT_BL_READ_MEMORY, hdev);
-	if (report && report->maxfield == 1 && report->field[0]->report_size == 8)
-		data->addr_sz = report->field[0]->report_count - 1;
-	else
-		data->addr_sz = -1;
-#endif
+	picolcd_init_devfs(data, NULL, NULL,
+			picolcd_out_report(REPORT_BL_READ_MEMORY, hdev),
+			picolcd_out_report(REPORT_BL_WRITE_MEMORY, hdev), NULL);
 	return 0;
 }
 
@@ -2100,6 +2504,7 @@ static void picolcd_remove(struct hid_device *hdev)
 	data->status |= PICOLCD_FAILED;
 	spin_unlock_irqrestore(&data->lock, flags);
 
+	picolcd_exit_devfs(data);
 	device_remove_file(&hdev->dev, &dev_attr_operation_mode);
 	hdev->ll_driver->close(hdev);
 	hid_hw_stop(hdev);
-- 
1.6.4.4


WARNING: multiple messages have this Message-ID (diff)
From: "Bruno Prémont" <bonbons-ud5FBsm0p/xEiooADzr8i9i2O/JbrIOy@public.gmane.org>
To: Jiri Kosina <jkosina-AlSwsSmVLrQ@public.gmane.org>
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-usb-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-fbdev-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	"Rick L. Vinyard Jr."
	<rvinyard-qcTL/1vZYtiVc3sceRu5cw@public.gmane.org>,
	Nicu Pavel <npavel-VxACSXvuqMTQT0dZR+AlfA@public.gmane.org>,
	Oliver Neukum <oliver-GvhC2dPhHPQdnm+yROfE0A@public.gmane.org>,
	Jaya Kumar
	<jayakumar.lkml-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
	Dmitry Torokhov
	<dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Subject: [PATCH v3 6/6] hid: add experimental access to PicoLCD device's EEPROM and FLASH
Date: Wed, 24 Mar 2010 23:58:35 +0100	[thread overview]
Message-ID: <20100324235835.0e7e1bcb@neptune.home> (raw)
In-Reply-To: <20100324233707.7243b04d-hY15tx4IgV39zxVx7UNMDg@public.gmane.org>

The PicoLCD device has a small amount of EEPROM and also provides access
to its FLASH where firmware and splash image are saved.
In flasher mode FLASH access is the only active feature.

Give read/write access to both via debugfs files.

NOTE: EEPROM and FLASH access should be switched to better suited API,
      until then they will reside in debugfs

Changes since v2:
 - Drop inline keyword on non-stub functions
 - Prevent interleaved flash writes via mutex
 - Add description of flash/eeprom access schematics

Signed-off-by: Bruno Prémont <bonbons-ud5FBsm0p/xEiooADzr8i9i2O/JbrIOy@public.gmane.org>
---
 drivers/hid/Kconfig       |    2 +-
 drivers/hid/hid-picolcd.c |  439 +++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 423 insertions(+), 18 deletions(-)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 34f6593..a2ecd83 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -281,9 +281,9 @@ config HID_PICOLCD
 	  - Backlight control         (needs CONFIG_BACKLIGHT_CLASS_DEVICE)
 	  - Contrast control          (needs CONFIG_LCD_CLASS_DEVICE)
 	  - General purpose outputs   (needs CONFIG_LEDS_CLASS)
+	  - EEProm / Flash access     (via debugfs)
 	  Features that are not (yet) supported:
 	  - IR
-	  - EEProm / Flash access
 
 config HID_QUANTA
 	tristate "Quanta Optical Touch"
diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c
index ff70aa2..6a88a72 100644
--- a/drivers/hid/hid-picolcd.c
+++ b/drivers/hid/hid-picolcd.c
@@ -169,6 +169,10 @@ struct picolcd_pending {
 struct picolcd_data {
 	struct hid_device *hdev;
 #ifdef CONFIG_DEBUG_FS
+	struct dentry *debug_reset;
+	struct dentry *debug_eeprom;
+	struct dentry *debug_flash;
+	struct mutex mutex_flash;
 	int addr_sz;
 #endif
 	u8 version[2];
@@ -1301,6 +1305,357 @@ static DEVICE_ATTR(operation_mode, 0644, picolcd_operation_mode_show,
 
 #ifdef CONFIG_DEBUG_FS
 /*
+ * The "reset" file
+ */
+static int picolcd_debug_reset_show(struct seq_file *f, void *p)
+{
+	if (picolcd_fbinfo((struct picolcd_data *)f->private))
+		seq_printf(f, "all fb\n");
+	else
+		seq_printf(f, "all\n");
+	return 0;
+}
+
+static ssize_t picolcd_debug_reset_open(struct inode *inode, struct file *f)
+{
+	return single_open(f, picolcd_debug_reset_show, inode->i_private);
+}
+
+static ssize_t picolcd_debug_reset_write(struct file *f, const char __user *user_buf,
+		size_t count, loff_t *ppos)
+{
+	struct picolcd_data *data = ((struct seq_file *)f->private_data)->private;
+	char buf[32];
+	size_t cnt = min(count, sizeof(buf)-1);
+	if (copy_from_user(buf, user_buf, cnt))
+		return -EFAULT;
+
+	while (cnt > 0 && (buf[cnt-1] == ' ' || buf[cnt-1] == '\n'))
+		cnt--;
+	buf[cnt] = '\0';
+	if (strcmp(buf, "all") == 0) {
+		picolcd_reset(data->hdev);
+		picolcd_fb_reset(data, 1);
+	} else if (strcmp(buf, "fb") == 0) {
+		picolcd_fb_reset(data, 1);
+	} else {
+		return -EINVAL;
+	}
+	return count;
+}
+
+static const struct file_operations picolcd_debug_reset_fops = {
+	.owner    = THIS_MODULE,
+	.open     = picolcd_debug_reset_open,
+	.read     = seq_read,
+	.llseek   = seq_lseek,
+	.write    = picolcd_debug_reset_write,
+	.release  = single_release,
+};
+
+/*
+ * The "eeprom" file
+ */
+static int picolcd_debug_eeprom_open(struct inode *i, struct file *f)
+{
+	f->private_data = i->i_private;
+	return 0;
+}
+
+static ssize_t picolcd_debug_eeprom_read(struct file *f, char __user *u,
+		size_t s, loff_t *off)
+{
+	struct picolcd_data *data = f->private_data;
+	struct picolcd_pending *resp;
+	u8 raw_data[3];
+	ssize_t ret = -EIO;
+
+	if (s == 0)
+		return -EINVAL;
+	if (*off > 0x0ff)
+		return 0;
+
+	/* prepare buffer with info about what we want to read (addr & len) */
+	raw_data[0] = *off & 0xff;
+	raw_data[1] = (*off >> 8) && 0xff;
+	raw_data[2] = s < 20 ? s : 20;
+	if (*off + raw_data[2] > 0xff)
+		raw_data[2] = 0x100 - *off;
+	resp = picolcd_send_and_wait(data->hdev, REPORT_EE_READ, raw_data,
+			sizeof(raw_data));
+	if (!resp)
+		return -EIO;
+
+	if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) {
+		/* successful read :) */
+		ret = resp->raw_data[2];
+		if (ret > s)
+			ret = s;
+		if (copy_to_user(u, resp->raw_data+3, ret))
+			ret = -EFAULT;
+		else
+			*off += ret;
+	} /* anything else is some kind of IO error */
+
+	kfree(resp);
+	return ret;
+}
+
+static ssize_t picolcd_debug_eeprom_write(struct file *f, const char __user *u,
+		size_t s, loff_t *off)
+{
+	struct picolcd_data *data = f->private_data;
+	struct picolcd_pending *resp;
+	ssize_t ret = -EIO;
+	u8 raw_data[23];
+
+	if (s == 0)
+		return -EINVAL;
+	if (*off > 0x0ff)
+		return -ENOSPC;
+
+	memset(raw_data, 0, sizeof(raw_data));
+	raw_data[0] = *off & 0xff;
+	raw_data[1] = (*off >> 8) && 0xff;
+	raw_data[2] = s < 20 ? s : 20;
+	if (*off + raw_data[2] > 0xff)
+		raw_data[2] = 0x100 - *off;
+
+	if (copy_from_user(raw_data+3, u, raw_data[2]))
+		return -EFAULT;
+	resp = picolcd_send_and_wait(data->hdev, REPORT_EE_WRITE, raw_data,
+			sizeof(raw_data));
+
+	if (!resp)
+		return -EIO;
+
+	if (resp->in_report && resp->in_report->id == REPORT_EE_DATA) {
+		/* check if written data matches */
+		if (memcmp(raw_data, resp->raw_data, 3+raw_data[2]) == 0) {
+			*off += raw_data[2];
+			ret = raw_data[2];
+		}
+	}
+	kfree(resp);
+	return ret;
+}
+
+/*
+ * Notes:
+ * - read/write happens in chunks of at most 20 bytes, it's up to userspace
+ *   to loop in order to get more data.
+ * - on write errors on otherwise correct write request the bytes
+ *   that should have been written are in undefined state.
+ */
+static const struct file_operations picolcd_debug_eeprom_fops = {
+	.owner    = THIS_MODULE,
+	.open     = picolcd_debug_eeprom_open,
+	.read     = picolcd_debug_eeprom_read,
+	.write    = picolcd_debug_eeprom_write,
+	.llseek   = generic_file_llseek,
+};
+
+/*
+ * The "flash" file
+ */
+static int picolcd_debug_flash_open(struct inode *i, struct file *f)
+{
+	f->private_data = i->i_private;
+	return 0;
+}
+
+/* record a flash address to buf (bounds check to be done by caller) */
+static int _picolcd_flash_setaddr(struct picolcd_data *data, u8 *buf, long off)
+{
+	buf[0] = off & 0xff;
+	buf[1] = (off >> 8) & 0xff;
+	if (data->addr_sz == 3)
+		buf[2] = (off >> 16) & 0xff;
+	return data->addr_sz == 2 ? 2 : 3;
+}
+
+/* read a given size of data (bounds check to be done by caller) */
+static ssize_t _picolcd_flash_read(struct picolcd_data *data, int report_id,
+		char __user *u, size_t s, loff_t *off)
+{
+	struct picolcd_pending *resp;
+	u8 raw_data[4];
+	ssize_t ret = 0;
+	int len_off, err = -EIO;
+
+	while (s > 0) {
+		err = -EIO;
+		len_off = _picolcd_flash_setaddr(data, raw_data, *off);
+		raw_data[len_off] = s > 32 ? 32 : s;
+		resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off+1);
+		if (!resp || !resp->in_report)
+			goto skip;
+		if (resp->in_report->id == REPORT_MEMORY ||
+			resp->in_report->id == REPORT_BL_READ_MEMORY) {
+			if (memcmp(raw_data, resp->raw_data, len_off+1) != 0)
+				goto skip;
+			if (copy_to_user(u+ret, resp->raw_data+len_off+1, raw_data[len_off])) {
+				err = -EFAULT;
+				goto skip;
+			}
+			*off += raw_data[len_off];
+			s    -= raw_data[len_off];
+			ret  += raw_data[len_off];
+			err   = 0;
+		}
+skip:
+		kfree(resp);
+		if (err)
+			return ret > 0 ? ret : err;
+	}
+	return ret;
+}
+
+static ssize_t picolcd_debug_flash_read(struct file *f, char __user *u,
+		size_t s, loff_t *off)
+{
+	struct picolcd_data *data = f->private_data;
+
+	if (s == 0)
+		return -EINVAL;
+	if (*off > 0x05fff)
+		return 0;
+	if (*off + s > 0x05fff)
+		s = 0x06000 - *off;
+
+	if (data->status & PICOLCD_BOOTLOADER)
+		return _picolcd_flash_read(data, REPORT_BL_READ_MEMORY, u, s, off);
+	else
+		return _picolcd_flash_read(data, REPORT_READ_MEMORY, u, s, off);
+}
+
+/* erase block aligned to 64bytes boundary */
+static ssize_t _picolcd_flash_erase64(struct picolcd_data *data, int report_id,
+		loff_t *off)
+{
+	struct picolcd_pending *resp;
+	u8 raw_data[3];
+	int len_off;
+	ssize_t ret = -EIO;
+
+	if (*off & 0x3f)
+		return -EINVAL;
+
+	len_off = _picolcd_flash_setaddr(data, raw_data, *off);
+	resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off);
+	if (!resp || !resp->in_report)
+		goto skip;
+	if (resp->in_report->id == REPORT_MEMORY ||
+		resp->in_report->id == REPORT_BL_ERASE_MEMORY) {
+		if (memcmp(raw_data, resp->raw_data, len_off) != 0)
+			goto skip;
+		ret = 0;
+	}
+skip:
+	kfree(resp);
+	return ret;
+}
+
+/* write a given size of data (bounds check to be done by caller) */
+static ssize_t _picolcd_flash_write(struct picolcd_data *data, int report_id,
+		const char __user *u, size_t s, loff_t *off)
+{
+	struct picolcd_pending *resp;
+	u8 raw_data[36];
+	ssize_t ret = 0;
+	int len_off, err = -EIO;
+
+	while (s > 0) {
+		err = -EIO;
+		len_off = _picolcd_flash_setaddr(data, raw_data, *off);
+		raw_data[len_off] = s > 32 ? 32 : s;
+		if (copy_from_user(raw_data+len_off+1, u, raw_data[len_off])) {
+			err = -EFAULT;
+			goto skip;
+		}
+		resp = picolcd_send_and_wait(data->hdev, report_id, raw_data,
+				len_off+1+raw_data[len_off]);
+		if (!resp || !resp->in_report)
+			goto skip;
+		if (resp->in_report->id == REPORT_MEMORY ||
+			resp->in_report->id == REPORT_BL_WRITE_MEMORY) {
+			if (memcmp(raw_data, resp->raw_data, len_off+1+raw_data[len_off]) != 0)
+				goto skip;
+			*off += raw_data[len_off];
+			s    -= raw_data[len_off];
+			ret  += raw_data[len_off];
+			err   = 0;
+		}
+skip:
+		kfree(resp);
+		if (err)
+			break;
+	}
+	return ret > 0 ? ret : err;
+}
+
+static ssize_t picolcd_debug_flash_write(struct file *f, const char __user *u,
+		size_t s, loff_t *off)
+{
+	struct picolcd_data *data = f->private_data;
+	ssize_t err, ret = 0;
+	int report_erase, report_write;
+
+	if (s == 0)
+		return -EINVAL;
+	if (*off > 0x5fff)
+		return -ENOSPC;
+	if (s & 0x3f)
+		return -EINVAL;
+	if (*off & 0x3f)
+		return -EINVAL;
+
+	if (data->status & PICOLCD_BOOTLOADER) {
+		report_erase = REPORT_BL_ERASE_MEMORY;
+		report_write = REPORT_BL_WRITE_MEMORY;
+	} else {
+		report_erase = REPORT_ERASE_MEMORY;
+		report_write = REPORT_WRITE_MEMORY;
+	}
+	mutex_lock(&data->mutex_flash);
+	while (s > 0) {
+		err = _picolcd_flash_erase64(data, report_erase, off);
+		if (err)
+			break;
+		err = _picolcd_flash_write(data, report_write, u, 64, off);
+		if (err < 0)
+			break;
+		ret += err;
+		*off += err;
+		s -= err;
+		if (err != 64)
+			break;
+	}
+	mutex_unlock(&data->mutex_flash);
+	return ret > 0 ? ret : err;
+}
+
+/*
+ * Notes:
+ * - concurrent writing is prevented by mutex and all writes must be
+ *   n*64 bytes and 64-byte aligned, each write being preceeded by an
+ *   ERASE which erases a 64byte block.
+ *   If less than requested was written or an error is returned for an
+ *   otherwise correct write request the next 64-byte block which should
+ *   have been written is in undefined state (mostly: original, erased,
+ *   (half-)written with write error)
+ * - reading can happend without special restriction
+ */
+static const struct file_operations picolcd_debug_flash_fops = {
+	.owner    = THIS_MODULE,
+	.open     = picolcd_debug_flash_open,
+	.read     = picolcd_debug_flash_read,
+	.write    = picolcd_debug_flash_write,
+	.llseek   = generic_file_llseek,
+};
+
+
+/*
  * Helper code for HID report level dumping/debugging
  */
 static const char *error_codes[] = {
@@ -1775,9 +2130,66 @@ static void picolcd_debug_raw_event(struct picolcd_data *data,
 	wake_up_interruptible(&hdev->debug_wait);
 	kfree(buff);
 }
+
+static void picolcd_init_devfs(struct picolcd_data *data,
+		struct hid_report *eeprom_r, struct hid_report *eeprom_w,
+		struct hid_report *flash_r, struct hid_report *flash_w,
+		struct hid_report *reset)
+{
+	struct hid_device *hdev = data->hdev;
+
+	mutex_init(&data->mutex_flash);
+
+	/* reset */
+	if (reset)
+		data->debug_reset = debugfs_create_file("reset", 0600,
+				hdev->debug_dir, data, &picolcd_debug_reset_fops);
+
+	/* eeprom */
+	if (eeprom_r || eeprom_w)
+		data->debug_eeprom = debugfs_create_file("eeprom",
+			(eeprom_w ? S_IWUSR : 0) | (eeprom_r ? S_IRUSR : 0),
+			hdev->debug_dir, data, &picolcd_debug_eeprom_fops);
+
+	/* flash */
+	if (flash_r && flash_r->maxfield == 1 && flash_r->field[0]->report_size == 8)
+		data->addr_sz = flash_r->field[0]->report_count - 1;
+	else
+		data->addr_sz = -1;
+	if (data->addr_sz == 2 || data->addr_sz == 3) {
+		data->debug_flash = debugfs_create_file("flash",
+			(flash_w ? S_IWUSR : 0) | (flash_r ? S_IRUSR : 0),
+			hdev->debug_dir, data, &picolcd_debug_flash_fops);
+	} else if (flash_r || flash_w)
+		dev_warn(&hdev->dev, "Unexpected FLASH access reports, "
+				"please submit rdesc for review\n");
+}
+
+static void picolcd_exit_devfs(struct picolcd_data *data)
+{
+	struct dentry *dent;
+
+	dent = data->debug_reset;
+	data->debug_reset = NULL;
+	if (dent)
+		debugfs_remove(dent);
+	dent = data->debug_eeprom;
+	data->debug_eeprom = NULL;
+	if (dent)
+		debugfs_remove(dent);
+	dent = data->debug_flash;
+	data->debug_flash = NULL;
+	if (dent)
+		debugfs_remove(dent);
+	mutex_destroy(&data->mutex_flash);
+}
 #else
 #define picolcd_debug_raw_event(data, hdev, report, raw_data, size)
-#endif
+#define picolcd_init_devfs(data, eeprom_r, eeprom_w, flash_r, flash_w, reset)
+static void picolcd_exit_devfs(struct picolcd_data *data)
+{
+}
+#endif /* CONFIG_DEBUG_FS */
 
 /*
  * Handle raw report as sent by device
@@ -1927,7 +2339,6 @@ static inline void picolcd_exit_cir(struct picolcd_data *data)
 
 static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
 {
-	struct hid_report *report;
 	int error;
 
 	error = picolcd_check_version(hdev);
@@ -1969,13 +2380,11 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
 	if (error)
 		goto err;
 
-#ifdef CONFIG_DEBUG_FS
-	report = picolcd_out_report(REPORT_READ_MEMORY, hdev);
-	if (report && report->maxfield == 1 && report->field[0]->report_size == 8)
-		data->addr_sz = report->field[0]->report_count - 1;
-	else
-		data->addr_sz = -1;
-#endif
+	picolcd_init_devfs(data, picolcd_out_report(REPORT_EE_READ, hdev),
+			picolcd_out_report(REPORT_EE_WRITE, hdev),
+			picolcd_out_report(REPORT_READ_MEMORY, hdev),
+			picolcd_out_report(REPORT_WRITE_MEMORY, hdev),
+			picolcd_out_report(REPORT_RESET, hdev));
 	return 0;
 err:
 	picolcd_exit_leds(data);
@@ -1989,7 +2398,6 @@ err:
 
 static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data)
 {
-	struct hid_report *report;
 	int error;
 
 	error = picolcd_check_version(hdev);
@@ -2001,13 +2409,9 @@ static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data
 				"please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n",
 				dev_name(&hdev->dev));
 
-#ifdef CONFIG_DEBUG_FS
-	report = picolcd_out_report(REPORT_BL_READ_MEMORY, hdev);
-	if (report && report->maxfield == 1 && report->field[0]->report_size == 8)
-		data->addr_sz = report->field[0]->report_count - 1;
-	else
-		data->addr_sz = -1;
-#endif
+	picolcd_init_devfs(data, NULL, NULL,
+			picolcd_out_report(REPORT_BL_READ_MEMORY, hdev),
+			picolcd_out_report(REPORT_BL_WRITE_MEMORY, hdev), NULL);
 	return 0;
 }
 
@@ -2100,6 +2504,7 @@ static void picolcd_remove(struct hid_device *hdev)
 	data->status |= PICOLCD_FAILED;
 	spin_unlock_irqrestore(&data->lock, flags);
 
+	picolcd_exit_devfs(data);
 	device_remove_file(&hdev->dev, &dev_attr_operation_mode);
 	hdev->ll_driver->close(hdev);
 	hid_hw_stop(hdev);
-- 
1.6.4.4

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo-u79uwXL29TY76Z2rM5mHXA@public.gmane.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

WARNING: multiple messages have this Message-ID (diff)
From: "Bruno Prémont" <bonbons@linux-vserver.org>
To: Jiri Kosina <jkosina-AlSwsSmVLrQ@public.gmane.org>
Cc: linux-input-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-usb-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-fbdev-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	linux-kernel-u79uwXL29TY76Z2rM5mHXA@public.gmane.org,
	"Rick L. Vinyard Jr."
	<rvinyard-qcTL/1vZYtiVc3sceRu5cw@public.gmane.org>,
	Nicu Pavel <npavel-VxACSXvuqMTQT0dZR+AlfA@public.gmane.org>,
	Oliver Neukum <oliver-GvhC2dPhHPQdnm+yROfE0A@public.gmane.org>,
	Jaya Kumar
	<jayakumar.lkml-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>,
	Dmitry Torokhov
	<dmitry.torokhov-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
Subject: [PATCH v3 6/6] hid: add experimental access to PicoLCD device's
Date: Wed, 24 Mar 2010 22:58:35 +0000	[thread overview]
Message-ID: <20100324235835.0e7e1bcb@neptune.home> (raw)
In-Reply-To: <20100324233707.7243b04d-hY15tx4IgV39zxVx7UNMDg@public.gmane.org>

The PicoLCD device has a small amount of EEPROM and also provides access
to its FLASH where firmware and splash image are saved.
In flasher mode FLASH access is the only active feature.

Give read/write access to both via debugfs files.

NOTE: EEPROM and FLASH access should be switched to better suited API,
      until then they will reside in debugfs

Changes since v2:
 - Drop inline keyword on non-stub functions
 - Prevent interleaved flash writes via mutex
 - Add description of flash/eeprom access schematics

Signed-off-by: Bruno Prémont <bonbons@linux-vserver.org>
---
 drivers/hid/Kconfig       |    2 +-
 drivers/hid/hid-picolcd.c |  439 +++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 423 insertions(+), 18 deletions(-)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 34f6593..a2ecd83 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -281,9 +281,9 @@ config HID_PICOLCD
 	  - Backlight control         (needs CONFIG_BACKLIGHT_CLASS_DEVICE)
 	  - Contrast control          (needs CONFIG_LCD_CLASS_DEVICE)
 	  - General purpose outputs   (needs CONFIG_LEDS_CLASS)
+	  - EEProm / Flash access     (via debugfs)
 	  Features that are not (yet) supported:
 	  - IR
-	  - EEProm / Flash access
 
 config HID_QUANTA
 	tristate "Quanta Optical Touch"
diff --git a/drivers/hid/hid-picolcd.c b/drivers/hid/hid-picolcd.c
index ff70aa2..6a88a72 100644
--- a/drivers/hid/hid-picolcd.c
+++ b/drivers/hid/hid-picolcd.c
@@ -169,6 +169,10 @@ struct picolcd_pending {
 struct picolcd_data {
 	struct hid_device *hdev;
 #ifdef CONFIG_DEBUG_FS
+	struct dentry *debug_reset;
+	struct dentry *debug_eeprom;
+	struct dentry *debug_flash;
+	struct mutex mutex_flash;
 	int addr_sz;
 #endif
 	u8 version[2];
@@ -1301,6 +1305,357 @@ static DEVICE_ATTR(operation_mode, 0644, picolcd_operation_mode_show,
 
 #ifdef CONFIG_DEBUG_FS
 /*
+ * The "reset" file
+ */
+static int picolcd_debug_reset_show(struct seq_file *f, void *p)
+{
+	if (picolcd_fbinfo((struct picolcd_data *)f->private))
+		seq_printf(f, "all fb\n");
+	else
+		seq_printf(f, "all\n");
+	return 0;
+}
+
+static ssize_t picolcd_debug_reset_open(struct inode *inode, struct file *f)
+{
+	return single_open(f, picolcd_debug_reset_show, inode->i_private);
+}
+
+static ssize_t picolcd_debug_reset_write(struct file *f, const char __user *user_buf,
+		size_t count, loff_t *ppos)
+{
+	struct picolcd_data *data = ((struct seq_file *)f->private_data)->private;
+	char buf[32];
+	size_t cnt = min(count, sizeof(buf)-1);
+	if (copy_from_user(buf, user_buf, cnt))
+		return -EFAULT;
+
+	while (cnt > 0 && (buf[cnt-1] = ' ' || buf[cnt-1] = '\n'))
+		cnt--;
+	buf[cnt] = '\0';
+	if (strcmp(buf, "all") = 0) {
+		picolcd_reset(data->hdev);
+		picolcd_fb_reset(data, 1);
+	} else if (strcmp(buf, "fb") = 0) {
+		picolcd_fb_reset(data, 1);
+	} else {
+		return -EINVAL;
+	}
+	return count;
+}
+
+static const struct file_operations picolcd_debug_reset_fops = {
+	.owner    = THIS_MODULE,
+	.open     = picolcd_debug_reset_open,
+	.read     = seq_read,
+	.llseek   = seq_lseek,
+	.write    = picolcd_debug_reset_write,
+	.release  = single_release,
+};
+
+/*
+ * The "eeprom" file
+ */
+static int picolcd_debug_eeprom_open(struct inode *i, struct file *f)
+{
+	f->private_data = i->i_private;
+	return 0;
+}
+
+static ssize_t picolcd_debug_eeprom_read(struct file *f, char __user *u,
+		size_t s, loff_t *off)
+{
+	struct picolcd_data *data = f->private_data;
+	struct picolcd_pending *resp;
+	u8 raw_data[3];
+	ssize_t ret = -EIO;
+
+	if (s = 0)
+		return -EINVAL;
+	if (*off > 0x0ff)
+		return 0;
+
+	/* prepare buffer with info about what we want to read (addr & len) */
+	raw_data[0] = *off & 0xff;
+	raw_data[1] = (*off >> 8) && 0xff;
+	raw_data[2] = s < 20 ? s : 20;
+	if (*off + raw_data[2] > 0xff)
+		raw_data[2] = 0x100 - *off;
+	resp = picolcd_send_and_wait(data->hdev, REPORT_EE_READ, raw_data,
+			sizeof(raw_data));
+	if (!resp)
+		return -EIO;
+
+	if (resp->in_report && resp->in_report->id = REPORT_EE_DATA) {
+		/* successful read :) */
+		ret = resp->raw_data[2];
+		if (ret > s)
+			ret = s;
+		if (copy_to_user(u, resp->raw_data+3, ret))
+			ret = -EFAULT;
+		else
+			*off += ret;
+	} /* anything else is some kind of IO error */
+
+	kfree(resp);
+	return ret;
+}
+
+static ssize_t picolcd_debug_eeprom_write(struct file *f, const char __user *u,
+		size_t s, loff_t *off)
+{
+	struct picolcd_data *data = f->private_data;
+	struct picolcd_pending *resp;
+	ssize_t ret = -EIO;
+	u8 raw_data[23];
+
+	if (s = 0)
+		return -EINVAL;
+	if (*off > 0x0ff)
+		return -ENOSPC;
+
+	memset(raw_data, 0, sizeof(raw_data));
+	raw_data[0] = *off & 0xff;
+	raw_data[1] = (*off >> 8) && 0xff;
+	raw_data[2] = s < 20 ? s : 20;
+	if (*off + raw_data[2] > 0xff)
+		raw_data[2] = 0x100 - *off;
+
+	if (copy_from_user(raw_data+3, u, raw_data[2]))
+		return -EFAULT;
+	resp = picolcd_send_and_wait(data->hdev, REPORT_EE_WRITE, raw_data,
+			sizeof(raw_data));
+
+	if (!resp)
+		return -EIO;
+
+	if (resp->in_report && resp->in_report->id = REPORT_EE_DATA) {
+		/* check if written data matches */
+		if (memcmp(raw_data, resp->raw_data, 3+raw_data[2]) = 0) {
+			*off += raw_data[2];
+			ret = raw_data[2];
+		}
+	}
+	kfree(resp);
+	return ret;
+}
+
+/*
+ * Notes:
+ * - read/write happens in chunks of at most 20 bytes, it's up to userspace
+ *   to loop in order to get more data.
+ * - on write errors on otherwise correct write request the bytes
+ *   that should have been written are in undefined state.
+ */
+static const struct file_operations picolcd_debug_eeprom_fops = {
+	.owner    = THIS_MODULE,
+	.open     = picolcd_debug_eeprom_open,
+	.read     = picolcd_debug_eeprom_read,
+	.write    = picolcd_debug_eeprom_write,
+	.llseek   = generic_file_llseek,
+};
+
+/*
+ * The "flash" file
+ */
+static int picolcd_debug_flash_open(struct inode *i, struct file *f)
+{
+	f->private_data = i->i_private;
+	return 0;
+}
+
+/* record a flash address to buf (bounds check to be done by caller) */
+static int _picolcd_flash_setaddr(struct picolcd_data *data, u8 *buf, long off)
+{
+	buf[0] = off & 0xff;
+	buf[1] = (off >> 8) & 0xff;
+	if (data->addr_sz = 3)
+		buf[2] = (off >> 16) & 0xff;
+	return data->addr_sz = 2 ? 2 : 3;
+}
+
+/* read a given size of data (bounds check to be done by caller) */
+static ssize_t _picolcd_flash_read(struct picolcd_data *data, int report_id,
+		char __user *u, size_t s, loff_t *off)
+{
+	struct picolcd_pending *resp;
+	u8 raw_data[4];
+	ssize_t ret = 0;
+	int len_off, err = -EIO;
+
+	while (s > 0) {
+		err = -EIO;
+		len_off = _picolcd_flash_setaddr(data, raw_data, *off);
+		raw_data[len_off] = s > 32 ? 32 : s;
+		resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off+1);
+		if (!resp || !resp->in_report)
+			goto skip;
+		if (resp->in_report->id = REPORT_MEMORY ||
+			resp->in_report->id = REPORT_BL_READ_MEMORY) {
+			if (memcmp(raw_data, resp->raw_data, len_off+1) != 0)
+				goto skip;
+			if (copy_to_user(u+ret, resp->raw_data+len_off+1, raw_data[len_off])) {
+				err = -EFAULT;
+				goto skip;
+			}
+			*off += raw_data[len_off];
+			s    -= raw_data[len_off];
+			ret  += raw_data[len_off];
+			err   = 0;
+		}
+skip:
+		kfree(resp);
+		if (err)
+			return ret > 0 ? ret : err;
+	}
+	return ret;
+}
+
+static ssize_t picolcd_debug_flash_read(struct file *f, char __user *u,
+		size_t s, loff_t *off)
+{
+	struct picolcd_data *data = f->private_data;
+
+	if (s = 0)
+		return -EINVAL;
+	if (*off > 0x05fff)
+		return 0;
+	if (*off + s > 0x05fff)
+		s = 0x06000 - *off;
+
+	if (data->status & PICOLCD_BOOTLOADER)
+		return _picolcd_flash_read(data, REPORT_BL_READ_MEMORY, u, s, off);
+	else
+		return _picolcd_flash_read(data, REPORT_READ_MEMORY, u, s, off);
+}
+
+/* erase block aligned to 64bytes boundary */
+static ssize_t _picolcd_flash_erase64(struct picolcd_data *data, int report_id,
+		loff_t *off)
+{
+	struct picolcd_pending *resp;
+	u8 raw_data[3];
+	int len_off;
+	ssize_t ret = -EIO;
+
+	if (*off & 0x3f)
+		return -EINVAL;
+
+	len_off = _picolcd_flash_setaddr(data, raw_data, *off);
+	resp = picolcd_send_and_wait(data->hdev, report_id, raw_data, len_off);
+	if (!resp || !resp->in_report)
+		goto skip;
+	if (resp->in_report->id = REPORT_MEMORY ||
+		resp->in_report->id = REPORT_BL_ERASE_MEMORY) {
+		if (memcmp(raw_data, resp->raw_data, len_off) != 0)
+			goto skip;
+		ret = 0;
+	}
+skip:
+	kfree(resp);
+	return ret;
+}
+
+/* write a given size of data (bounds check to be done by caller) */
+static ssize_t _picolcd_flash_write(struct picolcd_data *data, int report_id,
+		const char __user *u, size_t s, loff_t *off)
+{
+	struct picolcd_pending *resp;
+	u8 raw_data[36];
+	ssize_t ret = 0;
+	int len_off, err = -EIO;
+
+	while (s > 0) {
+		err = -EIO;
+		len_off = _picolcd_flash_setaddr(data, raw_data, *off);
+		raw_data[len_off] = s > 32 ? 32 : s;
+		if (copy_from_user(raw_data+len_off+1, u, raw_data[len_off])) {
+			err = -EFAULT;
+			goto skip;
+		}
+		resp = picolcd_send_and_wait(data->hdev, report_id, raw_data,
+				len_off+1+raw_data[len_off]);
+		if (!resp || !resp->in_report)
+			goto skip;
+		if (resp->in_report->id = REPORT_MEMORY ||
+			resp->in_report->id = REPORT_BL_WRITE_MEMORY) {
+			if (memcmp(raw_data, resp->raw_data, len_off+1+raw_data[len_off]) != 0)
+				goto skip;
+			*off += raw_data[len_off];
+			s    -= raw_data[len_off];
+			ret  += raw_data[len_off];
+			err   = 0;
+		}
+skip:
+		kfree(resp);
+		if (err)
+			break;
+	}
+	return ret > 0 ? ret : err;
+}
+
+static ssize_t picolcd_debug_flash_write(struct file *f, const char __user *u,
+		size_t s, loff_t *off)
+{
+	struct picolcd_data *data = f->private_data;
+	ssize_t err, ret = 0;
+	int report_erase, report_write;
+
+	if (s = 0)
+		return -EINVAL;
+	if (*off > 0x5fff)
+		return -ENOSPC;
+	if (s & 0x3f)
+		return -EINVAL;
+	if (*off & 0x3f)
+		return -EINVAL;
+
+	if (data->status & PICOLCD_BOOTLOADER) {
+		report_erase = REPORT_BL_ERASE_MEMORY;
+		report_write = REPORT_BL_WRITE_MEMORY;
+	} else {
+		report_erase = REPORT_ERASE_MEMORY;
+		report_write = REPORT_WRITE_MEMORY;
+	}
+	mutex_lock(&data->mutex_flash);
+	while (s > 0) {
+		err = _picolcd_flash_erase64(data, report_erase, off);
+		if (err)
+			break;
+		err = _picolcd_flash_write(data, report_write, u, 64, off);
+		if (err < 0)
+			break;
+		ret += err;
+		*off += err;
+		s -= err;
+		if (err != 64)
+			break;
+	}
+	mutex_unlock(&data->mutex_flash);
+	return ret > 0 ? ret : err;
+}
+
+/*
+ * Notes:
+ * - concurrent writing is prevented by mutex and all writes must be
+ *   n*64 bytes and 64-byte aligned, each write being preceeded by an
+ *   ERASE which erases a 64byte block.
+ *   If less than requested was written or an error is returned for an
+ *   otherwise correct write request the next 64-byte block which should
+ *   have been written is in undefined state (mostly: original, erased,
+ *   (half-)written with write error)
+ * - reading can happend without special restriction
+ */
+static const struct file_operations picolcd_debug_flash_fops = {
+	.owner    = THIS_MODULE,
+	.open     = picolcd_debug_flash_open,
+	.read     = picolcd_debug_flash_read,
+	.write    = picolcd_debug_flash_write,
+	.llseek   = generic_file_llseek,
+};
+
+
+/*
  * Helper code for HID report level dumping/debugging
  */
 static const char *error_codes[] = {
@@ -1775,9 +2130,66 @@ static void picolcd_debug_raw_event(struct picolcd_data *data,
 	wake_up_interruptible(&hdev->debug_wait);
 	kfree(buff);
 }
+
+static void picolcd_init_devfs(struct picolcd_data *data,
+		struct hid_report *eeprom_r, struct hid_report *eeprom_w,
+		struct hid_report *flash_r, struct hid_report *flash_w,
+		struct hid_report *reset)
+{
+	struct hid_device *hdev = data->hdev;
+
+	mutex_init(&data->mutex_flash);
+
+	/* reset */
+	if (reset)
+		data->debug_reset = debugfs_create_file("reset", 0600,
+				hdev->debug_dir, data, &picolcd_debug_reset_fops);
+
+	/* eeprom */
+	if (eeprom_r || eeprom_w)
+		data->debug_eeprom = debugfs_create_file("eeprom",
+			(eeprom_w ? S_IWUSR : 0) | (eeprom_r ? S_IRUSR : 0),
+			hdev->debug_dir, data, &picolcd_debug_eeprom_fops);
+
+	/* flash */
+	if (flash_r && flash_r->maxfield = 1 && flash_r->field[0]->report_size = 8)
+		data->addr_sz = flash_r->field[0]->report_count - 1;
+	else
+		data->addr_sz = -1;
+	if (data->addr_sz = 2 || data->addr_sz = 3) {
+		data->debug_flash = debugfs_create_file("flash",
+			(flash_w ? S_IWUSR : 0) | (flash_r ? S_IRUSR : 0),
+			hdev->debug_dir, data, &picolcd_debug_flash_fops);
+	} else if (flash_r || flash_w)
+		dev_warn(&hdev->dev, "Unexpected FLASH access reports, "
+				"please submit rdesc for review\n");
+}
+
+static void picolcd_exit_devfs(struct picolcd_data *data)
+{
+	struct dentry *dent;
+
+	dent = data->debug_reset;
+	data->debug_reset = NULL;
+	if (dent)
+		debugfs_remove(dent);
+	dent = data->debug_eeprom;
+	data->debug_eeprom = NULL;
+	if (dent)
+		debugfs_remove(dent);
+	dent = data->debug_flash;
+	data->debug_flash = NULL;
+	if (dent)
+		debugfs_remove(dent);
+	mutex_destroy(&data->mutex_flash);
+}
 #else
 #define picolcd_debug_raw_event(data, hdev, report, raw_data, size)
-#endif
+#define picolcd_init_devfs(data, eeprom_r, eeprom_w, flash_r, flash_w, reset)
+static void picolcd_exit_devfs(struct picolcd_data *data)
+{
+}
+#endif /* CONFIG_DEBUG_FS */
 
 /*
  * Handle raw report as sent by device
@@ -1927,7 +2339,6 @@ static inline void picolcd_exit_cir(struct picolcd_data *data)
 
 static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
 {
-	struct hid_report *report;
 	int error;
 
 	error = picolcd_check_version(hdev);
@@ -1969,13 +2380,11 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
 	if (error)
 		goto err;
 
-#ifdef CONFIG_DEBUG_FS
-	report = picolcd_out_report(REPORT_READ_MEMORY, hdev);
-	if (report && report->maxfield = 1 && report->field[0]->report_size = 8)
-		data->addr_sz = report->field[0]->report_count - 1;
-	else
-		data->addr_sz = -1;
-#endif
+	picolcd_init_devfs(data, picolcd_out_report(REPORT_EE_READ, hdev),
+			picolcd_out_report(REPORT_EE_WRITE, hdev),
+			picolcd_out_report(REPORT_READ_MEMORY, hdev),
+			picolcd_out_report(REPORT_WRITE_MEMORY, hdev),
+			picolcd_out_report(REPORT_RESET, hdev));
 	return 0;
 err:
 	picolcd_exit_leds(data);
@@ -1989,7 +2398,6 @@ err:
 
 static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data)
 {
-	struct hid_report *report;
 	int error;
 
 	error = picolcd_check_version(hdev);
@@ -2001,13 +2409,9 @@ static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data
 				"please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n",
 				dev_name(&hdev->dev));
 
-#ifdef CONFIG_DEBUG_FS
-	report = picolcd_out_report(REPORT_BL_READ_MEMORY, hdev);
-	if (report && report->maxfield = 1 && report->field[0]->report_size = 8)
-		data->addr_sz = report->field[0]->report_count - 1;
-	else
-		data->addr_sz = -1;
-#endif
+	picolcd_init_devfs(data, NULL, NULL,
+			picolcd_out_report(REPORT_BL_READ_MEMORY, hdev),
+			picolcd_out_report(REPORT_BL_WRITE_MEMORY, hdev), NULL);
 	return 0;
 }
 
@@ -2100,6 +2504,7 @@ static void picolcd_remove(struct hid_device *hdev)
 	data->status |= PICOLCD_FAILED;
 	spin_unlock_irqrestore(&data->lock, flags);
 
+	picolcd_exit_devfs(data);
 	device_remove_file(&hdev->dev, &dev_attr_operation_mode);
 	hdev->ll_driver->close(hdev);
 	hid_hw_stop(hdev);
-- 
1.6.4.4


  parent reply	other threads:[~2010-03-24 23:01 UTC|newest]

Thread overview: 106+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2010-03-24 22:37 [PATCH v3 0/6] hid: new driver for PicoLCD device Bruno Prémont
2010-03-24 22:37 ` Bruno Prémont
2010-03-24 22:40 ` [PATCH v3 1/6] " Bruno Prémont
2010-03-24 22:40   ` Bruno Prémont
2010-03-24 22:40   ` Bruno Prémont
2010-03-26  6:56   ` Dmitry Torokhov
2010-03-26  6:56     ` Dmitry Torokhov
2010-03-26  9:29     ` Bruno Prémont
2010-03-26  9:29       ` Bruno Prémont
2010-03-26  9:29       ` Bruno Prémont
2010-03-26 20:59       ` Jiri Kosina
2010-03-26 20:59         ` Jiri Kosina
2010-03-26 20:59         ` Jiri Kosina
2010-03-26 21:16         ` Dmitry Torokhov
2010-03-26 21:16           ` Dmitry Torokhov
2010-03-26 21:16           ` Dmitry Torokhov
2010-03-26 21:39           ` Bruno Prémont
2010-03-26 21:39             ` Bruno Prémont
2010-03-26 21:39             ` Bruno Prémont
2010-03-27  0:22         ` [PATCH v4 " Bruno Prémont
2010-03-27  0:22           ` Bruno Prémont
2010-03-29  9:47           ` Jiri Kosina
2010-03-29  9:47             ` Jiri Kosina
2010-03-29  9:47             ` Jiri Kosina
2010-03-29 10:16             ` Bruno Prémont
2010-03-29 10:16               ` Bruno Prémont
2010-03-30  8:12               ` Jiri Kosina
2010-03-30  8:12                 ` Jiri Kosina
2010-03-30  8:12                 ` Jiri Kosina
2010-03-30 20:32                 ` [PATCH v6 0/8] " Bruno Prémont
2010-03-30 20:32                   ` Bruno Prémont
2010-03-30 20:32                   ` Bruno Prémont
2010-03-30 20:33                   ` [PATCH v6 1/8] " Bruno Prémont
2010-03-30 20:33                     ` Bruno Prémont
2010-03-30 20:33                     ` Bruno Prémont
2010-04-01 16:58                     ` Oliver Neukum
2010-04-01 16:58                       ` Oliver Neukum
2010-04-01 16:58                       ` Oliver Neukum
2010-04-25 19:29                       ` [PATCH] hid: split picolcd's operation_mode sysfs attribute Bruno Prémont
2010-04-25 19:29                         ` Bruno Prémont
2010-04-25 19:29                         ` Bruno Prémont
2010-04-27 13:32                         ` Jiri Kosina
2010-04-27 13:32                           ` Jiri Kosina
2010-04-27 13:32                           ` Jiri Kosina
2010-03-30 20:34                   ` [PATCH v6 2/8] hid: add framebuffer support to PicoLCD device Bruno Prémont
2010-03-30 20:34                     ` Bruno Prémont
2010-03-30 20:35                   ` [PATCH v6 3/8] hid: add backlight " Bruno Prémont
2010-03-30 20:35                     ` Bruno Prémont
2010-03-30 20:35                     ` Bruno Prémont
2010-03-30 20:36                   ` [PATCH v6 4/8] hid: add lcd " Bruno Prémont
2010-03-30 20:36                     ` Bruno Prémont
2010-03-30 20:36                     ` Bruno Prémont
2010-03-30 20:36                   ` [PATCH v6 5/8] hid: add GPO (leds) " Bruno Prémont
2010-03-30 20:36                     ` Bruno Prémont
2010-03-30 20:36                     ` Bruno Prémont
2010-03-30 20:38                   ` [PATCH v6 6/8] hid: add experimental access to PicoLCD device's EEPROM and FLASH Bruno Prémont
2010-03-30 20:38                     ` [PATCH v6 6/8] hid: add experimental access to PicoLCD device's Bruno Prémont
2010-03-30 20:42                   ` [PATCH v6 7/8, needs improvement] hid: add suspend/resume hooks for hid drivers Bruno Prémont
2010-03-30 20:42                     ` [PATCH v6 7/8, needs improvement] hid: add suspend/resume hooks for Bruno Prémont
2010-03-31 12:15                     ` [PATCH v6 7/8, needs improvement] hid: add suspend/resume hooks for hid drivers Jiri Kosina
2010-03-31 12:15                       ` [PATCH v6 7/8, needs improvement] hid: add suspend/resume hooks Jiri Kosina
2010-03-31 12:15                       ` [PATCH v6 7/8, needs improvement] hid: add suspend/resume hooks for hid drivers Jiri Kosina
2010-04-11 11:02                       ` Bruno Prémont
2010-04-11 11:02                         ` Bruno Prémont
2010-04-11 13:38                         ` Oliver Neukum
2010-04-11 13:38                           ` Oliver Neukum
2010-04-11 18:31                         ` Jiri Kosina
2010-04-11 18:31                           ` Jiri Kosina
2010-04-11 18:40                           ` Bruno Prémont
2010-04-11 20:27                             ` Oliver Neukum
2010-04-11 20:27                               ` Oliver Neukum
2010-04-12 11:43                             ` Jiri Kosina
2010-04-12 16:56                               ` Bruno Prémont
2010-04-12 16:56                                 ` Bruno Prémont
2010-04-12 19:45                                 ` Oliver Neukum
2010-04-12 19:45                                   ` Oliver Neukum
2010-03-30 20:43                   ` [PATCH v6 8/8] hid: add PM support to PicoLCD device Bruno Prémont
2010-03-30 20:43                     ` Bruno Prémont
2010-03-30 20:43                     ` Bruno Prémont
2010-03-31  9:28                   ` [PATCH v6 0/8] hid: new driver for " Jiri Kosina
2010-03-31  9:28                     ` Jiri Kosina
2010-03-31  9:28                     ` Jiri Kosina
2010-03-24 22:49 ` [PATCH v3 2/6] hid: add framebuffer support to " Bruno Prémont
2010-03-24 22:49   ` Bruno Prémont
2010-03-24 22:49   ` Bruno Prémont
2010-03-27  0:22   ` [PATCH v4 " Bruno Prémont
2010-03-27  0:22     ` Bruno Prémont
2010-03-29 20:30     ` [PATCH v5 " Bruno Prémont
2010-03-29 20:30       ` Bruno Prémont
2010-03-24 22:51 ` [PATCH v3 3/6] hid: add backlight " Bruno Prémont
2010-03-24 22:51   ` Bruno Prémont
2010-03-24 22:51   ` Bruno Prémont
2010-03-24 22:54 ` [PATCH v3 4/6] hid: add lcd " Bruno Prémont
2010-03-24 22:54   ` Bruno Prémont
2010-03-24 22:54   ` Bruno Prémont
2010-03-24 22:55 ` [PATCH v3 5/6] hid: add GPO (leds) " Bruno Prémont
2010-03-24 22:55   ` Bruno Prémont
2010-03-24 22:55   ` Bruno Prémont
2010-03-24 22:58 ` Bruno Prémont [this message]
2010-03-24 22:58   ` [PATCH v3 6/6] hid: add experimental access to PicoLCD device's Bruno Prémont
2010-03-24 22:58   ` [PATCH v3 6/6] hid: add experimental access to PicoLCD device's EEPROM and FLASH Bruno Prémont
2010-03-29  9:44   ` Jiri Kosina
2010-03-29  9:44     ` [PATCH v3 6/6] hid: add experimental access to PicoLCD device's Jiri Kosina
2010-03-29 20:34     ` [PATCH v4 6/6] hid: add experimental access to PicoLCD device's EEPROM and FLASH Bruno Prémont
2010-03-29 20:34       ` [PATCH v4 6/6] hid: add experimental access to PicoLCD device's Bruno Prémont
2010-03-29 20:34       ` [PATCH v4 6/6] hid: add experimental access to PicoLCD device's EEPROM and FLASH Bruno Prémont

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20100324235835.0e7e1bcb@neptune.home \
    --to=bonbons@linux-vserver.org \
    --cc=dmitry.torokhov@gmail.com \
    --cc=jayakumar.lkml@gmail.com \
    --cc=jkosina@suse.cz \
    --cc=linux-fbdev@vger.kernel.org \
    --cc=linux-input@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=npavel@ituner.com \
    --cc=oliver@neukum.org \
    --cc=rvinyard@cs.nmsu.edu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.