now = ktime_get_real();
        if (ping)
                call->ping_time = now;
-       conn->params.peer->last_tx_at = ktime_get_real();
+       conn->params.peer->last_tx_at = ktime_get_seconds();
        if (ret < 0)
                trace_rxrpc_tx_fail(call->debug_id, serial, ret,
                                    rxrpc_tx_fail_call_ack);
 
        ret = kernel_sendmsg(conn->params.local->socket,
                             &msg, iov, 1, sizeof(pkt));
-       conn->params.peer->last_tx_at = ktime_get_real();
+       conn->params.peer->last_tx_at = ktime_get_seconds();
        if (ret < 0)
                trace_rxrpc_tx_fail(call->debug_id, serial, ret,
                                    rxrpc_tx_fail_call_abort);
         *     message and update the peer record
         */
        ret = kernel_sendmsg(conn->params.local->socket, &msg, iov, 2, len);
-       conn->params.peer->last_tx_at = ktime_get_real();
+       conn->params.peer->last_tx_at = ktime_get_seconds();
 
        up_read(&conn->params.local->defrag_sem);
        if (ret < 0)
                if (ret == 0) {
                        ret = kernel_sendmsg(conn->params.local->socket, &msg,
                                             iov, 2, len);
-                       conn->params.peer->last_tx_at = ktime_get_real();
+                       conn->params.peer->last_tx_at = ktime_get_seconds();
 
                        opt = IP_PMTUDISC_DO;
                        kernel_setsockopt(conn->params.local->socket, SOL_IP,
                if (ret == 0) {
                        ret = kernel_sendmsg(conn->params.local->socket, &msg,
                                             iov, 2, len);
-                       conn->params.peer->last_tx_at = ktime_get_real();
+                       conn->params.peer->last_tx_at = ktime_get_seconds();
 
                        opt = IPV6_PMTUDISC_DO;
                        kernel_setsockopt(conn->params.local->socket,
                trace_rxrpc_tx_fail(peer->debug_id, 0, ret,
                                    rxrpc_tx_fail_version_keepalive);
 
-       peer->last_tx_at = ktime_get_real();
+       peer->last_tx_at = ktime_get_seconds();
        _leave("");
 }
 
 }
 
 /*
- * Perform keep-alive pings with VERSION packets to keep any NAT alive.
+ * Perform keep-alive pings.
  */
-void rxrpc_peer_keepalive_worker(struct work_struct *work)
+static void rxrpc_peer_keepalive_dispatch(struct rxrpc_net *rxnet,
+                                         struct list_head *collector,
+                                         time64_t base,
+                                         u8 cursor)
 {
-       struct rxrpc_net *rxnet =
-               container_of(work, struct rxrpc_net, peer_keepalive_work);
        struct rxrpc_peer *peer;
-       unsigned long delay;
-       ktime_t base, now = ktime_get_real();
-       s64 diff;
-       u8 cursor, slot;
+       const u8 mask = ARRAY_SIZE(rxnet->peer_keepalive) - 1;
+       time64_t keepalive_at;
+       int slot;
 
-       base = rxnet->peer_keepalive_base;
-       cursor = rxnet->peer_keepalive_cursor;
+       spin_lock_bh(&rxnet->peer_hash_lock);
 
-       _enter("%u,%lld", cursor, ktime_sub(now, base));
+       while (!list_empty(collector)) {
+               peer = list_entry(collector->next,
+                                 struct rxrpc_peer, keepalive_link);
 
-next_bucket:
-       diff = ktime_to_ns(ktime_sub(now, base));
-       if (diff < 0)
-               goto resched;
+               list_del_init(&peer->keepalive_link);
+               if (!rxrpc_get_peer_maybe(peer))
+                       continue;
 
-       _debug("at %u", cursor);
-       spin_lock_bh(&rxnet->peer_hash_lock);
-next_peer:
-       if (!rxnet->live) {
                spin_unlock_bh(&rxnet->peer_hash_lock);
-               goto out;
-       }
 
-       /* Everything in the bucket at the cursor is processed this second; the
-        * bucket at cursor + 1 goes now + 1s and so on...
-        */
-       if (hlist_empty(&rxnet->peer_keepalive[cursor])) {
-               if (hlist_empty(&rxnet->peer_keepalive_new)) {
-                       spin_unlock_bh(&rxnet->peer_hash_lock);
-                       goto emptied_bucket;
+               keepalive_at = peer->last_tx_at + RXRPC_KEEPALIVE_TIME;
+               slot = keepalive_at - base;
+               _debug("%02x peer %u t=%d {%pISp}",
+                      cursor, peer->debug_id, slot, &peer->srx.transport);
+
+               if (keepalive_at <= base ||
+                   keepalive_at > base + RXRPC_KEEPALIVE_TIME) {
+                       rxrpc_send_keepalive(peer);
+                       slot = RXRPC_KEEPALIVE_TIME;
                }
 
-               hlist_move_list(&rxnet->peer_keepalive_new,
-                               &rxnet->peer_keepalive[cursor]);
+               /* A transmission to this peer occurred since last we examined
+                * it so put it into the appropriate future bucket.
+                */
+               slot += cursor;
+               slot &= mask;
+               spin_lock_bh(&rxnet->peer_hash_lock);
+               list_add_tail(&peer->keepalive_link,
+                             &rxnet->peer_keepalive[slot & mask]);
+               rxrpc_put_peer(peer);
        }
 
-       peer = hlist_entry(rxnet->peer_keepalive[cursor].first,
-                          struct rxrpc_peer, keepalive_link);
-       hlist_del_init(&peer->keepalive_link);
-       if (!rxrpc_get_peer_maybe(peer))
-               goto next_peer;
-
        spin_unlock_bh(&rxnet->peer_hash_lock);
+}
 
-       _debug("peer %u {%pISp}", peer->debug_id, &peer->srx.transport);
+/*
+ * Perform keep-alive pings with VERSION packets to keep any NAT alive.
+ */
+void rxrpc_peer_keepalive_worker(struct work_struct *work)
+{
+       struct rxrpc_net *rxnet =
+               container_of(work, struct rxrpc_net, peer_keepalive_work);
+       const u8 mask = ARRAY_SIZE(rxnet->peer_keepalive) - 1;
+       time64_t base, now, delay;
+       u8 cursor, stop;
+       LIST_HEAD(collector);
 
-recalc:
-       diff = ktime_divns(ktime_sub(peer->last_tx_at, base), NSEC_PER_SEC);
-       if (diff < -30 || diff > 30)
-               goto send; /* LSW of 64-bit time probably wrapped on 32-bit */
-       diff += RXRPC_KEEPALIVE_TIME - 1;
-       if (diff < 0)
-               goto send;
+       now = ktime_get_seconds();
+       base = rxnet->peer_keepalive_base;
+       cursor = rxnet->peer_keepalive_cursor;
+       _enter("%lld,%u", base - now, cursor);
 
-       slot = (diff > RXRPC_KEEPALIVE_TIME - 1) ? RXRPC_KEEPALIVE_TIME - 1 : diff;
-       if (slot == 0)
-               goto send;
+       if (!rxnet->live)
+               return;
 
-       /* A transmission to this peer occurred since last we examined it so
-        * put it into the appropriate future bucket.
+       /* Remove to a temporary list all the peers that are currently lodged
+        * in expired buckets plus all new peers.
+        *
+        * Everything in the bucket at the cursor is processed this
+        * second; the bucket at cursor + 1 goes at now + 1s and so
+        * on...
         */
-       slot = (slot + cursor) % ARRAY_SIZE(rxnet->peer_keepalive);
        spin_lock_bh(&rxnet->peer_hash_lock);
-       hlist_add_head(&peer->keepalive_link, &rxnet->peer_keepalive[slot]);
-       rxrpc_put_peer(peer);
-       goto next_peer;
-
-send:
-       rxrpc_send_keepalive(peer);
-       now = ktime_get_real();
-       goto recalc;
+       list_splice_init(&rxnet->peer_keepalive_new, &collector);
+
+       stop = cursor + ARRAY_SIZE(rxnet->peer_keepalive);
+       while (base <= now && (s8)(cursor - stop) < 0) {
+               list_splice_tail_init(&rxnet->peer_keepalive[cursor & mask],
+                                     &collector);
+               base++;
+               cursor++;
+       }
 
-emptied_bucket:
-       cursor++;
-       if (cursor >= ARRAY_SIZE(rxnet->peer_keepalive))
-               cursor = 0;
-       base = ktime_add_ns(base, NSEC_PER_SEC);
-       goto next_bucket;
+       base = now;
+       spin_unlock_bh(&rxnet->peer_hash_lock);
 
-resched:
        rxnet->peer_keepalive_base = base;
        rxnet->peer_keepalive_cursor = cursor;
-       delay = nsecs_to_jiffies(-diff) + 1;
-       timer_reduce(&rxnet->peer_keepalive_timer, jiffies + delay);
-out:
+       rxrpc_peer_keepalive_dispatch(rxnet, &collector, base, cursor);
+       ASSERT(list_empty(&collector));
+
+       /* Schedule the timer for the next occupied timeslot. */
+       cursor = rxnet->peer_keepalive_cursor;
+       stop = cursor + RXRPC_KEEPALIVE_TIME - 1;
+       for (; (s8)(cursor - stop) < 0; cursor++) {
+               if (!list_empty(&rxnet->peer_keepalive[cursor & mask]))
+                       break;
+               base++;
+       }
+
+       now = ktime_get_seconds();
+       delay = base - now;
+       if (delay < 1)
+               delay = 1;
+       delay *= HZ;
+       if (rxnet->live)
+               timer_reduce(&rxnet->peer_keepalive_timer, jiffies + delay);
+
        _leave("");
 }