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,

Reply via email to