git: ad9ab26ab52a - main - smb(4): Add SMB Block Process Call command

From: Pouria Mousavizadeh Tehrani <pouria_at_FreeBSD.org>
Date: Wed, 28 Oct 2026 15:10:22 UTC
The branch main has been updated by pouria:

URL: https://cgit.FreeBSD.org/src/commit/?id=ad9ab26ab52a0a88933806c81f34d2670c0f69a9

commit ad9ab26ab52a0a88933806c81f34d2670c0f69a9
Author:     YannickV <Y.Vossen@beckhoff.com>
AuthorDate: 2026-06-26 06:52:44 +0000
Commit:     Pouria Mousavizadeh Tehrani <pouria@FreeBSD.org>
CommitDate: 2026-10-28 15:06:05 +0000

    smb(4): Add SMB Block Process Call command
    
    Add support for the SMBus Block Process Call (bpcall) command. This
    command writes a block of data to a slave device and reads a block of
    data back in a single transaction, as defined in the SMBus 2.0
    specification.
    
    Add the SMB_BPCALL ioctl to the smb(4) character device to allow
    userspace to issue Block Process Call transactions.
    
    Implement ichsmb_bpcall() for the ICH SMBus controller using the
    32-byte block buffer mode.  Older ICH variants (ICH, ICH0, ICH2, ICH3)
    that lack the block buffer feature will return SMB_ENOTSUPP.
    
    The PIIX4 compatible hardware (intpm) does not support Block Process
    Call at all and returns SMB_ENOTSUPP unconditionally.
    
    This has been tested on several Beckhoff Industrial PCs using real
    SMBus hardware. It has been tested on Tigerlake, Elkhart and
    Amstonlake Platforms.
    
    Signed-off-by:  YannickV <Y.Vossen@beckhoff.com>
    Reviewed by:    adrian, corvink, ziaee (manpages)
    Pull Request:   https://github.com/freebsd/freebsd-src/pull/2305
---
 share/man/man4/smb.4        | 16 ++++++++++++
 sys/dev/ichsmb/ichsmb.c     | 64 ++++++++++++++++++++++++++++++++++++++++++++-
 sys/dev/ichsmb/ichsmb_pci.c |  1 +
 sys/dev/ichsmb/ichsmb_reg.h |  1 +
 sys/dev/ichsmb/ichsmb_var.h |  1 +
 sys/dev/intpm/intpm.c       |  9 +++++++
 sys/dev/smbus/smb.c         | 23 ++++++++++++++++
 sys/dev/smbus/smb.h         |  1 +
 sys/dev/smbus/smbconf.h     |  2 ++
 sys/dev/smbus/smbus_if.m    | 13 +++++++++
 10 files changed, 130 insertions(+), 1 deletion(-)

diff --git a/share/man/man4/smb.4 b/share/man/man4/smb.4
index 8e9c569b33f4..838e24bb7fd9 100644
--- a/share/man/man4/smb.4
+++ b/share/man/man4/smb.4
@@ -162,6 +162,22 @@ The count is returned in
 .Fa rcount .
 The data is returned in the buffer pointed to by
 .Fa rbuf .
+.It Dv SMB_BPCALL Ta
+.Em BlockProcessCall
+first sends the byte from
+.Fa cmd
+to the device, followed by the byte from
+.Fa wcount
+and then
+.Fa wcount
+bytes of data from the buffer pointed to by
+.Fa wbuf .
+It then reads a count of data bytes from the device followed
+by that many bytes of data.
+The read count is returned in
+.Fa rcount .
+The data is returned in the buffer pointed to by
+.Fa rbuf .
 .El
 .Pp
 The
diff --git a/sys/dev/ichsmb/ichsmb.c b/sys/dev/ichsmb/ichsmb.c
index 0df757d9cc0e..fb3bc7f6c5cb 100644
--- a/sys/dev/ichsmb/ichsmb.c
+++ b/sys/dev/ichsmb/ichsmb.c
@@ -472,6 +472,66 @@ ichsmb_bread(device_t dev, u_char slave, char cmd, u_char *count, char *buf)
 	return (smb_error);
 }
 
+int
+ichsmb_bpcall(device_t dev, u_char slave, char cmd, u_char wcount, char *wbuf,
+    u_char *rcount, char *rbuf)
+{
+	const sc_p sc = device_get_softc(dev);
+	int smb_error, i;
+
+	DBG("slave=0x%02x cmd=0x%02x wcount=%d\n", slave, (u_char)cmd, wcount);
+	KASSERT(sc->ich_cmd == -1,
+	    ("%s: ich_cmd=%d\n", __func__ , sc->ich_cmd));
+	if (!(sc->features & ICHSMB_FEATURE_BLOCK_BUFFER))
+		return (SMB_ENOTSUPP);
+	if (wcount < 1 || wcount > 32)
+		return (SMB_EINVAL);
+
+	mtx_lock(&sc->mutex);
+
+	/* Enable block buffer mode */
+	bus_write_1(sc->io_res, ICH_AUX_CNT,
+	    bus_read_1(sc->io_res, ICH_AUX_CNT) | ICH_AUX_CNT_E32B);
+
+	sc->ich_cmd = ICH_HST_CNT_SMB_CMD_BLOCK_PROC;
+	bus_write_1(sc->io_res, ICH_XMIT_SLVA,
+	    slave | ICH_XMIT_SLVA_WRITE);
+	bus_write_1(sc->io_res, ICH_HST_CMD, cmd);
+
+	/* Write block count and reset data buffer */
+	bus_write_1(sc->io_res, ICH_D0, wcount);
+	bus_read_1(sc->io_res, ICH_HST_CNT);
+
+	/* Fill block buffer with write data and start transaction */
+	for (i = 0; i < wcount; i++)
+		bus_write_1(sc->io_res, ICH_BLOCK_DB, wbuf[i]);
+	bus_write_1(sc->io_res, ICH_HST_CNT,
+	    ICH_HST_CNT_START | ICH_HST_CNT_INTREN | sc->ich_cmd);
+
+	/* Wait for transaction to complete */
+	if ((smb_error = ichsmb_wait(sc)) == SMB_ENOERR) {
+		*rcount = bus_read_1(sc->io_res, ICH_D0);
+		if (*rcount < 1 || *rcount > 32) {
+			smb_error = SMB_EBUSERR;
+			*rcount = 0;
+		} else {
+			bus_read_1(sc->io_res, ICH_HST_CNT);
+
+			/* Read response data from block buffer */
+			for (i = 0; i < *rcount; i++)
+				rbuf[i] = bus_read_1(sc->io_res, ICH_BLOCK_DB);
+		}
+	}
+
+	/* Disable block buffer mode */
+	bus_write_1(sc->io_res, ICH_AUX_CNT,
+	    bus_read_1(sc->io_res, ICH_AUX_CNT) & ~ICH_AUX_CNT_E32B);
+
+	mtx_unlock(&sc->mutex);
+	DBG("smb_error=%d\n", smb_error);
+	return (smb_error);
+}
+
 /********************************************************************
 			OTHER FUNCTIONS
 ********************************************************************/
@@ -496,7 +556,9 @@ static const u_int8_t ichsmb_state_irqs[] = {
 	    | ICH_HST_STA_BYTE_DONE_STS),
 	/* i2c read (not used) */
 	(ICH_HST_STA_BUS_ERR | ICH_HST_STA_DEV_ERR | ICH_HST_STA_INTR
-	    | ICH_HST_STA_BYTE_DONE_STS)
+	    | ICH_HST_STA_BYTE_DONE_STS),
+	/* block process call (only uses block buffer mode) */
+	(ICH_HST_STA_BUS_ERR | ICH_HST_STA_DEV_ERR | ICH_HST_STA_INTR),
 };
 
 /*
diff --git a/sys/dev/ichsmb/ichsmb_pci.c b/sys/dev/ichsmb/ichsmb_pci.c
index 7b85fe382a2d..be1983c4d04b 100644
--- a/sys/dev/ichsmb/ichsmb_pci.c
+++ b/sys/dev/ichsmb/ichsmb_pci.c
@@ -314,6 +314,7 @@ static device_method_t ichsmb_pci_methods[] = {
         DEVMETHOD(smbus_pcall, ichsmb_pcall),
         DEVMETHOD(smbus_bwrite, ichsmb_bwrite),
         DEVMETHOD(smbus_bread, ichsmb_bread),
+        DEVMETHOD(smbus_bpcall, ichsmb_bpcall),
 
 	DEVMETHOD_END
 };
diff --git a/sys/dev/ichsmb/ichsmb_reg.h b/sys/dev/ichsmb/ichsmb_reg.h
index be3b9e4a899d..c990e9eb1ce8 100644
--- a/sys/dev/ichsmb/ichsmb_reg.h
+++ b/sys/dev/ichsmb/ichsmb_reg.h
@@ -75,6 +75,7 @@
 #define   ICH_HST_CNT_SMB_CMD_PROC_CALL	0x10	/*   command: process call */
 #define   ICH_HST_CNT_SMB_CMD_BLOCK	0x14	/*   command: block */
 #define   ICH_HST_CNT_SMB_CMD_I2C_READ	0x18	/*   command: i2c read */
+#define   ICH_HST_CNT_SMB_CMD_BLOCK_PROC 0x1c	/*   command: block process call */
 #define   ICH_HST_CNT_KILL		0x02	/*   kill current transaction */
 #define   ICH_HST_CNT_INTREN		0x01	/*   enable interrupt */
 #define ICH_HST_CMD			0x03	/* host command */
diff --git a/sys/dev/ichsmb/ichsmb_var.h b/sys/dev/ichsmb/ichsmb_var.h
index f16a40be9777..a252d559d25a 100644
--- a/sys/dev/ichsmb/ichsmb_var.h
+++ b/sys/dev/ichsmb/ichsmb_var.h
@@ -80,6 +80,7 @@ extern smbus_readw_t	ichsmb_readw;
 extern smbus_pcall_t	ichsmb_pcall;	
 extern smbus_bwrite_t	ichsmb_bwrite;	
 extern smbus_bread_t	ichsmb_bread;	
+extern smbus_bpcall_t	ichsmb_bpcall;
 
 /* Other functions */
 extern void	ichsmb_device_intr(void *cookie);
diff --git a/sys/dev/intpm/intpm.c b/sys/dev/intpm/intpm.c
index fd3838bbcc14..c5bfed0a509b 100644
--- a/sys/dev/intpm/intpm.c
+++ b/sys/dev/intpm/intpm.c
@@ -887,6 +887,14 @@ intsmb_bread(device_t dev, u_char slave, char cmd, u_char *count, char *buf)
 	return (error);
 }
 
+static int
+intsmb_bpcall(device_t dev, u_char slave, char cmd, u_char wcount, char *wbuf,
+    u_char *rcount, char *rbuf)
+{
+
+	return (SMB_ENOTSUPP);
+}
+
 static device_method_t intsmb_methods[] = {
 	/* Device interface */
 	DEVMETHOD(device_probe,		intsmb_probe),
@@ -905,6 +913,7 @@ static device_method_t intsmb_methods[] = {
 	DEVMETHOD(smbus_pcall,		intsmb_pcall),
 	DEVMETHOD(smbus_bwrite,		intsmb_bwrite),
 	DEVMETHOD(smbus_bread,		intsmb_bread),
+	DEVMETHOD(smbus_bpcall,		intsmb_bpcall),
 
 	DEVMETHOD_END
 };
diff --git a/sys/dev/smbus/smb.c b/sys/dev/smbus/smb.c
index fa28be3a71b0..0a4ff0535828 100644
--- a/sys/dev/smbus/smb.c
+++ b/sys/dev/smbus/smb.c
@@ -190,6 +190,7 @@ static int
 smbioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *td)
 {
 	char buf[SMB_MAXBLOCKSIZE];
+	char buf2[SMB_MAXBLOCKSIZE];
 	device_t parent;
 #ifdef COMPAT_FREEBSD32
 	struct smbcmd sswab;
@@ -383,6 +384,28 @@ smbioctl(struct cdev *dev, u_long cmd, caddr_t data, int flags, struct thread *t
 		error = copyout(buf, s->rbuf, s->rcount);
 		break;
 
+	case SMB_BPCALL:
+		if (s->wcount < 0 || s->rcount < 0) {
+			error = EINVAL;
+			break;
+		}
+		if (s->wcount > SMB_MAXBLOCKSIZE)
+			s->wcount = SMB_MAXBLOCKSIZE;
+		if (s->rcount > SMB_MAXBLOCKSIZE)
+			s->rcount = SMB_MAXBLOCKSIZE;
+		if (s->wcount)
+			error = copyin(s->wbuf, buf, s->wcount);
+		if (error)
+			break;
+		error = smbus_error(smbus_bpcall(parent, s->slave, s->cmd,
+		    s->wcount, buf, &bcount, buf2));
+		if (error)
+			break;
+		if (s->rcount > bcount)
+			s->rcount = bcount;
+		error = copyout(buf2, s->rbuf, s->rcount);
+		break;
+
 	default:
 		error = ENOTTY;
 	}
diff --git a/sys/dev/smbus/smb.h b/sys/dev/smbus/smb.h
index d7ed378dbb15..eba5a9c75825 100644
--- a/sys/dev/smbus/smb.h
+++ b/sys/dev/smbus/smb.h
@@ -69,5 +69,6 @@ struct smbcmd {
 #define SMB_BWRITE	_IOW('i', 10, struct smbcmd)
 #define SMB_BREAD	_IOWR('i', 11, struct smbcmd)
 #define SMB_OLD_TRANS	_IOWR('i', 12, struct smbcmd)
+#define SMB_BPCALL	_IOWR('i', 13, struct smbcmd)
 
 #endif
diff --git a/sys/dev/smbus/smbconf.h b/sys/dev/smbus/smbconf.h
index 6d2d03f11def..5e39aad6acd4 100644
--- a/sys/dev/smbus/smbconf.h
+++ b/sys/dev/smbus/smbconf.h
@@ -114,6 +114,8 @@ extern driver_t smbus_driver;
 	(SMBUS_BWRITE(device_get_parent(bus), slave, cmd, count, buf))
 #define smbus_bread(bus,slave,cmd,count,buf) \
 	(SMBUS_BREAD(device_get_parent(bus), slave, cmd, count, buf))
+#define smbus_bpcall(bus,slave,cmd,wcount,wbuf,rcount,rbuf) \
+	(SMBUS_BPCALL(device_get_parent(bus), slave, cmd, wcount, wbuf, rcount, rbuf))
 #define smbus_trans(bus,slave,cmd,op,wbuf,wcount,rbuf,rcount,actualp) \
 	(SMBUS_TRANS(device_get_parent(bus), slave, cmd, op, \
 	wbuf, wcount, rbuf, rcount, actualp))
diff --git a/sys/dev/smbus/smbus_if.m b/sys/dev/smbus/smbus_if.m
index e8eaa7b10281..815933cd6369 100644
--- a/sys/dev/smbus/smbus_if.m
+++ b/sys/dev/smbus/smbus_if.m
@@ -148,3 +148,16 @@ METHOD int bread {
 	u_char *count;
 	char *buf;
 };
+
+#
+# Block Process Call command
+#
+METHOD int bpcall {
+	device_t dev;
+	u_char slave;
+	char cmd;
+	u_char wcount;
+	char *wbuf;
+	u_char *rcount;
+	char *rbuf;
+};