Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9bbf2b98 authored by Bruno Prémont's avatar Bruno Prémont Committed by Jiri Kosina
Browse files

HID: add experimental access to PicoLCD device's EEPROM and FLASH



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 the will reside in debugfs

Signed-off-by: default avatarBruno Prémont <bonbons@linux-vserver.org>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent 467d6523
Loading
Loading
Loading
Loading
+1 −1
Original line number Original line Diff line number Diff line
@@ -281,9 +281,9 @@ config HID_PICOLCD
	  - Backlight control         (needs CONFIG_BACKLIGHT_CLASS_DEVICE)
	  - Backlight control         (needs CONFIG_BACKLIGHT_CLASS_DEVICE)
	  - Contrast control          (needs CONFIG_LCD_CLASS_DEVICE)
	  - Contrast control          (needs CONFIG_LCD_CLASS_DEVICE)
	  - General purpose outputs   (needs CONFIG_LEDS_CLASS)
	  - General purpose outputs   (needs CONFIG_LEDS_CLASS)
	  - EEProm / Flash access     (via debugfs)
	  Features that are not (yet) supported:
	  Features that are not (yet) supported:
	  - IR
	  - IR
	  - EEProm / Flash access


config HID_QUANTA
config HID_QUANTA
	tristate "Quanta Optical Touch"
	tristate "Quanta Optical Touch"
+422 −17
Original line number Original line Diff line number Diff line
@@ -169,6 +169,10 @@ struct picolcd_pending {
struct picolcd_data {
struct picolcd_data {
	struct hid_device *hdev;
	struct hid_device *hdev;
#ifdef CONFIG_DEBUG_FS
#ifdef CONFIG_DEBUG_FS
	struct dentry *debug_reset;
	struct dentry *debug_eeprom;
	struct dentry *debug_flash;
	struct mutex mutex_flash;
	int addr_sz;
	int addr_sz;
#endif
#endif
	u8 version[2];
	u8 version[2];
@@ -1313,6 +1317,357 @@ static DEVICE_ATTR(operation_mode, 0644, picolcd_operation_mode_show,




#ifdef CONFIG_DEBUG_FS
#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 int 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
 * Helper code for HID report level dumping/debugging
 */
 */
@@ -1788,9 +2143,66 @@ static void picolcd_debug_raw_event(struct picolcd_data *data,
	wake_up_interruptible(&hdev->debug_wait);
	wake_up_interruptible(&hdev->debug_wait);
	kfree(buff);
	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
#else
#define picolcd_debug_raw_event(data, hdev, report, raw_data, size)
#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
 * Handle raw report as sent by device
@@ -1900,7 +2312,6 @@ static inline void picolcd_exit_cir(struct picolcd_data *data)


static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
{
{
	struct hid_report *report;
	int error;
	int error;


	error = picolcd_check_version(hdev);
	error = picolcd_check_version(hdev);
@@ -1942,13 +2353,11 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)
	if (error)
	if (error)
		goto err;
		goto err;


#ifdef CONFIG_DEBUG_FS
	picolcd_init_devfs(data, picolcd_out_report(REPORT_EE_READ, hdev),
	report = picolcd_out_report(REPORT_READ_MEMORY, hdev);
			picolcd_out_report(REPORT_EE_WRITE, hdev),
	if (report && report->maxfield == 1 && report->field[0]->report_size == 8)
			picolcd_out_report(REPORT_READ_MEMORY, hdev),
		data->addr_sz = report->field[0]->report_count - 1;
			picolcd_out_report(REPORT_WRITE_MEMORY, hdev),
	else
			picolcd_out_report(REPORT_RESET, hdev));
		data->addr_sz = -1;
#endif
	return 0;
	return 0;
err:
err:
	picolcd_exit_leds(data);
	picolcd_exit_leds(data);
@@ -1962,7 +2371,6 @@ static int picolcd_probe_lcd(struct hid_device *hdev, struct picolcd_data *data)


static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data)
static int picolcd_probe_bootloader(struct hid_device *hdev, struct picolcd_data *data)
{
{
	struct hid_report *report;
	int error;
	int error;


	error = picolcd_check_version(hdev);
	error = picolcd_check_version(hdev);
@@ -1974,13 +2382,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",
				"please submit /sys/kernel/debug/hid/%s/rdesc for this device.\n",
				dev_name(&hdev->dev));
				dev_name(&hdev->dev));


#ifdef CONFIG_DEBUG_FS
	picolcd_init_devfs(data, NULL, NULL,
	report = picolcd_out_report(REPORT_BL_READ_MEMORY, hdev);
			picolcd_out_report(REPORT_BL_READ_MEMORY, hdev),
	if (report && report->maxfield == 1 && report->field[0]->report_size == 8)
			picolcd_out_report(REPORT_BL_WRITE_MEMORY, hdev), NULL);
		data->addr_sz = report->field[0]->report_count - 1;
	else
		data->addr_sz = -1;
#endif
	return 0;
	return 0;
}
}


@@ -2073,6 +2477,7 @@ static void picolcd_remove(struct hid_device *hdev)
	data->status |= PICOLCD_FAILED;
	data->status |= PICOLCD_FAILED;
	spin_unlock_irqrestore(&data->lock, flags);
	spin_unlock_irqrestore(&data->lock, flags);


	picolcd_exit_devfs(data);
	device_remove_file(&hdev->dev, &dev_attr_operation_mode);
	device_remove_file(&hdev->dev, &dev_attr_operation_mode);
	hdev->ll_driver->close(hdev);
	hdev->ll_driver->close(hdev);
	hid_hw_stop(hdev);
	hid_hw_stop(hdev);