diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index d8873079bed4..ac47d1845fdb 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c @@ -3808,6 +3808,147 @@ static void ftrace_free_entry(struct ftrace_func_probe *entry) kfree(entry); } +struct ftrace_func_map { + struct ftrace_func_entry entry; + void *data; +}; + +struct ftrace_func_mapper { + struct ftrace_hash hash; +}; + +/** + * allocate_ftrace_func_mapper - allocate a new ftrace_func_mapper + * + * Returns a ftrace_func_mapper descriptor that can be used to map ips to data. + */ +struct ftrace_func_mapper *allocate_ftrace_func_mapper(void) +{ + struct ftrace_hash *hash; + + /* + * The mapper is simply a ftrace_hash, but since the entries + * in the hash are not ftrace_func_entry type, we define it + * as a separate structure. + */ + hash = alloc_ftrace_hash(FTRACE_HASH_DEFAULT_BITS); + return (struct ftrace_func_mapper *)hash; +} + +/** + * ftrace_func_mapper_find_ip - Find some data mapped to an ip + * @mapper: The mapper that has the ip maps + * @ip: the instruction pointer to find the data for + * + * Returns the data mapped to @ip if found otherwise NULL. The return + * is actually the address of the mapper data pointer. The address is + * returned for use cases where the data is no bigger than a long, and + * the user can use the data pointer as its data instead of having to + * allocate more memory for the reference. + */ +void **ftrace_func_mapper_find_ip(struct ftrace_func_mapper *mapper, + unsigned long ip) +{ + struct ftrace_func_entry *entry; + struct ftrace_func_map *map; + + entry = ftrace_lookup_ip(&mapper->hash, ip); + if (!entry) + return NULL; + + map = (struct ftrace_func_map *)entry; + return &map->data; +} + +/** + * ftrace_func_mapper_add_ip - Map some data to an ip + * @mapper: The mapper that has the ip maps + * @ip: The instruction pointer address to map @data to + * @data: The data to map to @ip + * + * Returns 0 on succes otherwise an error. + */ +int ftrace_func_mapper_add_ip(struct ftrace_func_mapper *mapper, + unsigned long ip, void *data) +{ + struct ftrace_func_entry *entry; + struct ftrace_func_map *map; + + entry = ftrace_lookup_ip(&mapper->hash, ip); + if (entry) + return -EBUSY; + + map = kmalloc(sizeof(*map), GFP_KERNEL); + if (!map) + return -ENOMEM; + + map->entry.ip = ip; + map->data = data; + + __add_hash_entry(&mapper->hash, &map->entry); + + return 0; +} + +/** + * ftrace_func_mapper_remove_ip - Remove an ip from the mapping + * @mapper: The mapper that has the ip maps + * @ip: The instruction pointer address to remove the data from + * + * Returns the data if it is found, otherwise NULL. + * Note, if the data pointer is used as the data itself, (see + * ftrace_func_mapper_find_ip(), then the return value may be meaningless, + * if the data pointer was set to zero. + */ +void *ftrace_func_mapper_remove_ip(struct ftrace_func_mapper *mapper, + unsigned long ip) +{ + struct ftrace_func_entry *entry; + struct ftrace_func_map *map; + void *data; + + entry = ftrace_lookup_ip(&mapper->hash, ip); + if (!entry) + return NULL; + + map = (struct ftrace_func_map *)entry; + data = map->data; + + remove_hash_entry(&mapper->hash, entry); + kfree(entry); + + return data; +} + +/** + * free_ftrace_func_mapper - free a mapping of ips and data + * @mapper: The mapper that has the ip maps + * @free_func: A function to be called on each data item. + * + * This is used to free the function mapper. The @free_func is optional + * and can be used if the data needs to be freed as well. + */ +void free_ftrace_func_mapper(struct ftrace_func_mapper *mapper, + ftrace_mapper_func free_func) +{ + struct ftrace_func_entry *entry; + struct ftrace_func_map *map; + struct hlist_head *hhd; + int size = 1 << mapper->hash.size_bits; + int i; + + if (free_func && mapper->hash.count) { + for (i = 0; i < size; i++) { + hhd = &mapper->hash.buckets[i]; + hlist_for_each_entry(entry, hhd, hlist) { + map = (struct ftrace_func_map *)entry; + free_func(map); + } + } + } + free_ftrace_hash(&mapper->hash); +} + int register_ftrace_function_probe(char *glob, struct ftrace_probe_ops *ops, void *data) diff --git a/kernel/trace/trace.h b/kernel/trace/trace.h index 0f915c264c19..dbbdee21bcc4 100644 --- a/kernel/trace/trace.h +++ b/kernel/trace/trace.h @@ -944,8 +944,22 @@ struct ftrace_probe_ops { unsigned long ip, struct ftrace_probe_ops *ops, void *data); + void *private_data; }; +struct ftrace_func_mapper; +typedef int (*ftrace_mapper_func)(void *data); + +struct ftrace_func_mapper *allocate_ftrace_func_mapper(void); +void **ftrace_func_mapper_find_ip(struct ftrace_func_mapper *mapper, + unsigned long ip); +int ftrace_func_mapper_add_ip(struct ftrace_func_mapper *mapper, + unsigned long ip, void *data); +void *ftrace_func_mapper_remove_ip(struct ftrace_func_mapper *mapper, + unsigned long ip); +void free_ftrace_func_mapper(struct ftrace_func_mapper *mapper, + ftrace_mapper_func free_func); + extern int register_ftrace_function_probe(char *glob, struct ftrace_probe_ops *ops, void *data); diff --git a/kernel/trace/trace_events.c b/kernel/trace/trace_events.c index 9dbac1881b03..ee308312fe87 100644 --- a/kernel/trace/trace_events.c +++ b/kernel/trace/trace_events.c @@ -2460,32 +2460,44 @@ struct event_probe_data { bool enable; }; -static void -event_enable_probe(unsigned long ip, unsigned long parent_ip, - struct ftrace_probe_ops *ops, void **_data) +static void update_event_probe(struct event_probe_data *data) { - struct event_probe_data **pdata = (struct event_probe_data **)_data; - struct event_probe_data *data = *pdata; - - if (!data) - return; - if (data->enable) clear_bit(EVENT_FILE_FL_SOFT_DISABLED_BIT, &data->file->flags); else set_bit(EVENT_FILE_FL_SOFT_DISABLED_BIT, &data->file->flags); } +static void +event_enable_probe(unsigned long ip, unsigned long parent_ip, + struct ftrace_probe_ops *ops, void **_data) +{ + struct ftrace_func_mapper *mapper = ops->private_data; + struct event_probe_data *data; + void **pdata; + + pdata = ftrace_func_mapper_find_ip(mapper, ip); + if (!pdata || !*pdata) + return; + + data = *pdata; + update_event_probe(data); +} + static void event_enable_count_probe(unsigned long ip, unsigned long parent_ip, struct ftrace_probe_ops *ops, void **_data) { - struct event_probe_data **pdata = (struct event_probe_data **)_data; - struct event_probe_data *data = *pdata; + struct ftrace_func_mapper *mapper = ops->private_data; + struct event_probe_data *data; + void **pdata; - if (!data) + pdata = ftrace_func_mapper_find_ip(mapper, ip); + if (!pdata || !*pdata) return; + data = *pdata; + if (!data->count) return; @@ -2496,14 +2508,23 @@ event_enable_count_probe(unsigned long ip, unsigned long parent_ip, if (data->count != -1) (data->count)--; - event_enable_probe(ip, parent_ip, ops, _data); + update_event_probe(data); } static int event_enable_print(struct seq_file *m, unsigned long ip, struct ftrace_probe_ops *ops, void *_data) { - struct event_probe_data *data = _data; + struct ftrace_func_mapper *mapper = ops->private_data; + struct event_probe_data *data; + void **pdata; + + pdata = ftrace_func_mapper_find_ip(mapper, ip); + + if (WARN_ON_ONCE(!pdata || !*pdata)) + return 0; + + data = *pdata; seq_printf(m, "%ps:", (void *)ip); @@ -2524,10 +2545,17 @@ static int event_enable_init(struct ftrace_probe_ops *ops, unsigned long ip, void **_data) { + struct ftrace_func_mapper *mapper = ops->private_data; struct event_probe_data **pdata = (struct event_probe_data **)_data; struct event_probe_data *data = *pdata; + int ret; + + ret = ftrace_func_mapper_add_ip(mapper, ip, data); + if (ret < 0) + return ret; data->ref++; + return 0; } @@ -2535,8 +2563,13 @@ static void event_enable_free(struct ftrace_probe_ops *ops, unsigned long ip, void **_data) { - struct event_probe_data **pdata = (struct event_probe_data **)_data; - struct event_probe_data *data = *pdata; + struct ftrace_func_mapper *mapper = ops->private_data; + struct event_probe_data *data; + + data = ftrace_func_mapper_remove_ip(mapper, ip); + + if (WARN_ON_ONCE(!data)) + return; if (WARN_ON_ONCE(data->ref <= 0)) return; @@ -2548,7 +2581,6 @@ event_enable_free(struct ftrace_probe_ops *ops, unsigned long ip, module_put(data->file->event_call->mod); kfree(data); } - *pdata = NULL; } static struct ftrace_probe_ops event_enable_probe_ops = { @@ -2627,6 +2659,13 @@ event_enable_func(struct ftrace_hash *hash, } ret = -ENOMEM; + + if (!ops->private_data) { + ops->private_data = allocate_ftrace_func_mapper(); + if (!ops->private_data) + goto out; + } + data = kzalloc(sizeof(*data), GFP_KERNEL); if (!data) goto out; @@ -2663,6 +2702,7 @@ event_enable_func(struct ftrace_hash *hash, ret = __ftrace_event_enable_disable(file, 1, 1); if (ret < 0) goto out_put; + ret = register_ftrace_function_probe(glob, ops, data); /* * The above returns on success the # of functions enabled,