Index: head/sys/dev/hyperv/netvsc/hv_net_vsc.h =================================================================== --- head/sys/dev/hyperv/netvsc/hv_net_vsc.h +++ head/sys/dev/hyperv/netvsc/hv_net_vsc.h @@ -207,7 +207,6 @@ struct ifnet *hn_ifp; struct ifmedia hn_media; device_t hn_dev; - int hn_carrier; int hn_if_flags; struct sx hn_lock; struct vmbus_channel *hn_prichan; @@ -236,6 +235,9 @@ struct taskqueue *hn_mgmt_taskq; struct taskqueue *hn_mgmt_taskq0; struct task hn_link_task; + struct task hn_netchg_init; + struct timeout_task hn_netchg_status; + uint32_t hn_link_flags; /* HN_LINK_FLAG_ */ uint32_t hn_caps; /* HN_CAP_ */ uint32_t hn_flags; /* HN_FLAG_ */ @@ -271,6 +273,9 @@ #define HN_CAP_TSO6 0x0100 #define HN_CAP_HASHVAL 0x0200 +#define HN_LINK_FLAG_LINKUP 0x0001 +#define HN_LINK_FLAG_NETCHG 0x0002 + /* * Externs */ Index: head/sys/dev/hyperv/netvsc/hv_netvsc_drv_freebsd.c =================================================================== --- head/sys/dev/hyperv/netvsc/hv_netvsc_drv_freebsd.c +++ head/sys/dev/hyperv/netvsc/hv_netvsc_drv_freebsd.c @@ -335,6 +335,8 @@ static void hn_start_taskfunc(void *, int); static void hn_start_txeof_taskfunc(void *, int); static void hn_link_taskfunc(void *, int); +static void hn_netchg_init_taskfunc(void *, int); +static void hn_netchg_status_taskfunc(void *, int); static void hn_suspend_mgmt_taskfunc(void *, int); static int hn_encap(struct hn_tx_ring *, struct hn_txdesc *, struct mbuf **); static int hn_create_rx_data(struct hn_softc *sc, int); @@ -360,6 +362,7 @@ static void hn_tx_resume(struct hn_softc *, int); static void hn_tx_ring_qflush(struct hn_tx_ring *); static int netvsc_detach(device_t dev); +static void hn_link_status(struct hn_softc *); static void hn_nvs_handle_notify(struct hn_softc *sc, const struct vmbus_chanpkt_hdr *pkt); @@ -482,7 +485,7 @@ ifmr->ifm_status = IFM_AVALID; ifmr->ifm_active = IFM_ETHER; - if (!sc->hn_carrier) { + if ((sc->hn_link_flags & HN_LINK_FLAG_LINKUP) == 0) { ifmr->ifm_active |= IFM_NONE; return; } @@ -563,6 +566,9 @@ taskqueue_start_threads(&sc->hn_mgmt_taskq0, 1, PI_NET, "%s mgmt", device_get_nameunit(dev)); TASK_INIT(&sc->hn_link_task, 0, hn_link_taskfunc, sc); + TASK_INIT(&sc->hn_netchg_init, 0, hn_netchg_init_taskfunc, sc); + TIMEOUT_TASK_INIT(sc->hn_mgmt_taskq0, &sc->hn_netchg_status, 0, + hn_netchg_status_taskfunc, sc); /* * Allocate ifnet and setup its name earlier, so that if_printf @@ -808,10 +814,8 @@ } static void -hn_link_taskfunc(void *xsc, int pending __unused) +hn_link_status(struct hn_softc *sc) { - struct hn_softc *sc = xsc; - struct ifnet *ifp = sc->hn_ifp; uint32_t link_status; int error; @@ -822,11 +826,51 @@ } if (link_status == NDIS_MEDIA_STATE_CONNECTED) - sc->hn_carrier = 1; + sc->hn_link_flags |= HN_LINK_FLAG_LINKUP; else - sc->hn_carrier = 0; - if_link_state_change(ifp, - sc->hn_carrier ? LINK_STATE_UP : LINK_STATE_DOWN); + sc->hn_link_flags &= ~HN_LINK_FLAG_LINKUP; + if_link_state_change(sc->hn_ifp, + (sc->hn_link_flags & HN_LINK_FLAG_LINKUP) ? + LINK_STATE_UP : LINK_STATE_DOWN); +} + +static void +hn_link_taskfunc(void *xsc, int pending __unused) +{ + struct hn_softc *sc = xsc; + + if (sc->hn_link_flags & HN_LINK_FLAG_NETCHG) + return; + hn_link_status(sc); +} + +static void +hn_netchg_init_taskfunc(void *xsc, int pending __unused) +{ + struct hn_softc *sc = xsc; + + /* Prevent any link status checks from running. */ + sc->hn_link_flags |= HN_LINK_FLAG_NETCHG; + + /* + * Fake up a [link down --> link up] state change; 5 seconds + * delay is used, which closely simulates miibus reaction + * upon link down event. + */ + sc->hn_link_flags &= ~HN_LINK_FLAG_LINKUP; + if_link_state_change(sc->hn_ifp, LINK_STATE_DOWN); + taskqueue_enqueue_timeout(sc->hn_mgmt_taskq0, + &sc->hn_netchg_status, 5 * hz); +} + +static void +hn_netchg_status_taskfunc(void *xsc, int pending __unused) +{ + struct hn_softc *sc = xsc; + + /* Re-allow link status checks. */ + sc->hn_link_flags &= ~HN_LINK_FLAG_NETCHG; + hn_link_status(sc); } void @@ -837,6 +881,14 @@ taskqueue_enqueue(sc->hn_mgmt_taskq, &sc->hn_link_task); } +void +hn_network_change(struct hn_softc *sc) +{ + + if (sc->hn_mgmt_taskq != NULL) + taskqueue_enqueue(sc->hn_mgmt_taskq, &sc->hn_netchg_init); +} + static __inline int hn_txdesc_dmamap_load(struct hn_tx_ring *txr, struct hn_txdesc *txd, struct mbuf **m_head, bus_dma_segment_t *segs, int *nsegs) @@ -3719,6 +3771,8 @@ /* * Make sure that all pending management tasks are completed. */ + taskqueue_drain(sc->hn_mgmt_taskq0, &sc->hn_netchg_init); + taskqueue_drain_timeout(sc->hn_mgmt_taskq0, &sc->hn_netchg_status); taskqueue_drain_all(sc->hn_mgmt_taskq0); } @@ -3796,10 +3850,11 @@ { /* - * Kick off link status check. + * Kick off network change detection, which will + * do link status check too. */ sc->hn_mgmt_taskq = sc->hn_mgmt_taskq0; - hn_link_status_update(sc); + hn_network_change(sc); } static void Index: head/sys/dev/hyperv/netvsc/hv_rndis_filter.c =================================================================== --- head/sys/dev/hyperv/netvsc/hv_rndis_filter.c +++ head/sys/dev/hyperv/netvsc/hv_rndis_filter.c @@ -158,6 +158,7 @@ hv_rf_receive_indicate_status(struct hn_softc *sc, const void *data, int dlen) { const struct rndis_status_msg *msg; + int ofs; if (dlen < sizeof(*msg)) { if_printf(sc->hn_ifp, "invalid RNDIS status\n"); @@ -176,8 +177,19 @@ break; case RNDIS_STATUS_NETWORK_CHANGE: - /* TODO */ - if_printf(sc->hn_ifp, "network changed\n"); + ofs = RNDIS_STBUFOFFSET_ABS(msg->rm_stbufoffset); + if (dlen < ofs + msg->rm_stbuflen || + msg->rm_stbuflen < sizeof(uint32_t)) { + if_printf(sc->hn_ifp, "network changed\n"); + } else { + uint32_t change; + + memcpy(&change, ((const uint8_t *)msg) + ofs, + sizeof(change)); + if_printf(sc->hn_ifp, "network changed, change %u\n", + change); + } + hn_network_change(sc); break; default: Index: head/sys/dev/hyperv/netvsc/if_hnvar.h =================================================================== --- head/sys/dev/hyperv/netvsc/if_hnvar.h +++ head/sys/dev/hyperv/netvsc/if_hnvar.h @@ -139,6 +139,7 @@ const struct hn_recvinfo *info); void hn_chan_rollup(struct hn_rx_ring *rxr, struct hn_tx_ring *txr); void hn_link_status_update(struct hn_softc *sc); +void hn_network_change(struct hn_softc *sc); extern struct hn_send_ctx hn_send_ctx_none; Index: head/sys/dev/hyperv/netvsc/ndis.h =================================================================== --- head/sys/dev/hyperv/netvsc/ndis.h +++ head/sys/dev/hyperv/netvsc/ndis.h @@ -32,6 +32,10 @@ #define NDIS_MEDIA_STATE_CONNECTED 0 #define NDIS_MEDIA_STATE_DISCONNECTED 1 +#define NDIS_NETCHANGE_TYPE_POSSIBLE 1 +#define NDIS_NETCHANGE_TYPE_DEFINITE 2 +#define NDIS_NETCHANGE_TYPE_FROMMEDIA 3 + #define NDIS_OFFLOAD_SET_NOCHG 0 #define NDIS_OFFLOAD_SET_ON 1 #define NDIS_OFFLOAD_SET_OFF 2 Index: head/sys/net/rndis.h =================================================================== --- head/sys/net/rndis.h +++ head/sys/net/rndis.h @@ -320,6 +320,10 @@ /* rndis_diag_info */ }; +/* stbuf offset from the beginning of rndis_status_msg. */ +#define RNDIS_STBUFOFFSET_ABS(ofs) \ + ((ofs) + __offsetof(struct rndis_status_msg, rm_status)) + /* * Immediately after rndis_status_msg.rm_stbufoffset, if a control * message is malformatted, or a packet message contains inappropriate