Index: sys/net/if_lagg.h =================================================================== --- sys/net/if_lagg.h +++ sys/net/if_lagg.h @@ -208,9 +208,19 @@ /* List of interfaces to have the MAC address modified */ struct lagg_llq { + struct ifnet *llq_ifp; uint8_t llq_lladdr[ETHER_ADDR_LEN]; lagg_llqtype llq_type; + +#define LAGG_ASYNC_SET_MAC_ADDR 0x0001 +#define LAGG_ASYNC_SET_MTU 0x0002 +#define LAGG_ASYNC_SET_MASK (LAGG_ASYNC_SET_MAC_ADDR | LAGG_ASYNC_SET_MTU) + + uint16_t flags; + struct ifreq *ifr; /* Common to all lagg member ports */ + uint32_t old_mtu; + int (*llq_ioctl)(struct ifnet *, u_long, caddr_t); SLIST_ENTRY(lagg_llq) llq_entries; }; @@ -235,9 +245,9 @@ SLIST_HEAD(__tplhd, lagg_port) sc_ports; /* list of interfaces */ SLIST_ENTRY(lagg_softc) sc_entries; - struct task sc_lladdr_task; + struct task sc_llq_task; /* ASYNC ops enqueued here */ SLIST_HEAD(__llqhd, lagg_llq) sc_llq_head; /* interfaces to program - the lladdr on */ + the MAC, lladdr on */ eventhandler_tag vlan_attach; eventhandler_tag vlan_detach; struct callout sc_callout; Index: sys/net/if_lagg.c =================================================================== --- sys/net/if_lagg.c +++ sys/net/if_lagg.c @@ -101,7 +101,9 @@ static void lagg_lladdr(struct lagg_softc *, uint8_t *); static void lagg_capabilities(struct lagg_softc *); static void lagg_port_lladdr(struct lagg_port *, uint8_t *, lagg_llqtype); -static void lagg_port_setlladdr(void *, int); +static void lagg_port_ops(void *, int); +static void lagg_port_set_mtu(struct lagg_softc *, struct lagg_llq *); +static void lagg_port_setlladdr(struct lagg_llq *); static int lagg_port_create(struct lagg_softc *, struct ifnet *); static int lagg_port_destroy(struct lagg_port *, int); static struct mbuf *lagg_input(struct ifnet *, struct mbuf *); @@ -130,6 +132,10 @@ static void lagg_media_status(struct ifnet *, struct ifmediareq *); static struct lagg_port *lagg_link_active(struct lagg_softc *, struct lagg_port *); +static int lagg_change_mtu(struct ifnet *ifp, struct ifreq *ifr); +static struct lagg_llq *lagg_get_llq_entry(struct lagg_softc *, + struct ifnet *); +static void lagg_llq_list_cleanup(struct lagg_softc *); /* Simple round robin */ static void lagg_rr_attach(struct lagg_softc *); @@ -487,7 +493,7 @@ LAGG_LOCK_INIT(sc); SLIST_INIT(&sc->sc_ports); - TASK_INIT(&sc->sc_lladdr_task, 0, lagg_port_setlladdr, sc); + TASK_INIT(&sc->sc_llq_task, 0, lagg_port_ops, sc); /* Initialise pseudo media types */ ifmedia_init(&sc->sc_media, 0, lagg_media_change, @@ -553,7 +559,7 @@ SLIST_REMOVE(&V_lagg_list, sc, lagg_softc, sc_entries); LAGG_LIST_UNLOCK(); - taskqueue_drain(taskqueue_swi, &sc->sc_lladdr_task); + taskqueue_drain(taskqueue_swi, &sc->sc_llq_task); LAGG_LOCK_DESTROY(sc); free(sc, M_DEVBUF); } @@ -672,24 +678,24 @@ llq->llq_ifp = ifp; llq->llq_type = llq_type; + llq->flags |= LAGG_ASYNC_SET_MAC_ADDR; bcopy(lladdr, llq->llq_lladdr, ETHER_ADDR_LEN); /* XXX: We should insert to tail */ SLIST_INSERT_HEAD(&sc->sc_llq_head, llq, llq_entries); - taskqueue_enqueue(taskqueue_swi, &sc->sc_lladdr_task); + taskqueue_enqueue(taskqueue_swi, &sc->sc_llq_task); } /* - * Set the interface MAC address from a taskqueue to avoid a LOR. + * Set the interface MTU, MAC address from a taskqueue to avoid a LOR. * * Set noinline to be dtrace-friendly */ static __noinline void -lagg_port_setlladdr(void *arg, int pending) +lagg_port_ops(void *arg, int pending) { struct lagg_softc *sc = (struct lagg_softc *)arg; struct lagg_llq *llq, *head; - struct ifnet *ifp; /* Grab a local reference of the queue and remove it from the softc */ LAGG_WLOCK(sc); @@ -697,10 +703,172 @@ SLIST_FIRST(&sc->sc_llq_head) = NULL; LAGG_WUNLOCK(sc); + if (!head) + return; + /* * Traverse the queue and set the lladdr on each ifp. It is safe to do * unlocked as we have the only reference to it. */ + + if (head->flags & LAGG_ASYNC_SET_MTU) { + /* + * Set the new MTU on the lagg port and its members. + * If failed, REVERT to the old MTU. + */ + lagg_port_set_mtu(sc, head); + } + + if (head->flags & LAGG_ASYNC_SET_MAC_ADDR) { + /* + * Set the MAC address. + */ + lagg_port_setlladdr(head); + } + + if (!(head->flags & LAGG_ASYNC_SET_MASK)) + if_printf(sc->sc_ifp, + "Lagg port ops: flag (0x%x) not supported\n", head->flags); + + /* Finally free the per port llq entry; DO THIS OPERATION LAST */ + for (llq = head; llq != NULL; llq = head) { + head = SLIST_NEXT(llq, llq_entries); + free(llq, M_DEVBUF); + } +} + +static void +lagg_port_set_mtu(struct lagg_softc *sc, struct lagg_llq *head) +{ + struct lagg_llq *llq; + int err = 0; + + /* Set the new MTU on the lagg interface */ + LAGG_WLOCK(sc); + sc->sc_ifp->if_mtu = head->ifr->ifr_mtu; + LAGG_WUNLOCK(sc); + + /* Set the new MTU on the physical interface */ + for (llq = head; llq != NULL; llq = SLIST_NEXT(llq, llq_entries)) { + err = (*llq->llq_ioctl)(llq->llq_ifp, SIOCSIFMTU, (caddr_t)llq->ifr); + if (err) { + if_printf(llq->llq_ifp, + "Failed to change MTU from %d to %d (err %d)\n", + llq->old_mtu, llq->ifr->ifr_mtu, err); + break; + } + } + + if (err) { + /* Restore the old MTU on the lagg interface */ + LAGG_WLOCK(sc); + sc->sc_ifp->if_mtu = head->old_mtu; + LAGG_WUNLOCK(sc); + + /* Restore the old MTU on the physical interface */ + for (llq = head; llq != NULL; llq = SLIST_NEXT(llq, llq_entries)) { + llq->ifr->ifr_mtu = llq->old_mtu; + err = (*llq->llq_ioctl) + (llq->llq_ifp, SIOCSIFMTU, (caddr_t)llq->ifr); + if (err) { + if_printf(llq->llq_ifp, + "Failed to restore MTU to %d (err %d)\n", + llq->old_mtu, err); + } + } + } + + /* Common to all lagg member ports */ + free(head->ifr, M_DEVBUF); +} + +static void lagg_llq_list_cleanup(struct lagg_softc *sc) +{ + struct lagg_llq *head, *llq; + + LAGG_WLOCK_ASSERT(sc); + + head = SLIST_FIRST(&sc->sc_llq_head); + SLIST_FIRST(&sc->sc_llq_head) = NULL; + + for (llq = head; llq != NULL; llq = head) { + head = SLIST_NEXT(llq, llq_entries); + free(llq, M_DEVBUF); + } +} + +static struct lagg_llq * +lagg_get_llq_entry(struct lagg_softc *sc, struct ifnet *ifp) +{ + struct lagg_llq *llq; + + LAGG_WLOCK_ASSERT(sc); + + SLIST_FOREACH(llq, &sc->sc_llq_head, llq_entries) { + if (llq->llq_ifp == ifp) { + break; + } + } + return llq; +} + +static int +lagg_change_mtu(struct ifnet *ifp, struct ifreq *ifr) +{ + struct lagg_softc *sc = (struct lagg_softc *)ifp->if_softc; + struct lagg_port *lp; + struct lagg_llq *llq; + struct ifreq *ifr_copy, *old_ifr = NULL; + + /* No change in MTU */ + if (ifp->if_mtu == ifr->ifr_mtu) + return (0); + + ifr_copy = malloc(sizeof(struct ifreq), M_DEVBUF, M_NOWAIT); + if (ifr_copy == NULL) + return (ENOMEM); + + bcopy(ifr, ifr_copy, sizeof(struct ifreq)); + LAGG_WLOCK(sc); + /* All lagg ports (MTU change) shall be queued atomic */ + SLIST_FOREACH(lp, &sc->sc_ports, lp_entries) { + /* Check to make sure its not already queued */ + llq = lagg_get_llq_entry(sc, lp->lp_ifp); + if (!llq) { + llq = malloc(sizeof(struct lagg_llq), M_DEVBUF, M_NOWAIT); + if (llq == NULL) { + lagg_llq_list_cleanup(sc); + LAGG_WUNLOCK(sc); + free(ifr_copy, M_DEVBUF); + if (old_ifr) + free(old_ifr, M_DEVBUF); + return (ENOMEM); + } + SLIST_INSERT_HEAD(&sc->sc_llq_head, llq, llq_entries); + } else { + old_ifr = llq->ifr; + } + + /* Update the llq even if pending, it might have updated */ + llq->ifr = ifr_copy; + llq->old_mtu = ifp->if_mtu; + llq->llq_ifp = lp->lp_ifp; + llq->llq_ioctl = lp->lp_ioctl; + llq->flags |= LAGG_ASYNC_SET_MTU; + } + taskqueue_enqueue(taskqueue_swi, &sc->sc_llq_task); + LAGG_WUNLOCK(sc); + if (old_ifr) + free(old_ifr, M_DEVBUF); + return (0); +} + +static void +lagg_port_setlladdr(struct lagg_llq *head) +{ + struct lagg_llq *llq; + struct ifnet *ifp; + for (llq = head; llq != NULL; llq = head) { ifp = llq->llq_ifp; @@ -718,7 +886,6 @@ EVENTHANDLER_INVOKE(iflladdr_event, ifp); CURVNET_RESTORE(); head = SLIST_NEXT(llq, llq_entries); - free(llq, M_DEVBUF); } } @@ -1529,10 +1696,12 @@ break; case SIOCSIFCAP: - case SIOCSIFMTU: - /* Do not allow the MTU or caps to be directly changed */ + /* Do not allow the CAPs to be directly changed. */ error = EINVAL; break; + case SIOCSIFMTU: + error = lagg_change_mtu(ifp, ifr); + break; default: error = ether_ioctl(ifp, cmd, data);