Some additional info and a hacky workaround along with suggestions for a
proper fix,
Problem still exists in linux-image-6.12.73+deb13-amd64.
I wrote a patch to add the model detection logic from wacom_serial4 into
inputattach's wacom_iv_init() function.
(joystick-1.8.1/utils/inputattach.c:L637.) Adapting it to use the
write() call instead of serio_write(), and to output the result as a
printf() to the console. I then ran that modified version of inputattach
on my bare metal host machine. This resulted in the correct tablet model
being detected and written to the console by inputattach. From that
we've revealed two things:
1) The kernel's command definitions are correct for this device. (Wacom
Digitizer II in my case)
2) Bare metal usermode can communicate with the device just fine, but
the driver in kernel mode does _not_.
To confirm that, I wrote a patch for wacom_serial4 to add some
additional dev_info() / dev_err() / dev_dbg() calls into it's error
codepaths during wacom_connect(), wacom_setup(), and
wacom_send_and_wait(). So that we could see exactly where the error was
occurring. The result was that the -1 error was being returned by the
serio_write() call at drivers/input/tablet/wacom_serial4.c:L431. Which
was triggered by the first attempt at sending a command to the device
from the kernel module. (REQUEST_MODEL_AND_ROM_VERSION
at drivers/input/tablet/wacom_serial4.c:L510.) According to the header
for that inline function, serio_write() can fail if the given serio
structure has a NULL write fp member, so I altered the patch to test for
that, but it revealed that the structure's write fp member was defined
at the time of the error.
The _actual_ failure is due to the Keyspan USB serial port driver being
written to by wacom_serial4, before it has had the chance to send the
urb data to the device via it's relevant *_outdat_callback() function.
(In my specific case the usa2x_outdat_callback() function.) If
wacom_serial4 calls serio_write() in rapid succession before the
relevant *_outdat_callback() function can be called, then serio_write()
will always return failure during subsequent calls when using that UART
until the *_outdat_callback() function completes.
Unfortunately, there is seemingly no mechanism for serio to report when
the UART is busy waiting for an output to occur. I.e. No completion
object to wait on nor event to look for. Nor is there seemingly any
documentation about what the expectations of returning from
serio_write() makes of the underlying UART driver. I.e. Should the
underlying UART driver be ready to accept more data from the caller once
serio_write() returns? Or is it the responsibility of the caller to
resend the data if the underlying UART driver is still busy with
servicing a previous call to serio_write() that has returned to the
caller? The use of similar preexisting code elsewhere in the kernel to
wacom_serial4's call to serio_write() suggests the former, or at the
very least, I would assume any such similar code used in conjunction
with a Keyspan USB serial adapter to have similar issues as wacom_serial4.
As an additional setback for a proper fix, the Wacom Digitizer II with
ROM version 1.4, (the only device I have to test), doesn't echo back
sent bytes to the host. So we cannot use the interrupt handler as a
workaround for the lack of a write sync mechanism in the kernel. As the
handler is not invoked when writing individual bytes to the tablet
during the wacom_send() call.
Due to the above limitations, the attached patch for wacom_serial4 is a
hack if there's ever been one. A simple delay introduced into the
wacom_serial4 driver during sending to help prevent failures while
sending the initial detection and setup cmds. (Along with some extra
logging and clean up to make it easier to detect problems in the
future.) This was enough to get the device to work on my Debian 13
system, and allowed me to use GIMP with my Digitizer II successfully.
A proper fix would need official documentation from the kernel
developers on what the requirements of calls to serio_write() are on
UART drivers, and then either a write sync primitive for users of serio
to wait on after calls to serio_write(), or some official extension of
serio's API that will flush the underlying UART driver's write callback
so it's ready for new data immediately upon return to the caller. (Maybe
something like "serio_write_and_flush()"?)
Have a good Day!
-Patrick Hibbs
--- linux-6.12.73/drivers/input/tablet/wacom_serial4.c 2025-12-18 07:55:23.000000000 -0500
+++ linux-6.12.73/drivers/input/tablet/wacom_serial4.c 2026-03-17 00:47:57.295562707 -0400
@@ -108,12 +108,15 @@
#include <linux/serio.h>
#include <linux/slab.h>
#include <linux/string.h>
+#include <linux/delay.h>
MODULE_AUTHOR("Julian Squires <[email protected]>, Hans de Goede <[email protected]>");
MODULE_DESCRIPTION("Wacom protocol 4 serial tablet driver");
MODULE_LICENSE("GPL");
-#define REQUEST_MODEL_AND_ROM_VERSION "~#"
+#define MY_MOD_NAME "wacom_serial4"
+
+#define REQUEST_MODEL_AND_ROM_VERSION "\r~#"
#define REQUEST_MAX_COORDINATES "~C\r"
#define REQUEST_CONFIGURATION_STRING "~R\r"
#define REQUEST_RESET_TO_PROTOCOL_IV "\r#"
@@ -242,6 +245,12 @@
break;
case MODEL_ARTPAD_II:
+ wacom->dev->name = "Wacom ArtPad II";
+ wacom->dev->id.version = MODEL_ARTPAD_II;
+ if (major_v == 1 && minor_v <= 2)
+ wacom->extra_z_bits = 0; /* UNTESTED */
+ break;
+
case MODEL_DIGITIZER_II:
wacom->dev->name = "Wacom Digitizer II";
wacom->dev->id.version = MODEL_DIGITIZER_II;
@@ -300,7 +309,12 @@
case 'C':
wacom_handle_coordinates_response(wacom);
break;
- }
+ default:
+ dev_err(&wacom->dev->dev,
+ "Wacom got an unknown response: %s\n", wacom->data);
+ wacom->result = -EIO;
+ break;
+ };
}
complete(&wacom->cmd_done);
@@ -374,6 +388,11 @@
{
struct wacom *wacom = serio_get_drvdata(serio);
+ if (wacom == NULL) {
+ pr_err(MY_MOD_NAME " %s - unable to get wacom structure!", __func__);
+ return IRQ_HANDLED;
+ }
+
if (data & 0x80)
wacom->idx = 0;
@@ -421,9 +440,48 @@
static int wacom_send(struct serio *serio, const u8 *command)
{
int err = 0;
+ unsigned int x, retry = 0;
- for (; !err && *command; command++)
- err = serio_write(serio, *command);
+ if (serio == NULL) {
+ pr_debug(MY_MOD_NAME " %s - %s", __func__, "serio structure invalid!");
+ return -1;
+ }
+
+ if (command == NULL || *command == '\0') {
+ dev_dbg(&serio->dev, MY_MOD_NAME " %s CMD invalid!",
+ __func__);
+ return -1;
+ }
+
+ for (; !err && *command; command++) {
+ for (retry = 0; (retry == 0 || (retry < 5 && err)); retry++) {
+ err = serio_write(serio, ((const u8)*command));
+ dev_dbg(&serio->dev, MY_MOD_NAME " %s - Attempted to send [0x%x] to device. Result: %d, Attempt: %d.",
+ __func__, ((const u8)*command), err, retry);
+ if (!err) {
+ /* Need to wait a few HZ. (Really need a way to flush the output buffer from serio,
+ or have the underlying UART driver be in a known ready for more data state...)
+
+ Some usb serial devices (Keyspan) seem to get indigestion
+ if we send another byte too soon after the previous one.
+
+ (In the Keyspan case, it won't accept another byte until after
+ it's *_outdat_callback() pushes the urb with the previously sent byte to the
+ device.)
+ */
+ fsleep(10 * HZ);
+ if ((UINT_MAX - x) > 0) {
+ x = x + 1;
+ }
+ } else {
+ /* Give a little more time for the underlying UART driver to catch up.... */
+ fsleep(5 * HZ);
+ }
+ }
+ }
+
+ dev_dbg(&serio->dev, MY_MOD_NAME " %s - Err code: %d. Total bytes sent: %d.",
+ __func__, err, x);
return err;
}
@@ -464,15 +522,30 @@
static int wacom_send_and_wait(struct wacom *wacom, struct serio *serio,
const u8 *cmd, const char *desc)
{
- int err;
- unsigned long u;
+ int err = 0;
+ unsigned long u = 0;
- wacom->expect = cmd[1];
+ if (wacom == NULL || wacom->dev == NULL) {
+ pr_debug(MY_MOD_NAME " %s wacom structure invalid!",
+ __func__);
+ return -1;
+ }
+
+ if (cmd == NULL || *cmd == '\0') {
+ dev_dbg(&wacom->dev->dev, "%s CMD invalid!",
+ __func__);
+ return -1;
+ }
+
+ wacom->expect = (cmd[0] == '\r') ? cmd[2] : cmd[1];
init_completion(&wacom->cmd_done);
err = wacom_send(serio, cmd);
- if (err)
+ if (err) {
+ dev_err(&wacom->dev->dev, "%s - unable to send [%s] CMD to device. Status: %d",
+ __func__, (desc != NULL ? desc : "data"), err);
return err;
+ }
u = wait_for_completion_timeout(&wacom->cmd_done, HZ);
if (u == 0) {
@@ -486,20 +559,20 @@
static int wacom_setup(struct wacom *wacom, struct serio *serio)
{
- int err;
+ int err = 0;
/* Note that setting the link speed is the job of inputattach.
* We assume that reset negotiation has already happened,
* here. */
err = wacom_send_and_wait(wacom, serio, REQUEST_MODEL_AND_ROM_VERSION,
- "model and version");
+ "get model and version");
if (err)
return err;
if (!(wacom->res_x && wacom->res_y)) {
err = wacom_send_and_wait(wacom, serio,
REQUEST_CONFIGURATION_STRING,
- "configuration string");
+ "get configuration string");
if (err)
return err;
}
@@ -507,7 +580,7 @@
if (!(wacom->max_x && wacom->max_y)) {
err = wacom_send_and_wait(wacom, serio,
REQUEST_MAX_COORDINATES,
- "coordinates string");
+ "get coordinates string");
if (err)
return err;
}
@@ -552,12 +625,18 @@
serio_set_drvdata(serio, wacom);
err = serio_open(serio, drv);
- if (err)
+ if (err) {
+ dev_err(&wacom->dev->dev, "%s - failed serio_open() with status: %d",
+ __func__, err);
goto free_device;
+ }
err = wacom_setup(wacom, serio);
- if (err)
+ if (err) {
+ dev_err(&wacom->dev->dev, "%s - failed wacom_setup() with status: %d",
+ __func__, err);
goto close_serio;
+ }
set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
if (!(wacom->flags & F_COVERS_SCREEN))
@@ -577,11 +656,23 @@
(1 << (7 + wacom->extra_z_bits)) - 1, 0, 0);
err = input_register_device(wacom->dev);
- if (err)
- goto close_serio;
+ if (err) {
+ dev_err(&wacom->dev->dev, "%s - failed input device registration with status: %d",
+ __func__, err);
+ goto reset_device;
+ }
return 0;
+reset_device:
+ /* Try to put the device back into a known good state.
+ (So that inputattach --dump will output data from the device.)
+
+ Note: The Digitizer II doesn't always respond to START cmds, so use RESET
+ here.
+ */
+ wacom_send_and_wait(wacom, serio, REQUEST_RESET_TO_PROTOCOL_IV,
+ "protocol IV reset");
close_serio:
serio_close(serio);
free_device:
@@ -605,7 +696,7 @@
static struct serio_driver wacom_drv = {
.driver = {
- .name = "wacom_serial4",
+ .name = MY_MOD_NAME,
},
.description = "Wacom protocol 4 serial tablet driver",
.id_table = wacom_serio_ids,