git: ad9ab26ab52a - main - smb(4): Add SMB Block Process Call command
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
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;
+};