12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
243e5e7c6SDavid Herrmann /*
392eda7e4SDavid Herrmann  * Debug support for HID Nintendo Wii / Wii U peripherals
492eda7e4SDavid Herrmann  * Copyright (c) 2011-2013 David Herrmann <dh.herrmann@gmail.com>
543e5e7c6SDavid Herrmann  */
643e5e7c6SDavid Herrmann 
743e5e7c6SDavid Herrmann /*
843e5e7c6SDavid Herrmann  */
943e5e7c6SDavid Herrmann 
101d3452c6SDavid Herrmann #include <linux/debugfs.h>
1143e5e7c6SDavid Herrmann #include <linux/module.h>
1243d782aeSDavid Herrmann #include <linux/seq_file.h>
1343e5e7c6SDavid Herrmann #include <linux/spinlock.h>
141d3452c6SDavid Herrmann #include <linux/uaccess.h>
1543e5e7c6SDavid Herrmann #include "hid-wiimote.h"
1643e5e7c6SDavid Herrmann 
1743e5e7c6SDavid Herrmann struct wiimote_debug {
1843e5e7c6SDavid Herrmann 	struct wiimote_data *wdata;
191d3452c6SDavid Herrmann 	struct dentry *eeprom;
2043d782aeSDavid Herrmann 	struct dentry *drm;
211d3452c6SDavid Herrmann };
221d3452c6SDavid Herrmann 
wiidebug_eeprom_read(struct file * f,char __user * u,size_t s,loff_t * off)231d3452c6SDavid Herrmann static ssize_t wiidebug_eeprom_read(struct file *f, char __user *u, size_t s,
241d3452c6SDavid Herrmann 								loff_t *off)
251d3452c6SDavid Herrmann {
261d3452c6SDavid Herrmann 	struct wiimote_debug *dbg = f->private_data;
271d3452c6SDavid Herrmann 	struct wiimote_data *wdata = dbg->wdata;
281d3452c6SDavid Herrmann 	unsigned long flags;
291d3452c6SDavid Herrmann 	ssize_t ret;
301d3452c6SDavid Herrmann 	char buf[16];
31b77a989aSSimon Que 	__u16 size = 0;
321d3452c6SDavid Herrmann 
331d3452c6SDavid Herrmann 	if (s == 0)
341d3452c6SDavid Herrmann 		return -EINVAL;
351d3452c6SDavid Herrmann 	if (*off > 0xffffff)
361d3452c6SDavid Herrmann 		return 0;
371d3452c6SDavid Herrmann 	if (s > 16)
381d3452c6SDavid Herrmann 		s = 16;
391d3452c6SDavid Herrmann 
401d3452c6SDavid Herrmann 	ret = wiimote_cmd_acquire(wdata);
411d3452c6SDavid Herrmann 	if (ret)
421d3452c6SDavid Herrmann 		return ret;
431d3452c6SDavid Herrmann 
441d3452c6SDavid Herrmann 	spin_lock_irqsave(&wdata->state.lock, flags);
451d3452c6SDavid Herrmann 	wdata->state.cmd_read_size = s;
461d3452c6SDavid Herrmann 	wdata->state.cmd_read_buf = buf;
471d3452c6SDavid Herrmann 	wiimote_cmd_set(wdata, WIIPROTO_REQ_RMEM, *off & 0xffff);
481d3452c6SDavid Herrmann 	wiiproto_req_reeprom(wdata, *off, s);
491d3452c6SDavid Herrmann 	spin_unlock_irqrestore(&wdata->state.lock, flags);
501d3452c6SDavid Herrmann 
511d3452c6SDavid Herrmann 	ret = wiimote_cmd_wait(wdata);
521d3452c6SDavid Herrmann 	if (!ret)
531d3452c6SDavid Herrmann 		size = wdata->state.cmd_read_size;
541d3452c6SDavid Herrmann 
551d3452c6SDavid Herrmann 	spin_lock_irqsave(&wdata->state.lock, flags);
561d3452c6SDavid Herrmann 	wdata->state.cmd_read_buf = NULL;
571d3452c6SDavid Herrmann 	spin_unlock_irqrestore(&wdata->state.lock, flags);
581d3452c6SDavid Herrmann 
591d3452c6SDavid Herrmann 	wiimote_cmd_release(wdata);
601d3452c6SDavid Herrmann 
611d3452c6SDavid Herrmann 	if (ret)
621d3452c6SDavid Herrmann 		return ret;
631d3452c6SDavid Herrmann 	else if (size == 0)
641d3452c6SDavid Herrmann 		return -EIO;
651d3452c6SDavid Herrmann 
661d3452c6SDavid Herrmann 	if (copy_to_user(u, buf, size))
671d3452c6SDavid Herrmann 		return -EFAULT;
681d3452c6SDavid Herrmann 
691d3452c6SDavid Herrmann 	*off += size;
701d3452c6SDavid Herrmann 	ret = size;
711d3452c6SDavid Herrmann 
721d3452c6SDavid Herrmann 	return ret;
731d3452c6SDavid Herrmann }
741d3452c6SDavid Herrmann 
751d3452c6SDavid Herrmann static const struct file_operations wiidebug_eeprom_fops = {
761d3452c6SDavid Herrmann 	.owner = THIS_MODULE,
77234e3405SStephen Boyd 	.open = simple_open,
781d3452c6SDavid Herrmann 	.read = wiidebug_eeprom_read,
791d3452c6SDavid Herrmann 	.llseek = generic_file_llseek,
8043e5e7c6SDavid Herrmann };
8143e5e7c6SDavid Herrmann 
8243d782aeSDavid Herrmann static const char *wiidebug_drmmap[] = {
8343d782aeSDavid Herrmann 	[WIIPROTO_REQ_NULL] = "NULL",
8443d782aeSDavid Herrmann 	[WIIPROTO_REQ_DRM_K] = "K",
8543d782aeSDavid Herrmann 	[WIIPROTO_REQ_DRM_KA] = "KA",
8643d782aeSDavid Herrmann 	[WIIPROTO_REQ_DRM_KE] = "KE",
8743d782aeSDavid Herrmann 	[WIIPROTO_REQ_DRM_KAI] = "KAI",
8843d782aeSDavid Herrmann 	[WIIPROTO_REQ_DRM_KEE] = "KEE",
8943d782aeSDavid Herrmann 	[WIIPROTO_REQ_DRM_KAE] = "KAE",
9043d782aeSDavid Herrmann 	[WIIPROTO_REQ_DRM_KIE] = "KIE",
9143d782aeSDavid Herrmann 	[WIIPROTO_REQ_DRM_KAIE] = "KAIE",
9243d782aeSDavid Herrmann 	[WIIPROTO_REQ_DRM_E] = "E",
9343d782aeSDavid Herrmann 	[WIIPROTO_REQ_DRM_SKAI1] = "SKAI1",
9443d782aeSDavid Herrmann 	[WIIPROTO_REQ_DRM_SKAI2] = "SKAI2",
9543d782aeSDavid Herrmann 	[WIIPROTO_REQ_MAX] = NULL
9643d782aeSDavid Herrmann };
9743d782aeSDavid Herrmann 
wiidebug_drm_show(struct seq_file * f,void * p)9843d782aeSDavid Herrmann static int wiidebug_drm_show(struct seq_file *f, void *p)
9943d782aeSDavid Herrmann {
10043d782aeSDavid Herrmann 	struct wiimote_debug *dbg = f->private;
10143d782aeSDavid Herrmann 	const char *str = NULL;
10243d782aeSDavid Herrmann 	unsigned long flags;
10343d782aeSDavid Herrmann 	__u8 drm;
10443d782aeSDavid Herrmann 
10543d782aeSDavid Herrmann 	spin_lock_irqsave(&dbg->wdata->state.lock, flags);
10643d782aeSDavid Herrmann 	drm = dbg->wdata->state.drm;
10743d782aeSDavid Herrmann 	spin_unlock_irqrestore(&dbg->wdata->state.lock, flags);
10843d782aeSDavid Herrmann 
10943d782aeSDavid Herrmann 	if (drm < WIIPROTO_REQ_MAX)
11043d782aeSDavid Herrmann 		str = wiidebug_drmmap[drm];
11143d782aeSDavid Herrmann 	if (!str)
11243d782aeSDavid Herrmann 		str = "unknown";
11343d782aeSDavid Herrmann 
11443d782aeSDavid Herrmann 	seq_printf(f, "%s\n", str);
11543d782aeSDavid Herrmann 
11643d782aeSDavid Herrmann 	return 0;
11743d782aeSDavid Herrmann }
11843d782aeSDavid Herrmann 
wiidebug_drm_open(struct inode * i,struct file * f)11943d782aeSDavid Herrmann static int wiidebug_drm_open(struct inode *i, struct file *f)
12043d782aeSDavid Herrmann {
12143d782aeSDavid Herrmann 	return single_open(f, wiidebug_drm_show, i->i_private);
12243d782aeSDavid Herrmann }
12343d782aeSDavid Herrmann 
wiidebug_drm_write(struct file * f,const char __user * u,size_t s,loff_t * off)12443d782aeSDavid Herrmann static ssize_t wiidebug_drm_write(struct file *f, const char __user *u,
12543d782aeSDavid Herrmann 							size_t s, loff_t *off)
12643d782aeSDavid Herrmann {
12751103c70SDavid Herrmann 	struct seq_file *sf = f->private_data;
12851103c70SDavid Herrmann 	struct wiimote_debug *dbg = sf->private;
12943d782aeSDavid Herrmann 	unsigned long flags;
13043d782aeSDavid Herrmann 	char buf[16];
13143d782aeSDavid Herrmann 	ssize_t len;
13243d782aeSDavid Herrmann 	int i;
13343d782aeSDavid Herrmann 
13443d782aeSDavid Herrmann 	if (s == 0)
13543d782aeSDavid Herrmann 		return -EINVAL;
13643d782aeSDavid Herrmann 
13743d782aeSDavid Herrmann 	len = min((size_t) 15, s);
13843d782aeSDavid Herrmann 	if (copy_from_user(buf, u, len))
13943d782aeSDavid Herrmann 		return -EFAULT;
14043d782aeSDavid Herrmann 
1410d57eb87SDavid Herrmann 	buf[len] = 0;
14243d782aeSDavid Herrmann 
14343d782aeSDavid Herrmann 	for (i = 0; i < WIIPROTO_REQ_MAX; ++i) {
14443d782aeSDavid Herrmann 		if (!wiidebug_drmmap[i])
14543d782aeSDavid Herrmann 			continue;
14643d782aeSDavid Herrmann 		if (!strcasecmp(buf, wiidebug_drmmap[i]))
14743d782aeSDavid Herrmann 			break;
14843d782aeSDavid Herrmann 	}
14943d782aeSDavid Herrmann 
15043d782aeSDavid Herrmann 	if (i == WIIPROTO_REQ_MAX)
1510d57eb87SDavid Herrmann 		i = simple_strtoul(buf, NULL, 16);
15243d782aeSDavid Herrmann 
15343d782aeSDavid Herrmann 	spin_lock_irqsave(&dbg->wdata->state.lock, flags);
154d76f89e1SDavid Herrmann 	dbg->wdata->state.flags &= ~WIIPROTO_FLAG_DRM_LOCKED;
15543d782aeSDavid Herrmann 	wiiproto_req_drm(dbg->wdata, (__u8) i);
156d76f89e1SDavid Herrmann 	if (i != WIIPROTO_REQ_NULL)
157d76f89e1SDavid Herrmann 		dbg->wdata->state.flags |= WIIPROTO_FLAG_DRM_LOCKED;
15843d782aeSDavid Herrmann 	spin_unlock_irqrestore(&dbg->wdata->state.lock, flags);
15943d782aeSDavid Herrmann 
16043d782aeSDavid Herrmann 	return len;
16143d782aeSDavid Herrmann }
16243d782aeSDavid Herrmann 
16343d782aeSDavid Herrmann static const struct file_operations wiidebug_drm_fops = {
16443d782aeSDavid Herrmann 	.owner = THIS_MODULE,
16543d782aeSDavid Herrmann 	.open = wiidebug_drm_open,
16643d782aeSDavid Herrmann 	.read = seq_read,
16743d782aeSDavid Herrmann 	.llseek = seq_lseek,
16843d782aeSDavid Herrmann 	.write = wiidebug_drm_write,
16943d782aeSDavid Herrmann 	.release = single_release,
17043d782aeSDavid Herrmann };
17143d782aeSDavid Herrmann 
wiidebug_init(struct wiimote_data * wdata)17243e5e7c6SDavid Herrmann int wiidebug_init(struct wiimote_data *wdata)
17343e5e7c6SDavid Herrmann {
17443e5e7c6SDavid Herrmann 	struct wiimote_debug *dbg;
17543e5e7c6SDavid Herrmann 	unsigned long flags;
17643e5e7c6SDavid Herrmann 
17743e5e7c6SDavid Herrmann 	dbg = kzalloc(sizeof(*dbg), GFP_KERNEL);
17843e5e7c6SDavid Herrmann 	if (!dbg)
17943e5e7c6SDavid Herrmann 		return -ENOMEM;
18043e5e7c6SDavid Herrmann 
18143e5e7c6SDavid Herrmann 	dbg->wdata = wdata;
18243e5e7c6SDavid Herrmann 
1831d3452c6SDavid Herrmann 	dbg->eeprom = debugfs_create_file("eeprom", S_IRUSR,
1841d3452c6SDavid Herrmann 		dbg->wdata->hdev->debug_dir, dbg, &wiidebug_eeprom_fops);
18543d782aeSDavid Herrmann 
18643d782aeSDavid Herrmann 	dbg->drm = debugfs_create_file("drm", S_IRUSR,
18743d782aeSDavid Herrmann 			dbg->wdata->hdev->debug_dir, dbg, &wiidebug_drm_fops);
1881d3452c6SDavid Herrmann 
18943e5e7c6SDavid Herrmann 	spin_lock_irqsave(&wdata->state.lock, flags);
19043e5e7c6SDavid Herrmann 	wdata->debug = dbg;
19143e5e7c6SDavid Herrmann 	spin_unlock_irqrestore(&wdata->state.lock, flags);
19243e5e7c6SDavid Herrmann 
19343e5e7c6SDavid Herrmann 	return 0;
19443d782aeSDavid Herrmann 
19543e5e7c6SDavid Herrmann }
19643e5e7c6SDavid Herrmann 
wiidebug_deinit(struct wiimote_data * wdata)19743e5e7c6SDavid Herrmann void wiidebug_deinit(struct wiimote_data *wdata)
19843e5e7c6SDavid Herrmann {
19943e5e7c6SDavid Herrmann 	struct wiimote_debug *dbg = wdata->debug;
20043e5e7c6SDavid Herrmann 	unsigned long flags;
20143e5e7c6SDavid Herrmann 
20243e5e7c6SDavid Herrmann 	if (!dbg)
20343e5e7c6SDavid Herrmann 		return;
20443e5e7c6SDavid Herrmann 
20543e5e7c6SDavid Herrmann 	spin_lock_irqsave(&wdata->state.lock, flags);
20643e5e7c6SDavid Herrmann 	wdata->debug = NULL;
20743e5e7c6SDavid Herrmann 	spin_unlock_irqrestore(&wdata->state.lock, flags);
20843e5e7c6SDavid Herrmann 
20943d782aeSDavid Herrmann 	debugfs_remove(dbg->drm);
2101d3452c6SDavid Herrmann 	debugfs_remove(dbg->eeprom);
21143e5e7c6SDavid Herrmann 	kfree(dbg);
21243e5e7c6SDavid Herrmann }
213