1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
25d1ea48bSJohannes Weiner #include <linux/swap_cgroup.h>
35d1ea48bSJohannes Weiner #include <linux/vmalloc.h>
45d1ea48bSJohannes Weiner #include <linux/mm.h>
55d1ea48bSJohannes Weiner
65d1ea48bSJohannes Weiner #include <linux/swapops.h> /* depends on mm.h include */
75d1ea48bSJohannes Weiner
85d1ea48bSJohannes Weiner static DEFINE_MUTEX(swap_cgroup_mutex);
98eb92ed2SRoman Gushchin
102b3a58b1SKairui Song /* Pack two cgroup id (short) of two entries in one swap_cgroup (atomic_t) */
112b3a58b1SKairui Song #define ID_PER_SC (sizeof(struct swap_cgroup) / sizeof(unsigned short))
122b3a58b1SKairui Song #define ID_SHIFT (BITS_PER_TYPE(unsigned short))
132b3a58b1SKairui Song #define ID_MASK (BIT(ID_SHIFT) - 1)
148eb92ed2SRoman Gushchin struct swap_cgroup {
152b3a58b1SKairui Song atomic_t ids;
168eb92ed2SRoman Gushchin };
178eb92ed2SRoman Gushchin
185d1ea48bSJohannes Weiner struct swap_cgroup_ctrl {
198eb92ed2SRoman Gushchin struct swap_cgroup *map;
205d1ea48bSJohannes Weiner };
215d1ea48bSJohannes Weiner
225d1ea48bSJohannes Weiner static struct swap_cgroup_ctrl swap_cgroup_ctrl[MAX_SWAPFILES];
235d1ea48bSJohannes Weiner
__swap_cgroup_id_lookup(struct swap_cgroup * map,pgoff_t offset)242b3a58b1SKairui Song static unsigned short __swap_cgroup_id_lookup(struct swap_cgroup *map,
252b3a58b1SKairui Song pgoff_t offset)
265d1ea48bSJohannes Weiner {
272b3a58b1SKairui Song unsigned int shift = (offset % ID_PER_SC) * ID_SHIFT;
282b3a58b1SKairui Song unsigned int old_ids = atomic_read(&map[offset / ID_PER_SC].ids);
295d1ea48bSJohannes Weiner
302b3a58b1SKairui Song BUILD_BUG_ON(!is_power_of_2(ID_PER_SC));
312b3a58b1SKairui Song BUILD_BUG_ON(sizeof(struct swap_cgroup) != sizeof(atomic_t));
322b3a58b1SKairui Song
332b3a58b1SKairui Song return (old_ids >> shift) & ID_MASK;
342b3a58b1SKairui Song }
352b3a58b1SKairui Song
__swap_cgroup_id_xchg(struct swap_cgroup * map,pgoff_t offset,unsigned short new_id)362b3a58b1SKairui Song static unsigned short __swap_cgroup_id_xchg(struct swap_cgroup *map,
372b3a58b1SKairui Song pgoff_t offset,
382b3a58b1SKairui Song unsigned short new_id)
392b3a58b1SKairui Song {
402b3a58b1SKairui Song unsigned short old_id;
412b3a58b1SKairui Song struct swap_cgroup *sc = &map[offset / ID_PER_SC];
422b3a58b1SKairui Song unsigned int shift = (offset % ID_PER_SC) * ID_SHIFT;
432b3a58b1SKairui Song unsigned int new_ids, old_ids = atomic_read(&sc->ids);
442b3a58b1SKairui Song
452b3a58b1SKairui Song do {
462b3a58b1SKairui Song old_id = (old_ids >> shift) & ID_MASK;
472b3a58b1SKairui Song new_ids = (old_ids & ~(ID_MASK << shift));
482b3a58b1SKairui Song new_ids |= ((unsigned int)new_id) << shift;
492b3a58b1SKairui Song } while (!atomic_try_cmpxchg(&sc->ids, &old_ids, new_ids));
502b3a58b1SKairui Song
512b3a58b1SKairui Song return old_id;
525d1ea48bSJohannes Weiner }
535d1ea48bSJohannes Weiner
545d1ea48bSJohannes Weiner /**
5567691831SKairui Song * swap_cgroup_record - record mem_cgroup for a set of swap entries.
5667691831SKairui Song * These entries must belong to one single folio, and that folio
5767691831SKairui Song * must be being charged for swap space (swap out), and these
5867691831SKairui Song * entries must not have been charged
5967691831SKairui Song *
6067691831SKairui Song * @folio: the folio that the swap entry belongs to
6173f839b6SMuchun Song * @id: mem_cgroup ID to be recorded
6267691831SKairui Song * @ent: the first swap entry to be recorded
6367691831SKairui Song */
swap_cgroup_record(struct folio * folio,unsigned short id,swp_entry_t ent)6473f839b6SMuchun Song void swap_cgroup_record(struct folio *folio, unsigned short id,
6573f839b6SMuchun Song swp_entry_t ent)
6667691831SKairui Song {
6767691831SKairui Song unsigned int nr_ents = folio_nr_pages(folio);
6867691831SKairui Song struct swap_cgroup *map;
6967691831SKairui Song pgoff_t offset, end;
7067691831SKairui Song unsigned short old;
7167691831SKairui Song
7267691831SKairui Song offset = swp_offset(ent);
7367691831SKairui Song end = offset + nr_ents;
7467691831SKairui Song map = swap_cgroup_ctrl[swp_type(ent)].map;
7567691831SKairui Song
7667691831SKairui Song do {
7773f839b6SMuchun Song old = __swap_cgroup_id_xchg(map, offset, id);
7867691831SKairui Song VM_BUG_ON(old);
7967691831SKairui Song } while (++offset != end);
8067691831SKairui Song }
8167691831SKairui Song
8267691831SKairui Song /**
8367691831SKairui Song * swap_cgroup_clear - clear mem_cgroup for a set of swap entries.
8467691831SKairui Song * These entries must be being uncharged from swap. They either
8567691831SKairui Song * belongs to one single folio in the swap cache (swap in for
8667691831SKairui Song * cgroup v1), or no longer have any users (slot freeing).
8767691831SKairui Song *
8838d8b4e6SHuang Ying * @ent: the first swap entry to be recorded into
8938d8b4e6SHuang Ying * @nr_ents: number of swap entries to be recorded
905d1ea48bSJohannes Weiner *
9167691831SKairui Song * Returns the existing old value.
925d1ea48bSJohannes Weiner */
swap_cgroup_clear(swp_entry_t ent,unsigned int nr_ents)9367691831SKairui Song unsigned short swap_cgroup_clear(swp_entry_t ent, unsigned int nr_ents)
945d1ea48bSJohannes Weiner {
95*d9a04a26SJohannes Weiner pgoff_t offset, end;
962b3a58b1SKairui Song struct swap_cgroup *map;
9767691831SKairui Song unsigned short old, iter = 0;
985d1ea48bSJohannes Weiner
9967691831SKairui Song offset = swp_offset(ent);
10067691831SKairui Song end = offset + nr_ents;
10167691831SKairui Song map = swap_cgroup_ctrl[swp_type(ent)].map;
1025d1ea48bSJohannes Weiner
1032b3a58b1SKairui Song do {
10467691831SKairui Song old = __swap_cgroup_id_xchg(map, offset, 0);
10567691831SKairui Song if (!iter)
10667691831SKairui Song iter = old;
1072b3a58b1SKairui Song VM_BUG_ON(iter != old);
1082b3a58b1SKairui Song } while (++offset != end);
1095d1ea48bSJohannes Weiner
1105d1ea48bSJohannes Weiner return old;
1115d1ea48bSJohannes Weiner }
1125d1ea48bSJohannes Weiner
1135d1ea48bSJohannes Weiner /**
1145d1ea48bSJohannes Weiner * lookup_swap_cgroup_id - lookup mem_cgroup id tied to swap entry
1155d1ea48bSJohannes Weiner * @ent: swap entry to be looked up.
1165d1ea48bSJohannes Weiner *
1175d1ea48bSJohannes Weiner * Returns ID of mem_cgroup at success. 0 at failure. (0 is invalid ID)
1185d1ea48bSJohannes Weiner */
lookup_swap_cgroup_id(swp_entry_t ent)1195d1ea48bSJohannes Weiner unsigned short lookup_swap_cgroup_id(swp_entry_t ent)
1205d1ea48bSJohannes Weiner {
1212b3a58b1SKairui Song struct swap_cgroup_ctrl *ctrl;
1222b3a58b1SKairui Song
123bea67dccSBarry Song if (mem_cgroup_disabled())
124bea67dccSBarry Song return 0;
1252b3a58b1SKairui Song
1262b3a58b1SKairui Song ctrl = &swap_cgroup_ctrl[swp_type(ent)];
1272b3a58b1SKairui Song return __swap_cgroup_id_lookup(ctrl->map, swp_offset(ent));
1285d1ea48bSJohannes Weiner }
1295d1ea48bSJohannes Weiner
swap_cgroup_swapon(int type,unsigned long max_pages)1305d1ea48bSJohannes Weiner int swap_cgroup_swapon(int type, unsigned long max_pages)
1315d1ea48bSJohannes Weiner {
1328eb92ed2SRoman Gushchin struct swap_cgroup *map;
1335d1ea48bSJohannes Weiner struct swap_cgroup_ctrl *ctrl;
1345d1ea48bSJohannes Weiner
135c91bdc93SJohannes Weiner if (mem_cgroup_disabled())
136c91bdc93SJohannes Weiner return 0;
137c91bdc93SJohannes Weiner
1382b3a58b1SKairui Song BUILD_BUG_ON(sizeof(unsigned short) * ID_PER_SC !=
1392b3a58b1SKairui Song sizeof(struct swap_cgroup));
14067691831SKairui Song map = vzalloc(DIV_ROUND_UP(max_pages, ID_PER_SC) *
1412b3a58b1SKairui Song sizeof(struct swap_cgroup));
1428eb92ed2SRoman Gushchin if (!map)
1435d1ea48bSJohannes Weiner goto nomem;
1445d1ea48bSJohannes Weiner
1455d1ea48bSJohannes Weiner ctrl = &swap_cgroup_ctrl[type];
1465d1ea48bSJohannes Weiner mutex_lock(&swap_cgroup_mutex);
1478eb92ed2SRoman Gushchin ctrl->map = map;
1485d1ea48bSJohannes Weiner mutex_unlock(&swap_cgroup_mutex);
1495d1ea48bSJohannes Weiner
1505d1ea48bSJohannes Weiner return 0;
1515d1ea48bSJohannes Weiner nomem:
1521170532bSJoe Perches pr_info("couldn't allocate enough memory for swap_cgroup\n");
1531170532bSJoe Perches pr_info("swap_cgroup can be disabled by swapaccount=0 boot option\n");
1545d1ea48bSJohannes Weiner return -ENOMEM;
1555d1ea48bSJohannes Weiner }
1565d1ea48bSJohannes Weiner
swap_cgroup_swapoff(int type)1575d1ea48bSJohannes Weiner void swap_cgroup_swapoff(int type)
1585d1ea48bSJohannes Weiner {
1598eb92ed2SRoman Gushchin struct swap_cgroup *map;
1605d1ea48bSJohannes Weiner struct swap_cgroup_ctrl *ctrl;
1615d1ea48bSJohannes Weiner
162c91bdc93SJohannes Weiner if (mem_cgroup_disabled())
163c91bdc93SJohannes Weiner return;
164c91bdc93SJohannes Weiner
1655d1ea48bSJohannes Weiner mutex_lock(&swap_cgroup_mutex);
1665d1ea48bSJohannes Weiner ctrl = &swap_cgroup_ctrl[type];
1675d1ea48bSJohannes Weiner map = ctrl->map;
1685d1ea48bSJohannes Weiner ctrl->map = NULL;
1695d1ea48bSJohannes Weiner mutex_unlock(&swap_cgroup_mutex);
1705d1ea48bSJohannes Weiner
1715d1ea48bSJohannes Weiner vfree(map);
1725d1ea48bSJohannes Weiner }
173