xref: /linux/mm/swap_cgroup.c (revision bbfd5594756011167b8f8de9a00e0c946afda1e6)
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