net: l2: ethernet: fix k_work API usage in carrier on/off handling

net_eth_carrier_on() and net_eth_carrier_off() call k_work_init() on
work item that can be pending or still be processed in another thread.
This results in undefined behavior.

Initialize work item once and use an atomic flag to switch between
up/down carrier state. Submit work to workqueue whenever up/down carrier
state changes, so that last state is always properly propagated to
network interface layer.

While at it, save network interface pointer during ethernet context
initialization, so that is becomes static (and thread-safe) during whole
ethernet context lifetime.

Signed-off-by: Marcin Niestroj <m.niestroj@grinn-global.com>
This commit is contained in:
Marcin Niestroj 2021-03-16 17:18:54 +01:00 committed by Jukka Rissanen
commit c1f7b9f45a
2 changed files with 51 additions and 43 deletions

View file

@ -1002,55 +1002,52 @@ int net_eth_vlan_disable(struct net_if *iface, uint16_t tag)
NET_L2_INIT(ETHERNET_L2, ethernet_recv, ethernet_send, ethernet_enable,
ethernet_flags);
static void carrier_on(struct k_work *work)
static void carrier_on_off(struct k_work *work)
{
struct ethernet_context *ctx = CONTAINER_OF(work,
struct ethernet_context,
carrier_mgmt.work);
struct ethernet_context *ctx = CONTAINER_OF(work, struct ethernet_context,
carrier_work);
bool eth_carrier_up;
NET_DBG("Carrier ON for interface %p", ctx->carrier_mgmt.iface);
if (ctx->iface == NULL) {
return;
}
ethernet_mgmt_raise_carrier_on_event(ctx->carrier_mgmt.iface);
eth_carrier_up = atomic_test_bit(&ctx->flags, ETH_CARRIER_UP);
net_if_up(ctx->carrier_mgmt.iface);
}
if (eth_carrier_up == ctx->is_net_carrier_up) {
return;
}
static void carrier_off(struct k_work *work)
{
struct ethernet_context *ctx = CONTAINER_OF(work,
struct ethernet_context,
carrier_mgmt.work);
ctx->is_net_carrier_up = eth_carrier_up;
NET_DBG("Carrier OFF for interface %p", ctx->carrier_mgmt.iface);
NET_DBG("Carrier %s for interface %p", eth_carrier_up ? "ON" : "OFF",
ctx->iface);
ethernet_mgmt_raise_carrier_off_event(ctx->carrier_mgmt.iface);
net_if_carrier_down(ctx->carrier_mgmt.iface);
}
static void handle_carrier(struct ethernet_context *ctx,
struct net_if *iface,
k_work_handler_t handler)
{
k_work_init(&ctx->carrier_mgmt.work, handler);
ctx->carrier_mgmt.iface = iface;
k_work_submit(&ctx->carrier_mgmt.work);
if (eth_carrier_up) {
ethernet_mgmt_raise_carrier_on_event(ctx->iface);
net_if_up(ctx->iface);
} else {
ethernet_mgmt_raise_carrier_off_event(ctx->iface);
net_if_carrier_down(ctx->iface);
}
}
void net_eth_carrier_on(struct net_if *iface)
{
struct ethernet_context *ctx = net_if_l2_data(iface);
handle_carrier(ctx, iface, carrier_on);
if (!atomic_test_and_set_bit(&ctx->flags, ETH_CARRIER_UP)) {
k_work_submit(&ctx->carrier_work);
}
}
void net_eth_carrier_off(struct net_if *iface)
{
struct ethernet_context *ctx = net_if_l2_data(iface);
handle_carrier(ctx, iface, carrier_off);
if (atomic_test_and_clear_bit(&ctx->flags, ETH_CARRIER_UP)) {
k_work_submit(&ctx->carrier_work);
}
}
#if defined(CONFIG_PTP_CLOCK)
@ -1149,6 +1146,8 @@ void ethernet_init(struct net_if *iface)
NET_DBG("Initializing Ethernet L2 %p for iface %p", ctx, iface);
ctx->ethernet_l2_flags = NET_L2_MULTICAST;
ctx->iface = iface;
k_work_init(&ctx->carrier_work, carrier_on_off);
if (net_eth_get_hw_capabilities(iface) & ETHERNET_PROMISC_MODE) {
ctx->ethernet_l2_flags |= NET_L2_PROMISC_MODE;