The branch main has been updated by adrian:

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

commit 21143476a5767b49ca722e6f28b19770703b537e
Author:     Abdelkader Boudih <[email protected]>
AuthorDate: 2026-07-17 04:02:36 +0000
Commit:     Adrian Chadd <[email protected]>
CommitDate: 2026-07-17 04:07:36 +0000

    firewire: per-unit-directory child device support
    
    Added structure to allow multiple device to attach to the same driver.
    Also removed the deprecation warning from the man page.
    
    Differential Revision:  https://reviews.freebsd.org/D58201
    Reviewed by:    adrian
---
 share/man/man4/firewire.4      |  26 ++++-
 sys/dev/firewire/firewire.c    | 229 ++++++++++++++++++++++++++++++++++++++---
 sys/dev/firewire/firewirereg.h |  53 ++++++++++
 sys/dev/firewire/if_fwe.c      |   8 +-
 sys/dev/firewire/if_fwip.c     |   8 +-
 sys/dev/firewire/sbp.c         |   5 +-
 sys/dev/firewire/sbp_targ.c    |   8 +-
 7 files changed, 310 insertions(+), 27 deletions(-)

diff --git a/share/man/man4/firewire.4 b/share/man/man4/firewire.4
index 99aadb308fc3..a7253443b5cf 100644
--- a/share/man/man4/firewire.4
+++ b/share/man/man4/firewire.4
@@ -29,7 +29,7 @@
 .\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 .\" POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd January 23, 2025
+.Dd July 12, 2026
 .Dt FIREWIRE 4
 .Os
 .Sh NAME
@@ -72,6 +72,27 @@ The
 bus attaches to the controller.
 Additional drivers can be attached to the bus.
 .Pp
+The
+.Nm
+bus parses each device's CSR configuration ROM to discover unit
+directories.
+A newbus child is created for each unit directory, carrying the
+.Va unit_spec_id
+and
+.Va unit_sw_version
+fields as ivars.
+Device-specific drivers such as
+.Xr fwcam 4 ,
+.Xr fwisound 4 ,
+and
+.Xr fwdv 4
+match against these fields in their probe routines.
+Protocol drivers such as
+.Xr sbp 4
+and
+.Xr fwe 4
+continue to use the legacy identify-based attachment model.
+.Pp
 Up to 63 devices, including the host itself, can be attached to
 a
 .Nm
@@ -99,8 +120,11 @@ for details on how to setup debugging with firewire.
 .El
 .Sh SEE ALSO
 .Xr dcons 4 ,
+.Xr fwcam 4 ,
+.Xr fwdv 4 ,
 .Xr fwe 4 ,
 .Xr fwip 4 ,
+.Xr fwisound 4 ,
 .Xr fwohci 4 ,
 .Xr pci 4 ,
 .Xr sbp 4 ,
diff --git a/sys/dev/firewire/firewire.c b/sys/dev/firewire/firewire.c
index dfb906ba34c1..c0a7c53cdd6c 100644
--- a/sys/dev/firewire/firewire.c
+++ b/sys/dev/firewire/firewire.c
@@ -86,12 +86,16 @@ static int firewire_detach(device_t);
 static int firewire_resume(device_t);
 static void firewire_xfer_timeout(void *, int);
 static device_t firewire_add_child(device_t, u_int, const char *, int);
+static void firewire_child_deleted(device_t, device_t);
 static void fw_try_bmr(void *);
 static void fw_try_bmr_callback(struct fw_xfer *);
 static void fw_asystart(struct fw_xfer *);
 static int fw_get_tlabel(struct firewire_comm *, struct fw_xfer *);
 static void fw_bus_probe(void *);
 static void fw_attach_dev(struct firewire_comm *);
+static void fw_parse_units(struct fw_device *);
+static void fw_create_unit_children(struct firewire_comm *, struct fw_device 
*);
+static int fw_remove_unit_children(struct firewire_comm *, struct fw_device *);
 static void fw_bus_probe_thread(void *);
 #ifdef FW_VMACCESS
 static void fw_vmaccess (struct fw_xfer *);
@@ -111,6 +115,7 @@ static device_method_t firewire_methods[] = {
 
        /* Bus interface */
        DEVMETHOD(bus_add_child,        firewire_add_child),
+       DEVMETHOD(bus_child_deleted,    firewire_child_deleted),
 
        DEVMETHOD_END
 };
@@ -473,17 +478,39 @@ firewire_add_child(device_t dev, u_int order, const char 
*name, int unit)
 {
        device_t child;
        struct firewire_softc *sc;
+       struct fw_child_ivars *iv;
 
        sc = device_get_softc(dev);
        child = device_add_child(dev, name, unit);
        if (child) {
-               device_set_ivars(child, sc->fc);
+               iv = malloc(sizeof(*iv), M_FW, M_NOWAIT | M_ZERO);
+               if (iv == NULL) {
+                       device_delete_child(dev, child);
+                       return (NULL);
+               }
+               iv->type = FW_CHILD_BUS;
+               iv->u.fc = sc->fc;
+               device_set_ivars(child, iv);
                device_probe_and_attach(child);
        }
 
        return child;
 }
 
+static void
+firewire_child_deleted(device_t dev, device_t child)
+{
+       struct fw_child_ivars *iv;
+
+       iv = device_get_ivars(child);
+       if (iv != NULL) {
+               if (iv->type == FW_CHILD_UNIT && iv->u.unit != NULL)
+                       iv->u.unit->dev = NULL;
+               free(iv, M_FW);
+               device_set_ivars(child, NULL);
+       }
+}
+
 static int
 firewire_resume(device_t dev)
 {
@@ -523,6 +550,13 @@ firewire_detach(device_t dev)
        if ((err = fwdev_destroydev(sc)) != 0)
                return err;
 
+       /* Remove unit children before bus_generic_detach */
+       for (fwdev = STAILQ_FIRST(&fc->devices); fwdev != NULL;
+            fwdev = fwdev_next) {
+               fwdev_next = STAILQ_NEXT(fwdev, link);
+               fw_remove_unit_children(fc, fwdev);
+       }
+
        if ((err = bus_generic_detach(dev)) != 0)
                return err;
 
@@ -721,13 +755,22 @@ fw_busreset(struct firewire_comm *fc, uint32_t new_status)
 
        fw_reset_crom(fc);
 
+       /*
+        * Skip unit children; only bus-level children have post_busreset.
+        */
        if (device_get_children(fc->bdev, &devlistp, &devcnt) == 0) {
-               for (i = 0; i < devcnt; i++)
-                       if (device_get_state(devlistp[i]) >= DS_ATTACHED) {
-                               fdc = device_get_softc(devlistp[i]);
-                               if (fdc->post_busreset != NULL)
-                                       fdc->post_busreset(fdc);
-                       }
+               for (i = 0; i < devcnt; i++) {
+                       struct fw_child_ivars *iv;
+
+                       if (device_get_state(devlistp[i]) < DS_ATTACHED)
+                               continue;
+                       iv = fw_get_ivars(devlistp[i]);
+                       if (iv == NULL || iv->type != FW_CHILD_BUS)
+                               continue;
+                       fdc = device_get_softc(devlistp[i]);
+                       if (fdc->post_busreset != NULL)
+                               fdc->post_busreset(fdc);
+               }
                free(devlistp, M_TEMP);
        }
 
@@ -1585,6 +1628,7 @@ fw_explore_node(struct fw_device *dfwdev)
                fwdev->dst = dfwdev->dst;
                fwdev->maxrec = dfwdev->maxrec;
                fwdev->status = dfwdev->status;
+               STAILQ_INIT(&fwdev->units);
 
                /*
                 * Pre-1394a-2000 didn't have link_spd in
@@ -1638,10 +1682,17 @@ fw_explore_node(struct fw_device *dfwdev)
                fwdev->status = FWDEVINIT;
                /* unchanged ? */
                if (bcmp(&csr[0], &fwdev->csrrom[0], sizeof(uint32_t) * 5) == 
0) {
-                       if (firewire_debug)
-                               device_printf(fc->dev,
-                                   "node%d: crom unchanged\n", node);
-                       return (0);
+                       if (!STAILQ_EMPTY(&fwdev->units)) {
+                               if (firewire_debug)
+                                       device_printf(fc->dev,
+                                           "node%d: crom unchanged\n", node);
+                               fwdev->rom_changed = 0;
+                               return (0);
+                       }
+                       /* Units lost after detach/reattach; re-parse. */
+                       fwdev->rom_changed = 0;
+               } else {
+                       fwdev->rom_changed = 1;
                }
        }
 
@@ -1764,6 +1815,136 @@ fw_bus_probe_thread(void *arg)
        kproc_exit(0);
 }
 
+static void
+fw_parse_units(struct fw_device *fwdev)
+{
+       struct csrhdr *hdr;
+       struct csrdirectory *rootdir, *udir;
+       struct csrreg *ureg;
+       struct fw_unit *unit;
+       uint32_t spec_id, sw_version;
+       uint32_t rom_quads, root_off, root_len;
+       uint32_t reg_off, udir_qoff, udir_len;
+       int ri, ui, uindex;
+
+       rom_quads = CSRROMSIZE / 4;
+       hdr = (struct csrhdr *)fwdev->csrrom;
+       if (hdr->info_len <= 1)
+               return;
+
+       root_off = 1 + hdr->info_len;
+       if (root_off >= rom_quads)
+               return;
+       rootdir = (struct csrdirectory *)&fwdev->csrrom[root_off];
+       root_len = rootdir->crc_len;
+       if (root_off + 1 + root_len > rom_quads)
+               return;
+
+       uindex = 0;
+       for (ri = 0; ri < (int)root_len; ri++) {
+               reg_off = root_off + 1 + ri;
+               if (reg_off >= rom_quads)
+                       break;
+               if (rootdir->entry[ri].key != CROM_UDIR)
+                       continue;
+
+               udir_qoff = reg_off + rootdir->entry[ri].val;
+               if (udir_qoff >= rom_quads)
+                       continue;
+               udir = (struct csrdirectory *)&fwdev->csrrom[udir_qoff];
+               udir_len = udir->crc_len;
+               if (udir_qoff + 1 + udir_len > rom_quads)
+                       continue;
+
+               spec_id = 0;
+               sw_version = 0;
+               for (ui = 0; ui < (int)udir_len; ui++) {
+                       if (udir_qoff + 1 + ui >= rom_quads)
+                               break;
+                       ureg = &udir->entry[ui];
+                       if (ureg->key == CSRKEY_SPEC)
+                               spec_id = ureg->val;
+                       else if (ureg->key == CSRKEY_VER)
+                               sw_version = ureg->val;
+               }
+               if (spec_id == 0 && sw_version == 0)
+                       continue;
+
+               unit = malloc(sizeof(*unit), M_FW, M_NOWAIT | M_ZERO);
+               if (unit == NULL)
+                       continue;
+               unit->fwdev = fwdev;
+               unit->spec_id = spec_id;
+               unit->sw_version = sw_version;
+               unit->dir_offset = udir_qoff * 4;
+               STAILQ_INSERT_TAIL(&fwdev->units, unit, link);
+               uindex++;
+       }
+       if (firewire_debug && uindex > 0)
+               device_printf(fwdev->fc->bdev,
+                   "node %d: %d unit director%s found\n",
+                   fwdev->dst, uindex, uindex == 1 ? "y" : "ies");
+}
+
+static void
+fw_create_unit_children(struct firewire_comm *fc, struct fw_device *fwdev)
+{
+       struct fw_unit *unit;
+       struct fw_child_ivars *iv;
+
+       bus_topo_lock();
+       STAILQ_FOREACH(unit, &fwdev->units, link) {
+               if (unit->dev != NULL)
+                       continue;
+               unit->dev = device_add_child(fc->bdev, NULL, DEVICE_UNIT_ANY);
+               if (unit->dev == NULL) {
+                       device_printf(fc->bdev,
+                           "failed to add unit child for node %d\n",
+                           fwdev->dst);
+                       continue;
+               }
+               iv = malloc(sizeof(*iv), M_FW, M_NOWAIT | M_ZERO);
+               if (iv == NULL) {
+                       device_delete_child(fc->bdev, unit->dev);
+                       unit->dev = NULL;
+                       continue;
+               }
+               iv->type = FW_CHILD_UNIT;
+               iv->u.unit = unit;
+               device_set_ivars(unit->dev, iv);
+               if (firewire_debug)
+                       device_printf(fc->bdev,
+                           "node %d: unit spec=0x%06x ver=0x%06x\n",
+                           fwdev->dst, unit->spec_id, unit->sw_version);
+               device_probe_and_attach(unit->dev);
+       }
+       bus_topo_unlock();
+}
+
+static int
+fw_remove_unit_children(struct firewire_comm *fc, struct fw_device *fwdev)
+{
+       struct fw_unit *unit, *next;
+       int err, ret;
+
+       ret = 0;
+       bus_topo_lock();
+       STAILQ_FOREACH_SAFE(unit, &fwdev->units, link, next) {
+               if (unit->dev != NULL) {
+                       err = device_delete_child(fc->bdev, unit->dev);
+                       if (err != 0) {
+                               ret = err;
+                               continue;
+                       }
+                       unit->dev = NULL;
+               }
+               STAILQ_REMOVE(&fwdev->units, unit, fw_unit, link);
+               free(unit, M_FW);
+       }
+       bus_topo_unlock();
+       return (ret);
+}
+
 /*
  * To attach sub-devices layer onto IEEE1394 bus.
  */
@@ -1780,6 +1961,15 @@ fw_attach_dev(struct firewire_comm *fc)
                next = STAILQ_NEXT(fwdev, link);
                if (fwdev->status == FWDEVINIT) {
                        fwdev->status = FWDEVATTACHED;
+                       if (fwdev->rom_changed &&
+                           !STAILQ_EMPTY(&fwdev->units)) {
+                               if (fw_remove_unit_children(fc, fwdev) == 0)
+                                       fwdev->rom_changed = 0;
+                       }
+                       if (STAILQ_EMPTY(&fwdev->units)) {
+                               fw_parse_units(fwdev);
+                       }
+                       fw_create_unit_children(fc, fwdev);
                } else if (fwdev->status == FWDEVINVAL) {
                        fwdev->rcnt++;
                        if (firewire_debug)
@@ -1791,6 +1981,8 @@ fw_attach_dev(struct firewire_comm *fc)
                                 * Remove devices which have not been seen
                                 * for a while.
                                 */
+                               if (fw_remove_unit_children(fc, fwdev) != 0)
+                                       continue;
                                STAILQ_REMOVE(&fc->devices, fwdev, fw_device,
                                    link);
                                free(fwdev, M_FW);
@@ -1801,11 +1993,16 @@ fw_attach_dev(struct firewire_comm *fc)
        err = device_get_children(fc->bdev, &devlistp, &devcnt);
        if (err == 0) {
                for (i = 0; i < devcnt; i++) {
-                       if (device_get_state(devlistp[i]) >= DS_ATTACHED) {
-                               fdc = device_get_softc(devlistp[i]);
-                               if (fdc->post_explore != NULL)
-                                       fdc->post_explore(fdc);
-                       }
+                       struct fw_child_ivars *iv;
+
+                       if (device_get_state(devlistp[i]) < DS_ATTACHED)
+                               continue;
+                       iv = fw_get_ivars(devlistp[i]);
+                       if (iv == NULL || iv->type != FW_CHILD_BUS)
+                               continue;
+                       fdc = device_get_softc(devlistp[i]);
+                       if (fdc->post_explore != NULL)
+                               fdc->post_explore(fdc);
                }
                free(devlistp, M_TEMP);
        }
diff --git a/sys/dev/firewire/firewirereg.h b/sys/dev/firewire/firewirereg.h
index 97a53606c001..a9f4f23e6a91 100644
--- a/sys/dev/firewire/firewirereg.h
+++ b/sys/dev/firewire/firewirereg.h
@@ -58,14 +58,38 @@ struct fw_device {
        int rommax;     /* offset from 0xffff f000 0000 */
        uint32_t csrrom[CSRROMSIZE / 4];
        int rcnt;
+       int rom_changed;
        struct firewire_comm *fc;
        uint32_t status;
 #define FWDEVINIT      1
 #define FWDEVATTACHED  2
 #define FWDEVINVAL     3
+       STAILQ_HEAD(, fw_unit) units;
        STAILQ_ENTRY(fw_device) link;
 };
 
+struct fw_unit {
+       device_t dev;
+       struct fw_device *fwdev;
+       uint32_t spec_id;
+       uint32_t sw_version;
+       uint32_t dir_offset;
+       STAILQ_ENTRY(fw_unit) link;
+};
+
+enum fw_child_type {
+       FW_CHILD_BUS,
+       FW_CHILD_UNIT,
+};
+
+struct fw_child_ivars {
+       enum fw_child_type type;
+       union {
+               struct firewire_comm *fc;
+               struct fw_unit *unit;
+       } u;
+};
+
 struct firewire_softc {
        struct cdev *dev;
        struct firewire_comm *fc;
@@ -294,6 +318,35 @@ extern int firewire_debug;
 extern devclass_t firewire_devclass;
 extern int firewire_phydma_enable;
 
+static __inline struct fw_child_ivars *
+fw_get_ivars(device_t dev)
+{
+
+       return ((struct fw_child_ivars *)device_get_ivars(dev));
+}
+
+static __inline struct firewire_comm *
+fw_get_comm(device_t dev)
+{
+       struct fw_child_ivars *iv;
+
+       iv = fw_get_ivars(dev);
+       if (iv->type == FW_CHILD_BUS)
+               return (iv->u.fc);
+       return (iv->u.unit->fwdev->fc);
+}
+
+static __inline struct fw_unit *
+fw_get_unit(device_t dev)
+{
+       struct fw_child_ivars *iv;
+
+       iv = fw_get_ivars(dev);
+       if (iv->type == FW_CHILD_UNIT)
+               return (iv->u.unit);
+       return (NULL);
+}
+
 #define        FWPRI           (PWAIT | PCATCH)
 
 #define CALLOUT_INIT(x) callout_init(x, 1 /* mpsafe */)
diff --git a/sys/dev/firewire/if_fwe.c b/sys/dev/firewire/if_fwe.c
index c8bfc439e166..ed288cab02c7 100644
--- a/sys/dev/firewire/if_fwe.c
+++ b/sys/dev/firewire/if_fwe.c
@@ -123,10 +123,12 @@ fwe_probe(device_t dev)
 {
        device_t pa;
 
+       if (fw_get_unit(dev) != NULL)
+               return (ENXIO);
+
        pa = device_get_parent(dev);
-       if (device_get_unit(dev) != device_get_unit(pa)) {
+       if (device_get_unit(dev) != device_get_unit(pa))
                return (ENXIO);
-       }
 
        device_set_desc(dev, "Ethernet over FireWire");
        return (0);
@@ -150,7 +152,7 @@ fwe_attach(device_t dev)
        fwe->stream_ch = stream_ch;
        fwe->dma_ch = -1;
 
-       fwe->fd.fc = device_get_ivars(dev);
+       fwe->fd.fc = fw_get_comm(dev);
        if (tx_speed < 0)
                tx_speed = fwe->fd.fc->speed;
 
diff --git a/sys/dev/firewire/if_fwip.c b/sys/dev/firewire/if_fwip.c
index bdbe76e2caf4..c3c6c595c775 100644
--- a/sys/dev/firewire/if_fwip.c
+++ b/sys/dev/firewire/if_fwip.c
@@ -134,10 +134,12 @@ fwip_probe(device_t dev)
 {
        device_t pa;
 
+       if (fw_get_unit(dev) != NULL)
+               return (ENXIO);
+
        pa = device_get_parent(dev);
-       if (device_get_unit(dev) != device_get_unit(pa)) {
+       if (device_get_unit(dev) != device_get_unit(pa))
                return (ENXIO);
-       }
 
        device_set_desc(dev, "IP over FireWire");
        return (0);
@@ -159,7 +161,7 @@ fwip_attach(device_t dev)
        /* XXX */
        fwip->dma_ch = -1;
 
-       fwip->fd.fc = device_get_ivars(dev);
+       fwip->fd.fc = fw_get_comm(dev);
        if (tx_speed < 0)
                tx_speed = fwip->fd.fc->speed;
 
diff --git a/sys/dev/firewire/sbp.c b/sys/dev/firewire/sbp.c
index 9e76000f5fb6..d8c5f2dd68b7 100644
--- a/sys/dev/firewire/sbp.c
+++ b/sys/dev/firewire/sbp.c
@@ -318,6 +318,9 @@ SBP_DEBUG(0)
        printf("sbp_probe\n");
 END_DEBUG
 
+       if (fw_get_unit(dev) != NULL)
+               return (ENXIO);
+
        device_set_desc(dev, "SBP-2/SCSI over FireWire");
 
 #if 0
@@ -1917,7 +1920,7 @@ END_DEBUG
                sbp_cold++;
        sbp = device_get_softc(dev);
        sbp->fd.dev = dev;
-       sbp->fd.fc = fc = device_get_ivars(dev);
+       sbp->fd.fc = fc = fw_get_comm(dev);
        mtx_init(&sbp->mtx, "sbp", NULL, MTX_DEF);
 
        if (max_speed < 0)
diff --git a/sys/dev/firewire/sbp_targ.c b/sys/dev/firewire/sbp_targ.c
index 96a86c25b684..ba40d2807ae8 100644
--- a/sys/dev/firewire/sbp_targ.c
+++ b/sys/dev/firewire/sbp_targ.c
@@ -249,10 +249,12 @@ sbp_targ_probe(device_t dev)
 {
        device_t pa;
 
+       if (fw_get_unit(dev) != NULL)
+               return (ENXIO);
+
        pa = device_get_parent(dev);
-       if (device_get_unit(dev) != device_get_unit(pa)) {
+       if (device_get_unit(dev) != device_get_unit(pa))
                return (ENXIO);
-       }
 
        device_set_desc(dev, "SBP-2/SCSI over FireWire target mode");
        return (0);
@@ -1949,7 +1951,7 @@ sbp_targ_attach(device_t dev)
        bzero((void *)sc, sizeof(struct sbp_targ_softc));
 
        mtx_init(&sc->mtx, "sbp_targ", NULL, MTX_DEF);
-       sc->fd.fc = fc = device_get_ivars(dev);
+       sc->fd.fc = fc = fw_get_comm(dev);
        sc->fd.dev = dev;
        sc->fd.post_explore = (void *) sbp_targ_post_explore;
        sc->fd.post_busreset = (void *) sbp_targ_post_busreset;

Reply via email to