add basics of the kernel driver :0

This commit is contained in:
Triss 2021-07-12 01:43:35 +02:00
parent 0f4566d0e7
commit f9eb86b60e
13 changed files with 970 additions and 46 deletions

View File

@ -434,7 +434,7 @@ enum itu_status i2ctu_dev_write(enum ki2c_flags flags, enum itu_command startsto
return ITU_STATUS_ADDR_ACK;
} else*/
{
int rv = i2cex_write_timeout_us(PINOUT_I2C_DEV, addr, bit10, buf, len, nostop, 1000 * 1000);
int rv = i2cex_write_timeout_us(PINOUT_I2C_DEV, addr, bit10, buf, len, nostop, 400 * 1000);
if (rv < 0 || (size_t)rv < len) return ITU_STATUS_ADDR_NAK;
return ITU_STATUS_ADDR_ACK;
}
@ -452,7 +452,7 @@ enum itu_status i2ctu_dev_read(enum ki2c_flags flags, enum itu_command startstop
return ITU_STATUS_ADDR_ACK;
} else*/
{
int rv = i2cex_read_timeout_us(PINOUT_I2C_DEV, addr, bit10, buf, len, nostop, 1000 * 1000);
int rv = i2cex_read_timeout_us(PINOUT_I2C_DEV, addr, bit10, buf, len, nostop, 400 * 1000);
// printf("p le rv=%d buf=%02x ", rv, buf[0]);
if (rv < 0 || (size_t)rv < len) return ITU_STATUS_ADDR_NAK;
return ITU_STATUS_ADDR_ACK;

17
dmctl3.py Executable file
View File

@ -0,0 +1,17 @@
#!/usr/bin/env python3
import os, struct, sys
f = os.open(sys.argv[1], os.O_RDWR | os.O_CLOEXEC) # TODO: windows: os.O_BINARY |
try:
os.write(f, b'\x00') # get version
resp = os.read(f, 4) # response: status, paylaod len (should be 2), payload
print("stat=%d plen=%d ver=%04x" % (resp[0], resp[1], struct.unpack('<H', resp[2:])[0]))
finally:
os.close(f)
#with open(sys.argv[1], 'rb') as f:
# f.write(b'\x00') # get version
# resp = f.read(4) # response
# print("stat=%d plen=%d ver=%04x" % (resp[0], resp[1], struct.unpack('<H', resp[2:])[0]))

20
host/modules/Makefile Normal file
View File

@ -0,0 +1,20 @@
obj-m := dmj.o dmj-char.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
CFLAGS += -Wall -Wpedantic
default:
$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
$(RM) -v *.o *.ko *.mod *.mod.c Module.symvers modules.order
load:
sudo insmod ./dmj.ko
sudo insmod ./dmj-char.ko
unload:
sudo rmmod dmj-char
sudo rmmod dmj

321
host/modules/dmj-char.c Normal file
View File

@ -0,0 +1,321 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for the DapperMime-JTAG USB multitool: character device for userspace
* access while the module is loaded
*
* Copyright (c) sys64738 and haskal
*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/device/class.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#if 0
#include <linux/driver/dmj.h>
#else
#include "dmj.h"
#endif
#define HARDWARE_NAME "DapperMime-JTAG"
#define DEVICE_NAME "dmj-char"
#define CLASS_NAME "dmj"
#define DMJ_READ_BUFSIZE 64
struct dmj_char_dev {
struct cdev cdev;
struct device *dev;
struct platform_device *pdev;
int minor;
spinlock_t devopen_lock;
size_t bufpos;
uint8_t rdbuf[DMJ_READ_BUFSIZE];
};
static int n_cdevs = 0;
static spinlock_t ndevs_lock;
static int dmj_char_major;
static struct class *dmj_char_class;
static ssize_t dmj_char_read(struct file *file, char *buf, size_t len, loff_t *loff)
{
int res, bsize;
struct dmj_char_dev *dmjch;
size_t bpos, todo, done = 0;
uint8_t *kbuf;
struct device *dev;
kbuf = kmalloc(len + DMJ_READ_BUFSIZE, GFP_KERNEL);
if (!kbuf) return -ENOMEM;
dmjch = file->private_data;
dev = dmjch->dev;
todo = len;
spin_lock(&dmjch->devopen_lock);
bpos = dmjch->bufpos;
/* TODO: ioctl to control this buffering? */
if (bpos) { /* data in buffer */
if (len <= DMJ_READ_BUFSIZE - bpos) {
/* doable in a single copy, no USB xfers needed */
memcpy(kbuf, &dmjch->rdbuf[bpos], len);
bpos += len;
if (bpos == DMJ_READ_BUFSIZE) bpos = 0;
done += len;
todo -= len;
} else {
/* initial copy stuff */
memcpy(kbuf, &dmjch->rdbuf[bpos], DMJ_READ_BUFSIZE - bpos);
todo -= DMJ_READ_BUFSIZE - bpos;
done += DMJ_READ_BUFSIZE - bpos;
bpos = 0;
}
}
if /*while*/ (todo) { /* TODO: do we want a while here? */
bsize = DMJ_READ_BUFSIZE;
res = dmj_read(dmjch->pdev, 0, &kbuf[done], &bsize);
if (res < 0 || bsize < 0) {
/* ah snap */
spin_unlock(&dmjch->devopen_lock);
return res;
}
if ((size_t)bsize > todo) {
if ((size_t)bsize > todo + DMJ_READ_BUFSIZE) {
/* can't hold all this data, time to bail out... */
dev_err(dev, "too much data (%zu B excess), can't buffer, AAAAAA",
(size_t)bsize - (todo + DMJ_READ_BUFSIZE));
spin_unlock(&dmjch->devopen_lock);
BUG(); /* some stuff somewhere will have been corrupted, so, get out while we can */
}
/* stuff for next call */
done = todo;
bpos = DMJ_READ_BUFSIZE - ((size_t)bsize - todo);
memcpy(&dmjch->rdbuf[bpos], &kbuf[done], (size_t)bsize - todo);
todo = 0;
} else {
todo -= (size_t)bsize;
done += (size_t)bsize;
}
}
dmjch->bufpos = bpos;
spin_unlock(&dmjch->devopen_lock);
res = copy_to_user(buf, kbuf, len);
if (res) return (res < 0) ? res : -EFAULT;
return done;
}
static ssize_t dmj_char_write(struct file *file, const char *buf, size_t len, loff_t *off)
{
unsigned long ret;
struct dmj_char_dev *dmjch;
void *kbuf;
dmjch = file->private_data;
kbuf = kmalloc(len, GFP_KERNEL);
if (!kbuf) return -ENOMEM;
ret = copy_from_user(kbuf, buf, len);
if (ret) {
kfree(kbuf);
return (ret < 0) ? ret : -EFAULT;
}
ret = dmj_transfer(dmjch->pdev, -1, 0, kbuf, len, NULL, NULL);
if (ret < 0) {
kfree(kbuf);
return ret;
}
kfree(kbuf);
return len;
}
static int dmj_char_open(struct inode *inode, struct file *file)
{
struct dmj_char_dev *dmjch;
int ret;
dmjch = container_of(inode->i_cdev, struct dmj_char_dev, cdev);
spin_lock(&dmjch->devopen_lock);
ret = dmjch->bufpos;
if (~ret == 0) dmjch->bufpos = 0;
spin_unlock(&dmjch->devopen_lock);
if (~ret != 0) return -ETXTBSY; // already open
file->private_data = dmjch;
return 0;
}
static int dmj_char_release(struct inode *inode, struct file *file)
{
struct dmj_char_dev *dmjch;
dmjch = container_of(inode->i_cdev, struct dmj_char_dev, cdev);
spin_lock(&dmjch->devopen_lock);
dmjch->bufpos = ~(size_t)0;
spin_unlock(&dmjch->devopen_lock);
return 0;
}
static const struct file_operations dmj_char_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = dmj_char_read,
.write = dmj_char_write,
.unlocked_ioctl = NULL,
.open = dmj_char_open,
.release = dmj_char_release
};
static int dmj_char_probe(struct platform_device *pdev)
{
int ret, minor;
struct device *device;
struct device *pd = &pdev->dev;
struct dmj_char_dev *dmjch;
spin_lock(&ndevs_lock);
minor = n_cdevs;
++n_cdevs;
spin_unlock(&ndevs_lock);
dev_info(pd, HARDWARE_NAME " /dev entries driver, major=%d, minor=%d\n",
dmj_char_major, minor);
dmjch = kzalloc(sizeof(*dmjch), GFP_KERNEL);
if (!dmjch) return -ENOMEM;
platform_set_drvdata(pdev, dmjch);
cdev_init(&dmjch->cdev, &dmj_char_fops);
ret = cdev_add(&dmjch->cdev, MKDEV(dmj_char_major, minor), 1);
if (ret < 0) {
dev_err(pd, "failed to create cdev: %d\n", ret);
return ret;
}
device = device_create(dmj_char_class, pd, MKDEV(dmj_char_major, minor), dmjch, "dmj-%d", minor);
if (IS_ERR(device)) {
ret = PTR_ERR(device);
dev_err(pd, "failed to create device: %d\n", ret);
cdev_del(&dmjch->cdev);
return ret;
}
dev_notice(device, "created device /dev/dmj-%d\n", minor);
dmjch->dev = device;
dmjch->minor = minor;
dmjch->pdev = pdev;
dmjch->bufpos = ~(size_t)0;
spin_lock_init(&dmjch->devopen_lock);
return 0;
}
static int dmj_char_remove(struct platform_device *pdev)
{
struct dmj_char_dev *dmjch = platform_get_drvdata(pdev);
device_destroy(dmj_char_class, MKDEV(dmj_char_major, dmjch->minor));
cdev_del(&dmjch->cdev);
unregister_chrdev(MKDEV(dmj_char_major, dmjch->minor), CLASS_NAME);
kfree(dmjch);
return 0;
}
static struct platform_driver dmj_char_driver = {
.driver = {
.name = "dmj-char"
},
.probe = dmj_char_probe,
.remove = dmj_char_remove
};
/*module_platform_driver(dmj_char_driver);*/
static int __init dmj_char_init(void)
{
int ret, major;
struct class *class;
dev_t devid;
spin_lock_init(&ndevs_lock);
n_cdevs = 0;
devid = MKDEV(0,0);
ret = alloc_chrdev_region(&devid, 0, 256, DEVICE_NAME);
if (ret < 0) {
printk(KERN_ERR " failed to register chrdev major number: %d\n", major);
return ret;
}
major = MAJOR(devid);
printk(KERN_NOTICE DEVICE_NAME " registered with major number %d\n", major);
class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(class)) {
ret = PTR_ERR(class);
printk(KERN_ERR " failed to create class: %d\n", ret);
unregister_chrdev(major, DEVICE_NAME); /* TODO: unregister_chrdev_rage */
return ret;
}
printk(KERN_DEBUG DEVICE_NAME " created class\n");
dmj_char_major = major;
dmj_char_class = class;
platform_driver_register(&dmj_char_driver);
return 0;
}
static void __exit dmj_char_exit(void)
{
platform_driver_unregister(&dmj_char_driver);
spin_lock(&ndevs_lock);
n_cdevs = 0;
spin_unlock(&ndevs_lock);
class_destroy(dmj_char_class);
unregister_chrdev(MKDEV(dmj_char_major, 0), CLASS_NAME); /* TODO: unregister_chrdev_rage */
dmj_char_major = -1;
dmj_char_class = NULL;
}
module_init(dmj_char_init);
module_exit(dmj_char_exit);
MODULE_AUTHOR("sys64738 <sys64738@disroot.org>");
MODULE_AUTHOR("haskal <haskal@awoo.systems>");
MODULE_DESCRIPTION("Character device for the " HARDWARE_NAME " USB multitool");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:dmj-char");
MODULE_DEPEND

538
host/modules/dmj.c Normal file
View File

@ -0,0 +1,538 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for the DapperMime-JTAG USB multitool: base MFD driver
*
* Copyright (c) sys64738 and haskal
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/mfd/core.h>
#include <linux/rculist.h>
#if 0
#include <linux/mfd/dmj.h>
#else
#include "dmj.h"
#endif
#define HARDWARE_NAME "DapperMime-JTAG"
#define HARDWARE_NAME_SYMBOLIC "dappermime-jtag"
#define DMJ_USB_TIMEOUT 500
#define DMJ_RESP_HDR_SIZE 4
/* endpoint indices, not addresses */
#define DMJ_VND_CFG_EP_OUT 0
#define DMJ_VND_CFG_EP_IN 1
struct dmj_dev {
struct usb_device *usb_dev;
struct usb_interface *interface;
uint8_t ep_in;
uint8_t ep_out;
spinlock_t disconnect_lock;
bool disconnect;
uint8_t dmj_mode, dmj_m1feature;
};
/* USB transfers */
static void *dmj_prep_buf(int cmd, const void *wbuf, int *wbufsize, gfp_t gfp)
{
int len;
uint8_t *buf;
if (cmd >= 0 && cmd <= 0xff) {
/* send extra cmd byte, and optionally a payload */
if (wbufsize && wbuf) len = *wbufsize + 1;
else len = 1;
buf = kmalloc(len, gfp);
if (!buf) return NULL;
buf[0] = (uint8_t)cmd;
if (wbuf && *wbufsize)
memcpy(&buf[1], wbuf, *wbufsize);
} else {
/* assuming we have a payload to send, but no (explicit) cmd */
len = *wbufsize;
buf = kmalloc(len, gfp);
if (!buf) return NULL;
memcpy(buf, wbuf, len);
}
*wbufsize = len;
return buf;
}
static int dmj_send_wait(struct dmj_dev *dmj, int cmd, const void *wbuf, int wbufsize)
{
int ret = 0;
int len = wbufsize, actual;
void *buf;
buf = dmj_prep_buf(cmd, wbuf, &len, GFP_KERNEL);
if (!buf) return -ENOMEM;
ret = usb_bulk_msg(dmj->usb_dev, usb_sndbulkpipe(dmj->usb_dev, dmj->ep_out),
buf, len, &actual, DMJ_USB_TIMEOUT);
kfree(buf);
return ret;
}
static int dmj_recv_wait(struct dmj_dev *dmj, void **kbuf, int rbufsize, bool parse_hdr)
{
int len, actual;
int ret;
void *buf;
*kbuf = NULL;
if (rbufsize <= 0) len = 0;
else if (parse_hdr) len = rbufsize + DMJ_RESP_HDR_SIZE;
else len = rbufsize;
buf = kmalloc(len, GFP_KERNEL);
if (!buf) return -ENOMEM;
ret = usb_bulk_msg(dmj->usb_dev, usb_rcvbulkpipe(dmj->usb_dev, dmj->ep_in),
buf, len, &actual, DMJ_USB_TIMEOUT);
if (ret < 0) kfree(buf);
*kbuf = buf;
return actual;
}
int dmj_xfer_internal(struct dmj_dev *dmj, int cmd, int recvflags,
const void *wbuf, int wbufsize, void *rbuf, int *rbufsize)
{
int ret = 0, actual;
struct device *dev = &dmj->interface->dev;
int respstat, total_len, bytes_read;
uint32_t pl_len;
void *buf;
uint8_t *dbuf;
ptrdiff_t pl_off;
spin_lock(&dmj->disconnect_lock);
if (dmj->disconnect) ret = -ENODEV;
spin_unlock(&dmj->disconnect_lock);
if (ret) return ret;
if ((cmd >= 0 && cmd <= 0xff) || (wbufsize && wbuf)) {
ret = dmj_send_wait(dmj, cmd, wbuf, wbufsize);
if (ret < 0) {
dev_err(dev, "USB write failed: %d\n", ret);
return ret;
}
}
if ((recvflags & DMJ_XFER_FLAGS_PARSE_RESP) == 0
&& !(rbufsize && *rbufsize > 0 && rbuf)) {
/* don't want any type of response? then dont do the urb stuff either */
return 0;
}
/* first recv buffer, with optional response header parsing */
ret = dmj_recv_wait(dmj, &buf, (rbufsize && *rbufsize > 0) ? *rbufsize : 0,
(recvflags & DMJ_XFER_FLAGS_PARSE_RESP) != 0);
if (ret < 0 || !buf) {
dev_err(dev, "USB read failed: %d\n", ret);
return ret;
}
actual = ret;
if (recvflags & DMJ_XFER_FLAGS_PARSE_RESP) {
dbuf = buf;
/* decode payload length */
if (dbuf[1] & 0x80) {
if (actual < 3) {
dev_err(dev, "short response (%d, expected at least 3)\n", actual);
kfree(buf);
return -EREMOTEIO;
}
pl_len = (uint32_t)(dbuf[1] & 0x7f);
if (dbuf[2] & 0x80) {
if (actual < 4) {
dev_err(dev, "short response (%d, expected at least 4)\n", actual);
kfree(buf);
return -EREMOTEIO;
}
pl_len |= (uint32_t)(dbuf[2] & 0x7f) << 7;
pl_len |= (uint32_t)dbuf[3] << 14;
pl_off = 4;
} else {
pl_len |= (uint32_t)dbuf[2] << 7;
pl_off = 3;
}
} else {
if (actual < 2) {
dev_err(dev, "short response (%d, expected at least 2)\n", actual);
kfree(buf);
return -EREMOTEIO;
}
pl_len = (uint32_t)dbuf[1];
pl_off = 2;
}
respstat = dbuf[0];
total_len = pl_len;
actual -= pl_off;
/*dev_dbg(dev, "pl_len=%d,off=%ld,resp=%d\n", pl_len, pl_off, respstat);*/
} else {
pl_off = 0;
if (rbufsize && *rbufsize > 0) total_len = *rbufsize;
else total_len = actual;
respstat = 0;
}
if (rbuf && rbufsize && *rbufsize > 0) {
if (*rbufsize < total_len) total_len = *rbufsize;
if (actual > total_len) {
/*if (recvflags & DMJ_XFER_FLAGS_FILL_RECVBUF)*/
{
dev_err(dev, "aaa msgsize %d > %d\n", actual, total_len);
kfree(buf);
return -EMSGSIZE;
} /*else {
actual = total_len;
}*/
}
memcpy(rbuf + bytes_read, buf + pl_off, actual);
kfree(buf);
pl_off = -1;
buf = NULL;
bytes_read = actual;
while (bytes_read < total_len && (recvflags & DMJ_XFER_FLAGS_FILL_RECVBUF) != 0) {
ret = dmj_recv_wait(dmj, &buf, total_len - bytes_read, false);
if (ret < 0 || !buf) {
dev_err(dev, "USB read failed: %d\n", ret);
return ret;
}
actual = ret;
if (bytes_read + actual > total_len) {
/*actual = total_len - bytes_read;*/
dev_err(dev, "aaa2 msgsize %d > %d\n", actual+bytes_read, total_len);
kfree(buf);
return -EMSGSIZE;
}
memcpy(rbuf + bytes_read, buf, actual);
kfree(buf);
buf = NULL;
bytes_read += actual;
}
} else {
bytes_read = 0;
kfree(buf);
}
*rbufsize = bytes_read;
/*dev_dbg(dev, "all good! resp=%02x\n", respstat);*/
return respstat;
}
int dmj_transfer(struct platform_device *pdev, int cmd, int recvflags,
const void *wbuf, int wbufsize, void *rbuf, int *rbufsize)
{
struct dmj_platform_data *dmj_pdata;
struct dmj_dev *dmj;
dmj = dev_get_drvdata(pdev->dev.parent);
dmj_pdata = dev_get_platdata(&pdev->dev); /* TODO: ??? */
return dmj_xfer_internal(dmj, cmd, recvflags, wbuf, wbufsize, rbuf, rbufsize);
}
EXPORT_SYMBOL(dmj_transfer);
/* stuff on init */
static int dmj_check_hw(struct dmj_dev *dmj)
{
struct device *dev = &dmj->interface->dev;
int ret;
__le16 protover;
int len = sizeof(protover);
ret = dmj_xfer_internal(dmj, DMJ_CMD_CFG_GET_VERSION,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, &protover, &len);
if (ret < 0) { /* TODO: positive error codes from respstat */
dev_err(dev, "USB fail: %d\n", ret);
return ret;
}
if (len < sizeof(protover)) {
dev_err(dev, "USB fail remoteio: %d\n", len);
return -EREMOTEIO;
}
if (len > sizeof(protover)) {
dev_err(dev, "USB fail msgsize: %d\n", len);
return -EMSGSIZE;
}
if (le16_to_cpu(protover) != DMJ_USB_CFG_PROTO_VER) {
dev_err(&dmj->interface->dev, HARDWARE_NAME " config protocol version 0x%04x too %s\n",
le16_to_cpu(protover), (le16_to_cpu(protover) > DMJ_USB_CFG_PROTO_VER) ? "new" : "old");
return -ENODEV;
}
return 0;
}
static int dmj_print_info(struct dmj_dev *dmj)
{
int ret, i, j, len;
__le16 modes, mversion;
uint8_t curmode, features;
struct device *dev = &dmj->interface->dev;
char strinfo[65];
char modeinfo[16];
/* info string */
len = sizeof(strinfo)-1;
ret = dmj_xfer_internal(dmj, DMJ_CMD_CFG_GET_INFOSTR,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, strinfo, &len);
if (ret < 0) return ret; /* TODO: positive error codes from respstat */
if (len >= sizeof(strinfo)) return -EMSGSIZE;
strinfo[len] = 0; /*strinfo[64] = 0;*/
dev_info(dev, HARDWARE_NAME " '%s'\n", strinfo);
/* cur mode */
len = sizeof(curmode);
ret = dmj_xfer_internal(dmj, DMJ_CMD_CFG_GET_CUR_MODE,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, &curmode, &len);
if (ret < 0) return ret;
if (len < sizeof(curmode)) return -EREMOTEIO;
if (len > sizeof(curmode)) return -EMSGSIZE;
dmj->dmj_mode = curmode;
/* map of available modes */
len = sizeof(modes);
ret = dmj_xfer_internal(dmj, DMJ_CMD_CFG_GET_MODES,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, &modes, &len);
if (ret < 0) return ret;
if (len < sizeof(modes)) return -EREMOTEIO;
if (len > sizeof(modes)) return -EMSGSIZE;
for (i = 1; i < 16; ++i) { /* build the string, uglily */
if (le16_to_cpu(modes) & (1<<i)) {
if (i < 0xa) modeinfo[i - 1] = '0'+i-0;
else modeinfo[i - 1] = 'a'+i-0xa;
} else modeinfo[i - 1] = '-';
}
modeinfo[15] = 0;
dev_dbg(dev, "available modes: x%s, currently %d\n", modeinfo, (int)curmode);
/* for each available mode print name, version and features */
for (i = 1; i < 16; ++i) {
if (!(le16_to_cpu(modes) & (1<<i))) continue; /* not available */
/* name */
len = sizeof(strinfo)-1;
ret = dmj_xfer_internal(dmj, (i<<4) | DMJ_CMD_MODE_GET_NAME,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, strinfo, &len);
if (ret < 0) return ret;
if (len >= sizeof(strinfo)) return -EMSGSIZE;
strinfo[len] = 0; /*strinfo[64] = 0;*/
/* version */
len = sizeof(mversion);
ret = dmj_xfer_internal(dmj, (i<<4) | DMJ_CMD_MODE_GET_VERSION,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, &mversion, &len);
if (ret < 0) return ret;
if (len < sizeof(mversion)) return -EREMOTEIO;
if (len > sizeof(mversion)) return -EMSGSIZE;
/* features */
len = sizeof(features);
ret = dmj_xfer_internal(dmj, (i<<4) | DMJ_CMD_MODE_GET_FEATURES,
DMJ_XFER_FLAGS_PARSE_RESP, NULL, 0, &features, &len);
if (ret < 0) return ret;
if (len < sizeof(features)) return -EREMOTEIO;
if (len > sizeof(features)) return -EMSGSIZE;
if (i == 1) dmj->dmj_m1feature = features;
for (j = 0; j < 8; ++j) {
if (features & (1<<j)) modeinfo[j] = '0'+j;
else modeinfo[j] = '-';
}
modeinfo[8] = 0;
dev_dbg(dev, "Mode %d: '%s' version 0x%04x, features: %s\n",
i, strinfo, mversion, modeinfo);
}
return 0;
}
static int dmj_hw_init(struct dmj_dev *dmj)
{
int ret;
ret = dmj_check_hw(dmj);
if (ret < 0) return ret;
return dmj_print_info(dmj);
}
/* MFD stuff */
static const struct mfd_cell dmj_mfd_devs[] = {
{ .name = "dmj-char" },
};
/* USB device control */
static int dmj_probe(struct usb_interface *itf, const struct usb_device_id *usb_id)
{
struct usb_host_interface *hostitf = itf->cur_altsetting;
struct usb_endpoint_descriptor *epin, *epout;
struct device *dev = &itf->dev;
struct dmj_dev *dmj;
int ret;
/*dev_dbg(dev, "dmj probe itfn=%d nendp=%d\n",
hostitf->desc.bInterfaceNumber, hostitf->desc.bNumEndpoints);*/
if (hostitf->desc.bInterfaceNumber != 0 || hostitf->desc.bNumEndpoints < 2)
return -ENODEV;
/* TODO: query endpoints in a better way */
epout = &hostitf->endpoint[DMJ_VND_CFG_EP_OUT].desc;
epin = &hostitf->endpoint[DMJ_VND_CFG_EP_IN].desc;
/*dev_dbg(dev, "epout addr=0x%02x, epin addr=0x%02x\n",
epout->bEndpointAddress, epin->bEndpointAddress);*/
if (!usb_endpoint_is_bulk_out(epout)) { dev_warn(dev, "aaa1\n"); return -ENODEV; }
if (!usb_endpoint_is_bulk_in(epin)) { dev_warn(dev, "aaa1\n"); return -ENODEV; }
dmj = kzalloc(sizeof(*dmj), GFP_KERNEL);
if (!dmj) return -ENOMEM;
/*dev_dbg(dev, "hi there!\n");*/
dmj->ep_out = epout->bEndpointAddress;
dmj->ep_in = epin->bEndpointAddress;
dmj->usb_dev = usb_get_dev(interface_to_usbdev(itf));
dmj->interface = itf;
usb_set_intfdata(itf, dmj);
spin_lock_init(&dmj->disconnect_lock);
ret = dmj_hw_init(dmj);
if (ret < 0) {
dev_err(dev, "failed to initialize hardware\n");
goto out_free;
}
if (dmj->dmj_mode == 1) {
ret = mfd_add_hotplug_devices(dev, dmj_mfd_devs, ARRAY_SIZE(dmj_mfd_devs));
if (ret) {
dev_err(dev, "failed to add MFD devices\n");
goto out_free;
}
if (dmj->dmj_mode & DMJ_FEATURE_MODE1_SPI) {
// TODO: add SPI MFD
}
if (dmj->dmj_mode & DMJ_FEATURE_MODE1_I2C) {
// TODO: add I2C MFD
}
if (dmj->dmj_mode & DMJ_FEATURE_MODE1_TEMPSENSOR) {
// TODO: add tempsensor MFD
}
}
return 0;
out_free:
usb_put_dev(dmj->usb_dev);
kfree(dmj);
return ret;
}
static void dmj_disconnect(struct usb_interface *itf)
{
struct dmj_dev *dmj = usb_get_intfdata(itf);
spin_lock(&dmj->disconnect_lock);
dmj->disconnect = true;
spin_unlock(&dmj->disconnect_lock);
mfd_remove_devices(&itf->dev);
usb_put_dev(dmj->usb_dev);
kfree(dmj);
}
static int dmj_suspend(struct usb_interface *itf, pm_message_t message)
{
struct dmj_dev *dmj = usb_get_intfdata(itf);
(void)message;
spin_lock(&dmj->disconnect_lock);
dmj->disconnect = true;
spin_unlock(&dmj->disconnect_lock);
return 0;
}
static int dmj_resume(struct usb_interface *itf)
{
struct dmj_dev *dmj = usb_get_intfdata(itf);
dmj->disconnect = false;
return 0;
}
static const struct usb_device_id dmj_table[] = {
{ USB_DEVICE(0xcafe, 0x1312) },
{ }
};
MODULE_DEVICE_TABLE(usb, dmj_table);
static struct usb_driver dmj_driver = {
.name = HARDWARE_NAME_SYMBOLIC,
.probe = dmj_probe,
.disconnect = dmj_disconnect,
.id_table = dmj_table,
.suspend = dmj_suspend,
.resume = dmj_resume,
};
module_usb_driver(dmj_driver);
MODULE_AUTHOR("sys64738 <sys64738@disroot.org>");
MODULE_AUTHOR("haskal <haskal@awoo.systems>");
MODULE_DESCRIPTION("Core driver for the " HARDWARE_NAME " USB multitool");
MODULE_LICENSE("GPL v2");
MODULE_SOFTDEP("post: dmj-char");

55
host/modules/dmj.h Normal file
View File

@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __LINUX_USB_DAPPERMIMEJTAG_H
#define __LINUX_USB_DAPPERMIMEJTAG_H
#define DMJ_USB_CFG_PROTO_VER 0x0010
#define DMJ_RESP_STAT_OK 0x00
#define DMJ_RESP_STAT_ILLCMD 0x01
#define DMJ_RESP_STAT_BADMODE 0x02
#define DMJ_RESP_STAT_NOSUCHMODE 0x03
#define DMJ_RESP_STAT_BADARG 0x04
#define DMJ_CMD_CFG_GET_VERSION 0x00
#define DMJ_CMD_CFG_GET_MODES 0x01
#define DMJ_CMD_CFG_GET_CUR_MODE 0x02
#define DMJ_CMD_CFG_SET_CUR_MODE 0x03
#define DMJ_CMD_CFG_GET_INFOSTR 0x04
#define DMJ_CMD_MODE_GET_NAME 0x00
#define DMJ_CMD_MODE_GET_VERSION 0x01
#define DMJ_CMD_MODE_GET_FEATURES 0x02
#define DMJ_CMD_MODE1_SPI 0x13
#define DMJ_CMD_MODE1_I2C 0x14
#define DMJ_CMD_MODE1_TEMPSENSOR 0x15
#define DMJ_FEATURE_MODE1_UART (1<<0)
#define DMJ_FEATURE_MODE1_CMSISDAP (1<<1)
#define DMJ_FEATURE_MODE1_SPI (1<<2)
#define DMJ_FEATURE_MODE1_I2C (1<<3)
#define DMJ_FEATURE_MODE1_TEMPSENSOR (1<<4)
struct dmj_platform_data {
uint8_t port;
};
#define DMJ_XFER_FLAGS_PARSE_RESP (1<<0)
#define DMJ_XFER_FLAGS_FILL_RECVBUF (1<<1)
int dmj_transfer(struct platform_device *pdev, int cmd, int recvflags,
const void *wbuf, int wbufsize, void *rbuf, int *rbufsize);
inline static int dmj_read(struct platform_device *pdev, int recvflags,
void *rbuf, int *rbufsize)
{
return dmj_transfer(pdev, -1, recvflags, NULL, 0, rbuf, rbufsize);
}
inline static int dmj_write(struct platform_device *pdev, int cmd,
const void *wbuf, int wbufsize)
{
return dmj_transfer(pdev, cmd, DMJ_XFER_FLAGS_PARSE_RESP, wbuf, wbufsize, NULL, NULL);
}
#endif

View File

@ -29,7 +29,7 @@
/*#define MODE_ENABLE_I2CTINYUSB*/
enum m_default_cmds {
mdef_cmd_spi = mode_cmd__specific,
mdef_cmd_spi = mode_cmd__specific | 0x10,
mdef_cmd_i2c,
mdef_cmd_tempsense
};

View File

@ -189,15 +189,19 @@ static void handle_cmd(uint8_t cmd, int ud, uint8_t (*read_byte)(void),
break;
case S_CMD_SPIOP: case S_CMD_SPI_READ: case S_CMD_SPI_WRITE: {
uint32_t slen, rlen;
uint32_t slen = 0, rlen = 0;
// clang-format off
slen = (uint32_t)read_byte();
slen |= (uint32_t)read_byte() << 8;
slen |= (uint32_t)read_byte() << 16;
rlen = (uint32_t)read_byte();
rlen |= (uint32_t)read_byte() << 8;
rlen |= (uint32_t)read_byte() << 16;
if (cmd == S_CMD_SPIOP || cmd == S_CMD_SPI_WRITE) {
slen = (uint32_t)read_byte();
slen |= (uint32_t)read_byte() << 8;
slen |= (uint32_t)read_byte() << 16;
}
if (cmd == S_CMD_SPIOP || cmd == S_CMD_SPI_READ) {
rlen = (uint32_t)read_byte();
rlen |= (uint32_t)read_byte() << 8;
rlen |= (uint32_t)read_byte() << 16;
}
// clang-format on
if (writehdr)

View File

@ -212,7 +212,8 @@ void i2ctu_bulk_cmd(void) {
uint16_t us;
uint32_t func, freq;
switch (vnd_cfg_read_byte()) {
uint8_t cmdb = vnd_cfg_read_byte();
switch (cmdb) {
case ITU_CMD_ECHO:
txbuf[0] = vnd_cfg_read_byte();
vnd_cfg_write_resp(cfg_resp_ok, 1, txbuf);
@ -247,7 +248,7 @@ void i2ctu_bulk_cmd(void) {
case ITU_CMD_I2C_IO_END:
case ITU_CMD_I2C_IO_BEGINEND: {
struct itu_cmd cmd;
cmd.cmd = vnd_cfg_read_byte();
cmd.cmd = cmdb;
cmd.flags = (uint16_t)vnd_cfg_read_byte();
cmd.flags |= (uint16_t)vnd_cfg_read_byte() << 8;
cmd.addr = (uint16_t)vnd_cfg_read_byte();

View File

@ -5,7 +5,8 @@
#include <stdint.h>
/* the configuration vendor interface must always be the first vendor itf. */
/* the configuration vendor interface must always be the first vendor itf, and
* have endpoint number 1. */
#define VND_CFG_PROTO_VER 0x0010
@ -45,38 +46,5 @@ void vnd_cfg_write_flush(void);
void vnd_cfg_write_byte(uint8_t v);
void vnd_cfg_write_resp(enum cfg_resp stat, uint32_t len, const void* data);
/*
* wire protocol:
* host sends messages, device sends replies. the device never initiates a xfer
*
* the first byte of a command is the combination of the mode it is meant for
* in the high nybble, and the command number itself in the low nybble. optional
* extra command bytes may follow, depending on the command itself.
* a high nybble is 0 signifies a general configuration command, not meant for
* a particular mode
*
* a response consists of a response status byte (enum cfg_resp), followed by
* a 7- or 15-bit VLQ int (little-endian) for the payload length, followed by
* the payload itself.
*
* general commands:
* * get vesion (0x00): returns a payload of 2 bytes with version data. should
* currently be 0x10 0x00 (00.10h)
* * get modes (0x01): returns 2 bytes with a bitmap containing all supported
* modes (bit 0 is for general cfg and must always be 1)
* * get cur mode (0x02): returns a single byte containing the current mode number
* * set cur mode (0x03): sets the current mode. one extra request byte, no
* response payload
* * get info string (0x04): get a string containing human-readable info about
* the device. for display only
*
* common mode commands:
* * get name (0x00): returns a name or other descriptive string in the payload.
* for display only
* * get version (0x01): returns a 2-byte version number in the payload
* * get_features (0x02): gets a bitmap of supported features. length and meaning
* of the bits depends on the mode
*/
#endif