Skip to content

Instantly share code, notes, and snippets.

@gretel
Last active November 21, 2024 01:10
Show Gist options
  • Save gretel/965f183a40c2115bf998b1db4dc2e4d9 to your computer and use it in GitHub Desktop.
Save gretel/965f183a40c2115bf998b1db4dc2e4d9 to your computer and use it in GitHub Desktop.
control 'cm108' based radio interfaces on openbsd (inspired by https://github.com/twilly/cm108)
/*
* CM108/CM119 GPIO control for OpenBSD
* For PTT control of USB audio devices via UHID driver
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbhid.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <err.h>
/* CM108 Constants */
#define CMEDIA_VID 0x0d8c
#define CM108_PID1_MIN 0x0008
#define CM108_PID1_MAX 0x000f
/* Report size for GPIO control */
#define REPORT_SIZE 4
#define DEBUG 1
static int
cm108_write(const char *device, int gpio_num, int state)
{
int fd;
if (DEBUG)
fprintf(stderr, "Opening %s...\n", device);
/* Open device */
fd = open(device, O_RDWR | O_NONBLOCK);
if (fd < 0) {
fprintf(stderr, "Cannot open %s: %s\n", device, strerror(errno));
return -1;
}
/* Get device info */
struct usb_device_info di;
if (ioctl(fd, USB_GET_DEVICEINFO, &di) != -1 && DEBUG) {
fprintf(stderr, "Device info:\n");
fprintf(stderr, " Vendor: 0x%04x\n", di.udi_vendorNo);
fprintf(stderr, " Product: 0x%04x\n", di.udi_productNo);
fprintf(stderr, " Release: 0x%04x\n", di.udi_releaseNo);
fprintf(stderr, " Class: 0x%02x\n", di.udi_class);
fprintf(stderr, " Product: %s\n", di.udi_product);
fprintf(stderr, " Vendor: %s\n", di.udi_vendor);
}
/* Get report size info */
struct usb_ctl_report_desc desc_info;
memset(&desc_info, 0, sizeof(desc_info));
if (ioctl(fd, USB_GET_REPORT_DESC, &desc_info) != -1 && DEBUG) {
fprintf(stderr, "Report descriptor size: %d\n", desc_info.ucrd_size);
}
/* Get report ID */
int report_id = 0;
if (ioctl(fd, USB_GET_REPORT_ID, &report_id) != -1 && DEBUG) {
fprintf(stderr, "Report ID: %d\n", report_id);
}
/* Prepare GPIO data */
unsigned char data[REPORT_SIZE] = {0};
data[0] = 0; /* Pad */
data[1] = 1 << (gpio_num - 1); /* GPIO direction (output) */
data[2] = state << (gpio_num - 1); /* GPIO value */
data[3] = 0; /* Pad */
if (DEBUG) {
fprintf(stderr, "Sending report data: ");
for(int i = 0; i < REPORT_SIZE; i++)
fprintf(stderr, "%02x ", data[i]);
fprintf(stderr, "\n");
}
/* Try direct write first */
ssize_t res = write(fd, data, REPORT_SIZE);
if (res < 0) {
fprintf(stderr, "Write failed: %s\n", strerror(errno));
/* Fall back to SET_REPORT */
struct usb_ctl_report ucr;
memset(&ucr, 0, sizeof(ucr));
ucr.ucr_report = UHID_OUTPUT_REPORT;
memcpy(&ucr.ucr_data, data, sizeof(data));
if (ioctl(fd, USB_SET_REPORT, &ucr) < 0) {
/* Try HID Get/Set Feature as last resort */
struct usb_ctl_report feature;
memset(&feature, 0, sizeof(feature));
feature.ucr_report = UHID_FEATURE_REPORT;
/* Try to get current state first */
if (ioctl(fd, USB_GET_REPORT, &feature) < 0) {
fprintf(stderr, "USB_GET_REPORT failed: %s\n", strerror(errno));
} else {
/* Update only our bits and write back */
feature.ucr_data[1] = data[1]; /* direction */
feature.ucr_data[2] = data[2]; /* value */
if (ioctl(fd, USB_SET_REPORT, &feature) < 0) {
fprintf(stderr, "USB_SET_REPORT failed: %s (errno=%d)\n",
strerror(errno), errno);
close(fd);
return -1;
}
}
}
}
if (DEBUG)
fprintf(stderr, "Report sent successfully\n");
close(fd);
return 0;
}
static void
list_devices(void)
{
char path[32];
struct stat st;
int fd;
printf("Scanning for USB devices:\n");
/* Check uhid devices */
for (int i = 0; i < 16; i++) {
snprintf(path, sizeof(path), "/dev/uhid%d", i);
if (stat(path, &st) == 0) {
printf(" %s exists (mode=%03o)\n", path, st.st_mode & 0777);
fd = open(path, O_RDWR | O_NONBLOCK);
if (fd != -1) {
struct usb_device_info di;
if (ioctl(fd, USB_GET_DEVICEINFO, &di) != -1) {
printf(" vid=0x%04x pid=0x%04x %s %s%s\n",
di.udi_vendorNo, di.udi_productNo,
di.udi_vendor, di.udi_product,
(di.udi_vendorNo == CMEDIA_VID &&
di.udi_productNo >= CM108_PID1_MIN &&
di.udi_productNo <= CM108_PID1_MAX) ? " [CM108]" : "");
}
close(fd);
} else {
printf(" cannot open: %s\n", strerror(errno));
}
}
}
}
void
usage(void)
{
fprintf(stderr, "usage: cm108 [-l] [-d device -p pin -s state]\n");
fprintf(stderr, " -l: list compatible devices\n");
fprintf(stderr, " -d device: device path (default: /dev/uhid0)\n");
fprintf(stderr, " -p pin: GPIO pin number (1-8)\n");
fprintf(stderr, " -s state: GPIO state (0 or 1)\n");
exit(1);
}
int
main(int argc, char *argv[])
{
char *device = "/dev/uhid0"; /* default to first device */
int ch, pin = 0, state = -1;
int list = 0;
/* Check if root */
if (geteuid() != 0)
errx(1, "must be run as superuser");
while ((ch = getopt(argc, argv, "ld:p:s:h")) != -1) {
switch (ch) {
case 'l':
list = 1;
break;
case 'd':
device = optarg;
break;
case 'p':
pin = atoi(optarg);
break;
case 's':
state = atoi(optarg);
break;
case 'h':
default:
usage();
}
}
if (list) {
list_devices();
return 0;
}
if (pin < 1 || pin > 8 || state < 0 || state > 1) {
usage();
}
return cm108_write(device, pin, state);
}
# CM108 GPIO control utility for OpenBSD
PROG= cm108
SRCS= cm108.c
MAN= # empty, no manpage yet
BINDIR= /usr/local/bin
BINOWN= root
BINGRP= bin
BINMODE= 555
CFLAGS+= -Wall -Wextra
LDADD=
DPADD=
.include <bsd.prog.mk>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment