diff --git a/core/dfc.h b/core/dfc.h index e94c38e7b0a6..bb3631d343ee 100644 --- a/core/dfc.h +++ b/core/dfc.h @@ -292,6 +292,28 @@ TRACE_EVENT(dfc_adjust_grant, __entry->rx_bytes, __entry->inflight, __entry->a_grant) ); +TRACE_EVENT(dfc_watchdog, + + TP_PROTO(u8 mux_id, u8 bearer_id, u8 event), + + TP_ARGS(mux_id, bearer_id, event), + + TP_STRUCT__entry( + __field(u8, mux_id) + __field(u8, bearer_id) + __field(u8, event) + ), + + TP_fast_assign( + __entry->mux_id = mux_id; + __entry->bearer_id = bearer_id; + __entry->event = event; + ), + + TP_printk("mid=%u bid=%u event=%u", + __entry->mux_id, __entry->bearer_id, __entry->event) +); + #endif /* _TRACE_DFC_H */ /* This part must be outside protection */ diff --git a/core/dfc_qmi.c b/core/dfc_qmi.c index 9fe29550c2d2..f1bb16b82713 100644 --- a/core/dfc_qmi.c +++ b/core/dfc_qmi.c @@ -1034,14 +1034,18 @@ static int dfc_update_fc_map(struct net_device *dev, struct qos_info *qos, itm->grant_size = adjusted_grant; /* No further query if the adjusted grant is less - * than 20% of the original grant + * than 20% of the original grant. Add to watch to + * recover if no indication is received. */ if (dfc_qmap && is_query && - itm->grant_size < (fc_info->num_bytes / 5)) + itm->grant_size < (fc_info->num_bytes / 5)) { itm->grant_thresh = itm->grant_size; - else + qmi_rmnet_watchdog_add(itm); + } else { itm->grant_thresh = qmi_rmnet_grant_per(itm->grant_size); + qmi_rmnet_watchdog_remove(itm); + } itm->seq = fc_info->seq_num; itm->ack_req = ack_req; @@ -1143,6 +1147,7 @@ static void dfc_update_tx_link_status(struct net_device *dev, itm->grant_size = 0; itm->tcp_bidir = false; itm->bytes_in_flight = 0; + qmi_rmnet_watchdog_remove(itm); dfc_bearer_flow_ctl(dev, itm, qos); } else if (itm->grant_size == 0 && tx_status && !itm->rat_switch) { itm->grant_size = DEFAULT_GRANT; diff --git a/core/qmi_rmnet.c b/core/qmi_rmnet.c index 293a40efa517..ef429f606f50 100644 --- a/core/qmi_rmnet.c +++ b/core/qmi_rmnet.c @@ -53,6 +53,7 @@ unsigned int rmnet_wq_frequency __read_mostly = 1000; 1 : rmnet_wq_frequency/10) * (HZ/100)) #define NO_DELAY (0x0000 * HZ) #define PS_INTERVAL_KT (ms_to_ktime(1000)) +#define WATCHDOG_EXPIRE_JF (msecs_to_jiffies(50)) #ifdef CONFIG_QTI_QMI_DFC static unsigned int qmi_rmnet_scale_factor = 5; @@ -233,6 +234,89 @@ static void qmi_rmnet_reset_txq(struct net_device *dev, unsigned int txq) } } +/** + * qmi_rmnet_watchdog_fn - watchdog timer func + */ +static void qmi_rmnet_watchdog_fn(struct timer_list *t) +{ + struct rmnet_bearer_map *bearer; + + bearer = container_of(t, struct rmnet_bearer_map, watchdog); + + trace_dfc_watchdog(bearer->qos->mux_id, bearer->bearer_id, 2); + + spin_lock_bh(&bearer->qos->qos_lock); + + if (bearer->watchdog_quit) + goto done; + + /* + * Possible stall, try to recover. Enable 80% query and jumpstart + * the bearer if disabled. + */ + bearer->watchdog_expire_cnt++; + bearer->bytes_in_flight = 0; + if (!bearer->grant_size) { + bearer->grant_size = DEFAULT_CALL_GRANT; + bearer->grant_thresh = qmi_rmnet_grant_per(bearer->grant_size); + dfc_bearer_flow_ctl(bearer->qos->vnd_dev, bearer, bearer->qos); + } else { + bearer->grant_thresh = qmi_rmnet_grant_per(bearer->grant_size); + } + +done: + bearer->watchdog_started = false; + spin_unlock_bh(&bearer->qos->qos_lock); +} + +/** + * qmi_rmnet_watchdog_add - add the bearer to watch + * Needs to be called with qos_lock + */ +void qmi_rmnet_watchdog_add(struct rmnet_bearer_map *bearer) +{ + bearer->watchdog_quit = false; + + if (bearer->watchdog_started) + return; + + bearer->watchdog_started = true; + mod_timer(&bearer->watchdog, jiffies + WATCHDOG_EXPIRE_JF); + + trace_dfc_watchdog(bearer->qos->mux_id, bearer->bearer_id, 1); +} + +/** + * qmi_rmnet_watchdog_remove - remove the bearer from watch + * Needs to be called with qos_lock + */ +void qmi_rmnet_watchdog_remove(struct rmnet_bearer_map *bearer) +{ + bearer->watchdog_quit = true; + + if (!bearer->watchdog_started) + return; + + del_timer(&bearer->watchdog); + bearer->watchdog_started = false; + + trace_dfc_watchdog(bearer->qos->mux_id, bearer->bearer_id, 0); +} + +/** + * qmi_rmnet_bearer_clean - clean the removed bearer + * Needs to be called with rtn_lock but not qos_lock + */ +static void qmi_rmnet_bearer_clean(struct qos_info *qos) +{ + if (qos->removed_bearer) { + qos->removed_bearer->watchdog_quit = true; + del_timer_sync(&qos->removed_bearer->watchdog); + kfree(qos->removed_bearer); + qos->removed_bearer = NULL; + } +} + static struct rmnet_bearer_map *__qmi_rmnet_bearer_get( struct qos_info *qos_info, u8 bearer_id) { @@ -252,6 +336,8 @@ static struct rmnet_bearer_map *__qmi_rmnet_bearer_get( bearer->grant_thresh = qmi_rmnet_grant_per(bearer->grant_size); bearer->mq_idx = INVALID_MQ; bearer->ack_mq_idx = INVALID_MQ; + bearer->qos = qos_info; + timer_setup(&bearer->watchdog, qmi_rmnet_watchdog_fn, 0); list_add(&bearer->list, &qos_info->bearer_head); } @@ -287,7 +373,7 @@ static void __qmi_rmnet_bearer_put(struct net_device *dev, /* Remove from bearer map */ list_del(&bearer->list); - kfree(bearer); + qos_info->removed_bearer = bearer; } } @@ -415,6 +501,9 @@ again: done: spin_unlock_bh(&qos_info->qos_lock); + + qmi_rmnet_bearer_clean(qos_info); + return rc; } @@ -458,6 +547,9 @@ qmi_rmnet_del_flow(struct net_device *dev, struct tcmsg *tcm, netif_tx_wake_all_queues(dev); spin_unlock_bh(&qos_info->qos_lock); + + qmi_rmnet_bearer_clean(qos_info); + return 0; } @@ -726,6 +818,8 @@ void qmi_rmnet_enable_all_flows(struct net_device *dev) bearer->tcp_bidir = false; bearer->rat_switch = false; + qmi_rmnet_watchdog_remove(bearer); + if (bearer->tx_off) continue; @@ -873,7 +967,8 @@ inline unsigned int qmi_rmnet_grant_per(unsigned int grant) } EXPORT_SYMBOL(qmi_rmnet_grant_per); -void *qmi_rmnet_qos_init(struct net_device *real_dev, u8 mux_id) +void *qmi_rmnet_qos_init(struct net_device *real_dev, + struct net_device *vnd_dev, u8 mux_id) { struct qos_info *qos; @@ -883,6 +978,7 @@ void *qmi_rmnet_qos_init(struct net_device *real_dev, u8 mux_id) qos->mux_id = mux_id; qos->real_dev = real_dev; + qos->vnd_dev = vnd_dev; qos->tran_num = 0; INIT_LIST_HEAD(&qos->flow_head); INIT_LIST_HEAD(&qos->bearer_head); @@ -894,10 +990,18 @@ EXPORT_SYMBOL(qmi_rmnet_qos_init); void qmi_rmnet_qos_exit_pre(void *qos) { + struct qos_info *qosi = (struct qos_info *)qos; + struct rmnet_bearer_map *bearer; + if (!qos) return; - list_add(&((struct qos_info *)qos)->list, &qos_cleanup_list); + list_for_each_entry(bearer, &qosi->bearer_head, list) { + bearer->watchdog_quit = true; + del_timer_sync(&bearer->watchdog); + } + + list_add(&qosi->list, &qos_cleanup_list); } EXPORT_SYMBOL(qmi_rmnet_qos_exit_pre); diff --git a/core/qmi_rmnet.h b/core/qmi_rmnet.h index 856295a94d26..80689e807236 100644 --- a/core/qmi_rmnet.h +++ b/core/qmi_rmnet.h @@ -55,7 +55,8 @@ qmi_rmnet_all_flows_enabled(struct net_device *dev) #endif #ifdef CONFIG_QTI_QMI_DFC -void *qmi_rmnet_qos_init(struct net_device *real_dev, u8 mux_id); +void *qmi_rmnet_qos_init(struct net_device *real_dev, + struct net_device *vnd_dev, u8 mux_id); void qmi_rmnet_qos_exit_pre(void *qos); void qmi_rmnet_qos_exit_post(void); void qmi_rmnet_burst_fc_check(struct net_device *dev, @@ -63,7 +64,8 @@ void qmi_rmnet_burst_fc_check(struct net_device *dev, int qmi_rmnet_get_queue(struct net_device *dev, struct sk_buff *skb); #else static inline void * -qmi_rmnet_qos_init(struct net_device *real_dev, u8 mux_id) +qmi_rmnet_qos_init(struct net_device *real_dev, + struct net_device *vnd_dev, u8 mux_id) { return NULL; } diff --git a/core/qmi_rmnet_i.h b/core/qmi_rmnet_i.h index e4fe7825dc3a..924074d5c761 100644 --- a/core/qmi_rmnet_i.h +++ b/core/qmi_rmnet_i.h @@ -16,6 +16,7 @@ #include #include +#include #define MAX_MQ_NUM 16 #define MAX_CLIENT_NUM 2 @@ -36,6 +37,8 @@ extern int dfc_mode; extern int dfc_qmap; +struct qos_info; + struct rmnet_bearer_map { struct list_head list; u8 bearer_id; @@ -54,6 +57,11 @@ struct rmnet_bearer_map { u32 ack_txid; u32 mq_idx; u32 ack_mq_idx; + struct qos_info *qos; + struct timer_list watchdog; + bool watchdog_started; + bool watchdog_quit; + u32 watchdog_expire_cnt; }; struct rmnet_flow_map { @@ -79,11 +87,13 @@ struct qos_info { struct list_head list; u8 mux_id; struct net_device *real_dev; + struct net_device *vnd_dev; struct list_head flow_head; struct list_head bearer_head; struct mq_map mq[MAX_MQ_NUM]; u32 tran_num; spinlock_t qos_lock; + struct rmnet_bearer_map *removed_bearer; }; struct qmi_info { @@ -154,6 +164,11 @@ void dfc_qmap_send_ack(struct qos_info *qos, u8 bearer_id, u16 seq, u8 type); struct rmnet_bearer_map *qmi_rmnet_get_bearer_noref(struct qos_info *qos_info, u8 bearer_id); + +void qmi_rmnet_watchdog_add(struct rmnet_bearer_map *bearer); + +void qmi_rmnet_watchdog_remove(struct rmnet_bearer_map *bearer); + #else static inline struct rmnet_flow_map * qmi_rmnet_get_flow_map(struct qos_info *qos_info, @@ -197,6 +212,10 @@ dfc_qmap_client_init(void *port, int index, struct svc_info *psvc, static inline void dfc_qmap_client_exit(void *dfc_data) { } + +static inline void qmi_rmnet_watchdog_remove(struct rmnet_bearer_map *bearer) +{ +} #endif #ifdef CONFIG_QTI_QMI_POWER_COLLAPSE diff --git a/core/rmnet_vnd.c b/core/rmnet_vnd.c index 55da35c684a8..3674b3f94ef6 100644 --- a/core/rmnet_vnd.c +++ b/core/rmnet_vnd.c @@ -128,7 +128,7 @@ static void rmnet_vnd_uninit(struct net_device *dev) gro_cells_destroy(&priv->gro_cells); free_percpu(priv->pcpu_stats); - qos = priv->qos_info; + qos = rcu_dereference(priv->qos_info); RCU_INIT_POINTER(priv->qos_info, NULL); qmi_rmnet_qos_exit_pre(qos); } @@ -402,7 +402,8 @@ int rmnet_vnd_newlink(u8 id, struct net_device *rmnet_dev, rmnet_dev->rtnl_link_ops = &rmnet_link_ops; priv->mux_id = id; - priv->qos_info = qmi_rmnet_qos_init(real_dev, id); + rcu_assign_pointer(priv->qos_info, + qmi_rmnet_qos_init(real_dev, rmnet_dev, id)); netdev_dbg(rmnet_dev, "rmnet dev created\n"); }