wired: ensure carrier changes clear queued state where appropriate

Buggy kernel drivers sometimes default the carrier to ON when they
are capable of link-detection but the carrier is actually off, and
they quickly switch the carrier OFF when they determine actual
carrier state (cdc-ether, for example).

The initial carrier ON event would trigger a queued state change
from UNAVAILABLE to DISCONNECTED, which may auto-activate the
device.  But before that state change happens, if a carrier OFF
event comes in, that queued state was not getting cleared and
the device would transition to DISCONNECTED even though the
carrier was off.

To ensure that never happens, and that we don't enter states that
aren't valid when the carrier is off, we need to clear any queued
state changes that wouldn't be valid in the new carrier state.
This commit is contained in:
Dan Williams 2012-08-30 15:59:31 -05:00
parent 46e0af2942
commit 2318b3c525
3 changed files with 42 additions and 18 deletions

View file

@ -71,4 +71,8 @@ NMConnectionProvider *nm_device_get_connection_provider (NMDevice *device);
void nm_device_recheck_available_connections (NMDevice *device);
void nm_device_queued_state_clear (NMDevice *device);
NMDeviceState nm_device_queued_state_peek (NMDevice *device);
#endif /* NM_DEVICE_PRIVATE_H */

View file

@ -147,6 +147,13 @@ carrier_action_defer_cb (gpointer user_data)
if (state == NM_DEVICE_STATE_UNAVAILABLE) {
if (priv->carrier)
nm_device_queue_state (NM_DEVICE (self), NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_CARRIER);
else {
/* clear any queued state changes if they wouldn't be valid when the
* carrier is off.
*/
if (nm_device_queued_state_peek (NM_DEVICE (self)) >= NM_DEVICE_STATE_DISCONNECTED)
nm_device_queued_state_clear (NM_DEVICE (self));
}
} else if (state >= NM_DEVICE_STATE_DISCONNECTED) {
if (!priv->carrier)
nm_device_queue_state (NM_DEVICE (self), NM_DEVICE_STATE_UNAVAILABLE, NM_DEVICE_STATE_REASON_CARRIER);

View file

@ -3061,19 +3061,6 @@ clear_act_request (NMDevice *self)
priv->act_request = NULL;
}
static void
queued_state_clear (NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
if (priv->queued_state.id) {
nm_log_dbg (LOGD_DEVICE, "(%s): clearing queued state transition (id %d)",
nm_device_get_iface (self), priv->queued_state.id);
g_source_remove (priv->queued_state.id);
}
memset (&priv->queued_state, 0, sizeof (priv->queued_state));
}
static void
dhcp4_cleanup (NMDevice *self, gboolean stop, gboolean release)
{
@ -3242,7 +3229,7 @@ nm_device_deactivate (NMDevice *self, NMDeviceStateReason reason)
}
/* Clear any queued transitions */
queued_state_clear (self);
nm_device_queued_state_clear (self);
priv->ip4_state = priv->ip6_state = IP_NONE;
@ -3810,7 +3797,7 @@ dispose (GObject *object)
}
/* Clear any queued transitions */
queued_state_clear (self);
nm_device_queued_state_clear (self);
/* Clean up and stop DHCP */
dhcp4_cleanup (self, take_down, FALSE);
@ -4563,7 +4550,7 @@ nm_device_state_changed (NMDevice *device,
reason);
/* Clear any queued transitions */
queued_state_clear (device);
nm_device_queued_state_clear (device);
/* Cache the activation request for the dispatcher */
req = priv->act_request ? g_object_ref (priv->act_request) : NULL;
@ -4683,7 +4670,7 @@ queued_set_state (gpointer user_data)
priv->queued_state.id = 0;
nm_device_state_changed (self, priv->queued_state.state, priv->queued_state.reason);
}
queued_state_clear (self);
nm_device_queued_state_clear (self);
return FALSE;
}
@ -4702,7 +4689,7 @@ nm_device_queue_state (NMDevice *self,
/* We should only ever have one delayed state transition at a time */
if (priv->queued_state.id) {
g_warn_if_fail (priv->queued_state.id == 0);
queued_state_clear (self);
nm_device_queued_state_clear (self);
}
priv->queued_state.state = state;
@ -4714,6 +4701,32 @@ nm_device_queue_state (NMDevice *self,
priv->queued_state.id);
}
NMDeviceState
nm_device_queued_state_peek (NMDevice *self)
{
NMDevicePrivate *priv;
g_return_val_if_fail (self != NULL, NM_DEVICE_STATE_UNKNOWN);
g_return_val_if_fail (NM_IS_DEVICE (self), NM_DEVICE_STATE_UNKNOWN);
priv = NM_DEVICE_GET_PRIVATE (self);
return priv->queued_state.id ? priv->queued_state.state : NM_DEVICE_STATE_UNKNOWN;
}
void
nm_device_queued_state_clear (NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE (self);
if (priv->queued_state.id) {
nm_log_dbg (LOGD_DEVICE, "(%s): clearing queued state transition (id %d)",
nm_device_get_iface (self), priv->queued_state.id);
g_source_remove (priv->queued_state.id);
}
memset (&priv->queued_state, 0, sizeof (priv->queued_state));
}
NMDeviceState
nm_device_get_state (NMDevice *device)
{