Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net/l2tp: convert tunnel rwlock_t to rcu

Previously commit e02d494d2c60 ("l2tp: Convert rwlock to RCU") converted
most, but not all, rwlock instances in the l2tp subsystem to RCU.

The remaining rwlock protects the per-tunnel hashlist of sessions which
is used for session lookups in the UDP-encap data path.

Convert the remaining rwlock to rcu to improve performance of UDP-encap
tunnels.

Note that the tunnel and session, which both live on RCU-protected
lists, use slightly different approaches to incrementing their refcounts
in the various getter functions.

The tunnel has to use refcount_inc_not_zero because the tunnel shutdown
process involves dropping the refcount to zero prior to synchronizing
RCU readers (via. kfree_rcu).

By contrast, the session shutdown removes the session from the list(s)
it is on, synchronizes with readers, and then decrements the session
refcount. Since the getter functions increment the session refcount
with the RCU read lock held we prevent getters seeing a zero session
refcount, and therefore don't need to use refcount_inc_not_zero.

Signed-off-by: Tom Parkin <tparkin@katalix.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Tom Parkin and committed by
David S. Miller
07b8ca37 275f37ea

+31 -36
+25 -27
net/l2tp/l2tp_core.c
··· 250 250 251 251 session_list = l2tp_session_id_hash(tunnel, session_id); 252 252 253 - read_lock_bh(&tunnel->hlist_lock); 254 - hlist_for_each_entry(session, session_list, hlist) 253 + rcu_read_lock_bh(); 254 + hlist_for_each_entry_rcu(session, session_list, hlist) 255 255 if (session->session_id == session_id) { 256 256 l2tp_session_inc_refcount(session); 257 - read_unlock_bh(&tunnel->hlist_lock); 257 + rcu_read_unlock_bh(); 258 258 259 259 return session; 260 260 } 261 - read_unlock_bh(&tunnel->hlist_lock); 261 + rcu_read_unlock_bh(); 262 262 263 263 return NULL; 264 264 } ··· 291 291 struct l2tp_session *session; 292 292 int count = 0; 293 293 294 - read_lock_bh(&tunnel->hlist_lock); 294 + rcu_read_lock_bh(); 295 295 for (hash = 0; hash < L2TP_HASH_SIZE; hash++) { 296 - hlist_for_each_entry(session, &tunnel->session_hlist[hash], hlist) { 296 + hlist_for_each_entry_rcu(session, &tunnel->session_hlist[hash], hlist) { 297 297 if (++count > nth) { 298 298 l2tp_session_inc_refcount(session); 299 - read_unlock_bh(&tunnel->hlist_lock); 299 + rcu_read_unlock_bh(); 300 300 return session; 301 301 } 302 302 } 303 303 } 304 304 305 - read_unlock_bh(&tunnel->hlist_lock); 305 + rcu_read_unlock_bh(); 306 306 307 307 return NULL; 308 308 } ··· 347 347 348 348 head = l2tp_session_id_hash(tunnel, session->session_id); 349 349 350 - write_lock_bh(&tunnel->hlist_lock); 350 + spin_lock_bh(&tunnel->hlist_lock); 351 351 if (!tunnel->acpt_newsess) { 352 352 err = -ENODEV; 353 353 goto err_tlock; ··· 384 384 l2tp_tunnel_inc_refcount(tunnel); 385 385 } 386 386 387 - hlist_add_head(&session->hlist, head); 388 - write_unlock_bh(&tunnel->hlist_lock); 387 + hlist_add_head_rcu(&session->hlist, head); 388 + spin_unlock_bh(&tunnel->hlist_lock); 389 389 390 390 trace_register_session(session); 391 391 ··· 394 394 err_tlock_pnlock: 395 395 spin_unlock_bh(&pn->l2tp_session_hlist_lock); 396 396 err_tlock: 397 - write_unlock_bh(&tunnel->hlist_lock); 397 + spin_unlock_bh(&tunnel->hlist_lock); 398 398 399 399 return err; 400 400 } ··· 1170 1170 /* Remove the session from core hashes */ 1171 1171 if (tunnel) { 1172 1172 /* Remove from the per-tunnel hash */ 1173 - write_lock_bh(&tunnel->hlist_lock); 1174 - hlist_del_init(&session->hlist); 1175 - write_unlock_bh(&tunnel->hlist_lock); 1173 + spin_lock_bh(&tunnel->hlist_lock); 1174 + hlist_del_init_rcu(&session->hlist); 1175 + spin_unlock_bh(&tunnel->hlist_lock); 1176 1176 1177 1177 /* For L2TPv3 we have a per-net hash: remove from there, too */ 1178 1178 if (tunnel->version != L2TP_HDR_VER_2) { ··· 1181 1181 spin_lock_bh(&pn->l2tp_session_hlist_lock); 1182 1182 hlist_del_init_rcu(&session->global_hlist); 1183 1183 spin_unlock_bh(&pn->l2tp_session_hlist_lock); 1184 - synchronize_rcu(); 1185 1184 } 1185 + 1186 + synchronize_rcu(); 1186 1187 } 1187 1188 } 1188 1189 ··· 1191 1190 */ 1192 1191 static void l2tp_tunnel_closeall(struct l2tp_tunnel *tunnel) 1193 1192 { 1194 - int hash; 1195 - struct hlist_node *walk; 1196 - struct hlist_node *tmp; 1197 1193 struct l2tp_session *session; 1194 + int hash; 1198 1195 1199 - write_lock_bh(&tunnel->hlist_lock); 1196 + spin_lock_bh(&tunnel->hlist_lock); 1200 1197 tunnel->acpt_newsess = false; 1201 1198 for (hash = 0; hash < L2TP_HASH_SIZE; hash++) { 1202 1199 again: 1203 - hlist_for_each_safe(walk, tmp, &tunnel->session_hlist[hash]) { 1204 - session = hlist_entry(walk, struct l2tp_session, hlist); 1205 - hlist_del_init(&session->hlist); 1200 + hlist_for_each_entry_rcu(session, &tunnel->session_hlist[hash], hlist) { 1201 + hlist_del_init_rcu(&session->hlist); 1206 1202 1207 - write_unlock_bh(&tunnel->hlist_lock); 1203 + spin_unlock_bh(&tunnel->hlist_lock); 1208 1204 l2tp_session_delete(session); 1209 - write_lock_bh(&tunnel->hlist_lock); 1205 + spin_lock_bh(&tunnel->hlist_lock); 1210 1206 1211 1207 /* Now restart from the beginning of this hash 1212 1208 * chain. We always remove a session from the ··· 1213 1215 goto again; 1214 1216 } 1215 1217 } 1216 - write_unlock_bh(&tunnel->hlist_lock); 1218 + spin_unlock_bh(&tunnel->hlist_lock); 1217 1219 } 1218 1220 1219 1221 /* Tunnel socket destroy hook for UDP encapsulation */ ··· 1406 1408 1407 1409 tunnel->magic = L2TP_TUNNEL_MAGIC; 1408 1410 sprintf(&tunnel->name[0], "tunl %u", tunnel_id); 1409 - rwlock_init(&tunnel->hlist_lock); 1411 + spin_lock_init(&tunnel->hlist_lock); 1410 1412 tunnel->acpt_newsess = true; 1411 1413 1412 1414 tunnel->encap = encap;
+1 -1
net/l2tp/l2tp_core.h
··· 160 160 unsigned long dead; 161 161 162 162 struct rcu_head rcu; 163 - rwlock_t hlist_lock; /* protect session_hlist */ 163 + spinlock_t hlist_lock; /* write-protection for session_hlist */ 164 164 bool acpt_newsess; /* indicates whether this tunnel accepts 165 165 * new sessions. Protected by hlist_lock. 166 166 */
+5 -8
net/l2tp/l2tp_debugfs.c
··· 120 120 static void l2tp_dfs_seq_tunnel_show(struct seq_file *m, void *v) 121 121 { 122 122 struct l2tp_tunnel *tunnel = v; 123 + struct l2tp_session *session; 123 124 int session_count = 0; 124 125 int hash; 125 - struct hlist_node *walk; 126 - struct hlist_node *tmp; 127 126 128 - read_lock_bh(&tunnel->hlist_lock); 127 + rcu_read_lock_bh(); 129 128 for (hash = 0; hash < L2TP_HASH_SIZE; hash++) { 130 - hlist_for_each_safe(walk, tmp, &tunnel->session_hlist[hash]) { 131 - struct l2tp_session *session; 132 - 133 - session = hlist_entry(walk, struct l2tp_session, hlist); 129 + hlist_for_each_entry_rcu(session, &tunnel->session_hlist[hash], hlist) { 130 + /* Session ID of zero is a dummy/reserved value used by pppol2tp */ 134 131 if (session->session_id == 0) 135 132 continue; 136 133 137 134 session_count++; 138 135 } 139 136 } 140 - read_unlock_bh(&tunnel->hlist_lock); 137 + rcu_read_unlock_bh(); 141 138 142 139 seq_printf(m, "\nTUNNEL %u peer %u", tunnel->tunnel_id, tunnel->peer_tunnel_id); 143 140 if (tunnel->sock) {