1
0
mirror of https://github.com/djhackersdev/bemanitools.git synced 2024-12-01 01:27:18 +01:00

aciodrv: add PANB support (+ aciotest handler)

add aciodrv-proc module

explanation : PANB works differently from other acio devices ; you send
one "start auto input" command without expecting a reply, and then the
piano keeps spamming "recv poll" commands on the acio bus and never replies
to any other commands.. failure to process these messages quickly enough
will saturate the serial buffer and cause checksum errors after a while.

for this reason, aciodrv-proc module was added in order to create a
thread which manages these recv poll commands and keeps the latest
known button state in memory so that it can be retrieved easily.
This commit is contained in:
CrazyRedMachine 2021-05-01 23:07:18 +02:00
parent fb593df4a1
commit 4ea1397af2
15 changed files with 465 additions and 8 deletions

View File

@ -77,6 +77,7 @@ avsvers_64 := 1700 1603 1601 1509 1508
imps += avs avs-ea3 imps += avs avs-ea3
include src/main/aciodrv/Module.mk include src/main/aciodrv/Module.mk
include src/main/aciodrv-proc/Module.mk
include src/main/acioemu/Module.mk include src/main/acioemu/Module.mk
include src/main/aciomgr/Module.mk include src/main/aciomgr/Module.mk
include src/main/aciotest/Module.mk include src/main/aciotest/Module.mk

View File

@ -9,6 +9,7 @@
#include "acio/hdxs.h" #include "acio/hdxs.h"
#include "acio/icca.h" #include "acio/icca.h"
#include "acio/kfca.h" #include "acio/kfca.h"
#include "acio/panb.h"
#define AC_IO_SOF 0xAA #define AC_IO_SOF 0xAA
#define AC_IO_ESCAPE 0xFF #define AC_IO_ESCAPE 0xFF
@ -38,6 +39,7 @@ enum ac_io_node_type {
AC_IO_NODE_TYPE_KFCA = 0x09060000, AC_IO_NODE_TYPE_KFCA = 0x09060000,
AC_IO_NODE_TYPE_BI2A = 0x0d060000, AC_IO_NODE_TYPE_BI2A = 0x0d060000,
AC_IO_NODE_TYPE_RVOL = 0x09060001, AC_IO_NODE_TYPE_RVOL = 0x09060001,
AC_IO_NODE_TYPE_PANB = 0x090E0000,
}; };
#pragma pack(push, 1) #pragma pack(push, 1)
@ -78,6 +80,9 @@ struct ac_io_message {
struct ac_io_kfca_poll_in kfca_poll_in; struct ac_io_kfca_poll_in kfca_poll_in;
struct ac_io_kfca_poll_out kfca_poll_out; struct ac_io_kfca_poll_out kfca_poll_out;
struct ac_io_panb_poll_in panb_poll_in;
struct ac_io_panb_poll_out panb_poll_out;
struct ac_io_hdxs_output hdxs_output; struct ac_io_hdxs_output hdxs_output;
}; };
} cmd; } cmd;

42
src/main/acio/panb.h Normal file
View File

@ -0,0 +1,42 @@
#ifndef AC_IO_PANB_H
#define AC_IO_PANB_H
#include <stdint.h>
#define AC_IO_CMD_PANB_POLL_REPLY 0x0110
#define AC_IO_CMD_PANB_SEND_LAMP 0x0111
#define AC_IO_CMD_PANB_START_AUTO_INPUT 0x0115
#define AC_IO_PANB_NUM_NODES 4
#define AC_IO_PANB_MAX_KEYS (7*AC_IO_PANB_NUM_NODES)
#define AC_IO_PANB_MAX_KEYPAIRS (AC_IO_PANB_MAX_KEYS/2)
struct ac_io_panb_keypair {
uint8_t key2 : 4;
uint8_t key1 : 4;
};
#pragma pack(push, 1)
struct ac_io_panb_poll_in {
/* last received command sequence number (start_auto_input / send_lamp) */
uint8_t sub_seq1;
/* auto-increment sequence number for autopoll reports */
uint8_t sub_seq2;
struct ac_io_panb_keypair keypair[AC_IO_PANB_MAX_KEYPAIRS];
};
#pragma pack(pop)
struct ac_io_panb_color {
uint8_t red;
uint8_t green;
uint8_t blue;
};
#pragma pack(push, 1)
struct ac_io_panb_poll_out {
struct ac_io_panb_color key[AC_IO_PANB_MAX_KEYS];
};
#pragma pack(pop)
#endif

View File

@ -0,0 +1,7 @@
libs += aciodrv-proc
libs_aciodrv-proc := \
src_aciodrv-proc := \
panb.c \

View File

@ -0,0 +1,90 @@
#define LOG_MODULE "aciodrv-proc-panb"
#include <string.h>
#include "aciodrv/device.h"
#include "aciodrv/panb.h"
#include "util/thread.h"
#include "util/log.h"
static int auto_poll_proc(void *auto_poll_param);
static int auto_poll_threadid;
static CRITICAL_SECTION keypair_lock;
static struct ac_io_panb_keypair _keypair[AC_IO_PANB_MAX_KEYPAIRS];
static CRITICAL_SECTION auto_poll_stop_lock;
static bool auto_poll_stop;
static int auto_poll_proc(void * param)
{
struct aciodrv_device_ctx * device = (struct aciodrv_device_ctx *) param;
struct ac_io_panb_poll_in poll_in;
bool stop;
do {
aciodrv_panb_recv_poll(device, &poll_in);
EnterCriticalSection(&keypair_lock);
memcpy(_keypair, poll_in.keypair, AC_IO_PANB_MAX_KEYPAIRS);
LeaveCriticalSection(&keypair_lock);
EnterCriticalSection(&auto_poll_stop_lock);
stop = auto_poll_stop;
LeaveCriticalSection(&auto_poll_stop_lock);
} while (!stop);
return 0;
}
bool aciodrv_proc_panb_init(struct aciodrv_device_ctx *device)
{
log_assert(device);
if (!aciodrv_panb_start_auto_input(device, 0, AC_IO_PANB_NUM_NODES)) {
return false;
}
auto_poll_stop = false;
InitializeCriticalSection(&keypair_lock);
InitializeCriticalSection(&auto_poll_stop_lock);
auto_poll_threadid = thread_create(auto_poll_proc, (void *)device, 0x4000, 0);
return true;
}
bool aciodrv_proc_panb_get_state(uint8_t *button_state)
{
struct ac_io_panb_keypair keypair[AC_IO_PANB_MAX_KEYPAIRS];
EnterCriticalSection(&keypair_lock);
memcpy(keypair, _keypair, AC_IO_PANB_MAX_KEYPAIRS);
LeaveCriticalSection(&keypair_lock);
/* splice the keypairs into separate button values */
for (int i=0; i<AC_IO_PANB_MAX_KEYPAIRS; i++) {
uint8_t but1 = keypair[i].key1;
uint8_t but2 = keypair[i].key2;
button_state[2*i] = but1;
button_state[2*i+1] = but2;
}
return true;
}
void aciodrv_proc_panb_fini(struct aciodrv_device_ctx *device)
{
EnterCriticalSection(&auto_poll_stop_lock);
auto_poll_stop = true;
LeaveCriticalSection(&auto_poll_stop_lock);
thread_join(auto_poll_threadid, NULL);
thread_destroy(auto_poll_threadid);
DeleteCriticalSection(&keypair_lock);
DeleteCriticalSection(&auto_poll_stop_lock);
/* reset is the only way to disable the auto polling on device side */
aciodrv_device_reset(device);
}

View File

@ -0,0 +1,32 @@
#ifndef ACIODRV_PROC_PANB_H
#define ACIODRV_PROC_PANB_H
#include "aciodrv/panb.h"
/**
* Initialize a PANB device. This will take care of setting up the auto-poll and processing
* the poll messages in a separate thread (this is necessary as failing to process messages
* fast enough will cause the device to malfunction).
*
* @param device Context of opened device
* @return True if successful, false on error.
* @note This function spawns a thread. Caller must call aciodrv_proc_panb_fini to properly
* terminate.
*/
bool aciodrv_proc_panb_init(struct aciodrv_device_ctx *device);
/**
* Retrieve latest known button state from the PANB device.
*
* @param button_state 28 cell array to store button state
* (mandatory, upon calling the array contains values between 0 to 15 indicating the keys velocity).
* @return True on success, false on error.
*/
bool aciodrv_proc_panb_get_state(uint8_t *button_state);
/**
* Properly terminate the thread and reset the device (this is the only way to stop the auto polling).
*/
void aciodrv_proc_panb_fini(struct aciodrv_device_ctx *device);
#endif

View File

@ -7,5 +7,6 @@ src_aciodrv := \
h44b.c \ h44b.c \
icca.c \ icca.c \
kfca.c \ kfca.c \
panb.c \
port.c \ port.c \

View File

@ -23,7 +23,7 @@ struct aciodrv_device_ctx {
uint8_t node_count; uint8_t node_count;
}; };
static bool aciodrv_device_reset(struct aciodrv_device_ctx *device) bool aciodrv_device_reset(struct aciodrv_device_ctx *device)
{ {
uint8_t reset_seq[525] = {0}; uint8_t reset_seq[525] = {0};
@ -109,7 +109,7 @@ static bool aciodrv_device_send(struct aciodrv_device_ctx *device, const uint8_t
} }
#ifdef AC_IO_MSG_LOG #ifdef AC_IO_MSG_LOG
aciodrv_device_log_buffer("Send (1)", device, buffer, length); aciodrv_device_log_buffer(device, "Send (1)", buffer, length);
#endif #endif
send_buf[send_buf_pos++] = AC_IO_SOF; send_buf[send_buf_pos++] = AC_IO_SOF;
@ -135,7 +135,7 @@ static bool aciodrv_device_send(struct aciodrv_device_ctx *device, const uint8_t
} }
#ifdef AC_IO_MSG_LOG #ifdef AC_IO_MSG_LOG
aciodrv_device_log_buffer("Send (2)", device, send_buf, send_buf_pos); aciodrv_device_log_buffer(device, "Send (2)", send_buf, send_buf_pos);
#endif #endif
if (aciodrv_port_write(device->fd, send_buf, send_buf_pos) != send_buf_pos) { if (aciodrv_port_write(device->fd, send_buf, send_buf_pos) != send_buf_pos) {
@ -209,7 +209,7 @@ static int aciodrv_device_receive(struct aciodrv_device_ctx *device, uint8_t *bu
} }
#ifdef AC_IO_MSG_LOG #ifdef AC_IO_MSG_LOG
aciodrv_device_log_buffer("Recv (1)", device, recv_buf, recv_size); aciodrv_device_log_buffer(device, "Recv (1)", recv_buf, recv_size);
log_warning("Expected %d got %d", max_resp_size - 6, recv_buf[4]); log_warning("Expected %d got %d", max_resp_size - 6, recv_buf[4]);
#endif #endif
@ -226,7 +226,7 @@ static int aciodrv_device_receive(struct aciodrv_device_ctx *device, uint8_t *bu
result_size = recv_size - 1; result_size = recv_size - 1;
#ifdef AC_IO_MSG_LOG #ifdef AC_IO_MSG_LOG
aciodrv_device_log_buffer("Recv (2)", device, buffer, result_size); aciodrv_device_log_buffer(device, "Recv (2)", buffer, result_size);
#endif #endif
if (checksum != recv_buf[recv_size - 1]) { if (checksum != recv_buf[recv_size - 1]) {
@ -402,7 +402,7 @@ const struct aciodrv_device_node_version *aciodrv_device_get_node_product_versio
return &device->node_versions[node_id]; return &device->node_versions[node_id];
} }
bool aciodrv_send_and_recv(struct aciodrv_device_ctx *device, struct ac_io_message *msg, int max_resp_size) bool aciodrv_send(struct aciodrv_device_ctx *device, struct ac_io_message *msg)
{ {
msg->cmd.seq_no = device->msg_counter++; msg->cmd.seq_no = device->msg_counter++;
int send_size = offsetof(struct ac_io_message, cmd.raw) + msg->cmd.nbytes; int send_size = offsetof(struct ac_io_message, cmd.raw) + msg->cmd.nbytes;
@ -418,15 +418,31 @@ bool aciodrv_send_and_recv(struct aciodrv_device_ctx *device, struct ac_io_messa
if (aciodrv_device_send(device, (uint8_t *) msg, send_size) <= 0) { if (aciodrv_device_send(device, (uint8_t *) msg, send_size) <= 0) {
return false; return false;
} }
return true;
}
uint16_t req_code = msg->cmd.code; bool aciodrv_recv(struct aciodrv_device_ctx *device, struct ac_io_message *msg, int max_resp_size)
{
#ifdef AC_IO_MSG_LOG #ifdef AC_IO_MSG_LOG
log_info("[%p] Beginning recv: (%d b)", device->fd, max_resp_size); log_info("[%p] Beginning recv: (%d b)", device->fd, max_resp_size);
#endif #endif
if (aciodrv_device_receive(device, (uint8_t *) msg, max_resp_size) <= 0) { if (aciodrv_device_receive(device, (uint8_t *) msg, max_resp_size) <= 0) {
return false; return false;
} }
return true;
}
bool aciodrv_send_and_recv(struct aciodrv_device_ctx *device, struct ac_io_message *msg, int max_resp_size)
{
if (!aciodrv_send(device, msg)) {
return false;
}
uint16_t req_code = msg->cmd.code;
if (!aciodrv_recv(device, msg, max_resp_size)) {
return false;
}
if (req_code != msg->cmd.code) { if (req_code != msg->cmd.code) {
log_warning( log_warning(

View File

@ -74,6 +74,35 @@ const struct aciodrv_device_node_version *aciodrv_device_get_node_product_versio
*/ */
bool aciodrv_send_and_recv(struct aciodrv_device_ctx *device, struct ac_io_message *msg, int max_resp_size); bool aciodrv_send_and_recv(struct aciodrv_device_ctx *device, struct ac_io_message *msg, int max_resp_size);
/**
* Send a message to the ACIO bus.
*
* @param device Context of opened device
* @param msg Msg to send to the bus.
* @return True on success, false on error.
* @note Prefer the use of aciodrv_send_and_recv when possible. This is for commands which don't trigger a reply.
*/
bool aciodrv_send(struct aciodrv_device_ctx *device, struct ac_io_message *msg);
/**
* Read a message from the ACIO bus.
*
* @param device Context of opened device
* @param msg Msg to send to the bus. Make sure that the buffer
* is big enough to receive the response.
* @return True on success, false on error.
* @note Prefer the use of aciodrv_send_and_recv when possible. This is for unsollicited incoming messages.
*/
bool aciodrv_recv(struct aciodrv_device_ctx *device, struct ac_io_message *msg, int max_resp_size);
/**
* Reset an opened device.
*
* @param device Context of opened device
* @return Total num of nodes enumerated on the ACIO device.
*/
bool aciodrv_device_reset(struct aciodrv_device_ctx *device);
/** /**
* Close the previously opened ACIO device. * Close the previously opened ACIO device.
* *

80
src/main/aciodrv/panb.c Normal file
View File

@ -0,0 +1,80 @@
#define LOG_MODULE "aciodrv-panb"
#include <string.h>
#include "aciodrv/device.h"
#include "aciodrv/panb.h"
#include "util/thread.h"
#include "util/log.h"
bool aciodrv_panb_start_auto_input(struct aciodrv_device_ctx *device, uint8_t node_id, uint8_t node_count)
{
log_assert(device);
struct ac_io_message msg;
/* only the first node is handling the commands */
if (node_id != 0) {
return true;
}
msg.addr = node_id + 1;
msg.cmd.code = ac_io_u16(AC_IO_CMD_PANB_START_AUTO_INPUT);
msg.cmd.nbytes = 1;
msg.cmd.count = node_count;
if (!aciodrv_send(device, &msg)) {
log_warning("Starting auto input failed");
return false;
}
log_info("Started auto input for %d nodes", node_count);
return true;
}
bool aciodrv_panb_recv_poll(struct aciodrv_device_ctx *device, struct ac_io_panb_poll_in *poll_in)
{
log_assert(device);
struct ac_io_message msg;
struct ac_io_panb_poll_in *poll_res = (struct ac_io_panb_poll_in *) &msg.cmd.raw;
msg.cmd.code = ac_io_u16(AC_IO_CMD_PANB_POLL_REPLY);
msg.cmd.nbytes = sizeof(struct ac_io_panb_poll_in);
if (!aciodrv_recv(device,
&msg, offsetof(struct ac_io_message, cmd.raw) + msg.cmd.nbytes + 1)) {
log_warning("Getting state failed");
return false;
}
if (poll_in != NULL){
memcpy(poll_in, poll_res, sizeof(struct ac_io_panb_poll_in));
}
return true;
}
bool aciodrv_panb_send_lamp(struct aciodrv_device_ctx *device,
uint8_t node_id, struct ac_io_panb_poll_out *state)
{
log_assert(device);
struct ac_io_message msg;
/* only the first node is handling the commands */
if (node_id != 0) {
return true;
}
msg.addr = node_id + 1;
msg.cmd.code = ac_io_u16(AC_IO_CMD_PANB_SEND_LAMP);
msg.cmd.nbytes = 0x54;
memcpy(&msg.cmd.raw, state, sizeof(struct ac_io_panb_poll_out));
if (!aciodrv_send(device, &msg)) {
log_warning("Sending lamp state failed");
return false;
}
return true;
}

56
src/main/aciodrv/panb.h Normal file
View File

@ -0,0 +1,56 @@
#ifndef ACIODRV_PANB_H
#define ACIODRV_PANB_H
#include "acio/panb.h"
/**
* Send the AC_IO_CMD_PANB_START_AUTO_INPUT command on a PANB device.
*
* @param device Context of opened device.
* @param node_id node_id Id of the node to query (0 based).
* @param node_count The number of nodes to poll.
* @return True if successful, false on error.
* @note node_id should be 0, PANB internally manages the other nodes.
* @note Upon calling, the device will stop replying to commands and just keep sending
* AC_IO_CMD_PANB_POLL_REPLY messages indefinitely. Failure to retrieve them fast enough
* will cause the device to malfunction. It is thus advised to make use of the aciodrv-proc
* module to spawn a thread that will handle these messages and provide an easy access to the
* latest input state.
* @note This module is supposed to be used in combination with the common
* device driver foundation.
* @see driver.h
*/
bool aciodrv_panb_start_auto_input(struct aciodrv_device_ctx *device, uint8_t node_id, uint8_t node_count);
/**
* Retrieve a AC_IO_CMD_PANB_POLL_REPLY message from a PANB device. This assumes that there
* is such message incoming (ie. that start_auto_input has been called prior).
*
* @param device Context of opened device.
* @param poll_in Buffer to hold the received message, or NULL.
* @return True if successful, false on error.
* @note node_id should be 0, PANB internally manages the other nodes.
* @note Failure to retrieve the incoming messages fast enough will cause the device to malfunction.
* It is thus advised to make use of the aciodrv-proc module to spawn a thread that will handle these
* messages and provide an easy access to the latest input state.
* @note This module is supposed to be used in combination with the common
* device driver foundation.
* @see driver.h
*/
bool aciodrv_panb_recv_poll(struct aciodrv_device_ctx *device, struct ac_io_panb_poll_in *poll_in);
/**
* Light lamps on a PANB device.
*
* @param node_id Id of the node to query (0 based).
* @param state Pointer to a lamp state struct
* (mandatory).
* @return True on success, false on error.
* @note node_id should be 0, PANB internally manages the other nodes.
* @note This module is supposed to be used in combination with the common
* device driver foundation.
* @see driver.h
*/
bool aciodrv_panb_send_lamp(struct aciodrv_device_ctx *device, uint8_t node_id, struct ac_io_panb_poll_out *state);
#endif

View File

@ -3,10 +3,12 @@ exes += aciotest
libs_aciotest := \ libs_aciotest := \
bio2drv \ bio2drv \
aciodrv \ aciodrv \
aciodrv-proc \
util \ util \
src_aciotest := \ src_aciotest := \
icca.c \ icca.c \
kfca.c \ kfca.c \
bi2a-sdvx.c \ bi2a-sdvx.c \
panb.c \
main.c \ main.c \

View File

@ -10,6 +10,7 @@
#include "aciotest/handler.h" #include "aciotest/handler.h"
#include "aciotest/icca.h" #include "aciotest/icca.h"
#include "aciotest/kfca.h" #include "aciotest/kfca.h"
#include "aciotest/panb.h"
#include "util/log.h" #include "util/log.h"
@ -37,6 +38,13 @@ static bool aciotest_assign_handler(
return true; return true;
} }
if (!memcmp(product, "PANB", 4)) {
handler->init = aciotest_panb_handler_init;
handler->update = aciotest_panb_handler_update;
return true;
}
if (!memcmp(product, "BI2A", 4)) { if (!memcmp(product, "BI2A", 4)) {
if (bi2a_mode == 0) { if (bi2a_mode == 0) {
handler->init = aciotest_bi2a_sdvx_handler_init; handler->init = aciotest_bi2a_sdvx_handler_init;

76
src/main/aciotest/panb.c Normal file
View File

@ -0,0 +1,76 @@
#include "aciotest/panb.h"
#include <stdio.h>
#include <stdlib.h>
#include "aciodrv/panb.h"
#include "aciodrv-proc/panb.h"
struct panb_handler_ctx {
bool running;
};
bool aciotest_panb_handler_init(struct aciodrv_device_ctx *device, uint8_t node_id, void **ctx)
{
if (node_id != 0) {
return true;
}
*ctx = malloc(sizeof(struct panb_handler_ctx));
struct panb_handler_ctx *panb_ctx = (struct panb_handler_ctx*)*ctx;
panb_ctx->running = true;
return aciodrv_proc_panb_init(device);
}
bool aciotest_panb_handler_update(struct aciodrv_device_ctx *device, uint8_t node_id, void *ctx)
{
uint8_t button[AC_IO_PANB_MAX_KEYS];
struct ac_io_panb_poll_out state;
struct panb_handler_ctx *panb_ctx = (struct panb_handler_ctx *) ctx;
if (node_id != 0) {
return true;
}
if (!panb_ctx->running) {
printf(">>> PANB:\nDevice has been closed. Press Ctrl+C to exit.");
return true;
}
if (!aciodrv_proc_panb_get_state(button)) {
return false;
}
printf(">>> PANB:\nPress first and last keys to close device\n");
for (int i=0; i<AC_IO_PANB_MAX_KEYS; i++) {
printf("%01X ", button[i]);
}
printf("\n");
/* I added a key combo to properly close the module.
* Leaving the PANB device in autopolling state without ever
* reading packets from the serial interface eventually
* makes it fall into a state where it doesn't send anything anymore
* and won't even respond to a reset, thus requiring a power cycle.
*/
if (button[0] && button[AC_IO_PANB_MAX_KEYS-1]) {
aciodrv_proc_panb_fini(device);
panb_ctx->running = false;
return true;
}
/* light the panel */
for (int i=0; i<AC_IO_PANB_MAX_KEYS; i++) {
state.key[i].green = (button[i])? 0x7F : 0;
state.key[i].red = 0;
state.key[i].blue = (button[i])? 0x23 : 0;
}
if (!aciodrv_panb_send_lamp(device, node_id, &state)) {
return false;
}
return true;
}

12
src/main/aciotest/panb.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef ACIOTEST_PANB_H
#define ACIOTEST_PANB_H
#include <stdbool.h>
#include <stdint.h>
#include "aciodrv/device.h"
bool aciotest_panb_handler_init(struct aciodrv_device_ctx *device, uint8_t node_id, void **ctx);
bool aciotest_panb_handler_update(struct aciodrv_device_ctx *device, uint8_t node_id, void *ctx);
#endif