482 lines · 10.6 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0#include <linux/init.h>#include <linux/static_call.h>#include <linux/bug.h>#include <linux/smp.h>#include <linux/sort.h>#include <linux/slab.h>#include <linux/module.h>#include <linux/cpu.h>#include <linux/processor.h>#include <asm/sections.h> extern struct static_call_site __start_static_call_sites[],			       __stop_static_call_sites[]; static bool static_call_initialized; /* mutex to protect key modules/sites */static DEFINE_MUTEX(static_call_mutex); static void static_call_lock(void){	mutex_lock(&static_call_mutex);} static void static_call_unlock(void){	mutex_unlock(&static_call_mutex);} static inline void *static_call_addr(struct static_call_site *site){	return (void *)((long)site->addr + (long)&site->addr);}  static inline struct static_call_key *static_call_key(const struct static_call_site *site){	return (struct static_call_key *)		(((long)site->key + (long)&site->key) & ~STATIC_CALL_SITE_FLAGS);} /* These assume the key is word-aligned. */static inline bool static_call_is_init(struct static_call_site *site){	return ((long)site->key + (long)&site->key) & STATIC_CALL_SITE_INIT;} static inline bool static_call_is_tail(struct static_call_site *site){	return ((long)site->key + (long)&site->key) & STATIC_CALL_SITE_TAIL;} static inline void static_call_set_init(struct static_call_site *site){	site->key = ((long)static_call_key(site) | STATIC_CALL_SITE_INIT) -		    (long)&site->key;} static int static_call_site_cmp(const void *_a, const void *_b){	const struct static_call_site *a = _a;	const struct static_call_site *b = _b;	const struct static_call_key *key_a = static_call_key(a);	const struct static_call_key *key_b = static_call_key(b); 	if (key_a < key_b)		return -1; 	if (key_a > key_b)		return 1; 	return 0;} static void static_call_site_swap(void *_a, void *_b, int size){	long delta = (unsigned long)_a - (unsigned long)_b;	struct static_call_site *a = _a;	struct static_call_site *b = _b;	struct static_call_site tmp = *a; 	a->addr = b->addr  - delta;	a->key  = b->key   - delta; 	b->addr = tmp.addr + delta;	b->key  = tmp.key  + delta;} static inline void static_call_sort_entries(struct static_call_site *start,					    struct static_call_site *stop){	sort(start, stop - start, sizeof(struct static_call_site),	     static_call_site_cmp, static_call_site_swap);} static inline bool static_call_key_has_mods(struct static_call_key *key){	return !(key->type & 1);} static inline struct static_call_mod *static_call_key_next(struct static_call_key *key){	if (!static_call_key_has_mods(key))		return NULL; 	return key->mods;} static inline struct static_call_site *static_call_key_sites(struct static_call_key *key){	if (static_call_key_has_mods(key))		return NULL; 	return (struct static_call_site *)(key->type & ~1);} void __static_call_update(struct static_call_key *key, void *tramp, void *func){	struct static_call_site *site, *stop;	struct static_call_mod *site_mod, first; 	cpus_read_lock();	static_call_lock(); 	if (key->func == func)		goto done; 	key->func = func; 	arch_static_call_transform(NULL, tramp, func, false); 	/*	 * If uninitialized, we'll not update the callsites, but they still	 * point to the trampoline and we just patched that.	 */	if (WARN_ON_ONCE(!static_call_initialized))		goto done; 	first = (struct static_call_mod){		.next = static_call_key_next(key),		.mod = NULL,		.sites = static_call_key_sites(key),	}; 	for (site_mod = &first; site_mod; site_mod = site_mod->next) {		struct module *mod = site_mod->mod; 		if (!site_mod->sites) {			/*			 * This can happen if the static call key is defined in			 * a module which doesn't use it.			 *			 * It also happens in the has_mods case, where the			 * 'first' entry has no sites associated with it.			 */			continue;		} 		stop = __stop_static_call_sites; #ifdef CONFIG_MODULES		if (mod) {			stop = mod->static_call_sites +			       mod->num_static_call_sites;		}#endif 		for (site = site_mod->sites;		     site < stop && static_call_key(site) == key; site++) {			void *site_addr = static_call_addr(site); 			if (static_call_is_init(site)) {				/*				 * Don't write to call sites which were in				 * initmem and have since been freed.				 */				if (!mod && system_state >= SYSTEM_RUNNING)					continue;				if (mod && !within_module_init((unsigned long)site_addr, mod))					continue;			} 			if (!kernel_text_address((unsigned long)site_addr)) {				WARN_ONCE(1, "can't patch static call site at %pS",					  site_addr);				continue;			} 			arch_static_call_transform(site_addr, NULL, func,				static_call_is_tail(site));		}	} done:	static_call_unlock();	cpus_read_unlock();}EXPORT_SYMBOL_GPL(__static_call_update); static int __static_call_init(struct module *mod,			      struct static_call_site *start,			      struct static_call_site *stop){	struct static_call_site *site;	struct static_call_key *key, *prev_key = NULL;	struct static_call_mod *site_mod; 	if (start == stop)		return 0; 	static_call_sort_entries(start, stop); 	for (site = start; site < stop; site++) {		void *site_addr = static_call_addr(site); 		if ((mod && within_module_init((unsigned long)site_addr, mod)) ||		    (!mod && init_section_contains(site_addr, 1)))			static_call_set_init(site); 		key = static_call_key(site);		if (key != prev_key) {			prev_key = key; 			/*			 * For vmlinux (!mod) avoid the allocation by storing			 * the sites pointer in the key itself. Also see			 * __static_call_update()'s @first.			 *			 * This allows architectures (eg. x86) to call			 * static_call_init() before memory allocation works.			 */			if (!mod) {				key->sites = site;				key->type |= 1;				goto do_transform;			} 			site_mod = kzalloc(sizeof(*site_mod), GFP_KERNEL);			if (!site_mod)				return -ENOMEM; 			/*			 * When the key has a direct sites pointer, extract			 * that into an explicit struct static_call_mod, so we			 * can have a list of modules.			 */			if (static_call_key_sites(key)) {				site_mod->mod = NULL;				site_mod->next = NULL;				site_mod->sites = static_call_key_sites(key); 				key->mods = site_mod; 				site_mod = kzalloc(sizeof(*site_mod), GFP_KERNEL);				if (!site_mod)					return -ENOMEM;			} 			site_mod->mod = mod;			site_mod->sites = site;			site_mod->next = static_call_key_next(key);			key->mods = site_mod;		} do_transform:		arch_static_call_transform(site_addr, NULL, key->func,				static_call_is_tail(site));	} 	return 0;} static int addr_conflict(struct static_call_site *site, void *start, void *end){	unsigned long addr = (unsigned long)static_call_addr(site); 	if (addr <= (unsigned long)end &&	    addr + CALL_INSN_SIZE > (unsigned long)start)		return 1; 	return 0;} static int __static_call_text_reserved(struct static_call_site *iter_start,				       struct static_call_site *iter_stop,				       void *start, void *end){	struct static_call_site *iter = iter_start; 	while (iter < iter_stop) {		if (addr_conflict(iter, start, end))			return 1;		iter++;	} 	return 0;} #ifdef CONFIG_MODULES static int __static_call_mod_text_reserved(void *start, void *end){	struct module *mod;	int ret; 	preempt_disable();	mod = __module_text_address((unsigned long)start);	WARN_ON_ONCE(__module_text_address((unsigned long)end) != mod);	if (!try_module_get(mod))		mod = NULL;	preempt_enable(); 	if (!mod)		return 0; 	ret = __static_call_text_reserved(mod->static_call_sites,			mod->static_call_sites + mod->num_static_call_sites,			start, end); 	module_put(mod); 	return ret;} static int static_call_add_module(struct module *mod){	return __static_call_init(mod, mod->static_call_sites,				  mod->static_call_sites + mod->num_static_call_sites);} static void static_call_del_module(struct module *mod){	struct static_call_site *start = mod->static_call_sites;	struct static_call_site *stop = mod->static_call_sites +					mod->num_static_call_sites;	struct static_call_key *key, *prev_key = NULL;	struct static_call_mod *site_mod, **prev;	struct static_call_site *site; 	for (site = start; site < stop; site++) {		key = static_call_key(site);		if (key == prev_key)			continue; 		prev_key = key; 		for (prev = &key->mods, site_mod = key->mods;		     site_mod && site_mod->mod != mod;		     prev = &site_mod->next, site_mod = site_mod->next)			; 		if (!site_mod)			continue; 		*prev = site_mod->next;		kfree(site_mod);	}} static int static_call_module_notify(struct notifier_block *nb,				     unsigned long val, void *data){	struct module *mod = data;	int ret = 0; 	cpus_read_lock();	static_call_lock(); 	switch (val) {	case MODULE_STATE_COMING:		ret = static_call_add_module(mod);		if (ret) {			WARN(1, "Failed to allocate memory for static calls");			static_call_del_module(mod);		}		break;	case MODULE_STATE_GOING:		static_call_del_module(mod);		break;	} 	static_call_unlock();	cpus_read_unlock(); 	return notifier_from_errno(ret);} static struct notifier_block static_call_module_nb = {	.notifier_call = static_call_module_notify,}; #else static inline int __static_call_mod_text_reserved(void *start, void *end){	return 0;} #endif /* CONFIG_MODULES */ int static_call_text_reserved(void *start, void *end){	int ret = __static_call_text_reserved(__start_static_call_sites,			__stop_static_call_sites, start, end); 	if (ret)		return ret; 	return __static_call_mod_text_reserved(start, end);} int __init static_call_init(void){	int ret; 	if (static_call_initialized)		return 0; 	cpus_read_lock();	static_call_lock();	ret = __static_call_init(NULL, __start_static_call_sites,				 __stop_static_call_sites);	static_call_unlock();	cpus_read_unlock(); 	if (ret) {		pr_err("Failed to allocate memory for static_call!\n");		BUG();	} 	static_call_initialized = true; #ifdef CONFIG_MODULES	register_module_notifier(&static_call_module_nb);#endif	return 0;}early_initcall(static_call_init); #ifdef CONFIG_STATIC_CALL_SELFTEST static int func_a(int x){	return x+1;} static int func_b(int x){	return x+2;} DEFINE_STATIC_CALL(sc_selftest, func_a); static struct static_call_data {      int (*func)(int);      int val;      int expect;} static_call_data [] __initdata = {      { NULL,   2, 3 },      { func_b, 2, 4 },      { func_a, 2, 3 }}; static int __init test_static_call_init(void){      int i;       for (i = 0; i < ARRAY_SIZE(static_call_data); i++ ) {	      struct static_call_data *scd = &static_call_data[i];               if (scd->func)                      static_call_update(sc_selftest, scd->func);               WARN_ON(static_call(sc_selftest)(scd->val) != scd->expect);      }       return 0;}early_initcall(test_static_call_init); #endif /* CONFIG_STATIC_CALL_SELFTEST */