mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Tom Keller <tom@tompkel.net>
To: Dave Penkler <dpenkler@gmail.com>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: linux-kernel@vger.kernel.org, Tom Keller <tom@tompkel.net>
Subject: [PATCH] gpib: agilent_82357a: Support some 82357b clones
Date: Tue, 01 Sep 2026 23:36:40 +0000	[thread overview]
Message-ID: <20260901233621.1823280-1-tom@tompkel.net> (raw)


[-- Attachment #1.1: Type: text/plain, Size: 9232 bytes --]

Some adapters marked as genuine Agilent/Keysight 82357B devices are in
fact fake. These clones have their own EEPROM instead of loading
firmware on power up, and only respond to the initialization sequence
sent by the vendor drivers. Detect clone adapters that don't respond to
the 0xA0 firmware load request and run the alternate init sequence
against them.

Signed-off-by: Tom Keller <tom@tompkel.net>
---
Because I do not have a genuine 82357B, I can't test whether or not this
patch breaks working adapters, but I did confirm that the clone adapter
doesn't respond to a firmware load 0xA0 request, and the adapter
enumerates as 0957:0718 without loading firmware. A CPUCS read would be
answered by real hardware, but it isn't by the clone.

Vendor init sequences were obtained by running the latest vendor control
bundle (21.3.293) on a Windows 11 VM and capturing the USB packets with
pcap. I confirmed that this change does make my clone function, and as 
far as I can tell th
e clone is fingerprinting the specific sequences. The
clone has poor error handling in hardware and can freeze on a failed or
aborted transaction; in that case, power-cycling the adapter is required.

 drivers/gpib/agilent_82357a/agilent_82357a.c | 151 ++++++++++++++++++-
 drivers/gpib/agilent_82357a/agilent_82357a.h |   2 +
 2 files changed, 152 insertions(+), 1 deletion(-)

diff --git a/drivers/gpib/agilent_82357a/agilent_82357a.c b/drivers/gpib/agilent_82357a/agilent_82357a.c
index 2468a471d..b5c0d34f0 100644
--- a/drivers/gpib/agilent_82357a/agilent_82357a.c
+++ b/drivers/gpib/agilent_82357a/agilent_82357a.c
@@ -566,8 +566,11 @@ static ssize_t agilent_82357a_generic_write(struct gpib_board *board,
 	out_data[i++] = 0; // primary address when AWF_NO_ADDRESS is not set
 	out_data[i++] = 0; // secondary address when AWF_NO_ADDRESS is not set
 	out_data[i] = AWF_NO_ADDRESS | AWF_NO_FAST_TALKER_FIRST_BYTE;
-	if (send_commands)
+	if (send_commands) {
 		out_data[i] |= A
WF_ATN | AWF_NO_FAST_TALKER;
+		if (a_priv->is_clone_82357b)
+			out_data[i] &= ~AWF_NO_FAST_TALKER_FIRST_BYTE;
+	}
 	if (send_eoi)
 		out_data[i] |= AWF_SEND_EOI;
 	++i;
@@ -652,6 +655,15 @@ static ssize_t agilent_82357a_generic_write(struct gpib_board *board,
 		return -ETIMEDOUT;
 	}
 
+	/*
+	 * The write-complete interrupt carries the number of bytes transferred
+	 * in bytes 2..5. Clone adapters do not properly answer XFER_STATUS
+	 */
+	if (a_priv->is_clone_82357b) {
+		mutex_unlock(&a_priv->bulk_transfer_lock);
+		*bytes_written = a_priv->write_complete_count;
+		return 0;
+	}
 	status_data = kmalloc(STATUS_DATA_LEN, GFP_KERNEL);
 	if (!status_data) {
 		mutex_unlock(&a_priv->bulk_transfer_lock);
@@ -1114,6 +1126,10 @@ static void agilent_82357a_interrupt_complete(struct urb *urb)
 	}
 
 	interrupt_flags = transfer_buffer[0];
+	if (urb->actual_length >= 6 && test_bit(AIF_WRITE_COMPLETE_BN, &interrupt_flags))
+		a_priv->write_complete_count = transfer_
buffer[2] |
+			(transfer_buffer[3] << 8) | (transfer_buffer[4] << 16) |
+			((u32)transfer_buffer[5] << 24);
 	if (test_bit(AIF_READ_COMPLETE_BN, &interrupt_flags))
 		set_bit(AIF_READ_COMPLETE_BN, &a_priv->interrupt_flags);
 	if (test_bit(AIF_WRITE_COMPLETE_BN, &interrupt_flags))
@@ -1214,6 +1230,135 @@ static void agilent_82357a_free_private(struct gpib_board *board)
 }
 
 #define INIT_NUM_REG_WRITES 18
+/* Some clone adapters contain firmware already. Genuine adapters only
+ * enumerate as 0x0718 after firmware load; clone adapters enumerate as 0x0718
+ * right away and refuse a firmware load request.
+ */
+static void agilent_82357a_detect_clone(struct agilent_82357a_priv *a_priv)
+{
+	struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface);
+	u8 *data;
+	int retval;
+
+	data = kmalloc(1, GFP_KERNEL);
+	if (!data)
+		return;
+	retval = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), 0xa0,
+				 USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_
DEVICE,
+			0xe600, 0, data, 1, 100);
+	kfree(data);
+	if (retval < 0) {
+		a_priv->is_clone_82357b = 1;
+		dev_info(&usb_dev->dev, "Clone 82357B detected (firmware load request returned %i), using vendor quirks\n",
+			 retval);
+	}
+}
+
+/*
+ * From usb captures of vendor software. Clone adapters require this specific
+ * sequence to function.
+ */
+static int agilent_82357a_init_82357b_clone(struct gpib_board *board)
+{
+	struct agilent_82357a_priv *a_priv = board->private_data;
+	struct usb_device *usb_dev = interface_to_usbdev(a_priv->bus_interface);
+	struct agilent_82357a_register_pairlet hw_control;
+	struct agilent_82357a_register_pairlet writes[19];
+	int retval;
+
+	writes[0].address = RESET_TO_POWERUP;
+	writes[0].value = RESET_SPACEBALL;
+	retval = agilent_82357a_write_registers(a_priv, writes, 1);
+	if (retval) {
+		dev_err(&usb_dev->dev, "write_registers() returned error\n");
+		return -EIO;
+	}
+	// Write init sequence
+	set_current_state(T
ASK_INTERRUPTIBLE);
+	if (schedule_timeout(usec_to_jiffies(2000)))
+		return -ERESTARTSYS;
+	writes[0].address = HW_CONTROL;
+	writes[0].value = NOT_TI_RESET | SYSTEM_CONTROLLER | NOT_PARALLEL_POLL;
+	writes[1].address = LED_CONTROL;
+	writes[1].value = FIRMWARE_LED_CONTROL;
+	writes[2].address = AUXCR;
+	writes[2].value = AUX_CS | AUX_CHIP_RESET;
+	writes[3].address = IMR0;
+	writes[3].value = HR_BOIE;
+	writes[4].address = IMR1;
+	writes[4].value = HR_SRQIE;
+	writes[5].address = AUXCR;
+	writes[5].value = AUX_NBAF;
+	writes[6].address = AUXCR;
+	writes[6].value = AUX_HLDE;
+	writes[7].address = ADR;
+	writes[7].value = board->pad & ADDRESS_MASK;
+	writes[8].address = PPR;
+	writes[8].value = 0;
+	writes[9].address = AUXCR;
+	writes[9].value = AUX_CS | AUX_STDL;
+	writes[10].address = AUXCR;
+	writes[10].value = AUX_LON;
+	writes[11].address = AUXCR;
+	writes[11].value = AUX_TON;
+	writes[12].address = SPMR;
+	writes[12].value = 0;
+	writes[13].address = 
AUXCR;
+	writes[13].value = AUX_RSV2;
+	writes[14].address = AUXCR;
+	writes[14].value = 0x20; // undocumented, sent by the vendor driver
+	writes[15].address = AUXCR;
+	writes[15].value = AUX_INVAL;
+	writes[16].address = AUXCR;
+	writes[16].value = AUX_CHIP_RESET;
+	writes[17].address = AUXCR;
+	writes[17].value = AUX_TCA;
+	writes[18].address = AUXCR;
+	writes[18].value = AUX_CS | AUX_SIC;
+	retval = agilent_82357a_write_registers(a_priv, writes, 19);
+	if (retval) {
+		dev_err(&usb_dev->dev, "write_registers() returned error\n");
+		return -EIO;
+	}
+
+	set_current_state(TASK_INTERRUPTIBLE);
+	if (schedule_timeout(usec_to_jiffies(15000)))
+		return -ERESTARTSYS;
+	writes[0].address = AUXCR;
+	writes[0].value = AUX_CS | AUX_SRE;
+	writes[1].address = AUXCR;
+	writes[1].value = AUX_RPP;
+	writes[2].address = AUXCR;
+	writes[2].value = AUX_SIC;
+	writes[3].address = 9;  // undocumented, sent by the vendor driver
+	writes[3].value = 3;
+	writes[4].address =
 PROTOCOL_CONTROL;
+	writes[4].value = WRITE_COMPLETE_INTERRUPT_EN;
+	retval = agilent_82357a_write_registers(a_priv, writes, 5);
+	if (retval) {
+		dev_err(&usb_dev->dev, "write_registers() returned error\n");
+		return -EIO;
+	}
+
+	writes[0].address = FAST_TALKER_T1;
+	writes[0].value = 0x27;
+	board->t1_nano_sec = 0x27 * 21;
+	retval = agilent_82357a_write_registers(a_priv, writes, 1);
+	if (retval) {
+		dev_err(&usb_dev->dev, "write_registers() returned error\n");
+		return -EIO;
+	}
+	hw_control.address = HW_CONTROL;
+	retval = agilent_82357a_read_registers(a_priv, &hw_control, 1, 1);
+	if (retval) {
+		dev_err(&usb_dev->dev, "read_registers() returned error\n");
+		return -EIO;
+	}
+	a_priv->hw_control_bits = (hw_control.value & ~0x7) | NOT_TI_RESET | NOT_PARALLEL_POLL;
+
+	return 0;
+}
+
 static int agilent_82357a_init(struct gpib_board *board)
 {
 	struct agilent_82357a_priv *a_priv = board->private_data;
@@ -1223,6 +1368,9 @@ static int agilent_8
2357a_init(struct gpib_board *board)
 	int retval;
 	unsigned int nanosec;
 
+	if (a_priv->is_clone_82357b)
+		return agilent_82357a_init_82357b_clone(board);
+
 	writes[0].address = LED_CONTROL;
 	writes[0].value = FAIL_LED_ON;
 	writes[1].address = RESET_TO_POWERUP;
@@ -1346,6 +1494,7 @@ static int agilent_82357a_attach(struct gpib_board *board, const struct gpib_boa
 	case USB_DEVICE_ID_AGILENT_82357B:
 		a_priv->bulk_out_endpoint = AGILENT_82357B_BULK_OUT_ENDPOINT;
 		a_priv->interrupt_in_endpoint = AGILENT_82357B_INTERRUPT_IN_ENDPOINT;
+		agilent_82357a_detect_clone(a_priv);
 		break;
 	default:
 		dev_err(&usb_dev->dev, "bug, unhandled product_id in switch?\n");
diff --git a/drivers/gpib/agilent_82357a/agilent_82357a.h b/drivers/gpib/agilent_82357a/agilent_82357a.h
index 33ac558e5..950d8d4ce 100644
--- a/drivers/gpib/agilent_82357a/agilent_82357a.h
+++ b/drivers/gpib/agilent_82357a/agilent_82357a.h
@@ -126,6 +126,7 @@ struct agilent_82357a_priv {
 	unsigned
 short eos_mode;
 	unsigned short hw_control_bits;
 	unsigned long interrupt_flags;
+	u32 write_complete_count;               // bytes transferred
 	struct urb *bulk_urb;
 	struct urb *interrupt_urb;
 	u8 *interrupt_buffer;
@@ -139,6 +140,7 @@ struct agilent_82357a_priv {
 	unsigned int interrupt_in_endpoint;
 	unsigned is_cic : 1;
 	unsigned ren_state : 1;
+	unsigned is_clone_82357b : 1;
 };
 
 struct agilent_82357a_register_pairlet {
-- 
2.55.0


[-- Attachment #1.2: publickey - tom@tompkel.net - 0x5A43B519.asc --]
[-- Type: application/pgp-keys, Size: 3184 bytes --]

[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 928 bytes --]

             reply	other threads:[~2026-09-01 23:36 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-01 23:36 Tom Keller [this message]
2026-09-02 13:20 ` Dave Penkler
2026-09-02 22:20   ` Tom Keller

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260901233621.1823280-1-tom@tompkel.net \
    --to=tom@tompkel.net \
    --cc=dpenkler@gmail.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®