sync to Linus v4.13-rc2 for subsystem developers to work against
This commit is contained in:
+1
-1
@@ -82,7 +82,7 @@ obj-$(CONFIG_KPROBES) += kprobes.o
|
||||
obj-$(CONFIG_KGDB) += debug/
|
||||
obj-$(CONFIG_DETECT_HUNG_TASK) += hung_task.o
|
||||
obj-$(CONFIG_LOCKUP_DETECTOR) += watchdog.o
|
||||
obj-$(CONFIG_HARDLOCKUP_DETECTOR) += watchdog_hld.o
|
||||
obj-$(CONFIG_HARDLOCKUP_DETECTOR_PERF) += watchdog_hld.o
|
||||
obj-$(CONFIG_SECCOMP) += seccomp.o
|
||||
obj-$(CONFIG_RELAY) += relay.o
|
||||
obj-$(CONFIG_SYSCTL) += utsname_sysctl.o
|
||||
|
||||
+4
-4
@@ -114,14 +114,14 @@ static void async_run_entry_fn(struct work_struct *work)
|
||||
ktime_t uninitialized_var(calltime), delta, rettime;
|
||||
|
||||
/* 1) run (and print duration) */
|
||||
if (initcall_debug && system_state == SYSTEM_BOOTING) {
|
||||
if (initcall_debug && system_state < SYSTEM_RUNNING) {
|
||||
pr_debug("calling %lli_%pF @ %i\n",
|
||||
(long long)entry->cookie,
|
||||
entry->func, task_pid_nr(current));
|
||||
calltime = ktime_get();
|
||||
}
|
||||
entry->func(entry->data, entry->cookie);
|
||||
if (initcall_debug && system_state == SYSTEM_BOOTING) {
|
||||
if (initcall_debug && system_state < SYSTEM_RUNNING) {
|
||||
rettime = ktime_get();
|
||||
delta = ktime_sub(rettime, calltime);
|
||||
pr_debug("initcall %lli_%pF returned 0 after %lld usecs\n",
|
||||
@@ -284,14 +284,14 @@ void async_synchronize_cookie_domain(async_cookie_t cookie, struct async_domain
|
||||
{
|
||||
ktime_t uninitialized_var(starttime), delta, endtime;
|
||||
|
||||
if (initcall_debug && system_state == SYSTEM_BOOTING) {
|
||||
if (initcall_debug && system_state < SYSTEM_RUNNING) {
|
||||
pr_debug("async_waiting @ %i\n", task_pid_nr(current));
|
||||
starttime = ktime_get();
|
||||
}
|
||||
|
||||
wait_event(async_done, lowest_in_progress(domain) >= cookie);
|
||||
|
||||
if (initcall_debug && system_state == SYSTEM_BOOTING) {
|
||||
if (initcall_debug && system_state < SYSTEM_RUNNING) {
|
||||
endtime = ktime_get();
|
||||
delta = ktime_sub(endtime, starttime);
|
||||
|
||||
|
||||
+30
-32
@@ -575,12 +575,16 @@ static void kauditd_retry_skb(struct sk_buff *skb)
|
||||
|
||||
/**
|
||||
* auditd_reset - Disconnect the auditd connection
|
||||
* @ac: auditd connection state
|
||||
*
|
||||
* Description:
|
||||
* Break the auditd/kauditd connection and move all the queued records into the
|
||||
* hold queue in case auditd reconnects.
|
||||
* hold queue in case auditd reconnects. It is important to note that the @ac
|
||||
* pointer should never be dereferenced inside this function as it may be NULL
|
||||
* or invalid, you can only compare the memory address! If @ac is NULL then
|
||||
* the connection will always be reset.
|
||||
*/
|
||||
static void auditd_reset(void)
|
||||
static void auditd_reset(const struct auditd_connection *ac)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct sk_buff *skb;
|
||||
@@ -590,17 +594,21 @@ static void auditd_reset(void)
|
||||
spin_lock_irqsave(&auditd_conn_lock, flags);
|
||||
ac_old = rcu_dereference_protected(auditd_conn,
|
||||
lockdep_is_held(&auditd_conn_lock));
|
||||
if (ac && ac != ac_old) {
|
||||
/* someone already registered a new auditd connection */
|
||||
spin_unlock_irqrestore(&auditd_conn_lock, flags);
|
||||
return;
|
||||
}
|
||||
rcu_assign_pointer(auditd_conn, NULL);
|
||||
spin_unlock_irqrestore(&auditd_conn_lock, flags);
|
||||
|
||||
if (ac_old)
|
||||
call_rcu(&ac_old->rcu, auditd_conn_free);
|
||||
|
||||
/* flush all of the main and retry queues to the hold queue */
|
||||
/* flush the retry queue to the hold queue, but don't touch the main
|
||||
* queue since we need to process that normally for multicast */
|
||||
while ((skb = skb_dequeue(&audit_retry_queue)))
|
||||
kauditd_hold_skb(skb);
|
||||
while ((skb = skb_dequeue(&audit_queue)))
|
||||
kauditd_hold_skb(skb);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -633,6 +641,7 @@ static int auditd_send_unicast_skb(struct sk_buff *skb)
|
||||
ac = rcu_dereference(auditd_conn);
|
||||
if (!ac) {
|
||||
rcu_read_unlock();
|
||||
kfree_skb(skb);
|
||||
rc = -ECONNREFUSED;
|
||||
goto err;
|
||||
}
|
||||
@@ -649,8 +658,8 @@ static int auditd_send_unicast_skb(struct sk_buff *skb)
|
||||
return rc;
|
||||
|
||||
err:
|
||||
if (rc == -ECONNREFUSED)
|
||||
auditd_reset();
|
||||
if (ac && rc == -ECONNREFUSED)
|
||||
auditd_reset(ac);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -795,9 +804,9 @@ static int kauditd_thread(void *dummy)
|
||||
rc = kauditd_send_queue(sk, portid,
|
||||
&audit_hold_queue, UNICAST_RETRIES,
|
||||
NULL, kauditd_rehold_skb);
|
||||
if (rc < 0) {
|
||||
if (ac && rc < 0) {
|
||||
sk = NULL;
|
||||
auditd_reset();
|
||||
auditd_reset(ac);
|
||||
goto main_queue;
|
||||
}
|
||||
|
||||
@@ -805,9 +814,9 @@ static int kauditd_thread(void *dummy)
|
||||
rc = kauditd_send_queue(sk, portid,
|
||||
&audit_retry_queue, UNICAST_RETRIES,
|
||||
NULL, kauditd_hold_skb);
|
||||
if (rc < 0) {
|
||||
if (ac && rc < 0) {
|
||||
sk = NULL;
|
||||
auditd_reset();
|
||||
auditd_reset(ac);
|
||||
goto main_queue;
|
||||
}
|
||||
|
||||
@@ -815,12 +824,13 @@ main_queue:
|
||||
/* process the main queue - do the multicast send and attempt
|
||||
* unicast, dump failed record sends to the retry queue; if
|
||||
* sk == NULL due to previous failures we will just do the
|
||||
* multicast send and move the record to the retry queue */
|
||||
* multicast send and move the record to the hold queue */
|
||||
rc = kauditd_send_queue(sk, portid, &audit_queue, 1,
|
||||
kauditd_send_multicast_skb,
|
||||
kauditd_retry_skb);
|
||||
if (sk == NULL || rc < 0)
|
||||
auditd_reset();
|
||||
(sk ?
|
||||
kauditd_retry_skb : kauditd_hold_skb));
|
||||
if (ac && rc < 0)
|
||||
auditd_reset(ac);
|
||||
sk = NULL;
|
||||
|
||||
/* drop our netns reference, no auditd sends past this line */
|
||||
@@ -1230,7 +1240,7 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
|
||||
auditd_pid, 1);
|
||||
|
||||
/* unregister the auditd connection */
|
||||
auditd_reset();
|
||||
auditd_reset(NULL);
|
||||
}
|
||||
}
|
||||
if (s.mask & AUDIT_STATUS_RATE_LIMIT) {
|
||||
@@ -1999,22 +2009,10 @@ void audit_log_cap(struct audit_buffer *ab, char *prefix, kernel_cap_t *cap)
|
||||
|
||||
static void audit_log_fcaps(struct audit_buffer *ab, struct audit_names *name)
|
||||
{
|
||||
kernel_cap_t *perm = &name->fcap.permitted;
|
||||
kernel_cap_t *inh = &name->fcap.inheritable;
|
||||
int log = 0;
|
||||
|
||||
if (!cap_isclear(*perm)) {
|
||||
audit_log_cap(ab, "cap_fp", perm);
|
||||
log = 1;
|
||||
}
|
||||
if (!cap_isclear(*inh)) {
|
||||
audit_log_cap(ab, "cap_fi", inh);
|
||||
log = 1;
|
||||
}
|
||||
|
||||
if (log)
|
||||
audit_log_format(ab, " cap_fe=%d cap_fver=%x",
|
||||
name->fcap.fE, name->fcap_ver);
|
||||
audit_log_cap(ab, "cap_fp", &name->fcap.permitted);
|
||||
audit_log_cap(ab, "cap_fi", &name->fcap.inheritable);
|
||||
audit_log_format(ab, " cap_fe=%d cap_fver=%x",
|
||||
name->fcap.fE, name->fcap_ver);
|
||||
}
|
||||
|
||||
static inline int audit_copy_fcaps(struct audit_names *name,
|
||||
|
||||
+15
-14
@@ -68,6 +68,7 @@ struct audit_cap_data {
|
||||
unsigned int fE; /* effective bit of file cap */
|
||||
kernel_cap_t effective; /* effective set of process */
|
||||
};
|
||||
kernel_cap_t ambient;
|
||||
};
|
||||
|
||||
/* When fs/namei.c:getname() is called, we store the pointer in name and bump
|
||||
@@ -247,13 +248,13 @@ struct audit_netlink_list {
|
||||
struct sk_buff_head q;
|
||||
};
|
||||
|
||||
int audit_send_list(void *);
|
||||
int audit_send_list(void *_dest);
|
||||
|
||||
extern int selinux_audit_rule_update(void);
|
||||
|
||||
extern struct mutex audit_filter_mutex;
|
||||
extern int audit_del_rule(struct audit_entry *);
|
||||
extern void audit_free_rule_rcu(struct rcu_head *);
|
||||
extern int audit_del_rule(struct audit_entry *entry);
|
||||
extern void audit_free_rule_rcu(struct rcu_head *head);
|
||||
extern struct list_head audit_filter_list[];
|
||||
|
||||
extern struct audit_entry *audit_dupe_rule(struct audit_krule *old);
|
||||
@@ -301,17 +302,17 @@ extern int audit_exe_compare(struct task_struct *tsk, struct audit_fsnotify_mark
|
||||
#endif /* CONFIG_AUDIT_WATCH */
|
||||
|
||||
#ifdef CONFIG_AUDIT_TREE
|
||||
extern struct audit_chunk *audit_tree_lookup(const struct inode *);
|
||||
extern void audit_put_chunk(struct audit_chunk *);
|
||||
extern bool audit_tree_match(struct audit_chunk *, struct audit_tree *);
|
||||
extern int audit_make_tree(struct audit_krule *, char *, u32);
|
||||
extern int audit_add_tree_rule(struct audit_krule *);
|
||||
extern int audit_remove_tree_rule(struct audit_krule *);
|
||||
extern struct audit_chunk *audit_tree_lookup(const struct inode *inode);
|
||||
extern void audit_put_chunk(struct audit_chunk *chunk);
|
||||
extern bool audit_tree_match(struct audit_chunk *chunk, struct audit_tree *tree);
|
||||
extern int audit_make_tree(struct audit_krule *rule, char *pathname, u32 op);
|
||||
extern int audit_add_tree_rule(struct audit_krule *rule);
|
||||
extern int audit_remove_tree_rule(struct audit_krule *rule);
|
||||
extern void audit_trim_trees(void);
|
||||
extern int audit_tag_tree(char *old, char *new);
|
||||
extern const char *audit_tree_path(struct audit_tree *);
|
||||
extern void audit_put_tree(struct audit_tree *);
|
||||
extern void audit_kill_trees(struct list_head *);
|
||||
extern const char *audit_tree_path(struct audit_tree *tree);
|
||||
extern void audit_put_tree(struct audit_tree *tree);
|
||||
extern void audit_kill_trees(struct list_head *list);
|
||||
#else
|
||||
#define audit_remove_tree_rule(rule) BUG()
|
||||
#define audit_add_tree_rule(rule) -EINVAL
|
||||
@@ -323,7 +324,7 @@ extern void audit_kill_trees(struct list_head *);
|
||||
#define audit_kill_trees(list) BUG()
|
||||
#endif
|
||||
|
||||
extern char *audit_unpack_string(void **, size_t *, size_t);
|
||||
extern char *audit_unpack_string(void **bufp, size_t *remain, size_t len);
|
||||
|
||||
extern pid_t audit_sig_pid;
|
||||
extern kuid_t audit_sig_uid;
|
||||
@@ -333,7 +334,7 @@ extern int audit_filter(int msgtype, unsigned int listtype);
|
||||
|
||||
#ifdef CONFIG_AUDITSYSCALL
|
||||
extern int audit_signal_info(int sig, struct task_struct *t);
|
||||
extern void audit_filter_inodes(struct task_struct *, struct audit_context *);
|
||||
extern void audit_filter_inodes(struct task_struct *tsk, struct audit_context *ctx);
|
||||
extern struct list_head *audit_killed_trees(void);
|
||||
#else
|
||||
#define audit_signal_info(s,t) AUDIT_DISABLED
|
||||
|
||||
+9
-3
@@ -1261,6 +1261,7 @@ static void show_special(struct audit_context *context, int *call_panic)
|
||||
audit_log_cap(ab, "cap_pi", &context->capset.cap.inheritable);
|
||||
audit_log_cap(ab, "cap_pp", &context->capset.cap.permitted);
|
||||
audit_log_cap(ab, "cap_pe", &context->capset.cap.effective);
|
||||
audit_log_cap(ab, "cap_pa", &context->capset.cap.ambient);
|
||||
break;
|
||||
case AUDIT_MMAP:
|
||||
audit_log_format(ab, "fd=%d flags=0x%x", context->mmap.fd,
|
||||
@@ -1382,9 +1383,11 @@ static void audit_log_exit(struct audit_context *context, struct task_struct *ts
|
||||
audit_log_cap(ab, "old_pp", &axs->old_pcap.permitted);
|
||||
audit_log_cap(ab, "old_pi", &axs->old_pcap.inheritable);
|
||||
audit_log_cap(ab, "old_pe", &axs->old_pcap.effective);
|
||||
audit_log_cap(ab, "new_pp", &axs->new_pcap.permitted);
|
||||
audit_log_cap(ab, "new_pi", &axs->new_pcap.inheritable);
|
||||
audit_log_cap(ab, "new_pe", &axs->new_pcap.effective);
|
||||
audit_log_cap(ab, "old_pa", &axs->old_pcap.ambient);
|
||||
audit_log_cap(ab, "pp", &axs->new_pcap.permitted);
|
||||
audit_log_cap(ab, "pi", &axs->new_pcap.inheritable);
|
||||
audit_log_cap(ab, "pe", &axs->new_pcap.effective);
|
||||
audit_log_cap(ab, "pa", &axs->new_pcap.ambient);
|
||||
break; }
|
||||
|
||||
}
|
||||
@@ -2342,10 +2345,12 @@ int __audit_log_bprm_fcaps(struct linux_binprm *bprm,
|
||||
ax->old_pcap.permitted = old->cap_permitted;
|
||||
ax->old_pcap.inheritable = old->cap_inheritable;
|
||||
ax->old_pcap.effective = old->cap_effective;
|
||||
ax->old_pcap.ambient = old->cap_ambient;
|
||||
|
||||
ax->new_pcap.permitted = new->cap_permitted;
|
||||
ax->new_pcap.inheritable = new->cap_inheritable;
|
||||
ax->new_pcap.effective = new->cap_effective;
|
||||
ax->new_pcap.ambient = new->cap_ambient;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2364,6 +2369,7 @@ void __audit_log_capset(const struct cred *new, const struct cred *old)
|
||||
context->capset.cap.effective = new->cap_effective;
|
||||
context->capset.cap.inheritable = new->cap_effective;
|
||||
context->capset.cap.permitted = new->cap_permitted;
|
||||
context->capset.cap.ambient = new->cap_ambient;
|
||||
context->type = AUDIT_CAPSET;
|
||||
}
|
||||
|
||||
|
||||
+35
-21
@@ -86,6 +86,7 @@ static struct bpf_map *array_map_alloc(union bpf_attr *attr)
|
||||
array->map.key_size = attr->key_size;
|
||||
array->map.value_size = attr->value_size;
|
||||
array->map.max_entries = attr->max_entries;
|
||||
array->map.map_flags = attr->map_flags;
|
||||
array->elem_size = elem_size;
|
||||
|
||||
if (!percpu)
|
||||
@@ -333,6 +334,26 @@ static void *fd_array_map_lookup_elem(struct bpf_map *map, void *key)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* only called from syscall */
|
||||
int bpf_fd_array_map_lookup_elem(struct bpf_map *map, void *key, u32 *value)
|
||||
{
|
||||
void **elem, *ptr;
|
||||
int ret = 0;
|
||||
|
||||
if (!map->ops->map_fd_sys_lookup_elem)
|
||||
return -ENOTSUPP;
|
||||
|
||||
rcu_read_lock();
|
||||
elem = array_map_lookup_elem(map, key);
|
||||
if (elem && (ptr = READ_ONCE(*elem)))
|
||||
*value = map->ops->map_fd_sys_lookup_elem(ptr);
|
||||
else
|
||||
ret = -ENOENT;
|
||||
rcu_read_unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* only called from syscall */
|
||||
int bpf_fd_array_map_update_elem(struct bpf_map *map, struct file *map_file,
|
||||
void *key, void *value, u64 map_flags)
|
||||
@@ -399,6 +420,11 @@ static void prog_fd_array_put_ptr(void *ptr)
|
||||
bpf_prog_put(ptr);
|
||||
}
|
||||
|
||||
static u32 prog_fd_array_sys_lookup_elem(void *ptr)
|
||||
{
|
||||
return ((struct bpf_prog *)ptr)->aux->id;
|
||||
}
|
||||
|
||||
/* decrement refcnt of all bpf_progs that are stored in this map */
|
||||
void bpf_fd_array_map_clear(struct bpf_map *map)
|
||||
{
|
||||
@@ -417,6 +443,7 @@ const struct bpf_map_ops prog_array_map_ops = {
|
||||
.map_delete_elem = fd_array_map_delete_elem,
|
||||
.map_fd_get_ptr = prog_fd_array_get_ptr,
|
||||
.map_fd_put_ptr = prog_fd_array_put_ptr,
|
||||
.map_fd_sys_lookup_elem = prog_fd_array_sys_lookup_elem,
|
||||
};
|
||||
|
||||
static struct bpf_event_entry *bpf_event_entry_gen(struct file *perf_file,
|
||||
@@ -451,38 +478,24 @@ static void bpf_event_entry_free_rcu(struct bpf_event_entry *ee)
|
||||
static void *perf_event_fd_array_get_ptr(struct bpf_map *map,
|
||||
struct file *map_file, int fd)
|
||||
{
|
||||
const struct perf_event_attr *attr;
|
||||
struct bpf_event_entry *ee;
|
||||
struct perf_event *event;
|
||||
struct file *perf_file;
|
||||
u64 value;
|
||||
|
||||
perf_file = perf_event_get(fd);
|
||||
if (IS_ERR(perf_file))
|
||||
return perf_file;
|
||||
|
||||
ee = ERR_PTR(-EOPNOTSUPP);
|
||||
event = perf_file->private_data;
|
||||
ee = ERR_PTR(-EINVAL);
|
||||
|
||||
attr = perf_event_attrs(event);
|
||||
if (IS_ERR(attr) || attr->inherit)
|
||||
if (perf_event_read_local(event, &value) == -EOPNOTSUPP)
|
||||
goto err_out;
|
||||
|
||||
switch (attr->type) {
|
||||
case PERF_TYPE_SOFTWARE:
|
||||
if (attr->config != PERF_COUNT_SW_BPF_OUTPUT)
|
||||
goto err_out;
|
||||
/* fall-through */
|
||||
case PERF_TYPE_RAW:
|
||||
case PERF_TYPE_HARDWARE:
|
||||
ee = bpf_event_entry_gen(perf_file, map_file);
|
||||
if (ee)
|
||||
return ee;
|
||||
ee = ERR_PTR(-ENOMEM);
|
||||
/* fall-through */
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ee = bpf_event_entry_gen(perf_file, map_file);
|
||||
if (ee)
|
||||
return ee;
|
||||
ee = ERR_PTR(-ENOMEM);
|
||||
err_out:
|
||||
fput(perf_file);
|
||||
return ee;
|
||||
@@ -598,4 +611,5 @@ const struct bpf_map_ops array_of_maps_map_ops = {
|
||||
.map_delete_elem = fd_array_map_delete_elem,
|
||||
.map_fd_get_ptr = bpf_map_fd_get_ptr,
|
||||
.map_fd_put_ptr = bpf_map_fd_put_ptr,
|
||||
.map_fd_sys_lookup_elem = bpf_map_fd_sys_lookup_elem,
|
||||
};
|
||||
|
||||
@@ -236,3 +236,40 @@ int __cgroup_bpf_run_filter_sk(struct sock *sk,
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(__cgroup_bpf_run_filter_sk);
|
||||
|
||||
/**
|
||||
* __cgroup_bpf_run_filter_sock_ops() - Run a program on a sock
|
||||
* @sk: socket to get cgroup from
|
||||
* @sock_ops: bpf_sock_ops_kern struct to pass to program. Contains
|
||||
* sk with connection information (IP addresses, etc.) May not contain
|
||||
* cgroup info if it is a req sock.
|
||||
* @type: The type of program to be exectuted
|
||||
*
|
||||
* socket passed is expected to be of type INET or INET6.
|
||||
*
|
||||
* The program type passed in via @type must be suitable for sock_ops
|
||||
* filtering. No further check is performed to assert that.
|
||||
*
|
||||
* This function will return %-EPERM if any if an attached program was found
|
||||
* and if it returned != 1 during execution. In all other cases, 0 is returned.
|
||||
*/
|
||||
int __cgroup_bpf_run_filter_sock_ops(struct sock *sk,
|
||||
struct bpf_sock_ops_kern *sock_ops,
|
||||
enum bpf_attach_type type)
|
||||
{
|
||||
struct cgroup *cgrp = sock_cgroup_ptr(&sk->sk_cgrp_data);
|
||||
struct bpf_prog *prog;
|
||||
int ret = 0;
|
||||
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
prog = rcu_dereference(cgrp->bpf.effective[type]);
|
||||
if (prog)
|
||||
ret = BPF_PROG_RUN(prog, sock_ops) == 1 ? 0 : -EPERM;
|
||||
|
||||
rcu_read_unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(__cgroup_bpf_run_filter_sock_ops);
|
||||
|
||||
+40
-9
@@ -763,10 +763,10 @@ EXPORT_SYMBOL_GPL(__bpf_call_base);
|
||||
*
|
||||
* Decode and execute eBPF instructions.
|
||||
*/
|
||||
static unsigned int __bpf_prog_run(void *ctx, const struct bpf_insn *insn)
|
||||
static unsigned int ___bpf_prog_run(u64 *regs, const struct bpf_insn *insn,
|
||||
u64 *stack)
|
||||
{
|
||||
u64 stack[MAX_BPF_STACK / sizeof(u64)];
|
||||
u64 regs[MAX_BPF_REG], tmp;
|
||||
u64 tmp;
|
||||
static const void *jumptable[256] = {
|
||||
[0 ... 255] = &&default_label,
|
||||
/* Now overwrite non-defaults ... */
|
||||
@@ -824,7 +824,7 @@ static unsigned int __bpf_prog_run(void *ctx, const struct bpf_insn *insn)
|
||||
[BPF_ALU64 | BPF_NEG] = &&ALU64_NEG,
|
||||
/* Call instruction */
|
||||
[BPF_JMP | BPF_CALL] = &&JMP_CALL,
|
||||
[BPF_JMP | BPF_CALL | BPF_X] = &&JMP_TAIL_CALL,
|
||||
[BPF_JMP | BPF_TAIL_CALL] = &&JMP_TAIL_CALL,
|
||||
/* Jumps */
|
||||
[BPF_JMP | BPF_JA] = &&JMP_JA,
|
||||
[BPF_JMP | BPF_JEQ | BPF_X] = &&JMP_JEQ_X,
|
||||
@@ -874,9 +874,6 @@ static unsigned int __bpf_prog_run(void *ctx, const struct bpf_insn *insn)
|
||||
#define CONT ({ insn++; goto select_insn; })
|
||||
#define CONT_JMP ({ insn++; goto select_insn; })
|
||||
|
||||
FP = (u64) (unsigned long) &stack[ARRAY_SIZE(stack)];
|
||||
ARG1 = (u64) (unsigned long) ctx;
|
||||
|
||||
select_insn:
|
||||
goto *jumptable[insn->code];
|
||||
|
||||
@@ -1219,7 +1216,39 @@ load_byte:
|
||||
WARN_RATELIMIT(1, "unknown opcode %02x\n", insn->code);
|
||||
return 0;
|
||||
}
|
||||
STACK_FRAME_NON_STANDARD(__bpf_prog_run); /* jump table */
|
||||
STACK_FRAME_NON_STANDARD(___bpf_prog_run); /* jump table */
|
||||
|
||||
#define PROG_NAME(stack_size) __bpf_prog_run##stack_size
|
||||
#define DEFINE_BPF_PROG_RUN(stack_size) \
|
||||
static unsigned int PROG_NAME(stack_size)(const void *ctx, const struct bpf_insn *insn) \
|
||||
{ \
|
||||
u64 stack[stack_size / sizeof(u64)]; \
|
||||
u64 regs[MAX_BPF_REG]; \
|
||||
\
|
||||
FP = (u64) (unsigned long) &stack[ARRAY_SIZE(stack)]; \
|
||||
ARG1 = (u64) (unsigned long) ctx; \
|
||||
return ___bpf_prog_run(regs, insn, stack); \
|
||||
}
|
||||
|
||||
#define EVAL1(FN, X) FN(X)
|
||||
#define EVAL2(FN, X, Y...) FN(X) EVAL1(FN, Y)
|
||||
#define EVAL3(FN, X, Y...) FN(X) EVAL2(FN, Y)
|
||||
#define EVAL4(FN, X, Y...) FN(X) EVAL3(FN, Y)
|
||||
#define EVAL5(FN, X, Y...) FN(X) EVAL4(FN, Y)
|
||||
#define EVAL6(FN, X, Y...) FN(X) EVAL5(FN, Y)
|
||||
|
||||
EVAL6(DEFINE_BPF_PROG_RUN, 32, 64, 96, 128, 160, 192);
|
||||
EVAL6(DEFINE_BPF_PROG_RUN, 224, 256, 288, 320, 352, 384);
|
||||
EVAL4(DEFINE_BPF_PROG_RUN, 416, 448, 480, 512);
|
||||
|
||||
#define PROG_NAME_LIST(stack_size) PROG_NAME(stack_size),
|
||||
|
||||
static unsigned int (*interpreters[])(const void *ctx,
|
||||
const struct bpf_insn *insn) = {
|
||||
EVAL6(PROG_NAME_LIST, 32, 64, 96, 128, 160, 192)
|
||||
EVAL6(PROG_NAME_LIST, 224, 256, 288, 320, 352, 384)
|
||||
EVAL4(PROG_NAME_LIST, 416, 448, 480, 512)
|
||||
};
|
||||
|
||||
bool bpf_prog_array_compatible(struct bpf_array *array,
|
||||
const struct bpf_prog *fp)
|
||||
@@ -1268,7 +1297,9 @@ static int bpf_check_tail_call(const struct bpf_prog *fp)
|
||||
*/
|
||||
struct bpf_prog *bpf_prog_select_runtime(struct bpf_prog *fp, int *err)
|
||||
{
|
||||
fp->bpf_func = (void *) __bpf_prog_run;
|
||||
u32 stack_depth = max_t(u32, fp->aux->stack_depth, 1);
|
||||
|
||||
fp->bpf_func = interpreters[(round_up(stack_depth, 32) / 32) - 1];
|
||||
|
||||
/* eBPF JITs can rewrite the program in case constant
|
||||
* blinding is active. However, in case of error during
|
||||
|
||||
@@ -1243,6 +1243,26 @@ static void fd_htab_map_free(struct bpf_map *map)
|
||||
htab_map_free(map);
|
||||
}
|
||||
|
||||
/* only called from syscall */
|
||||
int bpf_fd_htab_map_lookup_elem(struct bpf_map *map, void *key, u32 *value)
|
||||
{
|
||||
void **ptr;
|
||||
int ret = 0;
|
||||
|
||||
if (!map->ops->map_fd_sys_lookup_elem)
|
||||
return -ENOTSUPP;
|
||||
|
||||
rcu_read_lock();
|
||||
ptr = htab_map_lookup_elem(map, key);
|
||||
if (ptr)
|
||||
*value = map->ops->map_fd_sys_lookup_elem(READ_ONCE(*ptr));
|
||||
else
|
||||
ret = -ENOENT;
|
||||
rcu_read_unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* only called from syscall */
|
||||
int bpf_fd_htab_map_update_elem(struct bpf_map *map, struct file *map_file,
|
||||
void *key, void *value, u64 map_flags)
|
||||
@@ -1305,4 +1325,5 @@ const struct bpf_map_ops htab_of_maps_map_ops = {
|
||||
.map_delete_elem = htab_map_delete_elem,
|
||||
.map_fd_get_ptr = bpf_map_fd_get_ptr,
|
||||
.map_fd_put_ptr = bpf_map_fd_put_ptr,
|
||||
.map_fd_sys_lookup_elem = bpf_map_fd_sys_lookup_elem,
|
||||
};
|
||||
|
||||
+13
-3
@@ -377,10 +377,22 @@ static void bpf_evict_inode(struct inode *inode)
|
||||
bpf_any_put(inode->i_private, type);
|
||||
}
|
||||
|
||||
/*
|
||||
* Display the mount options in /proc/mounts.
|
||||
*/
|
||||
static int bpf_show_options(struct seq_file *m, struct dentry *root)
|
||||
{
|
||||
umode_t mode = d_inode(root)->i_mode & S_IALLUGO & ~S_ISVTX;
|
||||
|
||||
if (mode != S_IRWXUGO)
|
||||
seq_printf(m, ",mode=%o", mode);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct super_operations bpf_super_ops = {
|
||||
.statfs = simple_statfs,
|
||||
.drop_inode = generic_delete_inode,
|
||||
.show_options = generic_show_options,
|
||||
.show_options = bpf_show_options,
|
||||
.evict_inode = bpf_evict_inode,
|
||||
};
|
||||
|
||||
@@ -434,8 +446,6 @@ static int bpf_fill_super(struct super_block *sb, void *data, int silent)
|
||||
struct inode *inode;
|
||||
int ret;
|
||||
|
||||
save_mount_options(sb, data);
|
||||
|
||||
ret = bpf_parse_options(data, &opts);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -432,6 +432,7 @@ static struct bpf_map *trie_alloc(union bpf_attr *attr)
|
||||
trie->map.key_size = attr->key_size;
|
||||
trie->map.value_size = attr->value_size;
|
||||
trie->map.max_entries = attr->max_entries;
|
||||
trie->map.map_flags = attr->map_flags;
|
||||
trie->data_size = attr->key_size -
|
||||
offsetof(struct bpf_lpm_trie_key, data);
|
||||
trie->max_prefixlen = trie->data_size * 8;
|
||||
|
||||
@@ -95,3 +95,8 @@ void bpf_map_fd_put_ptr(void *ptr)
|
||||
*/
|
||||
bpf_map_put(ptr);
|
||||
}
|
||||
|
||||
u32 bpf_map_fd_sys_lookup_elem(void *ptr)
|
||||
{
|
||||
return ((struct bpf_map *)ptr)->id;
|
||||
}
|
||||
|
||||
@@ -19,5 +19,6 @@ bool bpf_map_meta_equal(const struct bpf_map *meta0,
|
||||
void *bpf_map_fd_get_ptr(struct bpf_map *map, struct file *map_file,
|
||||
int ufd);
|
||||
void bpf_map_fd_put_ptr(void *ptr);
|
||||
u32 bpf_map_fd_sys_lookup_elem(void *ptr);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -88,6 +88,7 @@ static struct bpf_map *stack_map_alloc(union bpf_attr *attr)
|
||||
smap->map.key_size = attr->key_size;
|
||||
smap->map.value_size = value_size;
|
||||
smap->map.max_entries = attr->max_entries;
|
||||
smap->map.map_flags = attr->map_flags;
|
||||
smap->n_buckets = n_buckets;
|
||||
smap->map.pages = round_up(cost, PAGE_SIZE) >> PAGE_SHIFT;
|
||||
|
||||
|
||||
+451
-59
@@ -22,8 +22,20 @@
|
||||
#include <linux/filter.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/idr.h>
|
||||
|
||||
#define IS_FD_ARRAY(map) ((map)->map_type == BPF_MAP_TYPE_PROG_ARRAY || \
|
||||
(map)->map_type == BPF_MAP_TYPE_PERF_EVENT_ARRAY || \
|
||||
(map)->map_type == BPF_MAP_TYPE_CGROUP_ARRAY || \
|
||||
(map)->map_type == BPF_MAP_TYPE_ARRAY_OF_MAPS)
|
||||
#define IS_FD_HASH(map) ((map)->map_type == BPF_MAP_TYPE_HASH_OF_MAPS)
|
||||
#define IS_FD_MAP(map) (IS_FD_ARRAY(map) || IS_FD_HASH(map))
|
||||
|
||||
DEFINE_PER_CPU(int, bpf_prog_active);
|
||||
static DEFINE_IDR(prog_idr);
|
||||
static DEFINE_SPINLOCK(prog_idr_lock);
|
||||
static DEFINE_IDR(map_idr);
|
||||
static DEFINE_SPINLOCK(map_idr_lock);
|
||||
|
||||
int sysctl_unprivileged_bpf_disabled __read_mostly;
|
||||
|
||||
@@ -114,6 +126,37 @@ static void bpf_map_uncharge_memlock(struct bpf_map *map)
|
||||
free_uid(user);
|
||||
}
|
||||
|
||||
static int bpf_map_alloc_id(struct bpf_map *map)
|
||||
{
|
||||
int id;
|
||||
|
||||
spin_lock_bh(&map_idr_lock);
|
||||
id = idr_alloc_cyclic(&map_idr, map, 1, INT_MAX, GFP_ATOMIC);
|
||||
if (id > 0)
|
||||
map->id = id;
|
||||
spin_unlock_bh(&map_idr_lock);
|
||||
|
||||
if (WARN_ON_ONCE(!id))
|
||||
return -ENOSPC;
|
||||
|
||||
return id > 0 ? 0 : id;
|
||||
}
|
||||
|
||||
static void bpf_map_free_id(struct bpf_map *map, bool do_idr_lock)
|
||||
{
|
||||
if (do_idr_lock)
|
||||
spin_lock_bh(&map_idr_lock);
|
||||
else
|
||||
__acquire(&map_idr_lock);
|
||||
|
||||
idr_remove(&map_idr, map->id);
|
||||
|
||||
if (do_idr_lock)
|
||||
spin_unlock_bh(&map_idr_lock);
|
||||
else
|
||||
__release(&map_idr_lock);
|
||||
}
|
||||
|
||||
/* called from workqueue */
|
||||
static void bpf_map_free_deferred(struct work_struct *work)
|
||||
{
|
||||
@@ -135,14 +178,21 @@ static void bpf_map_put_uref(struct bpf_map *map)
|
||||
/* decrement map refcnt and schedule it for freeing via workqueue
|
||||
* (unrelying map implementation ops->map_free() might sleep)
|
||||
*/
|
||||
void bpf_map_put(struct bpf_map *map)
|
||||
static void __bpf_map_put(struct bpf_map *map, bool do_idr_lock)
|
||||
{
|
||||
if (atomic_dec_and_test(&map->refcnt)) {
|
||||
/* bpf_map_free_id() must be called first */
|
||||
bpf_map_free_id(map, do_idr_lock);
|
||||
INIT_WORK(&map->work, bpf_map_free_deferred);
|
||||
schedule_work(&map->work);
|
||||
}
|
||||
}
|
||||
|
||||
void bpf_map_put(struct bpf_map *map)
|
||||
{
|
||||
__bpf_map_put(map, true);
|
||||
}
|
||||
|
||||
void bpf_map_put_with_uref(struct bpf_map *map)
|
||||
{
|
||||
bpf_map_put_uref(map);
|
||||
@@ -166,10 +216,12 @@ static void bpf_map_show_fdinfo(struct seq_file *m, struct file *filp)
|
||||
const struct bpf_map *map = filp->private_data;
|
||||
const struct bpf_array *array;
|
||||
u32 owner_prog_type = 0;
|
||||
u32 owner_jited = 0;
|
||||
|
||||
if (map->map_type == BPF_MAP_TYPE_PROG_ARRAY) {
|
||||
array = container_of(map, struct bpf_array, map);
|
||||
owner_prog_type = array->owner_prog_type;
|
||||
owner_jited = array->owner_jited;
|
||||
}
|
||||
|
||||
seq_printf(m,
|
||||
@@ -186,9 +238,12 @@ static void bpf_map_show_fdinfo(struct seq_file *m, struct file *filp)
|
||||
map->map_flags,
|
||||
map->pages * 1ULL << PAGE_SHIFT);
|
||||
|
||||
if (owner_prog_type)
|
||||
if (owner_prog_type) {
|
||||
seq_printf(m, "owner_prog_type:\t%u\n",
|
||||
owner_prog_type);
|
||||
seq_printf(m, "owner_jited:\t%u\n",
|
||||
owner_jited);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -236,11 +291,22 @@ static int map_create(union bpf_attr *attr)
|
||||
if (err)
|
||||
goto free_map_nouncharge;
|
||||
|
||||
err = bpf_map_new_fd(map);
|
||||
if (err < 0)
|
||||
/* failed to allocate fd */
|
||||
err = bpf_map_alloc_id(map);
|
||||
if (err)
|
||||
goto free_map;
|
||||
|
||||
err = bpf_map_new_fd(map);
|
||||
if (err < 0) {
|
||||
/* failed to allocate fd.
|
||||
* bpf_map_put() is needed because the above
|
||||
* bpf_map_alloc_id() has published the map
|
||||
* to the userspace and the userspace may
|
||||
* have refcnt-ed it through BPF_MAP_GET_FD_BY_ID.
|
||||
*/
|
||||
bpf_map_put(map);
|
||||
return err;
|
||||
}
|
||||
|
||||
trace_bpf_map_create(map, err);
|
||||
return err;
|
||||
|
||||
@@ -295,6 +361,28 @@ struct bpf_map *bpf_map_get_with_uref(u32 ufd)
|
||||
return map;
|
||||
}
|
||||
|
||||
/* map_idr_lock should have been held */
|
||||
static struct bpf_map *bpf_map_inc_not_zero(struct bpf_map *map,
|
||||
bool uref)
|
||||
{
|
||||
int refold;
|
||||
|
||||
refold = __atomic_add_unless(&map->refcnt, 1, 0);
|
||||
|
||||
if (refold >= BPF_MAX_REFCNT) {
|
||||
__bpf_map_put(map, false);
|
||||
return ERR_PTR(-EBUSY);
|
||||
}
|
||||
|
||||
if (!refold)
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
if (uref)
|
||||
atomic_inc(&map->usercnt);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
int __weak bpf_stackmap_copy(struct bpf_map *map, void *key, void *value)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
@@ -322,19 +410,18 @@ static int map_lookup_elem(union bpf_attr *attr)
|
||||
if (IS_ERR(map))
|
||||
return PTR_ERR(map);
|
||||
|
||||
err = -ENOMEM;
|
||||
key = kmalloc(map->key_size, GFP_USER);
|
||||
if (!key)
|
||||
key = memdup_user(ukey, map->key_size);
|
||||
if (IS_ERR(key)) {
|
||||
err = PTR_ERR(key);
|
||||
goto err_put;
|
||||
|
||||
err = -EFAULT;
|
||||
if (copy_from_user(key, ukey, map->key_size) != 0)
|
||||
goto free_key;
|
||||
}
|
||||
|
||||
if (map->map_type == BPF_MAP_TYPE_PERCPU_HASH ||
|
||||
map->map_type == BPF_MAP_TYPE_LRU_PERCPU_HASH ||
|
||||
map->map_type == BPF_MAP_TYPE_PERCPU_ARRAY)
|
||||
value_size = round_up(map->value_size, 8) * num_possible_cpus();
|
||||
else if (IS_FD_MAP(map))
|
||||
value_size = sizeof(u32);
|
||||
else
|
||||
value_size = map->value_size;
|
||||
|
||||
@@ -350,9 +437,10 @@ static int map_lookup_elem(union bpf_attr *attr)
|
||||
err = bpf_percpu_array_copy(map, key, value);
|
||||
} else if (map->map_type == BPF_MAP_TYPE_STACK_TRACE) {
|
||||
err = bpf_stackmap_copy(map, key, value);
|
||||
} else if (map->map_type == BPF_MAP_TYPE_ARRAY_OF_MAPS ||
|
||||
map->map_type == BPF_MAP_TYPE_HASH_OF_MAPS) {
|
||||
err = -ENOTSUPP;
|
||||
} else if (IS_FD_ARRAY(map)) {
|
||||
err = bpf_fd_array_map_lookup_elem(map, key, value);
|
||||
} else if (IS_FD_HASH(map)) {
|
||||
err = bpf_fd_htab_map_lookup_elem(map, key, value);
|
||||
} else {
|
||||
rcu_read_lock();
|
||||
ptr = map->ops->map_lookup_elem(map, key);
|
||||
@@ -402,14 +490,11 @@ static int map_update_elem(union bpf_attr *attr)
|
||||
if (IS_ERR(map))
|
||||
return PTR_ERR(map);
|
||||
|
||||
err = -ENOMEM;
|
||||
key = kmalloc(map->key_size, GFP_USER);
|
||||
if (!key)
|
||||
key = memdup_user(ukey, map->key_size);
|
||||
if (IS_ERR(key)) {
|
||||
err = PTR_ERR(key);
|
||||
goto err_put;
|
||||
|
||||
err = -EFAULT;
|
||||
if (copy_from_user(key, ukey, map->key_size) != 0)
|
||||
goto free_key;
|
||||
}
|
||||
|
||||
if (map->map_type == BPF_MAP_TYPE_PERCPU_HASH ||
|
||||
map->map_type == BPF_MAP_TYPE_LRU_PERCPU_HASH ||
|
||||
@@ -488,14 +573,11 @@ static int map_delete_elem(union bpf_attr *attr)
|
||||
if (IS_ERR(map))
|
||||
return PTR_ERR(map);
|
||||
|
||||
err = -ENOMEM;
|
||||
key = kmalloc(map->key_size, GFP_USER);
|
||||
if (!key)
|
||||
key = memdup_user(ukey, map->key_size);
|
||||
if (IS_ERR(key)) {
|
||||
err = PTR_ERR(key);
|
||||
goto err_put;
|
||||
|
||||
err = -EFAULT;
|
||||
if (copy_from_user(key, ukey, map->key_size) != 0)
|
||||
goto free_key;
|
||||
}
|
||||
|
||||
preempt_disable();
|
||||
__this_cpu_inc(bpf_prog_active);
|
||||
@@ -507,7 +589,6 @@ static int map_delete_elem(union bpf_attr *attr)
|
||||
|
||||
if (!err)
|
||||
trace_bpf_map_delete_elem(map, ufd, key);
|
||||
free_key:
|
||||
kfree(key);
|
||||
err_put:
|
||||
fdput(f);
|
||||
@@ -536,14 +617,11 @@ static int map_get_next_key(union bpf_attr *attr)
|
||||
return PTR_ERR(map);
|
||||
|
||||
if (ukey) {
|
||||
err = -ENOMEM;
|
||||
key = kmalloc(map->key_size, GFP_USER);
|
||||
if (!key)
|
||||
key = memdup_user(ukey, map->key_size);
|
||||
if (IS_ERR(key)) {
|
||||
err = PTR_ERR(key);
|
||||
goto err_put;
|
||||
|
||||
err = -EFAULT;
|
||||
if (copy_from_user(key, ukey, map->key_size) != 0)
|
||||
goto free_key;
|
||||
}
|
||||
} else {
|
||||
key = NULL;
|
||||
}
|
||||
@@ -650,6 +728,42 @@ static void bpf_prog_uncharge_memlock(struct bpf_prog *prog)
|
||||
free_uid(user);
|
||||
}
|
||||
|
||||
static int bpf_prog_alloc_id(struct bpf_prog *prog)
|
||||
{
|
||||
int id;
|
||||
|
||||
spin_lock_bh(&prog_idr_lock);
|
||||
id = idr_alloc_cyclic(&prog_idr, prog, 1, INT_MAX, GFP_ATOMIC);
|
||||
if (id > 0)
|
||||
prog->aux->id = id;
|
||||
spin_unlock_bh(&prog_idr_lock);
|
||||
|
||||
/* id is in [1, INT_MAX) */
|
||||
if (WARN_ON_ONCE(!id))
|
||||
return -ENOSPC;
|
||||
|
||||
return id > 0 ? 0 : id;
|
||||
}
|
||||
|
||||
static void bpf_prog_free_id(struct bpf_prog *prog, bool do_idr_lock)
|
||||
{
|
||||
/* cBPF to eBPF migrations are currently not in the idr store. */
|
||||
if (!prog->aux->id)
|
||||
return;
|
||||
|
||||
if (do_idr_lock)
|
||||
spin_lock_bh(&prog_idr_lock);
|
||||
else
|
||||
__acquire(&prog_idr_lock);
|
||||
|
||||
idr_remove(&prog_idr, prog->aux->id);
|
||||
|
||||
if (do_idr_lock)
|
||||
spin_unlock_bh(&prog_idr_lock);
|
||||
else
|
||||
__release(&prog_idr_lock);
|
||||
}
|
||||
|
||||
static void __bpf_prog_put_rcu(struct rcu_head *rcu)
|
||||
{
|
||||
struct bpf_prog_aux *aux = container_of(rcu, struct bpf_prog_aux, rcu);
|
||||
@@ -659,14 +773,21 @@ static void __bpf_prog_put_rcu(struct rcu_head *rcu)
|
||||
bpf_prog_free(aux->prog);
|
||||
}
|
||||
|
||||
void bpf_prog_put(struct bpf_prog *prog)
|
||||
static void __bpf_prog_put(struct bpf_prog *prog, bool do_idr_lock)
|
||||
{
|
||||
if (atomic_dec_and_test(&prog->aux->refcnt)) {
|
||||
trace_bpf_prog_put_rcu(prog);
|
||||
/* bpf_prog_free_id() must be called first */
|
||||
bpf_prog_free_id(prog, do_idr_lock);
|
||||
bpf_prog_kallsyms_del(prog);
|
||||
call_rcu(&prog->aux->rcu, __bpf_prog_put_rcu);
|
||||
}
|
||||
}
|
||||
|
||||
void bpf_prog_put(struct bpf_prog *prog)
|
||||
{
|
||||
__bpf_prog_put(prog, true);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bpf_prog_put);
|
||||
|
||||
static int bpf_prog_release(struct inode *inode, struct file *filp)
|
||||
@@ -748,6 +869,24 @@ struct bpf_prog *bpf_prog_inc(struct bpf_prog *prog)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bpf_prog_inc);
|
||||
|
||||
/* prog_idr_lock should have been held */
|
||||
static struct bpf_prog *bpf_prog_inc_not_zero(struct bpf_prog *prog)
|
||||
{
|
||||
int refold;
|
||||
|
||||
refold = __atomic_add_unless(&prog->aux->refcnt, 1, 0);
|
||||
|
||||
if (refold >= BPF_MAX_REFCNT) {
|
||||
__bpf_prog_put(prog, false);
|
||||
return ERR_PTR(-EBUSY);
|
||||
}
|
||||
|
||||
if (!refold)
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
return prog;
|
||||
}
|
||||
|
||||
static struct bpf_prog *__bpf_prog_get(u32 ufd, enum bpf_prog_type *type)
|
||||
{
|
||||
struct fd f = fdget(ufd);
|
||||
@@ -815,7 +954,9 @@ static int bpf_prog_load(union bpf_attr *attr)
|
||||
attr->kern_version != LINUX_VERSION_CODE)
|
||||
return -EINVAL;
|
||||
|
||||
if (type != BPF_PROG_TYPE_SOCKET_FILTER && !capable(CAP_SYS_ADMIN))
|
||||
if (type != BPF_PROG_TYPE_SOCKET_FILTER &&
|
||||
type != BPF_PROG_TYPE_CGROUP_SKB &&
|
||||
!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
/* plain bpf_prog allocation */
|
||||
@@ -855,11 +996,22 @@ static int bpf_prog_load(union bpf_attr *attr)
|
||||
if (err < 0)
|
||||
goto free_used_maps;
|
||||
|
||||
err = bpf_prog_new_fd(prog);
|
||||
if (err < 0)
|
||||
/* failed to allocate fd */
|
||||
err = bpf_prog_alloc_id(prog);
|
||||
if (err)
|
||||
goto free_used_maps;
|
||||
|
||||
err = bpf_prog_new_fd(prog);
|
||||
if (err < 0) {
|
||||
/* failed to allocate fd.
|
||||
* bpf_prog_put() is needed because the above
|
||||
* bpf_prog_alloc_id() has published the prog
|
||||
* to the userspace and the userspace may
|
||||
* have refcnt-ed it through BPF_PROG_GET_FD_BY_ID.
|
||||
*/
|
||||
bpf_prog_put(prog);
|
||||
return err;
|
||||
}
|
||||
|
||||
bpf_prog_kallsyms_add(prog);
|
||||
trace_bpf_prog_load(prog, err);
|
||||
return err;
|
||||
@@ -919,6 +1071,9 @@ static int bpf_prog_attach(const union bpf_attr *attr)
|
||||
case BPF_CGROUP_INET_SOCK_CREATE:
|
||||
ptype = BPF_PROG_TYPE_CGROUP_SOCK;
|
||||
break;
|
||||
case BPF_CGROUP_SOCK_OPS:
|
||||
ptype = BPF_PROG_TYPE_SOCK_OPS;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -959,6 +1114,7 @@ static int bpf_prog_detach(const union bpf_attr *attr)
|
||||
case BPF_CGROUP_INET_INGRESS:
|
||||
case BPF_CGROUP_INET_EGRESS:
|
||||
case BPF_CGROUP_INET_SOCK_CREATE:
|
||||
case BPF_CGROUP_SOCK_OPS:
|
||||
cgrp = cgroup_get_from_fd(attr->target_fd);
|
||||
if (IS_ERR(cgrp))
|
||||
return PTR_ERR(cgrp);
|
||||
@@ -973,6 +1129,7 @@ static int bpf_prog_detach(const union bpf_attr *attr)
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif /* CONFIG_CGROUP_BPF */
|
||||
|
||||
#define BPF_PROG_TEST_RUN_LAST_FIELD test.duration
|
||||
@@ -997,6 +1154,237 @@ static int bpf_prog_test_run(const union bpf_attr *attr,
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define BPF_OBJ_GET_NEXT_ID_LAST_FIELD next_id
|
||||
|
||||
static int bpf_obj_get_next_id(const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr,
|
||||
struct idr *idr,
|
||||
spinlock_t *lock)
|
||||
{
|
||||
u32 next_id = attr->start_id;
|
||||
int err = 0;
|
||||
|
||||
if (CHECK_ATTR(BPF_OBJ_GET_NEXT_ID) || next_id >= INT_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
next_id++;
|
||||
spin_lock_bh(lock);
|
||||
if (!idr_get_next(idr, &next_id))
|
||||
err = -ENOENT;
|
||||
spin_unlock_bh(lock);
|
||||
|
||||
if (!err)
|
||||
err = put_user(next_id, &uattr->next_id);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#define BPF_PROG_GET_FD_BY_ID_LAST_FIELD prog_id
|
||||
|
||||
static int bpf_prog_get_fd_by_id(const union bpf_attr *attr)
|
||||
{
|
||||
struct bpf_prog *prog;
|
||||
u32 id = attr->prog_id;
|
||||
int fd;
|
||||
|
||||
if (CHECK_ATTR(BPF_PROG_GET_FD_BY_ID))
|
||||
return -EINVAL;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
spin_lock_bh(&prog_idr_lock);
|
||||
prog = idr_find(&prog_idr, id);
|
||||
if (prog)
|
||||
prog = bpf_prog_inc_not_zero(prog);
|
||||
else
|
||||
prog = ERR_PTR(-ENOENT);
|
||||
spin_unlock_bh(&prog_idr_lock);
|
||||
|
||||
if (IS_ERR(prog))
|
||||
return PTR_ERR(prog);
|
||||
|
||||
fd = bpf_prog_new_fd(prog);
|
||||
if (fd < 0)
|
||||
bpf_prog_put(prog);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
#define BPF_MAP_GET_FD_BY_ID_LAST_FIELD map_id
|
||||
|
||||
static int bpf_map_get_fd_by_id(const union bpf_attr *attr)
|
||||
{
|
||||
struct bpf_map *map;
|
||||
u32 id = attr->map_id;
|
||||
int fd;
|
||||
|
||||
if (CHECK_ATTR(BPF_MAP_GET_FD_BY_ID))
|
||||
return -EINVAL;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
spin_lock_bh(&map_idr_lock);
|
||||
map = idr_find(&map_idr, id);
|
||||
if (map)
|
||||
map = bpf_map_inc_not_zero(map, true);
|
||||
else
|
||||
map = ERR_PTR(-ENOENT);
|
||||
spin_unlock_bh(&map_idr_lock);
|
||||
|
||||
if (IS_ERR(map))
|
||||
return PTR_ERR(map);
|
||||
|
||||
fd = bpf_map_new_fd(map);
|
||||
if (fd < 0)
|
||||
bpf_map_put(map);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int check_uarg_tail_zero(void __user *uaddr,
|
||||
size_t expected_size,
|
||||
size_t actual_size)
|
||||
{
|
||||
unsigned char __user *addr;
|
||||
unsigned char __user *end;
|
||||
unsigned char val;
|
||||
int err;
|
||||
|
||||
if (actual_size <= expected_size)
|
||||
return 0;
|
||||
|
||||
addr = uaddr + expected_size;
|
||||
end = uaddr + actual_size;
|
||||
|
||||
for (; addr < end; addr++) {
|
||||
err = get_user(val, addr);
|
||||
if (err)
|
||||
return err;
|
||||
if (val)
|
||||
return -E2BIG;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bpf_prog_get_info_by_fd(struct bpf_prog *prog,
|
||||
const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
struct bpf_prog_info __user *uinfo = u64_to_user_ptr(attr->info.info);
|
||||
struct bpf_prog_info info = {};
|
||||
u32 info_len = attr->info.info_len;
|
||||
char __user *uinsns;
|
||||
u32 ulen;
|
||||
int err;
|
||||
|
||||
err = check_uarg_tail_zero(uinfo, sizeof(info), info_len);
|
||||
if (err)
|
||||
return err;
|
||||
info_len = min_t(u32, sizeof(info), info_len);
|
||||
|
||||
if (copy_from_user(&info, uinfo, info_len))
|
||||
return err;
|
||||
|
||||
info.type = prog->type;
|
||||
info.id = prog->aux->id;
|
||||
|
||||
memcpy(info.tag, prog->tag, sizeof(prog->tag));
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN)) {
|
||||
info.jited_prog_len = 0;
|
||||
info.xlated_prog_len = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
ulen = info.jited_prog_len;
|
||||
info.jited_prog_len = prog->jited_len;
|
||||
if (info.jited_prog_len && ulen) {
|
||||
uinsns = u64_to_user_ptr(info.jited_prog_insns);
|
||||
ulen = min_t(u32, info.jited_prog_len, ulen);
|
||||
if (copy_to_user(uinsns, prog->bpf_func, ulen))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
ulen = info.xlated_prog_len;
|
||||
info.xlated_prog_len = bpf_prog_size(prog->len);
|
||||
if (info.xlated_prog_len && ulen) {
|
||||
uinsns = u64_to_user_ptr(info.xlated_prog_insns);
|
||||
ulen = min_t(u32, info.xlated_prog_len, ulen);
|
||||
if (copy_to_user(uinsns, prog->insnsi, ulen))
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
done:
|
||||
if (copy_to_user(uinfo, &info, info_len) ||
|
||||
put_user(info_len, &uattr->info.info_len))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bpf_map_get_info_by_fd(struct bpf_map *map,
|
||||
const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
struct bpf_map_info __user *uinfo = u64_to_user_ptr(attr->info.info);
|
||||
struct bpf_map_info info = {};
|
||||
u32 info_len = attr->info.info_len;
|
||||
int err;
|
||||
|
||||
err = check_uarg_tail_zero(uinfo, sizeof(info), info_len);
|
||||
if (err)
|
||||
return err;
|
||||
info_len = min_t(u32, sizeof(info), info_len);
|
||||
|
||||
info.type = map->map_type;
|
||||
info.id = map->id;
|
||||
info.key_size = map->key_size;
|
||||
info.value_size = map->value_size;
|
||||
info.max_entries = map->max_entries;
|
||||
info.map_flags = map->map_flags;
|
||||
|
||||
if (copy_to_user(uinfo, &info, info_len) ||
|
||||
put_user(info_len, &uattr->info.info_len))
|
||||
return -EFAULT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define BPF_OBJ_GET_INFO_BY_FD_LAST_FIELD info.info
|
||||
|
||||
static int bpf_obj_get_info_by_fd(const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
int ufd = attr->info.bpf_fd;
|
||||
struct fd f;
|
||||
int err;
|
||||
|
||||
if (CHECK_ATTR(BPF_OBJ_GET_INFO_BY_FD))
|
||||
return -EINVAL;
|
||||
|
||||
f = fdget(ufd);
|
||||
if (!f.file)
|
||||
return -EBADFD;
|
||||
|
||||
if (f.file->f_op == &bpf_prog_fops)
|
||||
err = bpf_prog_get_info_by_fd(f.file->private_data, attr,
|
||||
uattr);
|
||||
else if (f.file->f_op == &bpf_map_fops)
|
||||
err = bpf_map_get_info_by_fd(f.file->private_data, attr,
|
||||
uattr);
|
||||
else
|
||||
err = -EINVAL;
|
||||
|
||||
fdput(f);
|
||||
return err;
|
||||
}
|
||||
|
||||
SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, size)
|
||||
{
|
||||
union bpf_attr attr = {};
|
||||
@@ -1016,23 +1404,10 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz
|
||||
* user-space does not rely on any kernel feature
|
||||
* extensions we dont know about yet.
|
||||
*/
|
||||
if (size > sizeof(attr)) {
|
||||
unsigned char __user *addr;
|
||||
unsigned char __user *end;
|
||||
unsigned char val;
|
||||
|
||||
addr = (void __user *)uattr + sizeof(attr);
|
||||
end = (void __user *)uattr + size;
|
||||
|
||||
for (; addr < end; addr++) {
|
||||
err = get_user(val, addr);
|
||||
if (err)
|
||||
return err;
|
||||
if (val)
|
||||
return -E2BIG;
|
||||
}
|
||||
size = sizeof(attr);
|
||||
}
|
||||
err = check_uarg_tail_zero(uattr, sizeof(attr), size);
|
||||
if (err)
|
||||
return err;
|
||||
size = min_t(u32, size, sizeof(attr));
|
||||
|
||||
/* copy attributes from user space, may be less than sizeof(bpf_attr) */
|
||||
if (copy_from_user(&attr, uattr, size) != 0)
|
||||
@@ -1074,6 +1449,23 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz
|
||||
case BPF_PROG_TEST_RUN:
|
||||
err = bpf_prog_test_run(&attr, uattr);
|
||||
break;
|
||||
case BPF_PROG_GET_NEXT_ID:
|
||||
err = bpf_obj_get_next_id(&attr, uattr,
|
||||
&prog_idr, &prog_idr_lock);
|
||||
break;
|
||||
case BPF_MAP_GET_NEXT_ID:
|
||||
err = bpf_obj_get_next_id(&attr, uattr,
|
||||
&map_idr, &map_idr_lock);
|
||||
break;
|
||||
case BPF_PROG_GET_FD_BY_ID:
|
||||
err = bpf_prog_get_fd_by_id(&attr);
|
||||
break;
|
||||
case BPF_MAP_GET_FD_BY_ID:
|
||||
err = bpf_map_get_fd_by_id(&attr);
|
||||
break;
|
||||
case BPF_OBJ_GET_INFO_BY_FD:
|
||||
err = bpf_obj_get_info_by_fd(&attr, uattr);
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
break;
|
||||
|
||||
+283
-87
@@ -463,19 +463,22 @@ static const int caller_saved[CALLER_SAVED_REGS] = {
|
||||
BPF_REG_0, BPF_REG_1, BPF_REG_2, BPF_REG_3, BPF_REG_4, BPF_REG_5
|
||||
};
|
||||
|
||||
static void mark_reg_not_init(struct bpf_reg_state *regs, u32 regno)
|
||||
{
|
||||
BUG_ON(regno >= MAX_BPF_REG);
|
||||
|
||||
memset(®s[regno], 0, sizeof(regs[regno]));
|
||||
regs[regno].type = NOT_INIT;
|
||||
regs[regno].min_value = BPF_REGISTER_MIN_RANGE;
|
||||
regs[regno].max_value = BPF_REGISTER_MAX_RANGE;
|
||||
}
|
||||
|
||||
static void init_reg_state(struct bpf_reg_state *regs)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX_BPF_REG; i++) {
|
||||
regs[i].type = NOT_INIT;
|
||||
regs[i].imm = 0;
|
||||
regs[i].min_value = BPF_REGISTER_MIN_RANGE;
|
||||
regs[i].max_value = BPF_REGISTER_MAX_RANGE;
|
||||
regs[i].min_align = 0;
|
||||
regs[i].aux_off = 0;
|
||||
regs[i].aux_off_align = 0;
|
||||
}
|
||||
for (i = 0; i < MAX_BPF_REG; i++)
|
||||
mark_reg_not_init(regs, i);
|
||||
|
||||
/* frame pointer */
|
||||
regs[BPF_REG_FP].type = FRAME_PTR;
|
||||
@@ -501,6 +504,7 @@ static void reset_reg_range_values(struct bpf_reg_state *regs, u32 regno)
|
||||
{
|
||||
regs[regno].min_value = BPF_REGISTER_MIN_RANGE;
|
||||
regs[regno].max_value = BPF_REGISTER_MAX_RANGE;
|
||||
regs[regno].value_from_signed = false;
|
||||
regs[regno].min_align = 0;
|
||||
}
|
||||
|
||||
@@ -543,20 +547,6 @@ static int check_reg_arg(struct bpf_reg_state *regs, u32 regno,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bpf_size_to_bytes(int bpf_size)
|
||||
{
|
||||
if (bpf_size == BPF_W)
|
||||
return 4;
|
||||
else if (bpf_size == BPF_H)
|
||||
return 2;
|
||||
else if (bpf_size == BPF_B)
|
||||
return 1;
|
||||
else if (bpf_size == BPF_DW)
|
||||
return 8;
|
||||
else
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static bool is_spillable_regtype(enum bpf_reg_type type)
|
||||
{
|
||||
switch (type) {
|
||||
@@ -755,15 +745,29 @@ static int check_packet_access(struct bpf_verifier_env *env, u32 regno, int off,
|
||||
}
|
||||
|
||||
/* check access to 'struct bpf_context' fields */
|
||||
static int check_ctx_access(struct bpf_verifier_env *env, int off, int size,
|
||||
static int check_ctx_access(struct bpf_verifier_env *env, int insn_idx, int off, int size,
|
||||
enum bpf_access_type t, enum bpf_reg_type *reg_type)
|
||||
{
|
||||
struct bpf_insn_access_aux info = {
|
||||
.reg_type = *reg_type,
|
||||
};
|
||||
|
||||
/* for analyzer ctx accesses are already validated and converted */
|
||||
if (env->analyzer_ops)
|
||||
return 0;
|
||||
|
||||
if (env->prog->aux->ops->is_valid_access &&
|
||||
env->prog->aux->ops->is_valid_access(off, size, t, reg_type)) {
|
||||
env->prog->aux->ops->is_valid_access(off, size, t, &info)) {
|
||||
/* A non zero info.ctx_field_size indicates that this field is a
|
||||
* candidate for later verifier transformation to load the whole
|
||||
* field and then apply a mask when accessed with a narrower
|
||||
* access than actual ctx access size. A zero info.ctx_field_size
|
||||
* will only allow for whole field access and rejects any other
|
||||
* type of narrower access.
|
||||
*/
|
||||
env->insn_aux_data[insn_idx].ctx_field_size = info.ctx_field_size;
|
||||
*reg_type = info.reg_type;
|
||||
|
||||
/* remember the offset of last byte accessed in ctx */
|
||||
if (env->prog->aux->max_ctx_offset < off + size)
|
||||
env->prog->aux->max_ctx_offset = off + size;
|
||||
@@ -774,12 +778,13 @@ static int check_ctx_access(struct bpf_verifier_env *env, int off, int size,
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
static bool is_pointer_value(struct bpf_verifier_env *env, int regno)
|
||||
static bool __is_pointer_value(bool allow_ptr_leaks,
|
||||
const struct bpf_reg_state *reg)
|
||||
{
|
||||
if (env->allow_ptr_leaks)
|
||||
if (allow_ptr_leaks)
|
||||
return false;
|
||||
|
||||
switch (env->cur_state.regs[regno].type) {
|
||||
switch (reg->type) {
|
||||
case UNKNOWN_VALUE:
|
||||
case CONST_IMM:
|
||||
return false;
|
||||
@@ -788,6 +793,11 @@ static bool is_pointer_value(struct bpf_verifier_env *env, int regno)
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_pointer_value(struct bpf_verifier_env *env, int regno)
|
||||
{
|
||||
return __is_pointer_value(env->allow_ptr_leaks, &env->cur_state.regs[regno]);
|
||||
}
|
||||
|
||||
static int check_pkt_ptr_alignment(const struct bpf_reg_state *reg,
|
||||
int off, int size, bool strict)
|
||||
{
|
||||
@@ -808,11 +818,15 @@ static int check_pkt_ptr_alignment(const struct bpf_reg_state *reg,
|
||||
reg_off += reg->aux_off;
|
||||
}
|
||||
|
||||
/* skb->data is NET_IP_ALIGN-ed, but for strict alignment checking
|
||||
* we force this to 2 which is universally what architectures use
|
||||
* when they don't set CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS.
|
||||
/* For platforms that do not have a Kconfig enabling
|
||||
* CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS the value of
|
||||
* NET_IP_ALIGN is universally set to '2'. And on platforms
|
||||
* that do set CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS, we get
|
||||
* to this code only in strict mode where we want to emulate
|
||||
* the NET_IP_ALIGN==2 checking. Therefore use an
|
||||
* unconditional IP align value of '2'.
|
||||
*/
|
||||
ip_align = strict ? 2 : NET_IP_ALIGN;
|
||||
ip_align = 2;
|
||||
if ((ip_align + reg_off + off) % size != 0) {
|
||||
verbose("misaligned packet access off %d+%d+%d size %d\n",
|
||||
ip_align, reg_off, off, size);
|
||||
@@ -839,9 +853,6 @@ static int check_ptr_alignment(struct bpf_verifier_env *env,
|
||||
{
|
||||
bool strict = env->strict_alignment;
|
||||
|
||||
if (!IS_ENABLED(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS))
|
||||
strict = true;
|
||||
|
||||
switch (reg->type) {
|
||||
case PTR_TO_PACKET:
|
||||
return check_pkt_ptr_alignment(reg, off, size, strict);
|
||||
@@ -864,7 +875,7 @@ static int check_ptr_alignment(struct bpf_verifier_env *env,
|
||||
* if t==write && value_regno==-1, some unknown value is stored into memory
|
||||
* if t==read && value_regno==-1, don't care what we read from memory
|
||||
*/
|
||||
static int check_mem_access(struct bpf_verifier_env *env, u32 regno, int off,
|
||||
static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regno, int off,
|
||||
int bpf_size, enum bpf_access_type t,
|
||||
int value_regno)
|
||||
{
|
||||
@@ -907,7 +918,7 @@ static int check_mem_access(struct bpf_verifier_env *env, u32 regno, int off,
|
||||
verbose("R%d leaks addr into ctx\n", value_regno);
|
||||
return -EACCES;
|
||||
}
|
||||
err = check_ctx_access(env, off, size, t, ®_type);
|
||||
err = check_ctx_access(env, insn_idx, off, size, t, ®_type);
|
||||
if (!err && t == BPF_READ && value_regno >= 0) {
|
||||
mark_reg_unknown_value_and_range(state->regs,
|
||||
value_regno);
|
||||
@@ -922,6 +933,10 @@ static int check_mem_access(struct bpf_verifier_env *env, u32 regno, int off,
|
||||
verbose("invalid stack off=%d size=%d\n", off, size);
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
if (env->prog->aux->stack_depth < -off)
|
||||
env->prog->aux->stack_depth = -off;
|
||||
|
||||
if (t == BPF_WRITE) {
|
||||
if (!env->allow_ptr_leaks &&
|
||||
state->stack_slot_type[MAX_BPF_STACK + off] == STACK_SPILL &&
|
||||
@@ -964,7 +979,7 @@ static int check_mem_access(struct bpf_verifier_env *env, u32 regno, int off,
|
||||
return err;
|
||||
}
|
||||
|
||||
static int check_xadd(struct bpf_verifier_env *env, struct bpf_insn *insn)
|
||||
static int check_xadd(struct bpf_verifier_env *env, int insn_idx, struct bpf_insn *insn)
|
||||
{
|
||||
struct bpf_reg_state *regs = env->cur_state.regs;
|
||||
int err;
|
||||
@@ -985,14 +1000,19 @@ static int check_xadd(struct bpf_verifier_env *env, struct bpf_insn *insn)
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (is_pointer_value(env, insn->src_reg)) {
|
||||
verbose("R%d leaks addr into mem\n", insn->src_reg);
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
/* check whether atomic_add can read the memory */
|
||||
err = check_mem_access(env, insn->dst_reg, insn->off,
|
||||
err = check_mem_access(env, insn_idx, insn->dst_reg, insn->off,
|
||||
BPF_SIZE(insn->code), BPF_READ, -1);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* check whether atomic_add can write into the same memory */
|
||||
return check_mem_access(env, insn->dst_reg, insn->off,
|
||||
return check_mem_access(env, insn_idx, insn->dst_reg, insn->off,
|
||||
BPF_SIZE(insn->code), BPF_WRITE, -1);
|
||||
}
|
||||
|
||||
@@ -1028,6 +1048,9 @@ static int check_stack_boundary(struct bpf_verifier_env *env, int regno,
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
if (env->prog->aux->stack_depth < -off)
|
||||
env->prog->aux->stack_depth = -off;
|
||||
|
||||
if (meta && meta->raw_mode) {
|
||||
meta->access_size = access_size;
|
||||
meta->regno = regno;
|
||||
@@ -1335,8 +1358,8 @@ static void clear_all_pkt_pointers(struct bpf_verifier_env *env)
|
||||
if (reg->type != PTR_TO_PACKET &&
|
||||
reg->type != PTR_TO_PACKET_END)
|
||||
continue;
|
||||
reg->type = UNKNOWN_VALUE;
|
||||
reg->imm = 0;
|
||||
__mark_reg_unknown_value(state->spilled_regs,
|
||||
i / BPF_REG_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1345,7 +1368,6 @@ static int check_call(struct bpf_verifier_env *env, int func_id, int insn_idx)
|
||||
struct bpf_verifier_state *state = &env->cur_state;
|
||||
const struct bpf_func_proto *fn = NULL;
|
||||
struct bpf_reg_state *regs = state->regs;
|
||||
struct bpf_reg_state *reg;
|
||||
struct bpf_call_arg_meta meta;
|
||||
bool changes_data;
|
||||
int i, err;
|
||||
@@ -1406,17 +1428,14 @@ static int check_call(struct bpf_verifier_env *env, int func_id, int insn_idx)
|
||||
* is inferred from register state.
|
||||
*/
|
||||
for (i = 0; i < meta.access_size; i++) {
|
||||
err = check_mem_access(env, meta.regno, i, BPF_B, BPF_WRITE, -1);
|
||||
err = check_mem_access(env, insn_idx, meta.regno, i, BPF_B, BPF_WRITE, -1);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
/* reset caller saved regs */
|
||||
for (i = 0; i < CALLER_SAVED_REGS; i++) {
|
||||
reg = regs + caller_saved[i];
|
||||
reg->type = NOT_INIT;
|
||||
reg->imm = 0;
|
||||
}
|
||||
for (i = 0; i < CALLER_SAVED_REGS; i++)
|
||||
mark_reg_not_init(regs, caller_saved[i]);
|
||||
|
||||
/* update return register */
|
||||
if (fn->ret_type == RET_INTEGER) {
|
||||
@@ -1645,6 +1664,65 @@ static int evaluate_reg_alu(struct bpf_verifier_env *env, struct bpf_insn *insn)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int evaluate_reg_imm_alu_unknown(struct bpf_verifier_env *env,
|
||||
struct bpf_insn *insn)
|
||||
{
|
||||
struct bpf_reg_state *regs = env->cur_state.regs;
|
||||
struct bpf_reg_state *dst_reg = ®s[insn->dst_reg];
|
||||
struct bpf_reg_state *src_reg = ®s[insn->src_reg];
|
||||
u8 opcode = BPF_OP(insn->code);
|
||||
s64 imm_log2 = __ilog2_u64((long long)dst_reg->imm);
|
||||
|
||||
/* BPF_X code with src_reg->type UNKNOWN_VALUE here. */
|
||||
if (src_reg->imm > 0 && dst_reg->imm) {
|
||||
switch (opcode) {
|
||||
case BPF_ADD:
|
||||
/* dreg += sreg
|
||||
* where both have zero upper bits. Adding them
|
||||
* can only result making one more bit non-zero
|
||||
* in the larger value.
|
||||
* Ex. 0xffff (imm=48) + 1 (imm=63) = 0x10000 (imm=47)
|
||||
* 0xffff (imm=48) + 0xffff = 0x1fffe (imm=47)
|
||||
*/
|
||||
dst_reg->imm = min(src_reg->imm, 63 - imm_log2);
|
||||
dst_reg->imm--;
|
||||
break;
|
||||
case BPF_AND:
|
||||
/* dreg &= sreg
|
||||
* AND can not extend zero bits only shrink
|
||||
* Ex. 0x00..00ffffff
|
||||
* & 0x0f..ffffffff
|
||||
* ----------------
|
||||
* 0x00..00ffffff
|
||||
*/
|
||||
dst_reg->imm = max(src_reg->imm, 63 - imm_log2);
|
||||
break;
|
||||
case BPF_OR:
|
||||
/* dreg |= sreg
|
||||
* OR can only extend zero bits
|
||||
* Ex. 0x00..00ffffff
|
||||
* | 0x0f..ffffffff
|
||||
* ----------------
|
||||
* 0x0f..00ffffff
|
||||
*/
|
||||
dst_reg->imm = min(src_reg->imm, 63 - imm_log2);
|
||||
break;
|
||||
case BPF_SUB:
|
||||
case BPF_MUL:
|
||||
case BPF_RSH:
|
||||
case BPF_LSH:
|
||||
/* These may be flushed out later */
|
||||
default:
|
||||
mark_reg_unknown_value(regs, insn->dst_reg);
|
||||
}
|
||||
} else {
|
||||
mark_reg_unknown_value(regs, insn->dst_reg);
|
||||
}
|
||||
|
||||
dst_reg->type = UNKNOWN_VALUE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int evaluate_reg_imm_alu(struct bpf_verifier_env *env,
|
||||
struct bpf_insn *insn)
|
||||
{
|
||||
@@ -1654,6 +1732,9 @@ static int evaluate_reg_imm_alu(struct bpf_verifier_env *env,
|
||||
u8 opcode = BPF_OP(insn->code);
|
||||
u64 dst_imm = dst_reg->imm;
|
||||
|
||||
if (BPF_SRC(insn->code) == BPF_X && src_reg->type == UNKNOWN_VALUE)
|
||||
return evaluate_reg_imm_alu_unknown(env, insn);
|
||||
|
||||
/* dst_reg->type == CONST_IMM here. Simulate execution of insns
|
||||
* containing ALU ops. Don't care about overflow or negative
|
||||
* values, just add/sub/... them; registers are in u64.
|
||||
@@ -1758,10 +1839,24 @@ static void adjust_reg_min_max_vals(struct bpf_verifier_env *env,
|
||||
dst_align = dst_reg->min_align;
|
||||
|
||||
/* We don't know anything about what was done to this register, mark it
|
||||
* as unknown.
|
||||
* as unknown. Also, if both derived bounds came from signed/unsigned
|
||||
* mixed compares and one side is unbounded, we cannot really do anything
|
||||
* with them as boundaries cannot be trusted. Thus, arithmetic of two
|
||||
* regs of such kind will get invalidated bounds on the dst side.
|
||||
*/
|
||||
if (min_val == BPF_REGISTER_MIN_RANGE &&
|
||||
max_val == BPF_REGISTER_MAX_RANGE) {
|
||||
if ((min_val == BPF_REGISTER_MIN_RANGE &&
|
||||
max_val == BPF_REGISTER_MAX_RANGE) ||
|
||||
(BPF_SRC(insn->code) == BPF_X &&
|
||||
((min_val != BPF_REGISTER_MIN_RANGE &&
|
||||
max_val == BPF_REGISTER_MAX_RANGE) ||
|
||||
(min_val == BPF_REGISTER_MIN_RANGE &&
|
||||
max_val != BPF_REGISTER_MAX_RANGE) ||
|
||||
(dst_reg->min_value != BPF_REGISTER_MIN_RANGE &&
|
||||
dst_reg->max_value == BPF_REGISTER_MAX_RANGE) ||
|
||||
(dst_reg->min_value == BPF_REGISTER_MIN_RANGE &&
|
||||
dst_reg->max_value != BPF_REGISTER_MAX_RANGE)) &&
|
||||
regs[insn->dst_reg].value_from_signed !=
|
||||
regs[insn->src_reg].value_from_signed)) {
|
||||
reset_reg_range_values(regs, insn->dst_reg);
|
||||
return;
|
||||
}
|
||||
@@ -1945,9 +2040,11 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn)
|
||||
*/
|
||||
regs[insn->dst_reg].type = CONST_IMM;
|
||||
regs[insn->dst_reg].imm = insn->imm;
|
||||
regs[insn->dst_reg].id = 0;
|
||||
regs[insn->dst_reg].max_value = insn->imm;
|
||||
regs[insn->dst_reg].min_value = insn->imm;
|
||||
regs[insn->dst_reg].min_align = calc_align(insn->imm);
|
||||
regs[insn->dst_reg].value_from_signed = false;
|
||||
}
|
||||
|
||||
} else if (opcode > BPF_END) {
|
||||
@@ -2123,40 +2220,63 @@ static void reg_set_min_max(struct bpf_reg_state *true_reg,
|
||||
struct bpf_reg_state *false_reg, u64 val,
|
||||
u8 opcode)
|
||||
{
|
||||
bool value_from_signed = true;
|
||||
bool is_range = true;
|
||||
|
||||
switch (opcode) {
|
||||
case BPF_JEQ:
|
||||
/* If this is false then we know nothing Jon Snow, but if it is
|
||||
* true then we know for sure.
|
||||
*/
|
||||
true_reg->max_value = true_reg->min_value = val;
|
||||
is_range = false;
|
||||
break;
|
||||
case BPF_JNE:
|
||||
/* If this is true we know nothing Jon Snow, but if it is false
|
||||
* we know the value for sure;
|
||||
*/
|
||||
false_reg->max_value = false_reg->min_value = val;
|
||||
is_range = false;
|
||||
break;
|
||||
case BPF_JGT:
|
||||
/* Unsigned comparison, the minimum value is 0. */
|
||||
false_reg->min_value = 0;
|
||||
value_from_signed = false;
|
||||
/* fallthrough */
|
||||
case BPF_JSGT:
|
||||
if (true_reg->value_from_signed != value_from_signed)
|
||||
reset_reg_range_values(true_reg, 0);
|
||||
if (false_reg->value_from_signed != value_from_signed)
|
||||
reset_reg_range_values(false_reg, 0);
|
||||
if (opcode == BPF_JGT) {
|
||||
/* Unsigned comparison, the minimum value is 0. */
|
||||
false_reg->min_value = 0;
|
||||
}
|
||||
/* If this is false then we know the maximum val is val,
|
||||
* otherwise we know the min val is val+1.
|
||||
*/
|
||||
false_reg->max_value = val;
|
||||
false_reg->value_from_signed = value_from_signed;
|
||||
true_reg->min_value = val + 1;
|
||||
true_reg->value_from_signed = value_from_signed;
|
||||
break;
|
||||
case BPF_JGE:
|
||||
/* Unsigned comparison, the minimum value is 0. */
|
||||
false_reg->min_value = 0;
|
||||
value_from_signed = false;
|
||||
/* fallthrough */
|
||||
case BPF_JSGE:
|
||||
if (true_reg->value_from_signed != value_from_signed)
|
||||
reset_reg_range_values(true_reg, 0);
|
||||
if (false_reg->value_from_signed != value_from_signed)
|
||||
reset_reg_range_values(false_reg, 0);
|
||||
if (opcode == BPF_JGE) {
|
||||
/* Unsigned comparison, the minimum value is 0. */
|
||||
false_reg->min_value = 0;
|
||||
}
|
||||
/* If this is false then we know the maximum value is val - 1,
|
||||
* otherwise we know the mimimum value is val.
|
||||
*/
|
||||
false_reg->max_value = val - 1;
|
||||
false_reg->value_from_signed = value_from_signed;
|
||||
true_reg->min_value = val;
|
||||
true_reg->value_from_signed = value_from_signed;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -2164,6 +2284,12 @@ static void reg_set_min_max(struct bpf_reg_state *true_reg,
|
||||
|
||||
check_reg_overflow(false_reg);
|
||||
check_reg_overflow(true_reg);
|
||||
if (is_range) {
|
||||
if (__is_pointer_value(false, false_reg))
|
||||
reset_reg_range_values(false_reg, 0);
|
||||
if (__is_pointer_value(false, true_reg))
|
||||
reset_reg_range_values(true_reg, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Same as above, but for the case that dst_reg is a CONST_IMM reg and src_reg
|
||||
@@ -2173,41 +2299,64 @@ static void reg_set_min_max_inv(struct bpf_reg_state *true_reg,
|
||||
struct bpf_reg_state *false_reg, u64 val,
|
||||
u8 opcode)
|
||||
{
|
||||
bool value_from_signed = true;
|
||||
bool is_range = true;
|
||||
|
||||
switch (opcode) {
|
||||
case BPF_JEQ:
|
||||
/* If this is false then we know nothing Jon Snow, but if it is
|
||||
* true then we know for sure.
|
||||
*/
|
||||
true_reg->max_value = true_reg->min_value = val;
|
||||
is_range = false;
|
||||
break;
|
||||
case BPF_JNE:
|
||||
/* If this is true we know nothing Jon Snow, but if it is false
|
||||
* we know the value for sure;
|
||||
*/
|
||||
false_reg->max_value = false_reg->min_value = val;
|
||||
is_range = false;
|
||||
break;
|
||||
case BPF_JGT:
|
||||
/* Unsigned comparison, the minimum value is 0. */
|
||||
true_reg->min_value = 0;
|
||||
value_from_signed = false;
|
||||
/* fallthrough */
|
||||
case BPF_JSGT:
|
||||
if (true_reg->value_from_signed != value_from_signed)
|
||||
reset_reg_range_values(true_reg, 0);
|
||||
if (false_reg->value_from_signed != value_from_signed)
|
||||
reset_reg_range_values(false_reg, 0);
|
||||
if (opcode == BPF_JGT) {
|
||||
/* Unsigned comparison, the minimum value is 0. */
|
||||
true_reg->min_value = 0;
|
||||
}
|
||||
/*
|
||||
* If this is false, then the val is <= the register, if it is
|
||||
* true the register <= to the val.
|
||||
*/
|
||||
false_reg->min_value = val;
|
||||
false_reg->value_from_signed = value_from_signed;
|
||||
true_reg->max_value = val - 1;
|
||||
true_reg->value_from_signed = value_from_signed;
|
||||
break;
|
||||
case BPF_JGE:
|
||||
/* Unsigned comparison, the minimum value is 0. */
|
||||
true_reg->min_value = 0;
|
||||
value_from_signed = false;
|
||||
/* fallthrough */
|
||||
case BPF_JSGE:
|
||||
if (true_reg->value_from_signed != value_from_signed)
|
||||
reset_reg_range_values(true_reg, 0);
|
||||
if (false_reg->value_from_signed != value_from_signed)
|
||||
reset_reg_range_values(false_reg, 0);
|
||||
if (opcode == BPF_JGE) {
|
||||
/* Unsigned comparison, the minimum value is 0. */
|
||||
true_reg->min_value = 0;
|
||||
}
|
||||
/* If this is false then constant < register, if it is true then
|
||||
* the register < constant.
|
||||
*/
|
||||
false_reg->min_value = val + 1;
|
||||
false_reg->value_from_signed = value_from_signed;
|
||||
true_reg->max_value = val;
|
||||
true_reg->value_from_signed = value_from_signed;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -2215,6 +2364,12 @@ static void reg_set_min_max_inv(struct bpf_reg_state *true_reg,
|
||||
|
||||
check_reg_overflow(false_reg);
|
||||
check_reg_overflow(true_reg);
|
||||
if (is_range) {
|
||||
if (__is_pointer_value(false, false_reg))
|
||||
reset_reg_range_values(false_reg, 0);
|
||||
if (__is_pointer_value(false, true_reg))
|
||||
reset_reg_range_values(true_reg, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void mark_map_reg(struct bpf_reg_state *regs, u32 regno, u32 id,
|
||||
@@ -2402,6 +2557,7 @@ static int check_ld_imm(struct bpf_verifier_env *env, struct bpf_insn *insn)
|
||||
|
||||
regs[insn->dst_reg].type = CONST_IMM;
|
||||
regs[insn->dst_reg].imm = imm;
|
||||
regs[insn->dst_reg].id = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2444,7 +2600,6 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn)
|
||||
{
|
||||
struct bpf_reg_state *regs = env->cur_state.regs;
|
||||
u8 mode = BPF_MODE(insn->code);
|
||||
struct bpf_reg_state *reg;
|
||||
int i, err;
|
||||
|
||||
if (!may_access_skb(env->prog->type)) {
|
||||
@@ -2477,11 +2632,8 @@ static int check_ld_abs(struct bpf_verifier_env *env, struct bpf_insn *insn)
|
||||
}
|
||||
|
||||
/* reset caller saved regs to unreadable */
|
||||
for (i = 0; i < CALLER_SAVED_REGS; i++) {
|
||||
reg = regs + caller_saved[i];
|
||||
reg->type = NOT_INIT;
|
||||
reg->imm = 0;
|
||||
}
|
||||
for (i = 0; i < CALLER_SAVED_REGS; i++)
|
||||
mark_reg_not_init(regs, caller_saved[i]);
|
||||
|
||||
/* mark destination R0 register as readable, since it contains
|
||||
* the value fetched from the packet
|
||||
@@ -2692,7 +2844,8 @@ err_free:
|
||||
/* the following conditions reduce the number of explored insns
|
||||
* from ~140k to ~80k for ultra large programs that use a lot of ptr_to_packet
|
||||
*/
|
||||
static bool compare_ptrs_to_packet(struct bpf_reg_state *old,
|
||||
static bool compare_ptrs_to_packet(struct bpf_verifier_env *env,
|
||||
struct bpf_reg_state *old,
|
||||
struct bpf_reg_state *cur)
|
||||
{
|
||||
if (old->id != cur->id)
|
||||
@@ -2735,7 +2888,7 @@ static bool compare_ptrs_to_packet(struct bpf_reg_state *old,
|
||||
* 'if (R4 > data_end)' and all further insn were already good with r=20,
|
||||
* so they will be good with r=30 and we can prune the search.
|
||||
*/
|
||||
if (old->off <= cur->off &&
|
||||
if (!env->strict_alignment && old->off <= cur->off &&
|
||||
old->off >= old->range && cur->off >= cur->range)
|
||||
return true;
|
||||
|
||||
@@ -2806,7 +2959,7 @@ static bool states_equal(struct bpf_verifier_env *env,
|
||||
continue;
|
||||
|
||||
if (rold->type == PTR_TO_PACKET && rcur->type == PTR_TO_PACKET &&
|
||||
compare_ptrs_to_packet(rold, rcur))
|
||||
compare_ptrs_to_packet(env, rold, rcur))
|
||||
continue;
|
||||
|
||||
return false;
|
||||
@@ -2824,6 +2977,8 @@ static bool states_equal(struct bpf_verifier_env *env,
|
||||
return false;
|
||||
if (i % BPF_REG_SIZE)
|
||||
continue;
|
||||
if (old->stack_slot_type[i] != STACK_SPILL)
|
||||
continue;
|
||||
if (memcmp(&old->spilled_regs[i / BPF_REG_SIZE],
|
||||
&cur->spilled_regs[i / BPF_REG_SIZE],
|
||||
sizeof(old->spilled_regs[0])))
|
||||
@@ -2985,18 +3140,12 @@ static int do_check(struct bpf_verifier_env *env)
|
||||
/* check that memory (src_reg + off) is readable,
|
||||
* the state of dst_reg will be updated by this func
|
||||
*/
|
||||
err = check_mem_access(env, insn->src_reg, insn->off,
|
||||
err = check_mem_access(env, insn_idx, insn->src_reg, insn->off,
|
||||
BPF_SIZE(insn->code), BPF_READ,
|
||||
insn->dst_reg);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
if (BPF_SIZE(insn->code) != BPF_W &&
|
||||
BPF_SIZE(insn->code) != BPF_DW) {
|
||||
insn_idx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
prev_src_type = &env->insn_aux_data[insn_idx].ptr_type;
|
||||
|
||||
if (*prev_src_type == NOT_INIT) {
|
||||
@@ -3024,7 +3173,7 @@ static int do_check(struct bpf_verifier_env *env)
|
||||
enum bpf_reg_type *prev_dst_type, dst_reg_type;
|
||||
|
||||
if (BPF_MODE(insn->code) == BPF_XADD) {
|
||||
err = check_xadd(env, insn);
|
||||
err = check_xadd(env, insn_idx, insn);
|
||||
if (err)
|
||||
return err;
|
||||
insn_idx++;
|
||||
@@ -3043,7 +3192,7 @@ static int do_check(struct bpf_verifier_env *env)
|
||||
dst_reg_type = regs[insn->dst_reg].type;
|
||||
|
||||
/* check that memory (dst_reg + off) is writeable */
|
||||
err = check_mem_access(env, insn->dst_reg, insn->off,
|
||||
err = check_mem_access(env, insn_idx, insn->dst_reg, insn->off,
|
||||
BPF_SIZE(insn->code), BPF_WRITE,
|
||||
insn->src_reg);
|
||||
if (err)
|
||||
@@ -3072,7 +3221,7 @@ static int do_check(struct bpf_verifier_env *env)
|
||||
return err;
|
||||
|
||||
/* check that memory (dst_reg + off) is writeable */
|
||||
err = check_mem_access(env, insn->dst_reg, insn->off,
|
||||
err = check_mem_access(env, insn_idx, insn->dst_reg, insn->off,
|
||||
BPF_SIZE(insn->code), BPF_WRITE,
|
||||
-1);
|
||||
if (err)
|
||||
@@ -3170,7 +3319,8 @@ process_bpf_exit:
|
||||
insn_idx++;
|
||||
}
|
||||
|
||||
verbose("processed %d insns\n", insn_processed);
|
||||
verbose("processed %d insns, stack depth %d\n",
|
||||
insn_processed, env->prog->aux->stack_depth);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -3370,11 +3520,13 @@ static struct bpf_prog *bpf_patch_insn_data(struct bpf_verifier_env *env, u32 of
|
||||
static int convert_ctx_accesses(struct bpf_verifier_env *env)
|
||||
{
|
||||
const struct bpf_verifier_ops *ops = env->prog->aux->ops;
|
||||
int i, cnt, size, ctx_field_size, delta = 0;
|
||||
const int insn_cnt = env->prog->len;
|
||||
struct bpf_insn insn_buf[16], *insn;
|
||||
struct bpf_prog *new_prog;
|
||||
enum bpf_access_type type;
|
||||
int i, cnt, delta = 0;
|
||||
bool is_narrower_load;
|
||||
u32 target_size;
|
||||
|
||||
if (ops->gen_prologue) {
|
||||
cnt = ops->gen_prologue(insn_buf, env->seen_direct_write,
|
||||
@@ -3414,12 +3566,52 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env)
|
||||
if (env->insn_aux_data[i + delta].ptr_type != PTR_TO_CTX)
|
||||
continue;
|
||||
|
||||
cnt = ops->convert_ctx_access(type, insn, insn_buf, env->prog);
|
||||
if (cnt == 0 || cnt >= ARRAY_SIZE(insn_buf)) {
|
||||
ctx_field_size = env->insn_aux_data[i + delta].ctx_field_size;
|
||||
size = BPF_LDST_BYTES(insn);
|
||||
|
||||
/* If the read access is a narrower load of the field,
|
||||
* convert to a 4/8-byte load, to minimum program type specific
|
||||
* convert_ctx_access changes. If conversion is successful,
|
||||
* we will apply proper mask to the result.
|
||||
*/
|
||||
is_narrower_load = size < ctx_field_size;
|
||||
if (is_narrower_load) {
|
||||
u32 off = insn->off;
|
||||
u8 size_code;
|
||||
|
||||
if (type == BPF_WRITE) {
|
||||
verbose("bpf verifier narrow ctx access misconfigured\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
size_code = BPF_H;
|
||||
if (ctx_field_size == 4)
|
||||
size_code = BPF_W;
|
||||
else if (ctx_field_size == 8)
|
||||
size_code = BPF_DW;
|
||||
|
||||
insn->off = off & ~(ctx_field_size - 1);
|
||||
insn->code = BPF_LDX | BPF_MEM | size_code;
|
||||
}
|
||||
|
||||
target_size = 0;
|
||||
cnt = ops->convert_ctx_access(type, insn, insn_buf, env->prog,
|
||||
&target_size);
|
||||
if (cnt == 0 || cnt >= ARRAY_SIZE(insn_buf) ||
|
||||
(ctx_field_size && !target_size)) {
|
||||
verbose("bpf verifier is misconfigured\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (is_narrower_load && size < target_size) {
|
||||
if (ctx_field_size <= 4)
|
||||
insn_buf[cnt++] = BPF_ALU32_IMM(BPF_AND, insn->dst_reg,
|
||||
(1 << size * 8) - 1);
|
||||
else
|
||||
insn_buf[cnt++] = BPF_ALU64_IMM(BPF_AND, insn->dst_reg,
|
||||
(1 << size * 8) - 1);
|
||||
}
|
||||
|
||||
new_prog = bpf_patch_insn_data(env, i + delta, insn_buf, cnt);
|
||||
if (!new_prog)
|
||||
return -ENOMEM;
|
||||
@@ -3465,6 +3657,7 @@ static int fixup_bpf_calls(struct bpf_verifier_env *env)
|
||||
* the program array.
|
||||
*/
|
||||
prog->cb_access = 1;
|
||||
env->prog->aux->stack_depth = MAX_BPF_STACK;
|
||||
|
||||
/* mark bpf_tail_call as different opcode to avoid
|
||||
* conditional branch in the interpeter for every normal
|
||||
@@ -3472,7 +3665,7 @@ static int fixup_bpf_calls(struct bpf_verifier_env *env)
|
||||
* that doesn't support bpf_tail_call yet
|
||||
*/
|
||||
insn->imm = 0;
|
||||
insn->code |= BPF_X;
|
||||
insn->code = BPF_JMP | BPF_TAIL_CALL;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3584,10 +3777,10 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr)
|
||||
} else {
|
||||
log_level = 0;
|
||||
}
|
||||
if (attr->prog_flags & BPF_F_STRICT_ALIGNMENT)
|
||||
|
||||
env->strict_alignment = !!(attr->prog_flags & BPF_F_STRICT_ALIGNMENT);
|
||||
if (!IS_ENABLED(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS))
|
||||
env->strict_alignment = true;
|
||||
else
|
||||
env->strict_alignment = false;
|
||||
|
||||
ret = replace_map_fd_with_map_ptr(env);
|
||||
if (ret < 0)
|
||||
@@ -3693,7 +3886,10 @@ int bpf_analyzer(struct bpf_prog *prog, const struct bpf_ext_analyzer_ops *ops,
|
||||
mutex_lock(&bpf_verifier_lock);
|
||||
|
||||
log_level = 0;
|
||||
|
||||
env->strict_alignment = false;
|
||||
if (!IS_ENABLED(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS))
|
||||
env->strict_alignment = true;
|
||||
|
||||
env->explored_states = kcalloc(env->prog->len,
|
||||
sizeof(struct bpf_verifier_state_list *),
|
||||
|
||||
@@ -4,3 +4,4 @@ obj-$(CONFIG_CGROUP_FREEZER) += freezer.o
|
||||
obj-$(CONFIG_CGROUP_PIDS) += pids.o
|
||||
obj-$(CONFIG_CGROUP_RDMA) += rdma.o
|
||||
obj-$(CONFIG_CPUSETS) += cpuset.o
|
||||
obj-$(CONFIG_CGROUP_DEBUG) += debug.o
|
||||
|
||||
@@ -192,6 +192,8 @@ int cgroup_rmdir(struct kernfs_node *kn);
|
||||
int cgroup_show_path(struct seq_file *sf, struct kernfs_node *kf_node,
|
||||
struct kernfs_root *kf_root);
|
||||
|
||||
int cgroup_task_count(const struct cgroup *cgrp);
|
||||
|
||||
/*
|
||||
* namespace.c
|
||||
*/
|
||||
|
||||
+2
-153
@@ -334,19 +334,15 @@ static struct cgroup_pidlist *cgroup_pidlist_find_create(struct cgroup *cgrp,
|
||||
/**
|
||||
* cgroup_task_count - count the number of tasks in a cgroup.
|
||||
* @cgrp: the cgroup in question
|
||||
*
|
||||
* Return the number of tasks in the cgroup. The returned number can be
|
||||
* higher than the actual number of tasks due to css_set references from
|
||||
* namespace roots and temporary usages.
|
||||
*/
|
||||
static int cgroup_task_count(const struct cgroup *cgrp)
|
||||
int cgroup_task_count(const struct cgroup *cgrp)
|
||||
{
|
||||
int count = 0;
|
||||
struct cgrp_cset_link *link;
|
||||
|
||||
spin_lock_irq(&css_set_lock);
|
||||
list_for_each_entry(link, &cgrp->cset_links, cset_link)
|
||||
count += refcount_read(&link->cset->refcount);
|
||||
count += link->cset->nr_tasks;
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
return count;
|
||||
}
|
||||
@@ -1263,150 +1259,3 @@ static int __init cgroup_no_v1(char *str)
|
||||
return 1;
|
||||
}
|
||||
__setup("cgroup_no_v1=", cgroup_no_v1);
|
||||
|
||||
|
||||
#ifdef CONFIG_CGROUP_DEBUG
|
||||
static struct cgroup_subsys_state *
|
||||
debug_css_alloc(struct cgroup_subsys_state *parent_css)
|
||||
{
|
||||
struct cgroup_subsys_state *css = kzalloc(sizeof(*css), GFP_KERNEL);
|
||||
|
||||
if (!css)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
return css;
|
||||
}
|
||||
|
||||
static void debug_css_free(struct cgroup_subsys_state *css)
|
||||
{
|
||||
kfree(css);
|
||||
}
|
||||
|
||||
static u64 debug_taskcount_read(struct cgroup_subsys_state *css,
|
||||
struct cftype *cft)
|
||||
{
|
||||
return cgroup_task_count(css->cgroup);
|
||||
}
|
||||
|
||||
static u64 current_css_set_read(struct cgroup_subsys_state *css,
|
||||
struct cftype *cft)
|
||||
{
|
||||
return (u64)(unsigned long)current->cgroups;
|
||||
}
|
||||
|
||||
static u64 current_css_set_refcount_read(struct cgroup_subsys_state *css,
|
||||
struct cftype *cft)
|
||||
{
|
||||
u64 count;
|
||||
|
||||
rcu_read_lock();
|
||||
count = refcount_read(&task_css_set(current)->refcount);
|
||||
rcu_read_unlock();
|
||||
return count;
|
||||
}
|
||||
|
||||
static int current_css_set_cg_links_read(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct cgrp_cset_link *link;
|
||||
struct css_set *cset;
|
||||
char *name_buf;
|
||||
|
||||
name_buf = kmalloc(NAME_MAX + 1, GFP_KERNEL);
|
||||
if (!name_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_irq(&css_set_lock);
|
||||
rcu_read_lock();
|
||||
cset = rcu_dereference(current->cgroups);
|
||||
list_for_each_entry(link, &cset->cgrp_links, cgrp_link) {
|
||||
struct cgroup *c = link->cgrp;
|
||||
|
||||
cgroup_name(c, name_buf, NAME_MAX + 1);
|
||||
seq_printf(seq, "Root %d group %s\n",
|
||||
c->root->hierarchy_id, name_buf);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
kfree(name_buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MAX_TASKS_SHOWN_PER_CSS 25
|
||||
static int cgroup_css_links_read(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct cgroup_subsys_state *css = seq_css(seq);
|
||||
struct cgrp_cset_link *link;
|
||||
|
||||
spin_lock_irq(&css_set_lock);
|
||||
list_for_each_entry(link, &css->cgroup->cset_links, cset_link) {
|
||||
struct css_set *cset = link->cset;
|
||||
struct task_struct *task;
|
||||
int count = 0;
|
||||
|
||||
seq_printf(seq, "css_set %pK\n", cset);
|
||||
|
||||
list_for_each_entry(task, &cset->tasks, cg_list) {
|
||||
if (count++ > MAX_TASKS_SHOWN_PER_CSS)
|
||||
goto overflow;
|
||||
seq_printf(seq, " task %d\n", task_pid_vnr(task));
|
||||
}
|
||||
|
||||
list_for_each_entry(task, &cset->mg_tasks, cg_list) {
|
||||
if (count++ > MAX_TASKS_SHOWN_PER_CSS)
|
||||
goto overflow;
|
||||
seq_printf(seq, " task %d\n", task_pid_vnr(task));
|
||||
}
|
||||
continue;
|
||||
overflow:
|
||||
seq_puts(seq, " ...\n");
|
||||
}
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u64 releasable_read(struct cgroup_subsys_state *css, struct cftype *cft)
|
||||
{
|
||||
return (!cgroup_is_populated(css->cgroup) &&
|
||||
!css_has_online_children(&css->cgroup->self));
|
||||
}
|
||||
|
||||
static struct cftype debug_files[] = {
|
||||
{
|
||||
.name = "taskcount",
|
||||
.read_u64 = debug_taskcount_read,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "current_css_set",
|
||||
.read_u64 = current_css_set_read,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "current_css_set_refcount",
|
||||
.read_u64 = current_css_set_refcount_read,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "current_css_set_cg_links",
|
||||
.seq_show = current_css_set_cg_links_read,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "cgroup_css_links",
|
||||
.seq_show = cgroup_css_links_read,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "releasable",
|
||||
.read_u64 = releasable_read,
|
||||
},
|
||||
|
||||
{ } /* terminate */
|
||||
};
|
||||
|
||||
struct cgroup_subsys debug_cgrp_subsys = {
|
||||
.css_alloc = debug_css_alloc,
|
||||
.css_free = debug_css_free,
|
||||
.legacy_cftypes = debug_files,
|
||||
};
|
||||
#endif /* CONFIG_CGROUP_DEBUG */
|
||||
|
||||
+131
-29
@@ -573,6 +573,11 @@ static int css_set_count = 1; /* 1 for init_css_set */
|
||||
/**
|
||||
* css_set_populated - does a css_set contain any tasks?
|
||||
* @cset: target css_set
|
||||
*
|
||||
* css_set_populated() should be the same as !!cset->nr_tasks at steady
|
||||
* state. However, css_set_populated() can be called while a task is being
|
||||
* added to or removed from the linked list before the nr_tasks is
|
||||
* properly updated. Hence, we can't just look at ->nr_tasks here.
|
||||
*/
|
||||
static bool css_set_populated(struct css_set *cset)
|
||||
{
|
||||
@@ -1542,10 +1547,56 @@ int cgroup_show_path(struct seq_file *sf, struct kernfs_node *kf_node,
|
||||
return len;
|
||||
}
|
||||
|
||||
static int parse_cgroup_root_flags(char *data, unsigned int *root_flags)
|
||||
{
|
||||
char *token;
|
||||
|
||||
*root_flags = 0;
|
||||
|
||||
if (!data)
|
||||
return 0;
|
||||
|
||||
while ((token = strsep(&data, ",")) != NULL) {
|
||||
if (!strcmp(token, "nsdelegate")) {
|
||||
*root_flags |= CGRP_ROOT_NS_DELEGATE;
|
||||
continue;
|
||||
}
|
||||
|
||||
pr_err("cgroup2: unknown option \"%s\"\n", token);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void apply_cgroup_root_flags(unsigned int root_flags)
|
||||
{
|
||||
if (current->nsproxy->cgroup_ns == &init_cgroup_ns) {
|
||||
if (root_flags & CGRP_ROOT_NS_DELEGATE)
|
||||
cgrp_dfl_root.flags |= CGRP_ROOT_NS_DELEGATE;
|
||||
else
|
||||
cgrp_dfl_root.flags &= ~CGRP_ROOT_NS_DELEGATE;
|
||||
}
|
||||
}
|
||||
|
||||
static int cgroup_show_options(struct seq_file *seq, struct kernfs_root *kf_root)
|
||||
{
|
||||
if (cgrp_dfl_root.flags & CGRP_ROOT_NS_DELEGATE)
|
||||
seq_puts(seq, ",nsdelegate");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cgroup_remount(struct kernfs_root *kf_root, int *flags, char *data)
|
||||
{
|
||||
pr_err("remount is not allowed\n");
|
||||
return -EINVAL;
|
||||
unsigned int root_flags;
|
||||
int ret;
|
||||
|
||||
ret = parse_cgroup_root_flags(data, &root_flags);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
apply_cgroup_root_flags(root_flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1598,6 +1649,7 @@ static void cgroup_enable_task_cg_lists(void)
|
||||
css_set_update_populated(cset, true);
|
||||
list_add_tail(&p->cg_list, &cset->tasks);
|
||||
get_css_set(cset);
|
||||
cset->nr_tasks++;
|
||||
}
|
||||
spin_unlock(&p->sighand->siglock);
|
||||
} while_each_thread(g, p);
|
||||
@@ -1784,6 +1836,7 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
|
||||
{
|
||||
struct cgroup_namespace *ns = current->nsproxy->cgroup_ns;
|
||||
struct dentry *dentry;
|
||||
int ret;
|
||||
|
||||
get_cgroup_ns(ns);
|
||||
|
||||
@@ -1801,16 +1854,21 @@ static struct dentry *cgroup_mount(struct file_system_type *fs_type,
|
||||
cgroup_enable_task_cg_lists();
|
||||
|
||||
if (fs_type == &cgroup2_fs_type) {
|
||||
if (data) {
|
||||
pr_err("cgroup2: unknown option \"%s\"\n", (char *)data);
|
||||
unsigned int root_flags;
|
||||
|
||||
ret = parse_cgroup_root_flags(data, &root_flags);
|
||||
if (ret) {
|
||||
put_cgroup_ns(ns);
|
||||
return ERR_PTR(-EINVAL);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
cgrp_dfl_visible = true;
|
||||
cgroup_get_live(&cgrp_dfl_root.cgrp);
|
||||
|
||||
dentry = cgroup_do_mount(&cgroup2_fs_type, flags, &cgrp_dfl_root,
|
||||
CGROUP2_SUPER_MAGIC, ns);
|
||||
if (!IS_ERR(dentry))
|
||||
apply_cgroup_root_flags(root_flags);
|
||||
} else {
|
||||
dentry = cgroup1_mount(&cgroup_fs_type, flags, data,
|
||||
CGROUP_SUPER_MAGIC, ns);
|
||||
@@ -2064,8 +2122,10 @@ static int cgroup_migrate_execute(struct cgroup_mgctx *mgctx)
|
||||
struct css_set *to_cset = cset->mg_dst_cset;
|
||||
|
||||
get_css_set(to_cset);
|
||||
to_cset->nr_tasks++;
|
||||
css_set_move_task(task, from_cset, to_cset, true);
|
||||
put_css_set_locked(from_cset);
|
||||
from_cset->nr_tasks--;
|
||||
}
|
||||
}
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
@@ -2355,27 +2415,14 @@ static int cgroup_procs_write_permission(struct task_struct *task,
|
||||
struct cgroup *dst_cgrp,
|
||||
struct kernfs_open_file *of)
|
||||
{
|
||||
int ret = 0;
|
||||
struct super_block *sb = of->file->f_path.dentry->d_sb;
|
||||
struct cgroup_namespace *ns = current->nsproxy->cgroup_ns;
|
||||
struct cgroup *root_cgrp = ns->root_cset->dfl_cgrp;
|
||||
struct cgroup *src_cgrp, *com_cgrp;
|
||||
struct inode *inode;
|
||||
int ret;
|
||||
|
||||
if (cgroup_on_dfl(dst_cgrp)) {
|
||||
struct super_block *sb = of->file->f_path.dentry->d_sb;
|
||||
struct cgroup *cgrp;
|
||||
struct inode *inode;
|
||||
|
||||
spin_lock_irq(&css_set_lock);
|
||||
cgrp = task_cgroup_from_root(task, &cgrp_dfl_root);
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
|
||||
while (!cgroup_is_descendant(dst_cgrp, cgrp))
|
||||
cgrp = cgroup_parent(cgrp);
|
||||
|
||||
ret = -ENOMEM;
|
||||
inode = kernfs_get_inode(sb, cgrp->procs_file.kn);
|
||||
if (inode) {
|
||||
ret = inode_permission(inode, MAY_WRITE);
|
||||
iput(inode);
|
||||
}
|
||||
} else {
|
||||
if (!cgroup_on_dfl(dst_cgrp)) {
|
||||
const struct cred *cred = current_cred();
|
||||
const struct cred *tcred = get_task_cred(task);
|
||||
|
||||
@@ -2383,14 +2430,47 @@ static int cgroup_procs_write_permission(struct task_struct *task,
|
||||
* even if we're attaching all tasks in the thread group,
|
||||
* we only need to check permissions on one of them.
|
||||
*/
|
||||
if (!uid_eq(cred->euid, GLOBAL_ROOT_UID) &&
|
||||
!uid_eq(cred->euid, tcred->uid) &&
|
||||
!uid_eq(cred->euid, tcred->suid))
|
||||
if (uid_eq(cred->euid, GLOBAL_ROOT_UID) ||
|
||||
uid_eq(cred->euid, tcred->uid) ||
|
||||
uid_eq(cred->euid, tcred->suid))
|
||||
ret = 0;
|
||||
else
|
||||
ret = -EACCES;
|
||||
|
||||
put_cred(tcred);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
/* find the source cgroup */
|
||||
spin_lock_irq(&css_set_lock);
|
||||
src_cgrp = task_cgroup_from_root(task, &cgrp_dfl_root);
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
|
||||
/* and the common ancestor */
|
||||
com_cgrp = src_cgrp;
|
||||
while (!cgroup_is_descendant(dst_cgrp, com_cgrp))
|
||||
com_cgrp = cgroup_parent(com_cgrp);
|
||||
|
||||
/* %current should be authorized to migrate to the common ancestor */
|
||||
inode = kernfs_get_inode(sb, com_cgrp->procs_file.kn);
|
||||
if (!inode)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = inode_permission(inode, MAY_WRITE);
|
||||
iput(inode);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* If namespaces are delegation boundaries, %current must be able
|
||||
* to see both source and destination cgroups from its namespace.
|
||||
*/
|
||||
if ((cgrp_dfl_root.flags & CGRP_ROOT_NS_DELEGATE) &&
|
||||
(!cgroup_is_descendant(src_cgrp, root_cgrp) ||
|
||||
!cgroup_is_descendant(dst_cgrp, root_cgrp)))
|
||||
return -ENOENT;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2954,11 +3034,23 @@ static void cgroup_file_release(struct kernfs_open_file *of)
|
||||
static ssize_t cgroup_file_write(struct kernfs_open_file *of, char *buf,
|
||||
size_t nbytes, loff_t off)
|
||||
{
|
||||
struct cgroup_namespace *ns = current->nsproxy->cgroup_ns;
|
||||
struct cgroup *cgrp = of->kn->parent->priv;
|
||||
struct cftype *cft = of->kn->priv;
|
||||
struct cgroup_subsys_state *css;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* If namespaces are delegation boundaries, disallow writes to
|
||||
* files in an non-init namespace root from inside the namespace
|
||||
* except for the files explicitly marked delegatable -
|
||||
* cgroup.procs and cgroup.subtree_control.
|
||||
*/
|
||||
if ((cgrp->root->flags & CGRP_ROOT_NS_DELEGATE) &&
|
||||
!(cft->flags & CFTYPE_NS_DELEGATABLE) &&
|
||||
ns != &init_cgroup_ns && ns->root_cset->dfl_cgrp == cgrp)
|
||||
return -EPERM;
|
||||
|
||||
if (cft->write)
|
||||
return cft->write(of, buf, nbytes, off);
|
||||
|
||||
@@ -3792,6 +3884,7 @@ static int cgroup_procs_show(struct seq_file *s, void *v)
|
||||
static struct cftype cgroup_base_files[] = {
|
||||
{
|
||||
.name = "cgroup.procs",
|
||||
.flags = CFTYPE_NS_DELEGATABLE,
|
||||
.file_offset = offsetof(struct cgroup, procs_file),
|
||||
.release = cgroup_procs_release,
|
||||
.seq_start = cgroup_procs_start,
|
||||
@@ -3805,6 +3898,7 @@ static struct cftype cgroup_base_files[] = {
|
||||
},
|
||||
{
|
||||
.name = "cgroup.subtree_control",
|
||||
.flags = CFTYPE_NS_DELEGATABLE,
|
||||
.seq_show = cgroup_subtree_control_show,
|
||||
.write = cgroup_subtree_control_write,
|
||||
},
|
||||
@@ -4265,6 +4359,11 @@ static void kill_css(struct cgroup_subsys_state *css)
|
||||
{
|
||||
lockdep_assert_held(&cgroup_mutex);
|
||||
|
||||
if (css->flags & CSS_DYING)
|
||||
return;
|
||||
|
||||
css->flags |= CSS_DYING;
|
||||
|
||||
/*
|
||||
* This must happen before css is disassociated with its cgroup.
|
||||
* See seq_css() for details.
|
||||
@@ -4388,6 +4487,7 @@ int cgroup_rmdir(struct kernfs_node *kn)
|
||||
}
|
||||
|
||||
static struct kernfs_syscall_ops cgroup_kf_syscall_ops = {
|
||||
.show_options = cgroup_show_options,
|
||||
.remount_fs = cgroup_remount,
|
||||
.mkdir = cgroup_mkdir,
|
||||
.rmdir = cgroup_rmdir,
|
||||
@@ -4784,6 +4884,7 @@ void cgroup_post_fork(struct task_struct *child)
|
||||
cset = task_css_set(current);
|
||||
if (list_empty(&child->cg_list)) {
|
||||
get_css_set(cset);
|
||||
cset->nr_tasks++;
|
||||
css_set_move_task(child, NULL, cset, false);
|
||||
}
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
@@ -4833,6 +4934,7 @@ void cgroup_exit(struct task_struct *tsk)
|
||||
if (!list_empty(&tsk->cg_list)) {
|
||||
spin_lock_irq(&css_set_lock);
|
||||
css_set_move_task(tsk, cset, NULL, false);
|
||||
cset->nr_tasks--;
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
} else {
|
||||
get_css_set(cset);
|
||||
|
||||
+11
-26
@@ -176,9 +176,9 @@ typedef enum {
|
||||
} cpuset_flagbits_t;
|
||||
|
||||
/* convenient tests for these bits */
|
||||
static inline bool is_cpuset_online(const struct cpuset *cs)
|
||||
static inline bool is_cpuset_online(struct cpuset *cs)
|
||||
{
|
||||
return test_bit(CS_ONLINE, &cs->flags);
|
||||
return test_bit(CS_ONLINE, &cs->flags) && !css_is_dying(&cs->css);
|
||||
}
|
||||
|
||||
static inline int is_cpu_exclusive(const struct cpuset *cs)
|
||||
@@ -1038,40 +1038,25 @@ static void cpuset_post_attach(void)
|
||||
* @tsk: the task to change
|
||||
* @newmems: new nodes that the task will be set
|
||||
*
|
||||
* In order to avoid seeing no nodes if the old and new nodes are disjoint,
|
||||
* we structure updates as setting all new allowed nodes, then clearing newly
|
||||
* disallowed ones.
|
||||
* We use the mems_allowed_seq seqlock to safely update both tsk->mems_allowed
|
||||
* and rebind an eventual tasks' mempolicy. If the task is allocating in
|
||||
* parallel, it might temporarily see an empty intersection, which results in
|
||||
* a seqlock check and retry before OOM or allocation failure.
|
||||
*/
|
||||
static void cpuset_change_task_nodemask(struct task_struct *tsk,
|
||||
nodemask_t *newmems)
|
||||
{
|
||||
bool need_loop;
|
||||
|
||||
task_lock(tsk);
|
||||
/*
|
||||
* Determine if a loop is necessary if another thread is doing
|
||||
* read_mems_allowed_begin(). If at least one node remains unchanged and
|
||||
* tsk does not have a mempolicy, then an empty nodemask will not be
|
||||
* possible when mems_allowed is larger than a word.
|
||||
*/
|
||||
need_loop = task_has_mempolicy(tsk) ||
|
||||
!nodes_intersects(*newmems, tsk->mems_allowed);
|
||||
|
||||
if (need_loop) {
|
||||
local_irq_disable();
|
||||
write_seqcount_begin(&tsk->mems_allowed_seq);
|
||||
}
|
||||
local_irq_disable();
|
||||
write_seqcount_begin(&tsk->mems_allowed_seq);
|
||||
|
||||
nodes_or(tsk->mems_allowed, tsk->mems_allowed, *newmems);
|
||||
mpol_rebind_task(tsk, newmems, MPOL_REBIND_STEP1);
|
||||
|
||||
mpol_rebind_task(tsk, newmems, MPOL_REBIND_STEP2);
|
||||
mpol_rebind_task(tsk, newmems);
|
||||
tsk->mems_allowed = *newmems;
|
||||
|
||||
if (need_loop) {
|
||||
write_seqcount_end(&tsk->mems_allowed_seq);
|
||||
local_irq_enable();
|
||||
}
|
||||
write_seqcount_end(&tsk->mems_allowed_seq);
|
||||
local_irq_enable();
|
||||
|
||||
task_unlock(tsk);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
* Debug controller
|
||||
*
|
||||
* WARNING: This controller is for cgroup core debugging only.
|
||||
* Its interfaces are unstable and subject to changes at any time.
|
||||
*/
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include "cgroup-internal.h"
|
||||
|
||||
static struct cgroup_subsys_state *
|
||||
debug_css_alloc(struct cgroup_subsys_state *parent_css)
|
||||
{
|
||||
struct cgroup_subsys_state *css = kzalloc(sizeof(*css), GFP_KERNEL);
|
||||
|
||||
if (!css)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
return css;
|
||||
}
|
||||
|
||||
static void debug_css_free(struct cgroup_subsys_state *css)
|
||||
{
|
||||
kfree(css);
|
||||
}
|
||||
|
||||
/*
|
||||
* debug_taskcount_read - return the number of tasks in a cgroup.
|
||||
* @cgrp: the cgroup in question
|
||||
*/
|
||||
static u64 debug_taskcount_read(struct cgroup_subsys_state *css,
|
||||
struct cftype *cft)
|
||||
{
|
||||
return cgroup_task_count(css->cgroup);
|
||||
}
|
||||
|
||||
static int current_css_set_read(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct kernfs_open_file *of = seq->private;
|
||||
struct css_set *cset;
|
||||
struct cgroup_subsys *ss;
|
||||
struct cgroup_subsys_state *css;
|
||||
int i, refcnt;
|
||||
|
||||
if (!cgroup_kn_lock_live(of->kn, false))
|
||||
return -ENODEV;
|
||||
|
||||
spin_lock_irq(&css_set_lock);
|
||||
rcu_read_lock();
|
||||
cset = rcu_dereference(current->cgroups);
|
||||
refcnt = refcount_read(&cset->refcount);
|
||||
seq_printf(seq, "css_set %pK %d", cset, refcnt);
|
||||
if (refcnt > cset->nr_tasks)
|
||||
seq_printf(seq, " +%d", refcnt - cset->nr_tasks);
|
||||
seq_puts(seq, "\n");
|
||||
|
||||
/*
|
||||
* Print the css'es stored in the current css_set.
|
||||
*/
|
||||
for_each_subsys(ss, i) {
|
||||
css = cset->subsys[ss->id];
|
||||
if (!css)
|
||||
continue;
|
||||
seq_printf(seq, "%2d: %-4s\t- %lx[%d]\n", ss->id, ss->name,
|
||||
(unsigned long)css, css->id);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
cgroup_kn_unlock(of->kn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u64 current_css_set_refcount_read(struct cgroup_subsys_state *css,
|
||||
struct cftype *cft)
|
||||
{
|
||||
u64 count;
|
||||
|
||||
rcu_read_lock();
|
||||
count = refcount_read(&task_css_set(current)->refcount);
|
||||
rcu_read_unlock();
|
||||
return count;
|
||||
}
|
||||
|
||||
static int current_css_set_cg_links_read(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct cgrp_cset_link *link;
|
||||
struct css_set *cset;
|
||||
char *name_buf;
|
||||
|
||||
name_buf = kmalloc(NAME_MAX + 1, GFP_KERNEL);
|
||||
if (!name_buf)
|
||||
return -ENOMEM;
|
||||
|
||||
spin_lock_irq(&css_set_lock);
|
||||
rcu_read_lock();
|
||||
cset = rcu_dereference(current->cgroups);
|
||||
list_for_each_entry(link, &cset->cgrp_links, cgrp_link) {
|
||||
struct cgroup *c = link->cgrp;
|
||||
|
||||
cgroup_name(c, name_buf, NAME_MAX + 1);
|
||||
seq_printf(seq, "Root %d group %s\n",
|
||||
c->root->hierarchy_id, name_buf);
|
||||
}
|
||||
rcu_read_unlock();
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
kfree(name_buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MAX_TASKS_SHOWN_PER_CSS 25
|
||||
static int cgroup_css_links_read(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct cgroup_subsys_state *css = seq_css(seq);
|
||||
struct cgrp_cset_link *link;
|
||||
int dead_cnt = 0, extra_refs = 0;
|
||||
|
||||
spin_lock_irq(&css_set_lock);
|
||||
list_for_each_entry(link, &css->cgroup->cset_links, cset_link) {
|
||||
struct css_set *cset = link->cset;
|
||||
struct task_struct *task;
|
||||
int count = 0;
|
||||
int refcnt = refcount_read(&cset->refcount);
|
||||
|
||||
seq_printf(seq, " %d", refcnt);
|
||||
if (refcnt - cset->nr_tasks > 0) {
|
||||
int extra = refcnt - cset->nr_tasks;
|
||||
|
||||
seq_printf(seq, " +%d", extra);
|
||||
/*
|
||||
* Take out the one additional reference in
|
||||
* init_css_set.
|
||||
*/
|
||||
if (cset == &init_css_set)
|
||||
extra--;
|
||||
extra_refs += extra;
|
||||
}
|
||||
seq_puts(seq, "\n");
|
||||
|
||||
list_for_each_entry(task, &cset->tasks, cg_list) {
|
||||
if (count++ <= MAX_TASKS_SHOWN_PER_CSS)
|
||||
seq_printf(seq, " task %d\n",
|
||||
task_pid_vnr(task));
|
||||
}
|
||||
|
||||
list_for_each_entry(task, &cset->mg_tasks, cg_list) {
|
||||
if (count++ <= MAX_TASKS_SHOWN_PER_CSS)
|
||||
seq_printf(seq, " task %d\n",
|
||||
task_pid_vnr(task));
|
||||
}
|
||||
/* show # of overflowed tasks */
|
||||
if (count > MAX_TASKS_SHOWN_PER_CSS)
|
||||
seq_printf(seq, " ... (%d)\n",
|
||||
count - MAX_TASKS_SHOWN_PER_CSS);
|
||||
|
||||
if (cset->dead) {
|
||||
seq_puts(seq, " [dead]\n");
|
||||
dead_cnt++;
|
||||
}
|
||||
|
||||
WARN_ON(count != cset->nr_tasks);
|
||||
}
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
|
||||
if (!dead_cnt && !extra_refs)
|
||||
return 0;
|
||||
|
||||
seq_puts(seq, "\n");
|
||||
if (extra_refs)
|
||||
seq_printf(seq, "extra references = %d\n", extra_refs);
|
||||
if (dead_cnt)
|
||||
seq_printf(seq, "dead css_sets = %d\n", dead_cnt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cgroup_subsys_states_read(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct kernfs_open_file *of = seq->private;
|
||||
struct cgroup *cgrp;
|
||||
struct cgroup_subsys *ss;
|
||||
struct cgroup_subsys_state *css;
|
||||
char pbuf[16];
|
||||
int i;
|
||||
|
||||
cgrp = cgroup_kn_lock_live(of->kn, false);
|
||||
if (!cgrp)
|
||||
return -ENODEV;
|
||||
|
||||
for_each_subsys(ss, i) {
|
||||
css = rcu_dereference_check(cgrp->subsys[ss->id], true);
|
||||
if (!css)
|
||||
continue;
|
||||
|
||||
pbuf[0] = '\0';
|
||||
|
||||
/* Show the parent CSS if applicable*/
|
||||
if (css->parent)
|
||||
snprintf(pbuf, sizeof(pbuf) - 1, " P=%d",
|
||||
css->parent->id);
|
||||
seq_printf(seq, "%2d: %-4s\t- %lx[%d] %d%s\n", ss->id, ss->name,
|
||||
(unsigned long)css, css->id,
|
||||
atomic_read(&css->online_cnt), pbuf);
|
||||
}
|
||||
|
||||
cgroup_kn_unlock(of->kn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cgroup_masks_read_one(struct seq_file *seq, const char *name,
|
||||
u16 mask)
|
||||
{
|
||||
struct cgroup_subsys *ss;
|
||||
int ssid;
|
||||
bool first = true;
|
||||
|
||||
seq_printf(seq, "%-17s: ", name);
|
||||
for_each_subsys(ss, ssid) {
|
||||
if (!(mask & (1 << ssid)))
|
||||
continue;
|
||||
if (!first)
|
||||
seq_puts(seq, ", ");
|
||||
seq_puts(seq, ss->name);
|
||||
first = false;
|
||||
}
|
||||
seq_putc(seq, '\n');
|
||||
}
|
||||
|
||||
static int cgroup_masks_read(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct kernfs_open_file *of = seq->private;
|
||||
struct cgroup *cgrp;
|
||||
|
||||
cgrp = cgroup_kn_lock_live(of->kn, false);
|
||||
if (!cgrp)
|
||||
return -ENODEV;
|
||||
|
||||
cgroup_masks_read_one(seq, "subtree_control", cgrp->subtree_control);
|
||||
cgroup_masks_read_one(seq, "subtree_ss_mask", cgrp->subtree_ss_mask);
|
||||
|
||||
cgroup_kn_unlock(of->kn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u64 releasable_read(struct cgroup_subsys_state *css, struct cftype *cft)
|
||||
{
|
||||
return (!cgroup_is_populated(css->cgroup) &&
|
||||
!css_has_online_children(&css->cgroup->self));
|
||||
}
|
||||
|
||||
static struct cftype debug_legacy_files[] = {
|
||||
{
|
||||
.name = "taskcount",
|
||||
.read_u64 = debug_taskcount_read,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "current_css_set",
|
||||
.seq_show = current_css_set_read,
|
||||
.flags = CFTYPE_ONLY_ON_ROOT,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "current_css_set_refcount",
|
||||
.read_u64 = current_css_set_refcount_read,
|
||||
.flags = CFTYPE_ONLY_ON_ROOT,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "current_css_set_cg_links",
|
||||
.seq_show = current_css_set_cg_links_read,
|
||||
.flags = CFTYPE_ONLY_ON_ROOT,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "cgroup_css_links",
|
||||
.seq_show = cgroup_css_links_read,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "cgroup_subsys_states",
|
||||
.seq_show = cgroup_subsys_states_read,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "cgroup_masks",
|
||||
.seq_show = cgroup_masks_read,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "releasable",
|
||||
.read_u64 = releasable_read,
|
||||
},
|
||||
|
||||
{ } /* terminate */
|
||||
};
|
||||
|
||||
static struct cftype debug_files[] = {
|
||||
{
|
||||
.name = "taskcount",
|
||||
.read_u64 = debug_taskcount_read,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "current_css_set",
|
||||
.seq_show = current_css_set_read,
|
||||
.flags = CFTYPE_ONLY_ON_ROOT,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "current_css_set_refcount",
|
||||
.read_u64 = current_css_set_refcount_read,
|
||||
.flags = CFTYPE_ONLY_ON_ROOT,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "current_css_set_cg_links",
|
||||
.seq_show = current_css_set_cg_links_read,
|
||||
.flags = CFTYPE_ONLY_ON_ROOT,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "css_links",
|
||||
.seq_show = cgroup_css_links_read,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "csses",
|
||||
.seq_show = cgroup_subsys_states_read,
|
||||
},
|
||||
|
||||
{
|
||||
.name = "masks",
|
||||
.seq_show = cgroup_masks_read,
|
||||
},
|
||||
|
||||
{ } /* terminate */
|
||||
};
|
||||
|
||||
struct cgroup_subsys debug_cgrp_subsys = {
|
||||
.css_alloc = debug_css_alloc,
|
||||
.css_free = debug_css_free,
|
||||
.legacy_cftypes = debug_legacy_files,
|
||||
};
|
||||
|
||||
/*
|
||||
* On v2, debug is an implicit controller enabled by "cgroup_debug" boot
|
||||
* parameter.
|
||||
*/
|
||||
static int __init enable_cgroup_debug(char *str)
|
||||
{
|
||||
debug_cgrp_subsys.dfl_cftypes = debug_files;
|
||||
debug_cgrp_subsys.implicit_on_dfl = true;
|
||||
return 1;
|
||||
}
|
||||
__setup("cgroup_debug", enable_cgroup_debug);
|
||||
+171
-733
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,13 @@
|
||||
# KEEP ALPHABETICALLY SORTED
|
||||
# CONFIG_DEVKMEM is not set
|
||||
# CONFIG_DEVMEM is not set
|
||||
# CONFIG_FHANDLE is not set
|
||||
# CONFIG_INET_LRO is not set
|
||||
# CONFIG_MODULES is not set
|
||||
# CONFIG_NFSD is not set
|
||||
# CONFIG_NFS_FS is not set
|
||||
# CONFIG_OABI_COMPAT is not set
|
||||
# CONFIG_SYSVIPC is not set
|
||||
# CONFIG_USELIB is not set
|
||||
CONFIG_ANDROID=y
|
||||
CONFIG_ANDROID_BINDER_IPC=y
|
||||
CONFIG_ANDROID_LOW_MEMORY_KILLER=y
|
||||
@@ -13,6 +16,7 @@ CONFIG_ASHMEM=y
|
||||
CONFIG_AUDIT=y
|
||||
CONFIG_BLK_DEV_INITRD=y
|
||||
CONFIG_CGROUPS=y
|
||||
CONFIG_CGROUP_BPF=y
|
||||
CONFIG_CGROUP_CPUACCT=y
|
||||
CONFIG_CGROUP_DEBUG=y
|
||||
CONFIG_CGROUP_FREEZER=y
|
||||
@@ -23,6 +27,8 @@ CONFIG_EMBEDDED=y
|
||||
CONFIG_FB=y
|
||||
CONFIG_HARDENED_USERCOPY=y
|
||||
CONFIG_HIGH_RES_TIMERS=y
|
||||
CONFIG_IKCONFIG=y
|
||||
CONFIG_IKCONFIG_PROC=y
|
||||
CONFIG_INET6_AH=y
|
||||
CONFIG_INET6_ESP=y
|
||||
CONFIG_INET6_IPCOMP=y
|
||||
@@ -60,6 +66,9 @@ CONFIG_IP_NF_TARGET_MASQUERADE=y
|
||||
CONFIG_IP_NF_TARGET_NETMAP=y
|
||||
CONFIG_IP_NF_TARGET_REDIRECT=y
|
||||
CONFIG_IP_NF_TARGET_REJECT=y
|
||||
CONFIG_MODULES=y
|
||||
CONFIG_MODULE_UNLOAD=y
|
||||
CONFIG_MODVERSIONS=y
|
||||
CONFIG_NET=y
|
||||
CONFIG_NETDEVICES=y
|
||||
CONFIG_NETFILTER=y
|
||||
|
||||
@@ -6,13 +6,15 @@
|
||||
# CONFIG_NF_CONNTRACK_SIP is not set
|
||||
# CONFIG_PM_WAKELOCKS_GC is not set
|
||||
# CONFIG_VT is not set
|
||||
CONFIG_ARM64_SW_TTBR0_PAN=y
|
||||
CONFIG_BACKLIGHT_LCD_SUPPORT=y
|
||||
CONFIG_BLK_DEV_DM=y
|
||||
CONFIG_BLK_DEV_LOOP=y
|
||||
CONFIG_BLK_DEV_RAM=y
|
||||
CONFIG_BLK_DEV_RAM_SIZE=8192
|
||||
CONFIG_CC_STACKPROTECTOR_STRONG=y
|
||||
CONFIG_COMPACTION=y
|
||||
CONFIG_STRICT_KERNEL_RWX=y
|
||||
CONFIG_CPU_SW_DOMAIN_PAN=y
|
||||
CONFIG_DM_CRYPT=y
|
||||
CONFIG_DM_UEVENT=y
|
||||
CONFIG_DM_VERITY=y
|
||||
@@ -105,6 +107,7 @@ CONFIG_SCHEDSTATS=y
|
||||
CONFIG_SMARTJOYPLUS_FF=y
|
||||
CONFIG_SND=y
|
||||
CONFIG_SOUND=y
|
||||
CONFIG_STRICT_KERNEL_RWX=y
|
||||
CONFIG_SUSPEND_TIME=y
|
||||
CONFIG_TABLET_USB_ACECAD=y
|
||||
CONFIG_TABLET_USB_AIPTEK=y
|
||||
|
||||
+120
-168
@@ -27,6 +27,7 @@
|
||||
#include <linux/smpboot.h>
|
||||
#include <linux/relay.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/percpu-rwsem.h>
|
||||
|
||||
#include <trace/events/power.h>
|
||||
#define CREATE_TRACE_POINTS
|
||||
@@ -65,6 +66,12 @@ struct cpuhp_cpu_state {
|
||||
|
||||
static DEFINE_PER_CPU(struct cpuhp_cpu_state, cpuhp_state);
|
||||
|
||||
#if defined(CONFIG_LOCKDEP) && defined(CONFIG_SMP)
|
||||
static struct lock_class_key cpuhp_state_key;
|
||||
static struct lockdep_map cpuhp_state_lock_map =
|
||||
STATIC_LOCKDEP_MAP_INIT("cpuhp_state", &cpuhp_state_key);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* cpuhp_step - Hotplug state machine step
|
||||
* @name: Name of the step
|
||||
@@ -196,121 +203,41 @@ void cpu_maps_update_done(void)
|
||||
mutex_unlock(&cpu_add_remove_lock);
|
||||
}
|
||||
|
||||
/* If set, cpu_up and cpu_down will return -EBUSY and do nothing.
|
||||
/*
|
||||
* If set, cpu_up and cpu_down will return -EBUSY and do nothing.
|
||||
* Should always be manipulated under cpu_add_remove_lock
|
||||
*/
|
||||
static int cpu_hotplug_disabled;
|
||||
|
||||
#ifdef CONFIG_HOTPLUG_CPU
|
||||
|
||||
static struct {
|
||||
struct task_struct *active_writer;
|
||||
/* wait queue to wake up the active_writer */
|
||||
wait_queue_head_t wq;
|
||||
/* verifies that no writer will get active while readers are active */
|
||||
struct mutex lock;
|
||||
/*
|
||||
* Also blocks the new readers during
|
||||
* an ongoing cpu hotplug operation.
|
||||
*/
|
||||
atomic_t refcount;
|
||||
DEFINE_STATIC_PERCPU_RWSEM(cpu_hotplug_lock);
|
||||
|
||||
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
||||
struct lockdep_map dep_map;
|
||||
#endif
|
||||
} cpu_hotplug = {
|
||||
.active_writer = NULL,
|
||||
.wq = __WAIT_QUEUE_HEAD_INITIALIZER(cpu_hotplug.wq),
|
||||
.lock = __MUTEX_INITIALIZER(cpu_hotplug.lock),
|
||||
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
||||
.dep_map = STATIC_LOCKDEP_MAP_INIT("cpu_hotplug.dep_map", &cpu_hotplug.dep_map),
|
||||
#endif
|
||||
};
|
||||
|
||||
/* Lockdep annotations for get/put_online_cpus() and cpu_hotplug_begin/end() */
|
||||
#define cpuhp_lock_acquire_read() lock_map_acquire_read(&cpu_hotplug.dep_map)
|
||||
#define cpuhp_lock_acquire_tryread() \
|
||||
lock_map_acquire_tryread(&cpu_hotplug.dep_map)
|
||||
#define cpuhp_lock_acquire() lock_map_acquire(&cpu_hotplug.dep_map)
|
||||
#define cpuhp_lock_release() lock_map_release(&cpu_hotplug.dep_map)
|
||||
|
||||
|
||||
void get_online_cpus(void)
|
||||
void cpus_read_lock(void)
|
||||
{
|
||||
might_sleep();
|
||||
if (cpu_hotplug.active_writer == current)
|
||||
return;
|
||||
cpuhp_lock_acquire_read();
|
||||
mutex_lock(&cpu_hotplug.lock);
|
||||
atomic_inc(&cpu_hotplug.refcount);
|
||||
mutex_unlock(&cpu_hotplug.lock);
|
||||
percpu_down_read(&cpu_hotplug_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(get_online_cpus);
|
||||
EXPORT_SYMBOL_GPL(cpus_read_lock);
|
||||
|
||||
void put_online_cpus(void)
|
||||
void cpus_read_unlock(void)
|
||||
{
|
||||
int refcount;
|
||||
|
||||
if (cpu_hotplug.active_writer == current)
|
||||
return;
|
||||
|
||||
refcount = atomic_dec_return(&cpu_hotplug.refcount);
|
||||
if (WARN_ON(refcount < 0)) /* try to fix things up */
|
||||
atomic_inc(&cpu_hotplug.refcount);
|
||||
|
||||
if (refcount <= 0 && waitqueue_active(&cpu_hotplug.wq))
|
||||
wake_up(&cpu_hotplug.wq);
|
||||
|
||||
cpuhp_lock_release();
|
||||
|
||||
percpu_up_read(&cpu_hotplug_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(put_online_cpus);
|
||||
EXPORT_SYMBOL_GPL(cpus_read_unlock);
|
||||
|
||||
/*
|
||||
* This ensures that the hotplug operation can begin only when the
|
||||
* refcount goes to zero.
|
||||
*
|
||||
* Note that during a cpu-hotplug operation, the new readers, if any,
|
||||
* will be blocked by the cpu_hotplug.lock
|
||||
*
|
||||
* Since cpu_hotplug_begin() is always called after invoking
|
||||
* cpu_maps_update_begin(), we can be sure that only one writer is active.
|
||||
*
|
||||
* Note that theoretically, there is a possibility of a livelock:
|
||||
* - Refcount goes to zero, last reader wakes up the sleeping
|
||||
* writer.
|
||||
* - Last reader unlocks the cpu_hotplug.lock.
|
||||
* - A new reader arrives at this moment, bumps up the refcount.
|
||||
* - The writer acquires the cpu_hotplug.lock finds the refcount
|
||||
* non zero and goes to sleep again.
|
||||
*
|
||||
* However, this is very difficult to achieve in practice since
|
||||
* get_online_cpus() not an api which is called all that often.
|
||||
*
|
||||
*/
|
||||
void cpu_hotplug_begin(void)
|
||||
void cpus_write_lock(void)
|
||||
{
|
||||
DEFINE_WAIT(wait);
|
||||
|
||||
cpu_hotplug.active_writer = current;
|
||||
cpuhp_lock_acquire();
|
||||
|
||||
for (;;) {
|
||||
mutex_lock(&cpu_hotplug.lock);
|
||||
prepare_to_wait(&cpu_hotplug.wq, &wait, TASK_UNINTERRUPTIBLE);
|
||||
if (likely(!atomic_read(&cpu_hotplug.refcount)))
|
||||
break;
|
||||
mutex_unlock(&cpu_hotplug.lock);
|
||||
schedule();
|
||||
}
|
||||
finish_wait(&cpu_hotplug.wq, &wait);
|
||||
percpu_down_write(&cpu_hotplug_lock);
|
||||
}
|
||||
|
||||
void cpu_hotplug_done(void)
|
||||
void cpus_write_unlock(void)
|
||||
{
|
||||
cpu_hotplug.active_writer = NULL;
|
||||
mutex_unlock(&cpu_hotplug.lock);
|
||||
cpuhp_lock_release();
|
||||
percpu_up_write(&cpu_hotplug_lock);
|
||||
}
|
||||
|
||||
void lockdep_assert_cpus_held(void)
|
||||
{
|
||||
percpu_rwsem_assert_held(&cpu_hotplug_lock);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -344,13 +271,26 @@ void cpu_hotplug_enable(void)
|
||||
EXPORT_SYMBOL_GPL(cpu_hotplug_enable);
|
||||
#endif /* CONFIG_HOTPLUG_CPU */
|
||||
|
||||
/* Notifier wrappers for transitioning to state machine */
|
||||
static void __cpuhp_kick_ap_work(struct cpuhp_cpu_state *st);
|
||||
|
||||
static int bringup_wait_for_ap(unsigned int cpu)
|
||||
{
|
||||
struct cpuhp_cpu_state *st = per_cpu_ptr(&cpuhp_state, cpu);
|
||||
|
||||
/* Wait for the CPU to reach CPUHP_AP_ONLINE_IDLE */
|
||||
wait_for_completion(&st->done);
|
||||
if (WARN_ON_ONCE((!cpu_online(cpu))))
|
||||
return -ECANCELED;
|
||||
|
||||
/* Unpark the stopper thread and the hotplug thread of the target cpu */
|
||||
stop_machine_unpark(cpu);
|
||||
kthread_unpark(st->thread);
|
||||
|
||||
/* Should we go further up ? */
|
||||
if (st->target > CPUHP_AP_ONLINE_IDLE) {
|
||||
__cpuhp_kick_ap_work(st);
|
||||
wait_for_completion(&st->done);
|
||||
}
|
||||
return st->result;
|
||||
}
|
||||
|
||||
@@ -371,9 +311,7 @@ static int bringup_cpu(unsigned int cpu)
|
||||
irq_unlock_sparse();
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = bringup_wait_for_ap(cpu);
|
||||
BUG_ON(!cpu_online(cpu));
|
||||
return ret;
|
||||
return bringup_wait_for_ap(cpu);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -484,6 +422,7 @@ static void cpuhp_thread_fun(unsigned int cpu)
|
||||
|
||||
st->should_run = false;
|
||||
|
||||
lock_map_acquire(&cpuhp_state_lock_map);
|
||||
/* Single callback invocation for [un]install ? */
|
||||
if (st->single) {
|
||||
if (st->cb_state < CPUHP_AP_ONLINE) {
|
||||
@@ -510,6 +449,7 @@ static void cpuhp_thread_fun(unsigned int cpu)
|
||||
else if (st->state > st->target)
|
||||
ret = cpuhp_ap_offline(cpu, st);
|
||||
}
|
||||
lock_map_release(&cpuhp_state_lock_map);
|
||||
st->result = ret;
|
||||
complete(&st->done);
|
||||
}
|
||||
@@ -524,6 +464,9 @@ cpuhp_invoke_ap_callback(int cpu, enum cpuhp_state state, bool bringup,
|
||||
if (!cpu_online(cpu))
|
||||
return 0;
|
||||
|
||||
lock_map_acquire(&cpuhp_state_lock_map);
|
||||
lock_map_release(&cpuhp_state_lock_map);
|
||||
|
||||
/*
|
||||
* If we are up and running, use the hotplug thread. For early calls
|
||||
* we invoke the thread function directly.
|
||||
@@ -567,6 +510,8 @@ static int cpuhp_kick_ap_work(unsigned int cpu)
|
||||
enum cpuhp_state state = st->state;
|
||||
|
||||
trace_cpuhp_enter(cpu, st->target, state, cpuhp_kick_ap_work);
|
||||
lock_map_acquire(&cpuhp_state_lock_map);
|
||||
lock_map_release(&cpuhp_state_lock_map);
|
||||
__cpuhp_kick_ap_work(st);
|
||||
wait_for_completion(&st->done);
|
||||
trace_cpuhp_exit(cpu, st->state, state, st->result);
|
||||
@@ -630,30 +575,6 @@ void clear_tasks_mm_cpumask(int cpu)
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
static inline void check_for_tasks(int dead_cpu)
|
||||
{
|
||||
struct task_struct *g, *p;
|
||||
|
||||
read_lock(&tasklist_lock);
|
||||
for_each_process_thread(g, p) {
|
||||
if (!p->on_rq)
|
||||
continue;
|
||||
/*
|
||||
* We do the check with unlocked task_rq(p)->lock.
|
||||
* Order the reading to do not warn about a task,
|
||||
* which was running on this cpu in the past, and
|
||||
* it's just been woken on another cpu.
|
||||
*/
|
||||
rmb();
|
||||
if (task_cpu(p) != dead_cpu)
|
||||
continue;
|
||||
|
||||
pr_warn("Task %s (pid=%d) is on cpu %d (state=%ld, flags=%x)\n",
|
||||
p->comm, task_pid_nr(p), dead_cpu, p->state, p->flags);
|
||||
}
|
||||
read_unlock(&tasklist_lock);
|
||||
}
|
||||
|
||||
/* Take this CPU down. */
|
||||
static int take_cpu_down(void *_param)
|
||||
{
|
||||
@@ -701,7 +622,7 @@ static int takedown_cpu(unsigned int cpu)
|
||||
/*
|
||||
* So now all preempt/rcu users must observe !cpu_active().
|
||||
*/
|
||||
err = stop_machine(take_cpu_down, NULL, cpumask_of(cpu));
|
||||
err = stop_machine_cpuslocked(take_cpu_down, NULL, cpumask_of(cpu));
|
||||
if (err) {
|
||||
/* CPU refused to die */
|
||||
irq_unlock_sparse();
|
||||
@@ -773,7 +694,7 @@ static int __ref _cpu_down(unsigned int cpu, int tasks_frozen,
|
||||
if (!cpu_present(cpu))
|
||||
return -EINVAL;
|
||||
|
||||
cpu_hotplug_begin();
|
||||
cpus_write_lock();
|
||||
|
||||
cpuhp_tasks_frozen = tasks_frozen;
|
||||
|
||||
@@ -811,7 +732,7 @@ static int __ref _cpu_down(unsigned int cpu, int tasks_frozen,
|
||||
}
|
||||
|
||||
out:
|
||||
cpu_hotplug_done();
|
||||
cpus_write_unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -859,31 +780,20 @@ void notify_cpu_starting(unsigned int cpu)
|
||||
}
|
||||
|
||||
/*
|
||||
* Called from the idle task. We need to set active here, so we can kick off
|
||||
* the stopper thread and unpark the smpboot threads. If the target state is
|
||||
* beyond CPUHP_AP_ONLINE_IDLE we kick cpuhp thread and let it bring up the
|
||||
* cpu further.
|
||||
* Called from the idle task. Wake up the controlling task which brings the
|
||||
* stopper and the hotplug thread of the upcoming CPU up and then delegates
|
||||
* the rest of the online bringup to the hotplug thread.
|
||||
*/
|
||||
void cpuhp_online_idle(enum cpuhp_state state)
|
||||
{
|
||||
struct cpuhp_cpu_state *st = this_cpu_ptr(&cpuhp_state);
|
||||
unsigned int cpu = smp_processor_id();
|
||||
|
||||
/* Happens for the boot cpu */
|
||||
if (state != CPUHP_AP_ONLINE_IDLE)
|
||||
return;
|
||||
|
||||
st->state = CPUHP_AP_ONLINE_IDLE;
|
||||
|
||||
/* Unpark the stopper thread and the hotplug thread of this cpu */
|
||||
stop_machine_unpark(cpu);
|
||||
kthread_unpark(st->thread);
|
||||
|
||||
/* Should we go further up ? */
|
||||
if (st->target > CPUHP_AP_ONLINE_IDLE)
|
||||
__cpuhp_kick_ap_work(st);
|
||||
else
|
||||
complete(&st->done);
|
||||
complete(&st->done);
|
||||
}
|
||||
|
||||
/* Requires cpu_add_remove_lock to be held */
|
||||
@@ -893,7 +803,7 @@ static int _cpu_up(unsigned int cpu, int tasks_frozen, enum cpuhp_state target)
|
||||
struct task_struct *idle;
|
||||
int ret = 0;
|
||||
|
||||
cpu_hotplug_begin();
|
||||
cpus_write_lock();
|
||||
|
||||
if (!cpu_present(cpu)) {
|
||||
ret = -EINVAL;
|
||||
@@ -941,7 +851,7 @@ static int _cpu_up(unsigned int cpu, int tasks_frozen, enum cpuhp_state target)
|
||||
target = min((int)target, CPUHP_BRINGUP_CPU);
|
||||
ret = cpuhp_up_callbacks(cpu, st, target);
|
||||
out:
|
||||
cpu_hotplug_done();
|
||||
cpus_write_unlock();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1252,6 +1162,11 @@ static struct cpuhp_step cpuhp_ap_states[] = {
|
||||
.startup.single = smpboot_unpark_threads,
|
||||
.teardown.single = NULL,
|
||||
},
|
||||
[CPUHP_AP_IRQ_AFFINITY_ONLINE] = {
|
||||
.name = "irq/affinity:online",
|
||||
.startup.single = irq_affinity_online_cpu,
|
||||
.teardown.single = NULL,
|
||||
},
|
||||
[CPUHP_AP_PERF_ONLINE] = {
|
||||
.name = "perf:online",
|
||||
.startup.single = perf_event_init_cpu,
|
||||
@@ -1413,18 +1328,20 @@ static void cpuhp_rollback_install(int failedcpu, enum cpuhp_state state,
|
||||
}
|
||||
}
|
||||
|
||||
int __cpuhp_state_add_instance(enum cpuhp_state state, struct hlist_node *node,
|
||||
bool invoke)
|
||||
int __cpuhp_state_add_instance_cpuslocked(enum cpuhp_state state,
|
||||
struct hlist_node *node,
|
||||
bool invoke)
|
||||
{
|
||||
struct cpuhp_step *sp;
|
||||
int cpu;
|
||||
int ret;
|
||||
|
||||
lockdep_assert_cpus_held();
|
||||
|
||||
sp = cpuhp_get_step(state);
|
||||
if (sp->multi_instance == false)
|
||||
return -EINVAL;
|
||||
|
||||
get_online_cpus();
|
||||
mutex_lock(&cpuhp_state_mutex);
|
||||
|
||||
if (!invoke || !sp->startup.multi)
|
||||
@@ -1453,13 +1370,23 @@ add_node:
|
||||
hlist_add_head(node, &sp->list);
|
||||
unlock:
|
||||
mutex_unlock(&cpuhp_state_mutex);
|
||||
put_online_cpus();
|
||||
return ret;
|
||||
}
|
||||
|
||||
int __cpuhp_state_add_instance(enum cpuhp_state state, struct hlist_node *node,
|
||||
bool invoke)
|
||||
{
|
||||
int ret;
|
||||
|
||||
cpus_read_lock();
|
||||
ret = __cpuhp_state_add_instance_cpuslocked(state, node, invoke);
|
||||
cpus_read_unlock();
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__cpuhp_state_add_instance);
|
||||
|
||||
/**
|
||||
* __cpuhp_setup_state - Setup the callbacks for an hotplug machine state
|
||||
* __cpuhp_setup_state_cpuslocked - Setup the callbacks for an hotplug machine state
|
||||
* @state: The state to setup
|
||||
* @invoke: If true, the startup function is invoked for cpus where
|
||||
* cpu state >= @state
|
||||
@@ -1468,25 +1395,27 @@ EXPORT_SYMBOL_GPL(__cpuhp_state_add_instance);
|
||||
* @multi_instance: State is set up for multiple instances which get
|
||||
* added afterwards.
|
||||
*
|
||||
* The caller needs to hold cpus read locked while calling this function.
|
||||
* Returns:
|
||||
* On success:
|
||||
* Positive state number if @state is CPUHP_AP_ONLINE_DYN
|
||||
* 0 for all other states
|
||||
* On failure: proper (negative) error code
|
||||
*/
|
||||
int __cpuhp_setup_state(enum cpuhp_state state,
|
||||
const char *name, bool invoke,
|
||||
int (*startup)(unsigned int cpu),
|
||||
int (*teardown)(unsigned int cpu),
|
||||
bool multi_instance)
|
||||
int __cpuhp_setup_state_cpuslocked(enum cpuhp_state state,
|
||||
const char *name, bool invoke,
|
||||
int (*startup)(unsigned int cpu),
|
||||
int (*teardown)(unsigned int cpu),
|
||||
bool multi_instance)
|
||||
{
|
||||
int cpu, ret = 0;
|
||||
bool dynstate;
|
||||
|
||||
lockdep_assert_cpus_held();
|
||||
|
||||
if (cpuhp_cb_check(state) || !name)
|
||||
return -EINVAL;
|
||||
|
||||
get_online_cpus();
|
||||
mutex_lock(&cpuhp_state_mutex);
|
||||
|
||||
ret = cpuhp_store_callbacks(state, name, startup, teardown,
|
||||
@@ -1522,7 +1451,6 @@ int __cpuhp_setup_state(enum cpuhp_state state,
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&cpuhp_state_mutex);
|
||||
put_online_cpus();
|
||||
/*
|
||||
* If the requested state is CPUHP_AP_ONLINE_DYN, return the
|
||||
* dynamically allocated state in case of success.
|
||||
@@ -1531,6 +1459,22 @@ out:
|
||||
return state;
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(__cpuhp_setup_state_cpuslocked);
|
||||
|
||||
int __cpuhp_setup_state(enum cpuhp_state state,
|
||||
const char *name, bool invoke,
|
||||
int (*startup)(unsigned int cpu),
|
||||
int (*teardown)(unsigned int cpu),
|
||||
bool multi_instance)
|
||||
{
|
||||
int ret;
|
||||
|
||||
cpus_read_lock();
|
||||
ret = __cpuhp_setup_state_cpuslocked(state, name, invoke, startup,
|
||||
teardown, multi_instance);
|
||||
cpus_read_unlock();
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(__cpuhp_setup_state);
|
||||
|
||||
int __cpuhp_state_remove_instance(enum cpuhp_state state,
|
||||
@@ -1544,7 +1488,7 @@ int __cpuhp_state_remove_instance(enum cpuhp_state state,
|
||||
if (!sp->multi_instance)
|
||||
return -EINVAL;
|
||||
|
||||
get_online_cpus();
|
||||
cpus_read_lock();
|
||||
mutex_lock(&cpuhp_state_mutex);
|
||||
|
||||
if (!invoke || !cpuhp_get_teardown_cb(state))
|
||||
@@ -1565,29 +1509,30 @@ int __cpuhp_state_remove_instance(enum cpuhp_state state,
|
||||
remove:
|
||||
hlist_del(node);
|
||||
mutex_unlock(&cpuhp_state_mutex);
|
||||
put_online_cpus();
|
||||
cpus_read_unlock();
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__cpuhp_state_remove_instance);
|
||||
|
||||
/**
|
||||
* __cpuhp_remove_state - Remove the callbacks for an hotplug machine state
|
||||
* __cpuhp_remove_state_cpuslocked - Remove the callbacks for an hotplug machine state
|
||||
* @state: The state to remove
|
||||
* @invoke: If true, the teardown function is invoked for cpus where
|
||||
* cpu state >= @state
|
||||
*
|
||||
* The caller needs to hold cpus read locked while calling this function.
|
||||
* The teardown callback is currently not allowed to fail. Think
|
||||
* about module removal!
|
||||
*/
|
||||
void __cpuhp_remove_state(enum cpuhp_state state, bool invoke)
|
||||
void __cpuhp_remove_state_cpuslocked(enum cpuhp_state state, bool invoke)
|
||||
{
|
||||
struct cpuhp_step *sp = cpuhp_get_step(state);
|
||||
int cpu;
|
||||
|
||||
BUG_ON(cpuhp_cb_check(state));
|
||||
|
||||
get_online_cpus();
|
||||
lockdep_assert_cpus_held();
|
||||
|
||||
mutex_lock(&cpuhp_state_mutex);
|
||||
if (sp->multi_instance) {
|
||||
@@ -1615,7 +1560,14 @@ void __cpuhp_remove_state(enum cpuhp_state state, bool invoke)
|
||||
remove:
|
||||
cpuhp_store_callbacks(state, NULL, NULL, NULL, false);
|
||||
mutex_unlock(&cpuhp_state_mutex);
|
||||
put_online_cpus();
|
||||
}
|
||||
EXPORT_SYMBOL(__cpuhp_remove_state_cpuslocked);
|
||||
|
||||
void __cpuhp_remove_state(enum cpuhp_state state, bool invoke)
|
||||
{
|
||||
cpus_read_lock();
|
||||
__cpuhp_remove_state_cpuslocked(state, invoke);
|
||||
cpus_read_unlock();
|
||||
}
|
||||
EXPORT_SYMBOL(__cpuhp_remove_state);
|
||||
|
||||
@@ -1658,13 +1610,13 @@ static ssize_t write_cpuhp_target(struct device *dev,
|
||||
ret = !sp->name || sp->cant_stop ? -EINVAL : 0;
|
||||
mutex_unlock(&cpuhp_state_mutex);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto out;
|
||||
|
||||
if (st->state < target)
|
||||
ret = do_cpu_up(dev->id, target);
|
||||
else
|
||||
ret = do_cpu_down(dev->id, target);
|
||||
|
||||
out:
|
||||
unlock_device_hotplug();
|
||||
return ret ? ret : count;
|
||||
}
|
||||
@@ -1684,7 +1636,7 @@ static struct attribute *cpuhp_cpu_attrs[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group cpuhp_cpu_attr_group = {
|
||||
static const struct attribute_group cpuhp_cpu_attr_group = {
|
||||
.attrs = cpuhp_cpu_attrs,
|
||||
.name = "hotplug",
|
||||
NULL
|
||||
@@ -1716,7 +1668,7 @@ static struct attribute *cpuhp_cpu_root_attrs[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group cpuhp_cpu_root_attr_group = {
|
||||
static const struct attribute_group cpuhp_cpu_root_attr_group = {
|
||||
.attrs = cpuhp_cpu_root_attrs,
|
||||
.name = "hotplug",
|
||||
NULL
|
||||
|
||||
+38
-6
@@ -14,10 +14,12 @@
|
||||
#include <asm/sections.h>
|
||||
|
||||
/* vmcoreinfo stuff */
|
||||
static unsigned char vmcoreinfo_data[VMCOREINFO_BYTES];
|
||||
u32 vmcoreinfo_note[VMCOREINFO_NOTE_SIZE/4];
|
||||
size_t vmcoreinfo_size;
|
||||
size_t vmcoreinfo_max_size = sizeof(vmcoreinfo_data);
|
||||
static unsigned char *vmcoreinfo_data;
|
||||
static size_t vmcoreinfo_size;
|
||||
u32 *vmcoreinfo_note;
|
||||
|
||||
/* trusted vmcoreinfo, e.g. we can make a copy in the crash memory */
|
||||
static unsigned char *vmcoreinfo_data_safecopy;
|
||||
|
||||
/*
|
||||
* parsing the "crashkernel" commandline
|
||||
@@ -324,8 +326,23 @@ static void update_vmcoreinfo_note(void)
|
||||
final_note(buf);
|
||||
}
|
||||
|
||||
void crash_update_vmcoreinfo_safecopy(void *ptr)
|
||||
{
|
||||
if (ptr)
|
||||
memcpy(ptr, vmcoreinfo_data, vmcoreinfo_size);
|
||||
|
||||
vmcoreinfo_data_safecopy = ptr;
|
||||
}
|
||||
|
||||
void crash_save_vmcoreinfo(void)
|
||||
{
|
||||
if (!vmcoreinfo_note)
|
||||
return;
|
||||
|
||||
/* Use the safe copy to generate vmcoreinfo note if have */
|
||||
if (vmcoreinfo_data_safecopy)
|
||||
vmcoreinfo_data = vmcoreinfo_data_safecopy;
|
||||
|
||||
vmcoreinfo_append_str("CRASHTIME=%ld\n", get_seconds());
|
||||
update_vmcoreinfo_note();
|
||||
}
|
||||
@@ -340,7 +357,7 @@ void vmcoreinfo_append_str(const char *fmt, ...)
|
||||
r = vscnprintf(buf, sizeof(buf), fmt, args);
|
||||
va_end(args);
|
||||
|
||||
r = min(r, vmcoreinfo_max_size - vmcoreinfo_size);
|
||||
r = min(r, (size_t)VMCOREINFO_BYTES - vmcoreinfo_size);
|
||||
|
||||
memcpy(&vmcoreinfo_data[vmcoreinfo_size], buf, r);
|
||||
|
||||
@@ -356,11 +373,26 @@ void __weak arch_crash_save_vmcoreinfo(void)
|
||||
|
||||
phys_addr_t __weak paddr_vmcoreinfo_note(void)
|
||||
{
|
||||
return __pa_symbol((unsigned long)(char *)&vmcoreinfo_note);
|
||||
return __pa(vmcoreinfo_note);
|
||||
}
|
||||
|
||||
static int __init crash_save_vmcoreinfo_init(void)
|
||||
{
|
||||
vmcoreinfo_data = (unsigned char *)get_zeroed_page(GFP_KERNEL);
|
||||
if (!vmcoreinfo_data) {
|
||||
pr_warn("Memory allocation for vmcoreinfo_data failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
vmcoreinfo_note = alloc_pages_exact(VMCOREINFO_NOTE_SIZE,
|
||||
GFP_KERNEL | __GFP_ZERO);
|
||||
if (!vmcoreinfo_note) {
|
||||
free_page((unsigned long)vmcoreinfo_data);
|
||||
vmcoreinfo_data = NULL;
|
||||
pr_warn("Memory allocation for vmcoreinfo_note failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
VMCOREINFO_OSRELEASE(init_uts_ns.name.release);
|
||||
VMCOREINFO_PAGESIZE(PAGE_SIZE);
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* Task credentials management - see Documentation/security/credentials.txt
|
||||
/* Task credentials management - see Documentation/security/credentials.rst
|
||||
*
|
||||
* Copyright (C) 2008 Red Hat, Inc. All Rights Reserved.
|
||||
* Written by David Howells (dhowells@redhat.com)
|
||||
|
||||
+131
-64
@@ -389,6 +389,7 @@ static atomic_t nr_switch_events __read_mostly;
|
||||
static LIST_HEAD(pmus);
|
||||
static DEFINE_MUTEX(pmus_lock);
|
||||
static struct srcu_struct pmus_srcu;
|
||||
static cpumask_var_t perf_online_mask;
|
||||
|
||||
/*
|
||||
* perf event paranoia level:
|
||||
@@ -925,11 +926,6 @@ static inline int is_cgroup_event(struct perf_event *event)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline u64 perf_cgroup_event_cgrp_time(struct perf_event *event)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void update_cgrp_time_from_event(struct perf_event *event)
|
||||
{
|
||||
}
|
||||
@@ -1456,6 +1452,13 @@ static enum event_type_t get_event_type(struct perf_event *event)
|
||||
|
||||
lockdep_assert_held(&ctx->lock);
|
||||
|
||||
/*
|
||||
* It's 'group type', really, because if our group leader is
|
||||
* pinned, so are we.
|
||||
*/
|
||||
if (event->group_leader != event)
|
||||
event = event->group_leader;
|
||||
|
||||
event_type = event->attr.pinned ? EVENT_PINNED : EVENT_FLEXIBLE;
|
||||
if (!ctx->task)
|
||||
event_type |= EVENT_CPU;
|
||||
@@ -3636,10 +3639,10 @@ static inline u64 perf_event_count(struct perf_event *event)
|
||||
* will not be local and we cannot read them atomically
|
||||
* - must not have a pmu::count method
|
||||
*/
|
||||
u64 perf_event_read_local(struct perf_event *event)
|
||||
int perf_event_read_local(struct perf_event *event, u64 *value)
|
||||
{
|
||||
unsigned long flags;
|
||||
u64 val;
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* Disabling interrupts avoids all counter scheduling (context
|
||||
@@ -3647,25 +3650,37 @@ u64 perf_event_read_local(struct perf_event *event)
|
||||
*/
|
||||
local_irq_save(flags);
|
||||
|
||||
/* If this is a per-task event, it must be for current */
|
||||
WARN_ON_ONCE((event->attach_state & PERF_ATTACH_TASK) &&
|
||||
event->hw.target != current);
|
||||
|
||||
/* If this is a per-CPU event, it must be for this CPU */
|
||||
WARN_ON_ONCE(!(event->attach_state & PERF_ATTACH_TASK) &&
|
||||
event->cpu != smp_processor_id());
|
||||
|
||||
/*
|
||||
* It must not be an event with inherit set, we cannot read
|
||||
* all child counters from atomic context.
|
||||
*/
|
||||
WARN_ON_ONCE(event->attr.inherit);
|
||||
if (event->attr.inherit) {
|
||||
ret = -EOPNOTSUPP;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* It must not have a pmu::count method, those are not
|
||||
* NMI safe.
|
||||
*/
|
||||
WARN_ON_ONCE(event->pmu->count);
|
||||
if (event->pmu->count) {
|
||||
ret = -EOPNOTSUPP;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* If this is a per-task event, it must be for current */
|
||||
if ((event->attach_state & PERF_ATTACH_TASK) &&
|
||||
event->hw.target != current) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* If this is a per-CPU event, it must be for this CPU */
|
||||
if (!(event->attach_state & PERF_ATTACH_TASK) &&
|
||||
event->cpu != smp_processor_id()) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the event is currently on this CPU, its either a per-task event,
|
||||
@@ -3675,10 +3690,11 @@ u64 perf_event_read_local(struct perf_event *event)
|
||||
if (event->oncpu == smp_processor_id())
|
||||
event->pmu->read(event);
|
||||
|
||||
val = local64_read(&event->count);
|
||||
*value = local64_read(&event->count);
|
||||
out:
|
||||
local_irq_restore(flags);
|
||||
|
||||
return val;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int perf_event_read(struct perf_event *event, bool group)
|
||||
@@ -3812,14 +3828,6 @@ find_get_context(struct pmu *pmu, struct task_struct *task,
|
||||
if (perf_paranoid_cpu() && !capable(CAP_SYS_ADMIN))
|
||||
return ERR_PTR(-EACCES);
|
||||
|
||||
/*
|
||||
* We could be clever and allow to attach a event to an
|
||||
* offline CPU and activate it when the CPU comes up, but
|
||||
* that's for later.
|
||||
*/
|
||||
if (!cpu_online(cpu))
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
cpuctx = per_cpu_ptr(pmu->pmu_cpu_context, cpu);
|
||||
ctx = &cpuctx->ctx;
|
||||
get_ctx(ctx);
|
||||
@@ -4377,7 +4385,9 @@ EXPORT_SYMBOL_GPL(perf_event_read_value);
|
||||
static int __perf_read_group_add(struct perf_event *leader,
|
||||
u64 read_format, u64 *values)
|
||||
{
|
||||
struct perf_event_context *ctx = leader->ctx;
|
||||
struct perf_event *sub;
|
||||
unsigned long flags;
|
||||
int n = 1; /* skip @nr */
|
||||
int ret;
|
||||
|
||||
@@ -4407,12 +4417,15 @@ static int __perf_read_group_add(struct perf_event *leader,
|
||||
if (read_format & PERF_FORMAT_ID)
|
||||
values[n++] = primary_event_id(leader);
|
||||
|
||||
raw_spin_lock_irqsave(&ctx->lock, flags);
|
||||
|
||||
list_for_each_entry(sub, &leader->sibling_list, group_entry) {
|
||||
values[n++] += perf_event_count(sub);
|
||||
if (read_format & PERF_FORMAT_ID)
|
||||
values[n++] = primary_event_id(sub);
|
||||
}
|
||||
|
||||
raw_spin_unlock_irqrestore(&ctx->lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -5729,9 +5742,6 @@ static void perf_output_read_one(struct perf_output_handle *handle,
|
||||
__output_copy(handle, values, n * sizeof(u64));
|
||||
}
|
||||
|
||||
/*
|
||||
* XXX PERF_FORMAT_GROUP vs inherited events seems difficult.
|
||||
*/
|
||||
static void perf_output_read_group(struct perf_output_handle *handle,
|
||||
struct perf_event *event,
|
||||
u64 enabled, u64 running)
|
||||
@@ -5776,6 +5786,13 @@ static void perf_output_read_group(struct perf_output_handle *handle,
|
||||
#define PERF_FORMAT_TOTAL_TIMES (PERF_FORMAT_TOTAL_TIME_ENABLED|\
|
||||
PERF_FORMAT_TOTAL_TIME_RUNNING)
|
||||
|
||||
/*
|
||||
* XXX PERF_SAMPLE_READ vs inherited events seems difficult.
|
||||
*
|
||||
* The problem is that its both hard and excessively expensive to iterate the
|
||||
* child list, not to mention that its impossible to IPI the children running
|
||||
* on another CPU, from interrupt/NMI context.
|
||||
*/
|
||||
static void perf_output_read(struct perf_output_handle *handle,
|
||||
struct perf_event *event)
|
||||
{
|
||||
@@ -7703,7 +7720,8 @@ static int swevent_hlist_get_cpu(int cpu)
|
||||
int err = 0;
|
||||
|
||||
mutex_lock(&swhash->hlist_mutex);
|
||||
if (!swevent_hlist_deref(swhash) && cpu_online(cpu)) {
|
||||
if (!swevent_hlist_deref(swhash) &&
|
||||
cpumask_test_cpu(cpu, perf_online_mask)) {
|
||||
struct swevent_hlist *hlist;
|
||||
|
||||
hlist = kzalloc(sizeof(*hlist), GFP_KERNEL);
|
||||
@@ -7724,7 +7742,7 @@ static int swevent_hlist_get(void)
|
||||
{
|
||||
int err, cpu, failed_cpu;
|
||||
|
||||
get_online_cpus();
|
||||
mutex_lock(&pmus_lock);
|
||||
for_each_possible_cpu(cpu) {
|
||||
err = swevent_hlist_get_cpu(cpu);
|
||||
if (err) {
|
||||
@@ -7732,8 +7750,7 @@ static int swevent_hlist_get(void)
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
put_online_cpus();
|
||||
|
||||
mutex_unlock(&pmus_lock);
|
||||
return 0;
|
||||
fail:
|
||||
for_each_possible_cpu(cpu) {
|
||||
@@ -7741,8 +7758,7 @@ fail:
|
||||
break;
|
||||
swevent_hlist_put_cpu(cpu);
|
||||
}
|
||||
|
||||
put_online_cpus();
|
||||
mutex_unlock(&pmus_lock);
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -8037,12 +8053,8 @@ static int perf_event_set_bpf_prog(struct perf_event *event, u32 prog_fd)
|
||||
bool is_kprobe, is_tracepoint;
|
||||
struct bpf_prog *prog;
|
||||
|
||||
if (event->attr.type == PERF_TYPE_HARDWARE ||
|
||||
event->attr.type == PERF_TYPE_SOFTWARE)
|
||||
return perf_event_set_bpf_handler(event, prog_fd);
|
||||
|
||||
if (event->attr.type != PERF_TYPE_TRACEPOINT)
|
||||
return -EINVAL;
|
||||
return perf_event_set_bpf_handler(event, prog_fd);
|
||||
|
||||
if (event->tp_event->prog)
|
||||
return -EEXIST;
|
||||
@@ -8920,7 +8932,7 @@ perf_event_mux_interval_ms_store(struct device *dev,
|
||||
pmu->hrtimer_interval_ms = timer;
|
||||
|
||||
/* update all cpuctx for this PMU */
|
||||
get_online_cpus();
|
||||
cpus_read_lock();
|
||||
for_each_online_cpu(cpu) {
|
||||
struct perf_cpu_context *cpuctx;
|
||||
cpuctx = per_cpu_ptr(pmu->pmu_cpu_context, cpu);
|
||||
@@ -8929,7 +8941,7 @@ perf_event_mux_interval_ms_store(struct device *dev,
|
||||
cpu_function_call(cpu,
|
||||
(remote_function_f)perf_mux_hrtimer_restart, cpuctx);
|
||||
}
|
||||
put_online_cpus();
|
||||
cpus_read_unlock();
|
||||
mutex_unlock(&mux_interval_mutex);
|
||||
|
||||
return count;
|
||||
@@ -9059,6 +9071,7 @@ skip_type:
|
||||
lockdep_set_class(&cpuctx->ctx.mutex, &cpuctx_mutex);
|
||||
lockdep_set_class(&cpuctx->ctx.lock, &cpuctx_lock);
|
||||
cpuctx->ctx.pmu = pmu;
|
||||
cpuctx->online = cpumask_test_cpu(cpu, perf_online_mask);
|
||||
|
||||
__perf_mux_hrtimer_init(cpuctx, cpu);
|
||||
}
|
||||
@@ -9172,7 +9185,7 @@ static int perf_try_init_event(struct pmu *pmu, struct perf_event *event)
|
||||
|
||||
static struct pmu *perf_init_event(struct perf_event *event)
|
||||
{
|
||||
struct pmu *pmu = NULL;
|
||||
struct pmu *pmu;
|
||||
int idx;
|
||||
int ret;
|
||||
|
||||
@@ -9441,9 +9454,10 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu,
|
||||
local64_set(&hwc->period_left, hwc->sample_period);
|
||||
|
||||
/*
|
||||
* we currently do not support PERF_FORMAT_GROUP on inherited events
|
||||
* We currently do not support PERF_SAMPLE_READ on inherited events.
|
||||
* See perf_output_read().
|
||||
*/
|
||||
if (attr->inherit && (attr->read_format & PERF_FORMAT_GROUP))
|
||||
if (attr->inherit && (attr->sample_type & PERF_SAMPLE_READ))
|
||||
goto err_ns;
|
||||
|
||||
if (!has_branch_stack(event))
|
||||
@@ -9456,9 +9470,7 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu,
|
||||
}
|
||||
|
||||
pmu = perf_init_event(event);
|
||||
if (!pmu)
|
||||
goto err_ns;
|
||||
else if (IS_ERR(pmu)) {
|
||||
if (IS_ERR(pmu)) {
|
||||
err = PTR_ERR(pmu);
|
||||
goto err_ns;
|
||||
}
|
||||
@@ -9471,8 +9483,10 @@ perf_event_alloc(struct perf_event_attr *attr, int cpu,
|
||||
event->addr_filters_offs = kcalloc(pmu->nr_addr_filters,
|
||||
sizeof(unsigned long),
|
||||
GFP_KERNEL);
|
||||
if (!event->addr_filters_offs)
|
||||
if (!event->addr_filters_offs) {
|
||||
err = -ENOMEM;
|
||||
goto err_per_task;
|
||||
}
|
||||
|
||||
/* force hw sync on the address filters */
|
||||
event->addr_filters_gen = 1;
|
||||
@@ -9882,12 +9896,10 @@ SYSCALL_DEFINE5(perf_event_open,
|
||||
goto err_task;
|
||||
}
|
||||
|
||||
get_online_cpus();
|
||||
|
||||
if (task) {
|
||||
err = mutex_lock_interruptible(&task->signal->cred_guard_mutex);
|
||||
if (err)
|
||||
goto err_cpus;
|
||||
goto err_task;
|
||||
|
||||
/*
|
||||
* Reuse ptrace permission checks for now.
|
||||
@@ -10073,6 +10085,23 @@ SYSCALL_DEFINE5(perf_event_open,
|
||||
goto err_locked;
|
||||
}
|
||||
|
||||
if (!task) {
|
||||
/*
|
||||
* Check if the @cpu we're creating an event for is online.
|
||||
*
|
||||
* We use the perf_cpu_context::ctx::mutex to serialize against
|
||||
* the hotplug notifiers. See perf_event_{init,exit}_cpu().
|
||||
*/
|
||||
struct perf_cpu_context *cpuctx =
|
||||
container_of(ctx, struct perf_cpu_context, ctx);
|
||||
|
||||
if (!cpuctx->online) {
|
||||
err = -ENODEV;
|
||||
goto err_locked;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Must be under the same ctx::mutex as perf_install_in_context(),
|
||||
* because we need to serialize with concurrent event creation.
|
||||
@@ -10162,8 +10191,6 @@ SYSCALL_DEFINE5(perf_event_open,
|
||||
put_task_struct(task);
|
||||
}
|
||||
|
||||
put_online_cpus();
|
||||
|
||||
mutex_lock(¤t->perf_event_mutex);
|
||||
list_add_tail(&event->owner_entry, ¤t->perf_event_list);
|
||||
mutex_unlock(¤t->perf_event_mutex);
|
||||
@@ -10197,8 +10224,6 @@ err_alloc:
|
||||
err_cred:
|
||||
if (task)
|
||||
mutex_unlock(&task->signal->cred_guard_mutex);
|
||||
err_cpus:
|
||||
put_online_cpus();
|
||||
err_task:
|
||||
if (task)
|
||||
put_task_struct(task);
|
||||
@@ -10253,6 +10278,21 @@ perf_event_create_kernel_counter(struct perf_event_attr *attr, int cpu,
|
||||
goto err_unlock;
|
||||
}
|
||||
|
||||
if (!task) {
|
||||
/*
|
||||
* Check if the @cpu we're creating an event for is online.
|
||||
*
|
||||
* We use the perf_cpu_context::ctx::mutex to serialize against
|
||||
* the hotplug notifiers. See perf_event_{init,exit}_cpu().
|
||||
*/
|
||||
struct perf_cpu_context *cpuctx =
|
||||
container_of(ctx, struct perf_cpu_context, ctx);
|
||||
if (!cpuctx->online) {
|
||||
err = -ENODEV;
|
||||
goto err_unlock;
|
||||
}
|
||||
}
|
||||
|
||||
if (!exclusive_event_installable(event, ctx)) {
|
||||
err = -EBUSY;
|
||||
goto err_unlock;
|
||||
@@ -10920,6 +10960,8 @@ static void __init perf_event_init_all_cpus(void)
|
||||
struct swevent_htable *swhash;
|
||||
int cpu;
|
||||
|
||||
zalloc_cpumask_var(&perf_online_mask, GFP_KERNEL);
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
swhash = &per_cpu(swevent_htable, cpu);
|
||||
mutex_init(&swhash->hlist_mutex);
|
||||
@@ -10935,7 +10977,7 @@ static void __init perf_event_init_all_cpus(void)
|
||||
}
|
||||
}
|
||||
|
||||
int perf_event_init_cpu(unsigned int cpu)
|
||||
void perf_swevent_init_cpu(unsigned int cpu)
|
||||
{
|
||||
struct swevent_htable *swhash = &per_cpu(swevent_htable, cpu);
|
||||
|
||||
@@ -10948,7 +10990,6 @@ int perf_event_init_cpu(unsigned int cpu)
|
||||
rcu_assign_pointer(swhash->swevent_hlist, hlist);
|
||||
}
|
||||
mutex_unlock(&swhash->hlist_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if defined CONFIG_HOTPLUG_CPU || defined CONFIG_KEXEC_CORE
|
||||
@@ -10966,19 +11007,22 @@ static void __perf_event_exit_context(void *__info)
|
||||
|
||||
static void perf_event_exit_cpu_context(int cpu)
|
||||
{
|
||||
struct perf_cpu_context *cpuctx;
|
||||
struct perf_event_context *ctx;
|
||||
struct pmu *pmu;
|
||||
int idx;
|
||||
|
||||
idx = srcu_read_lock(&pmus_srcu);
|
||||
list_for_each_entry_rcu(pmu, &pmus, entry) {
|
||||
ctx = &per_cpu_ptr(pmu->pmu_cpu_context, cpu)->ctx;
|
||||
mutex_lock(&pmus_lock);
|
||||
list_for_each_entry(pmu, &pmus, entry) {
|
||||
cpuctx = per_cpu_ptr(pmu->pmu_cpu_context, cpu);
|
||||
ctx = &cpuctx->ctx;
|
||||
|
||||
mutex_lock(&ctx->mutex);
|
||||
smp_call_function_single(cpu, __perf_event_exit_context, ctx, 1);
|
||||
cpuctx->online = 0;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
}
|
||||
srcu_read_unlock(&pmus_srcu, idx);
|
||||
cpumask_clear_cpu(cpu, perf_online_mask);
|
||||
mutex_unlock(&pmus_lock);
|
||||
}
|
||||
#else
|
||||
|
||||
@@ -10986,6 +11030,29 @@ static void perf_event_exit_cpu_context(int cpu) { }
|
||||
|
||||
#endif
|
||||
|
||||
int perf_event_init_cpu(unsigned int cpu)
|
||||
{
|
||||
struct perf_cpu_context *cpuctx;
|
||||
struct perf_event_context *ctx;
|
||||
struct pmu *pmu;
|
||||
|
||||
perf_swevent_init_cpu(cpu);
|
||||
|
||||
mutex_lock(&pmus_lock);
|
||||
cpumask_set_cpu(cpu, perf_online_mask);
|
||||
list_for_each_entry(pmu, &pmus, entry) {
|
||||
cpuctx = per_cpu_ptr(pmu->pmu_cpu_context, cpu);
|
||||
ctx = &cpuctx->ctx;
|
||||
|
||||
mutex_lock(&ctx->mutex);
|
||||
cpuctx->online = 1;
|
||||
mutex_unlock(&ctx->mutex);
|
||||
}
|
||||
mutex_unlock(&pmus_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int perf_event_exit_cpu(unsigned int cpu)
|
||||
{
|
||||
perf_event_exit_cpu_context(cpu);
|
||||
|
||||
@@ -580,7 +580,7 @@ int rb_alloc_aux(struct ring_buffer *rb, struct perf_event *event,
|
||||
int ret = -ENOMEM, max_order = 0;
|
||||
|
||||
if (!has_aux(event))
|
||||
return -ENOTSUPP;
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (event->pmu->capabilities & PERF_PMU_CAP_AUX_NO_SG) {
|
||||
/*
|
||||
|
||||
+180
-162
@@ -51,7 +51,6 @@
|
||||
#include <linux/task_io_accounting_ops.h>
|
||||
#include <linux/tracehook.h>
|
||||
#include <linux/fs_struct.h>
|
||||
#include <linux/userfaultfd_k.h>
|
||||
#include <linux/init_task.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <trace/events/sched.h>
|
||||
@@ -62,6 +61,7 @@
|
||||
#include <linux/kcov.h>
|
||||
#include <linux/random.h>
|
||||
#include <linux/rcuwait.h>
|
||||
#include <linux/compat.h>
|
||||
|
||||
#include <linux/uaccess.h>
|
||||
#include <asm/unistd.h>
|
||||
@@ -318,19 +318,6 @@ void rcuwait_wake_up(struct rcuwait *w)
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
struct task_struct *try_get_task_struct(struct task_struct **ptask)
|
||||
{
|
||||
struct task_struct *task;
|
||||
|
||||
rcu_read_lock();
|
||||
task = task_rcu_dereference(ptask);
|
||||
if (task)
|
||||
get_task_struct(task);
|
||||
rcu_read_unlock();
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine if a process group is "orphaned", according to the POSIX
|
||||
* definition in 2.2.2.52. Orphaned process groups are not to be affected
|
||||
@@ -995,16 +982,23 @@ SYSCALL_DEFINE1(exit_group, int, error_code)
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct waitid_info {
|
||||
pid_t pid;
|
||||
uid_t uid;
|
||||
int status;
|
||||
int cause;
|
||||
};
|
||||
|
||||
struct wait_opts {
|
||||
enum pid_type wo_type;
|
||||
int wo_flags;
|
||||
struct pid *wo_pid;
|
||||
|
||||
struct siginfo __user *wo_info;
|
||||
int __user *wo_stat;
|
||||
struct rusage __user *wo_rusage;
|
||||
struct waitid_info *wo_info;
|
||||
int wo_stat;
|
||||
struct rusage *wo_rusage;
|
||||
|
||||
wait_queue_t child_wait;
|
||||
wait_queue_entry_t child_wait;
|
||||
int notask_error;
|
||||
};
|
||||
|
||||
@@ -1049,34 +1043,6 @@ eligible_child(struct wait_opts *wo, bool ptrace, struct task_struct *p)
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int wait_noreap_copyout(struct wait_opts *wo, struct task_struct *p,
|
||||
pid_t pid, uid_t uid, int why, int status)
|
||||
{
|
||||
struct siginfo __user *infop;
|
||||
int retval = wo->wo_rusage
|
||||
? getrusage(p, RUSAGE_BOTH, wo->wo_rusage) : 0;
|
||||
|
||||
put_task_struct(p);
|
||||
infop = wo->wo_info;
|
||||
if (infop) {
|
||||
if (!retval)
|
||||
retval = put_user(SIGCHLD, &infop->si_signo);
|
||||
if (!retval)
|
||||
retval = put_user(0, &infop->si_errno);
|
||||
if (!retval)
|
||||
retval = put_user((short)why, &infop->si_code);
|
||||
if (!retval)
|
||||
retval = put_user(pid, &infop->si_pid);
|
||||
if (!retval)
|
||||
retval = put_user(uid, &infop->si_uid);
|
||||
if (!retval)
|
||||
retval = put_user(status, &infop->si_status);
|
||||
}
|
||||
if (!retval)
|
||||
retval = pid;
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle sys_wait4 work for one task in state EXIT_ZOMBIE. We hold
|
||||
* read_lock(&tasklist_lock) on entry. If we return zero, we still hold
|
||||
@@ -1085,30 +1051,23 @@ static int wait_noreap_copyout(struct wait_opts *wo, struct task_struct *p,
|
||||
*/
|
||||
static int wait_task_zombie(struct wait_opts *wo, struct task_struct *p)
|
||||
{
|
||||
int state, retval, status;
|
||||
int state, status;
|
||||
pid_t pid = task_pid_vnr(p);
|
||||
uid_t uid = from_kuid_munged(current_user_ns(), task_uid(p));
|
||||
struct siginfo __user *infop;
|
||||
struct waitid_info *infop;
|
||||
|
||||
if (!likely(wo->wo_flags & WEXITED))
|
||||
return 0;
|
||||
|
||||
if (unlikely(wo->wo_flags & WNOWAIT)) {
|
||||
int exit_code = p->exit_code;
|
||||
int why;
|
||||
|
||||
status = p->exit_code;
|
||||
get_task_struct(p);
|
||||
read_unlock(&tasklist_lock);
|
||||
sched_annotate_sleep();
|
||||
|
||||
if ((exit_code & 0x7f) == 0) {
|
||||
why = CLD_EXITED;
|
||||
status = exit_code >> 8;
|
||||
} else {
|
||||
why = (exit_code & 0x80) ? CLD_DUMPED : CLD_KILLED;
|
||||
status = exit_code & 0x7f;
|
||||
}
|
||||
return wait_noreap_copyout(wo, p, pid, uid, why, status);
|
||||
if (wo->wo_rusage)
|
||||
getrusage(p, RUSAGE_BOTH, wo->wo_rusage);
|
||||
put_task_struct(p);
|
||||
goto out_info;
|
||||
}
|
||||
/*
|
||||
* Move the task's state to DEAD/TRACE, only one thread can do this.
|
||||
@@ -1181,38 +1140,11 @@ static int wait_task_zombie(struct wait_opts *wo, struct task_struct *p)
|
||||
spin_unlock_irq(¤t->sighand->siglock);
|
||||
}
|
||||
|
||||
retval = wo->wo_rusage
|
||||
? getrusage(p, RUSAGE_BOTH, wo->wo_rusage) : 0;
|
||||
if (wo->wo_rusage)
|
||||
getrusage(p, RUSAGE_BOTH, wo->wo_rusage);
|
||||
status = (p->signal->flags & SIGNAL_GROUP_EXIT)
|
||||
? p->signal->group_exit_code : p->exit_code;
|
||||
if (!retval && wo->wo_stat)
|
||||
retval = put_user(status, wo->wo_stat);
|
||||
|
||||
infop = wo->wo_info;
|
||||
if (!retval && infop)
|
||||
retval = put_user(SIGCHLD, &infop->si_signo);
|
||||
if (!retval && infop)
|
||||
retval = put_user(0, &infop->si_errno);
|
||||
if (!retval && infop) {
|
||||
int why;
|
||||
|
||||
if ((status & 0x7f) == 0) {
|
||||
why = CLD_EXITED;
|
||||
status >>= 8;
|
||||
} else {
|
||||
why = (status & 0x80) ? CLD_DUMPED : CLD_KILLED;
|
||||
status &= 0x7f;
|
||||
}
|
||||
retval = put_user((short)why, &infop->si_code);
|
||||
if (!retval)
|
||||
retval = put_user(status, &infop->si_status);
|
||||
}
|
||||
if (!retval && infop)
|
||||
retval = put_user(pid, &infop->si_pid);
|
||||
if (!retval && infop)
|
||||
retval = put_user(uid, &infop->si_uid);
|
||||
if (!retval)
|
||||
retval = pid;
|
||||
wo->wo_stat = status;
|
||||
|
||||
if (state == EXIT_TRACE) {
|
||||
write_lock_irq(&tasklist_lock);
|
||||
@@ -1229,7 +1161,21 @@ static int wait_task_zombie(struct wait_opts *wo, struct task_struct *p)
|
||||
if (state == EXIT_DEAD)
|
||||
release_task(p);
|
||||
|
||||
return retval;
|
||||
out_info:
|
||||
infop = wo->wo_info;
|
||||
if (infop) {
|
||||
if ((status & 0x7f) == 0) {
|
||||
infop->cause = CLD_EXITED;
|
||||
infop->status = status >> 8;
|
||||
} else {
|
||||
infop->cause = (status & 0x80) ? CLD_DUMPED : CLD_KILLED;
|
||||
infop->status = status & 0x7f;
|
||||
}
|
||||
infop->pid = pid;
|
||||
infop->uid = uid;
|
||||
}
|
||||
|
||||
return pid;
|
||||
}
|
||||
|
||||
static int *task_stopped_code(struct task_struct *p, bool ptrace)
|
||||
@@ -1265,8 +1211,8 @@ static int *task_stopped_code(struct task_struct *p, bool ptrace)
|
||||
static int wait_task_stopped(struct wait_opts *wo,
|
||||
int ptrace, struct task_struct *p)
|
||||
{
|
||||
struct siginfo __user *infop;
|
||||
int retval, exit_code, *p_code, why;
|
||||
struct waitid_info *infop;
|
||||
int exit_code, *p_code, why;
|
||||
uid_t uid = 0; /* unneeded, required by compiler */
|
||||
pid_t pid;
|
||||
|
||||
@@ -1311,34 +1257,21 @@ unlock_sig:
|
||||
why = ptrace ? CLD_TRAPPED : CLD_STOPPED;
|
||||
read_unlock(&tasklist_lock);
|
||||
sched_annotate_sleep();
|
||||
|
||||
if (unlikely(wo->wo_flags & WNOWAIT))
|
||||
return wait_noreap_copyout(wo, p, pid, uid, why, exit_code);
|
||||
|
||||
retval = wo->wo_rusage
|
||||
? getrusage(p, RUSAGE_BOTH, wo->wo_rusage) : 0;
|
||||
if (!retval && wo->wo_stat)
|
||||
retval = put_user((exit_code << 8) | 0x7f, wo->wo_stat);
|
||||
|
||||
infop = wo->wo_info;
|
||||
if (!retval && infop)
|
||||
retval = put_user(SIGCHLD, &infop->si_signo);
|
||||
if (!retval && infop)
|
||||
retval = put_user(0, &infop->si_errno);
|
||||
if (!retval && infop)
|
||||
retval = put_user((short)why, &infop->si_code);
|
||||
if (!retval && infop)
|
||||
retval = put_user(exit_code, &infop->si_status);
|
||||
if (!retval && infop)
|
||||
retval = put_user(pid, &infop->si_pid);
|
||||
if (!retval && infop)
|
||||
retval = put_user(uid, &infop->si_uid);
|
||||
if (!retval)
|
||||
retval = pid;
|
||||
if (wo->wo_rusage)
|
||||
getrusage(p, RUSAGE_BOTH, wo->wo_rusage);
|
||||
put_task_struct(p);
|
||||
|
||||
BUG_ON(!retval);
|
||||
return retval;
|
||||
if (likely(!(wo->wo_flags & WNOWAIT)))
|
||||
wo->wo_stat = (exit_code << 8) | 0x7f;
|
||||
|
||||
infop = wo->wo_info;
|
||||
if (infop) {
|
||||
infop->cause = why;
|
||||
infop->status = exit_code;
|
||||
infop->pid = pid;
|
||||
infop->uid = uid;
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1349,7 +1282,7 @@ unlock_sig:
|
||||
*/
|
||||
static int wait_task_continued(struct wait_opts *wo, struct task_struct *p)
|
||||
{
|
||||
int retval;
|
||||
struct waitid_info *infop;
|
||||
pid_t pid;
|
||||
uid_t uid;
|
||||
|
||||
@@ -1374,22 +1307,20 @@ static int wait_task_continued(struct wait_opts *wo, struct task_struct *p)
|
||||
get_task_struct(p);
|
||||
read_unlock(&tasklist_lock);
|
||||
sched_annotate_sleep();
|
||||
if (wo->wo_rusage)
|
||||
getrusage(p, RUSAGE_BOTH, wo->wo_rusage);
|
||||
put_task_struct(p);
|
||||
|
||||
if (!wo->wo_info) {
|
||||
retval = wo->wo_rusage
|
||||
? getrusage(p, RUSAGE_BOTH, wo->wo_rusage) : 0;
|
||||
put_task_struct(p);
|
||||
if (!retval && wo->wo_stat)
|
||||
retval = put_user(0xffff, wo->wo_stat);
|
||||
if (!retval)
|
||||
retval = pid;
|
||||
infop = wo->wo_info;
|
||||
if (!infop) {
|
||||
wo->wo_stat = 0xffff;
|
||||
} else {
|
||||
retval = wait_noreap_copyout(wo, p, pid, uid,
|
||||
CLD_CONTINUED, SIGCONT);
|
||||
BUG_ON(retval == 0);
|
||||
infop->cause = CLD_CONTINUED;
|
||||
infop->pid = pid;
|
||||
infop->uid = uid;
|
||||
infop->status = SIGCONT;
|
||||
}
|
||||
|
||||
return retval;
|
||||
return pid;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1541,7 +1472,7 @@ static int ptrace_do_wait(struct wait_opts *wo, struct task_struct *tsk)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int child_wait_callback(wait_queue_t *wait, unsigned mode,
|
||||
static int child_wait_callback(wait_queue_entry_t *wait, unsigned mode,
|
||||
int sync, void *key)
|
||||
{
|
||||
struct wait_opts *wo = container_of(wait, struct wait_opts,
|
||||
@@ -1617,8 +1548,8 @@ end:
|
||||
return retval;
|
||||
}
|
||||
|
||||
SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
|
||||
infop, int, options, struct rusage __user *, ru)
|
||||
static long kernel_waitid(int which, pid_t upid, struct waitid_info *infop,
|
||||
int options, struct rusage *ru)
|
||||
{
|
||||
struct wait_opts wo;
|
||||
struct pid *pid = NULL;
|
||||
@@ -1656,38 +1587,48 @@ SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
|
||||
wo.wo_pid = pid;
|
||||
wo.wo_flags = options;
|
||||
wo.wo_info = infop;
|
||||
wo.wo_stat = NULL;
|
||||
wo.wo_rusage = ru;
|
||||
ret = do_wait(&wo);
|
||||
|
||||
if (ret > 0) {
|
||||
ret = 0;
|
||||
} else if (infop) {
|
||||
/*
|
||||
* For a WNOHANG return, clear out all the fields
|
||||
* we would set so the user can easily tell the
|
||||
* difference.
|
||||
*/
|
||||
if (!ret)
|
||||
ret = put_user(0, &infop->si_signo);
|
||||
if (!ret)
|
||||
ret = put_user(0, &infop->si_errno);
|
||||
if (!ret)
|
||||
ret = put_user(0, &infop->si_code);
|
||||
if (!ret)
|
||||
ret = put_user(0, &infop->si_pid);
|
||||
if (!ret)
|
||||
ret = put_user(0, &infop->si_uid);
|
||||
if (!ret)
|
||||
ret = put_user(0, &infop->si_status);
|
||||
}
|
||||
|
||||
put_pid(pid);
|
||||
return ret;
|
||||
}
|
||||
|
||||
SYSCALL_DEFINE4(wait4, pid_t, upid, int __user *, stat_addr,
|
||||
int, options, struct rusage __user *, ru)
|
||||
SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
|
||||
infop, int, options, struct rusage __user *, ru)
|
||||
{
|
||||
struct rusage r;
|
||||
struct waitid_info info = {.status = 0};
|
||||
long err = kernel_waitid(which, upid, &info, options, ru ? &r : NULL);
|
||||
int signo = 0;
|
||||
if (err > 0) {
|
||||
signo = SIGCHLD;
|
||||
err = 0;
|
||||
}
|
||||
|
||||
if (!err) {
|
||||
if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
|
||||
return -EFAULT;
|
||||
}
|
||||
if (!infop)
|
||||
return err;
|
||||
|
||||
user_access_begin();
|
||||
unsafe_put_user(signo, &infop->si_signo, Efault);
|
||||
unsafe_put_user(0, &infop->si_errno, Efault);
|
||||
unsafe_put_user((short)info.cause, &infop->si_code, Efault);
|
||||
unsafe_put_user(info.pid, &infop->si_pid, Efault);
|
||||
unsafe_put_user(info.uid, &infop->si_uid, Efault);
|
||||
unsafe_put_user(info.status, &infop->si_status, Efault);
|
||||
user_access_end();
|
||||
return err;
|
||||
Efault:
|
||||
user_access_end();
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
long kernel_wait4(pid_t upid, int __user *stat_addr, int options,
|
||||
struct rusage *ru)
|
||||
{
|
||||
struct wait_opts wo;
|
||||
struct pid *pid = NULL;
|
||||
@@ -1698,6 +1639,10 @@ SYSCALL_DEFINE4(wait4, pid_t, upid, int __user *, stat_addr,
|
||||
__WNOTHREAD|__WCLONE|__WALL))
|
||||
return -EINVAL;
|
||||
|
||||
/* -INT_MIN is not defined */
|
||||
if (upid == INT_MIN)
|
||||
return -ESRCH;
|
||||
|
||||
if (upid == -1)
|
||||
type = PIDTYPE_MAX;
|
||||
else if (upid < 0) {
|
||||
@@ -1715,14 +1660,29 @@ SYSCALL_DEFINE4(wait4, pid_t, upid, int __user *, stat_addr,
|
||||
wo.wo_pid = pid;
|
||||
wo.wo_flags = options | WEXITED;
|
||||
wo.wo_info = NULL;
|
||||
wo.wo_stat = stat_addr;
|
||||
wo.wo_stat = 0;
|
||||
wo.wo_rusage = ru;
|
||||
ret = do_wait(&wo);
|
||||
put_pid(pid);
|
||||
if (ret > 0 && stat_addr && put_user(wo.wo_stat, stat_addr))
|
||||
ret = -EFAULT;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SYSCALL_DEFINE4(wait4, pid_t, upid, int __user *, stat_addr,
|
||||
int, options, struct rusage __user *, ru)
|
||||
{
|
||||
struct rusage r;
|
||||
long err = kernel_wait4(upid, stat_addr, options, ru ? &r : NULL);
|
||||
|
||||
if (err > 0) {
|
||||
if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
|
||||
return -EFAULT;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef __ARCH_WANT_SYS_WAITPID
|
||||
|
||||
/*
|
||||
@@ -1735,3 +1695,61 @@ SYSCALL_DEFINE3(waitpid, pid_t, pid, int __user *, stat_addr, int, options)
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
COMPAT_SYSCALL_DEFINE4(wait4,
|
||||
compat_pid_t, pid,
|
||||
compat_uint_t __user *, stat_addr,
|
||||
int, options,
|
||||
struct compat_rusage __user *, ru)
|
||||
{
|
||||
struct rusage r;
|
||||
long err = kernel_wait4(pid, stat_addr, options, ru ? &r : NULL);
|
||||
if (err > 0) {
|
||||
if (ru && put_compat_rusage(&r, ru))
|
||||
return -EFAULT;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
COMPAT_SYSCALL_DEFINE5(waitid,
|
||||
int, which, compat_pid_t, pid,
|
||||
struct compat_siginfo __user *, infop, int, options,
|
||||
struct compat_rusage __user *, uru)
|
||||
{
|
||||
struct rusage ru;
|
||||
struct waitid_info info = {.status = 0};
|
||||
long err = kernel_waitid(which, pid, &info, options, uru ? &ru : NULL);
|
||||
int signo = 0;
|
||||
if (err > 0) {
|
||||
signo = SIGCHLD;
|
||||
err = 0;
|
||||
}
|
||||
|
||||
if (!err && uru) {
|
||||
/* kernel_waitid() overwrites everything in ru */
|
||||
if (COMPAT_USE_64BIT_TIME)
|
||||
err = copy_to_user(uru, &ru, sizeof(ru));
|
||||
else
|
||||
err = put_compat_rusage(&ru, uru);
|
||||
if (err)
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
if (!infop)
|
||||
return err;
|
||||
|
||||
user_access_begin();
|
||||
unsafe_put_user(signo, &infop->si_signo, Efault);
|
||||
unsafe_put_user(0, &infop->si_errno, Efault);
|
||||
unsafe_put_user((short)info.cause, &infop->si_code, Efault);
|
||||
unsafe_put_user(info.pid, &infop->si_pid, Efault);
|
||||
unsafe_put_user(info.uid, &infop->si_uid, Efault);
|
||||
unsafe_put_user(info.status, &infop->si_status, Efault);
|
||||
user_access_end();
|
||||
return err;
|
||||
Efault:
|
||||
user_access_end();
|
||||
return -EFAULT;
|
||||
}
|
||||
#endif
|
||||
|
||||
+4
-3
@@ -55,7 +55,8 @@ const struct exception_table_entry *search_exception_tables(unsigned long addr)
|
||||
{
|
||||
const struct exception_table_entry *e;
|
||||
|
||||
e = search_extable(__start___ex_table, __stop___ex_table-1, addr);
|
||||
e = search_extable(__start___ex_table,
|
||||
__stop___ex_table - __start___ex_table, addr);
|
||||
if (!e)
|
||||
e = search_module_extables(addr);
|
||||
return e;
|
||||
@@ -69,13 +70,13 @@ static inline int init_kernel_text(unsigned long addr)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int core_kernel_text(unsigned long addr)
|
||||
int notrace core_kernel_text(unsigned long addr)
|
||||
{
|
||||
if (addr >= (unsigned long)_stext &&
|
||||
addr < (unsigned long)_etext)
|
||||
return 1;
|
||||
|
||||
if (system_state == SYSTEM_BOOTING &&
|
||||
if (system_state < SYSTEM_RUNNING &&
|
||||
init_kernel_text(addr))
|
||||
return 1;
|
||||
return 0;
|
||||
|
||||
+29
-24
@@ -205,19 +205,17 @@ static unsigned long *alloc_thread_stack_node(struct task_struct *tsk, int node)
|
||||
void *stack;
|
||||
int i;
|
||||
|
||||
local_irq_disable();
|
||||
for (i = 0; i < NR_CACHED_STACKS; i++) {
|
||||
struct vm_struct *s = this_cpu_read(cached_stacks[i]);
|
||||
struct vm_struct *s;
|
||||
|
||||
s = this_cpu_xchg(cached_stacks[i], NULL);
|
||||
|
||||
if (!s)
|
||||
continue;
|
||||
this_cpu_write(cached_stacks[i], NULL);
|
||||
|
||||
tsk->stack_vm_area = s;
|
||||
local_irq_enable();
|
||||
return s->addr;
|
||||
}
|
||||
local_irq_enable();
|
||||
|
||||
stack = __vmalloc_node_range(THREAD_SIZE, THREAD_SIZE,
|
||||
VMALLOC_START, VMALLOC_END,
|
||||
@@ -245,19 +243,15 @@ static inline void free_thread_stack(struct task_struct *tsk)
|
||||
{
|
||||
#ifdef CONFIG_VMAP_STACK
|
||||
if (task_stack_vm_area(tsk)) {
|
||||
unsigned long flags;
|
||||
int i;
|
||||
|
||||
local_irq_save(flags);
|
||||
for (i = 0; i < NR_CACHED_STACKS; i++) {
|
||||
if (this_cpu_read(cached_stacks[i]))
|
||||
if (this_cpu_cmpxchg(cached_stacks[i],
|
||||
NULL, tsk->stack_vm_area) != NULL)
|
||||
continue;
|
||||
|
||||
this_cpu_write(cached_stacks[i], tsk->stack_vm_area);
|
||||
local_irq_restore(flags);
|
||||
return;
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
|
||||
vfree_atomic(tsk->stack);
|
||||
return;
|
||||
@@ -326,8 +320,8 @@ static void account_kernel_stack(struct task_struct *tsk, int account)
|
||||
}
|
||||
|
||||
/* All stack pages belong to the same memcg. */
|
||||
memcg_kmem_update_page_stat(vm->pages[0], MEMCG_KERNEL_STACK_KB,
|
||||
account * (THREAD_SIZE / 1024));
|
||||
mod_memcg_page_state(vm->pages[0], MEMCG_KERNEL_STACK_KB,
|
||||
account * (THREAD_SIZE / 1024));
|
||||
} else {
|
||||
/*
|
||||
* All stack pages are in the same zone and belong to the
|
||||
@@ -338,8 +332,8 @@ static void account_kernel_stack(struct task_struct *tsk, int account)
|
||||
mod_zone_page_state(page_zone(first_page), NR_KERNEL_STACK_KB,
|
||||
THREAD_SIZE / 1024 * account);
|
||||
|
||||
memcg_kmem_update_page_stat(first_page, MEMCG_KERNEL_STACK_KB,
|
||||
account * (THREAD_SIZE / 1024));
|
||||
mod_memcg_page_state(first_page, MEMCG_KERNEL_STACK_KB,
|
||||
account * (THREAD_SIZE / 1024));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,7 +554,7 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
|
||||
set_task_stack_end_magic(tsk);
|
||||
|
||||
#ifdef CONFIG_CC_STACKPROTECTOR
|
||||
tsk->stack_canary = get_random_long();
|
||||
tsk->stack_canary = get_random_canary();
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -579,6 +573,10 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node)
|
||||
|
||||
kcov_task_init(tsk);
|
||||
|
||||
#ifdef CONFIG_FAULT_INJECTION
|
||||
tsk->fail_nth = 0;
|
||||
#endif
|
||||
|
||||
return tsk;
|
||||
|
||||
free_stack:
|
||||
@@ -1573,6 +1571,18 @@ static __latent_entropy struct task_struct *copy_process(
|
||||
if (!p)
|
||||
goto fork_out;
|
||||
|
||||
/*
|
||||
* This _must_ happen before we call free_task(), i.e. before we jump
|
||||
* to any of the bad_fork_* labels. This is to avoid freeing
|
||||
* p->set_child_tid which is (ab)used as a kthread's data pointer for
|
||||
* kernel threads (PF_KTHREAD).
|
||||
*/
|
||||
p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;
|
||||
/*
|
||||
* Clear TID on mm_release()?
|
||||
*/
|
||||
p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr : NULL;
|
||||
|
||||
ftrace_graph_init_task(p);
|
||||
|
||||
rt_mutex_init_task(p);
|
||||
@@ -1621,9 +1631,9 @@ static __latent_entropy struct task_struct *copy_process(
|
||||
prev_cputime_init(&p->prev_cputime);
|
||||
|
||||
#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN
|
||||
seqcount_init(&p->vtime_seqcount);
|
||||
p->vtime_snap = 0;
|
||||
p->vtime_snap_whence = VTIME_INACTIVE;
|
||||
seqcount_init(&p->vtime.seqcount);
|
||||
p->vtime.starttime = 0;
|
||||
p->vtime.state = VTIME_INACTIVE;
|
||||
#endif
|
||||
|
||||
#if defined(SPLIT_RSS_COUNTING)
|
||||
@@ -1739,11 +1749,6 @@ static __latent_entropy struct task_struct *copy_process(
|
||||
}
|
||||
}
|
||||
|
||||
p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;
|
||||
/*
|
||||
* Clear TID on mm_release()?
|
||||
*/
|
||||
p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr : NULL;
|
||||
#ifdef CONFIG_BLOCK
|
||||
p->plug = NULL;
|
||||
#endif
|
||||
|
||||
+23
-23
@@ -212,7 +212,7 @@ struct futex_pi_state {
|
||||
atomic_t refcount;
|
||||
|
||||
union futex_key key;
|
||||
};
|
||||
} __randomize_layout;
|
||||
|
||||
/**
|
||||
* struct futex_q - The hashed futex queue entry, one per waiting task
|
||||
@@ -225,7 +225,7 @@ struct futex_pi_state {
|
||||
* @requeue_pi_key: the requeue_pi target futex key
|
||||
* @bitset: bitset for the optional bitmasked wakeup
|
||||
*
|
||||
* We use this hashed waitqueue, instead of a normal wait_queue_t, so
|
||||
* We use this hashed waitqueue, instead of a normal wait_queue_entry_t, so
|
||||
* we can wake only the relevant ones (hashed queues may be shared).
|
||||
*
|
||||
* A futex_q has a woken state, just like tasks have TASK_RUNNING.
|
||||
@@ -246,7 +246,7 @@ struct futex_q {
|
||||
struct rt_mutex_waiter *rt_waiter;
|
||||
union futex_key *requeue_pi_key;
|
||||
u32 bitset;
|
||||
};
|
||||
} __randomize_layout;
|
||||
|
||||
static const struct futex_q futex_q_init = {
|
||||
/* list gets initialized in queue_me()*/
|
||||
@@ -488,7 +488,7 @@ static void drop_futex_key_refs(union futex_key *key)
|
||||
*
|
||||
* Return: a negative error code or 0
|
||||
*
|
||||
* The key words are stored in *key on success.
|
||||
* The key words are stored in @key on success.
|
||||
*
|
||||
* For shared mappings, it's (page->index, file_inode(vma->vm_file),
|
||||
* offset_within_page). For private mappings, it's (uaddr, current->mm).
|
||||
@@ -1259,9 +1259,9 @@ static int lock_pi_update_atomic(u32 __user *uaddr, u32 uval, u32 newval)
|
||||
* @set_waiters: force setting the FUTEX_WAITERS bit (1) or not (0)
|
||||
*
|
||||
* Return:
|
||||
* 0 - ready to wait;
|
||||
* 1 - acquired the lock;
|
||||
* <0 - error
|
||||
* - 0 - ready to wait;
|
||||
* - 1 - acquired the lock;
|
||||
* - <0 - error
|
||||
*
|
||||
* The hb->lock and futex_key refs shall be held by the caller.
|
||||
*/
|
||||
@@ -1717,9 +1717,9 @@ void requeue_pi_wake_futex(struct futex_q *q, union futex_key *key,
|
||||
* hb1 and hb2 must be held by the caller.
|
||||
*
|
||||
* Return:
|
||||
* 0 - failed to acquire the lock atomically;
|
||||
* >0 - acquired the lock, return value is vpid of the top_waiter
|
||||
* <0 - error
|
||||
* - 0 - failed to acquire the lock atomically;
|
||||
* - >0 - acquired the lock, return value is vpid of the top_waiter
|
||||
* - <0 - error
|
||||
*/
|
||||
static int futex_proxy_trylock_atomic(u32 __user *pifutex,
|
||||
struct futex_hash_bucket *hb1,
|
||||
@@ -1785,8 +1785,8 @@ static int futex_proxy_trylock_atomic(u32 __user *pifutex,
|
||||
* uaddr2 atomically on behalf of the top waiter.
|
||||
*
|
||||
* Return:
|
||||
* >=0 - on success, the number of tasks requeued or woken;
|
||||
* <0 - on error
|
||||
* - >=0 - on success, the number of tasks requeued or woken;
|
||||
* - <0 - on error
|
||||
*/
|
||||
static int futex_requeue(u32 __user *uaddr1, unsigned int flags,
|
||||
u32 __user *uaddr2, int nr_wake, int nr_requeue,
|
||||
@@ -2142,8 +2142,8 @@ static inline void queue_me(struct futex_q *q, struct futex_hash_bucket *hb)
|
||||
* be paired with exactly one earlier call to queue_me().
|
||||
*
|
||||
* Return:
|
||||
* 1 - if the futex_q was still queued (and we removed unqueued it);
|
||||
* 0 - if the futex_q was already removed by the waking thread
|
||||
* - 1 - if the futex_q was still queued (and we removed unqueued it);
|
||||
* - 0 - if the futex_q was already removed by the waking thread
|
||||
*/
|
||||
static int unqueue_me(struct futex_q *q)
|
||||
{
|
||||
@@ -2333,9 +2333,9 @@ static long futex_wait_restart(struct restart_block *restart);
|
||||
* acquire the lock. Must be called with the hb lock held.
|
||||
*
|
||||
* Return:
|
||||
* 1 - success, lock taken;
|
||||
* 0 - success, lock not taken;
|
||||
* <0 - on error (-EFAULT)
|
||||
* - 1 - success, lock taken;
|
||||
* - 0 - success, lock not taken;
|
||||
* - <0 - on error (-EFAULT)
|
||||
*/
|
||||
static int fixup_owner(u32 __user *uaddr, struct futex_q *q, int locked)
|
||||
{
|
||||
@@ -2422,8 +2422,8 @@ static void futex_wait_queue_me(struct futex_hash_bucket *hb, struct futex_q *q,
|
||||
* with no q.key reference on failure.
|
||||
*
|
||||
* Return:
|
||||
* 0 - uaddr contains val and hb has been locked;
|
||||
* <1 - -EFAULT or -EWOULDBLOCK (uaddr does not contain val) and hb is unlocked
|
||||
* - 0 - uaddr contains val and hb has been locked;
|
||||
* - <1 - -EFAULT or -EWOULDBLOCK (uaddr does not contain val) and hb is unlocked
|
||||
*/
|
||||
static int futex_wait_setup(u32 __user *uaddr, u32 val, unsigned int flags,
|
||||
struct futex_q *q, struct futex_hash_bucket **hb)
|
||||
@@ -2895,8 +2895,8 @@ pi_faulted:
|
||||
* called with the hb lock held.
|
||||
*
|
||||
* Return:
|
||||
* 0 = no early wakeup detected;
|
||||
* <0 = -ETIMEDOUT or -ERESTARTNOINTR
|
||||
* - 0 = no early wakeup detected;
|
||||
* - <0 = -ETIMEDOUT or -ERESTARTNOINTR
|
||||
*/
|
||||
static inline
|
||||
int handle_early_requeue_pi_wakeup(struct futex_hash_bucket *hb,
|
||||
@@ -2968,8 +2968,8 @@ int handle_early_requeue_pi_wakeup(struct futex_hash_bucket *hb,
|
||||
* If 4 or 7, we cleanup and return with -ETIMEDOUT.
|
||||
*
|
||||
* Return:
|
||||
* 0 - On success;
|
||||
* <0 - On error
|
||||
* - 0 - On success;
|
||||
* - <0 - On error
|
||||
*/
|
||||
static int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags,
|
||||
u32 val, ktime_t *abs_time, u32 bitset,
|
||||
|
||||
+11
-24
@@ -5,6 +5,7 @@
|
||||
#include <linux/export.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/security.h>
|
||||
#include <linux/sort.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/user_namespace.h>
|
||||
#include <linux/vmalloc.h>
|
||||
@@ -76,32 +77,18 @@ static int groups_from_user(struct group_info *group_info,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* a simple Shell sort */
|
||||
static int gid_cmp(const void *_a, const void *_b)
|
||||
{
|
||||
kgid_t a = *(kgid_t *)_a;
|
||||
kgid_t b = *(kgid_t *)_b;
|
||||
|
||||
return gid_gt(a, b) - gid_lt(a, b);
|
||||
}
|
||||
|
||||
static void groups_sort(struct group_info *group_info)
|
||||
{
|
||||
int base, max, stride;
|
||||
int gidsetsize = group_info->ngroups;
|
||||
|
||||
for (stride = 1; stride < gidsetsize; stride = 3 * stride + 1)
|
||||
; /* nothing */
|
||||
stride /= 3;
|
||||
|
||||
while (stride) {
|
||||
max = gidsetsize - stride;
|
||||
for (base = 0; base < max; base++) {
|
||||
int left = base;
|
||||
int right = left + stride;
|
||||
kgid_t tmp = group_info->gid[right];
|
||||
|
||||
while (left >= 0 && gid_gt(group_info->gid[left], tmp)) {
|
||||
group_info->gid[right] = group_info->gid[left];
|
||||
right = left;
|
||||
left -= stride;
|
||||
}
|
||||
group_info->gid[right] = tmp;
|
||||
}
|
||||
stride /= 3;
|
||||
}
|
||||
sort(group_info->gid, group_info->ngroups, sizeof(*group_info->gid),
|
||||
gid_cmp, NULL);
|
||||
}
|
||||
|
||||
/* a simple bsearch */
|
||||
|
||||
@@ -21,6 +21,10 @@ config GENERIC_IRQ_SHOW
|
||||
config GENERIC_IRQ_SHOW_LEVEL
|
||||
bool
|
||||
|
||||
# Supports effective affinity mask
|
||||
config GENERIC_IRQ_EFFECTIVE_AFF_MASK
|
||||
bool
|
||||
|
||||
# Facility to allocate a hardware interrupt. This is legacy support
|
||||
# and should not be used in new code. Use irq domains instead.
|
||||
config GENERIC_IRQ_LEGACY_ALLOC_HWIRQ
|
||||
@@ -81,6 +85,9 @@ config GENERIC_MSI_IRQ_DOMAIN
|
||||
config HANDLE_DOMAIN_IRQ
|
||||
bool
|
||||
|
||||
config IRQ_TIMINGS
|
||||
bool
|
||||
|
||||
config IRQ_DOMAIN_DEBUG
|
||||
bool "Expose hardware/virtual IRQ mapping via debugfs"
|
||||
depends on IRQ_DOMAIN && DEBUG_FS
|
||||
@@ -108,4 +115,15 @@ config SPARSE_IRQ
|
||||
|
||||
If you don't know what to do here, say N.
|
||||
|
||||
config GENERIC_IRQ_DEBUGFS
|
||||
bool "Expose irq internals in debugfs"
|
||||
depends on DEBUG_FS
|
||||
default n
|
||||
---help---
|
||||
|
||||
Exposes internal state information through debugfs. Mostly for
|
||||
developers and debugging of hard to diagnose interrupt problems.
|
||||
|
||||
If you don't know what to do here, say N.
|
||||
|
||||
endmenu
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
obj-y := irqdesc.o handle.o manage.o spurious.o resend.o chip.o dummychip.o devres.o
|
||||
obj-$(CONFIG_IRQ_TIMINGS) += timings.o
|
||||
obj-$(CONFIG_GENERIC_IRQ_CHIP) += generic-chip.o
|
||||
obj-$(CONFIG_GENERIC_IRQ_PROBE) += autoprobe.o
|
||||
obj-$(CONFIG_IRQ_DOMAIN) += irqdomain.o
|
||||
@@ -10,3 +11,4 @@ obj-$(CONFIG_PM_SLEEP) += pm.o
|
||||
obj-$(CONFIG_GENERIC_MSI_IRQ) += msi.o
|
||||
obj-$(CONFIG_GENERIC_IRQ_IPI) += ipi.o
|
||||
obj-$(CONFIG_SMP) += affinity.o
|
||||
obj-$(CONFIG_GENERIC_IRQ_DEBUGFS) += debugfs.o
|
||||
|
||||
+75
-14
@@ -1,4 +1,7 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 Thomas Gleixner.
|
||||
* Copyright (C) 2016-2017 Christoph Hellwig.
|
||||
*/
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
@@ -35,13 +38,54 @@ static void irq_spread_init_one(struct cpumask *irqmsk, struct cpumask *nmsk,
|
||||
}
|
||||
}
|
||||
|
||||
static int get_nodes_in_cpumask(const struct cpumask *mask, nodemask_t *nodemsk)
|
||||
static cpumask_var_t *alloc_node_to_present_cpumask(void)
|
||||
{
|
||||
cpumask_var_t *masks;
|
||||
int node;
|
||||
|
||||
masks = kcalloc(nr_node_ids, sizeof(cpumask_var_t), GFP_KERNEL);
|
||||
if (!masks)
|
||||
return NULL;
|
||||
|
||||
for (node = 0; node < nr_node_ids; node++) {
|
||||
if (!zalloc_cpumask_var(&masks[node], GFP_KERNEL))
|
||||
goto out_unwind;
|
||||
}
|
||||
|
||||
return masks;
|
||||
|
||||
out_unwind:
|
||||
while (--node >= 0)
|
||||
free_cpumask_var(masks[node]);
|
||||
kfree(masks);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void free_node_to_present_cpumask(cpumask_var_t *masks)
|
||||
{
|
||||
int node;
|
||||
|
||||
for (node = 0; node < nr_node_ids; node++)
|
||||
free_cpumask_var(masks[node]);
|
||||
kfree(masks);
|
||||
}
|
||||
|
||||
static void build_node_to_present_cpumask(cpumask_var_t *masks)
|
||||
{
|
||||
int cpu;
|
||||
|
||||
for_each_present_cpu(cpu)
|
||||
cpumask_set_cpu(cpu, masks[cpu_to_node(cpu)]);
|
||||
}
|
||||
|
||||
static int get_nodes_in_cpumask(cpumask_var_t *node_to_present_cpumask,
|
||||
const struct cpumask *mask, nodemask_t *nodemsk)
|
||||
{
|
||||
int n, nodes = 0;
|
||||
|
||||
/* Calculate the number of nodes in the supplied affinity mask */
|
||||
for_each_online_node(n) {
|
||||
if (cpumask_intersects(mask, cpumask_of_node(n))) {
|
||||
for_each_node(n) {
|
||||
if (cpumask_intersects(mask, node_to_present_cpumask[n])) {
|
||||
node_set(n, *nodemsk);
|
||||
nodes++;
|
||||
}
|
||||
@@ -64,7 +108,14 @@ irq_create_affinity_masks(int nvecs, const struct irq_affinity *affd)
|
||||
int last_affv = affv + affd->pre_vectors;
|
||||
nodemask_t nodemsk = NODE_MASK_NONE;
|
||||
struct cpumask *masks;
|
||||
cpumask_var_t nmsk;
|
||||
cpumask_var_t nmsk, *node_to_present_cpumask;
|
||||
|
||||
/*
|
||||
* If there aren't any vectors left after applying the pre/post
|
||||
* vectors don't bother with assigning affinity.
|
||||
*/
|
||||
if (!affv)
|
||||
return NULL;
|
||||
|
||||
if (!zalloc_cpumask_var(&nmsk, GFP_KERNEL))
|
||||
return NULL;
|
||||
@@ -73,13 +124,19 @@ irq_create_affinity_masks(int nvecs, const struct irq_affinity *affd)
|
||||
if (!masks)
|
||||
goto out;
|
||||
|
||||
node_to_present_cpumask = alloc_node_to_present_cpumask();
|
||||
if (!node_to_present_cpumask)
|
||||
goto out;
|
||||
|
||||
/* Fill out vectors at the beginning that don't need affinity */
|
||||
for (curvec = 0; curvec < affd->pre_vectors; curvec++)
|
||||
cpumask_copy(masks + curvec, irq_default_affinity);
|
||||
|
||||
/* Stabilize the cpumasks */
|
||||
get_online_cpus();
|
||||
nodes = get_nodes_in_cpumask(cpu_online_mask, &nodemsk);
|
||||
build_node_to_present_cpumask(node_to_present_cpumask);
|
||||
nodes = get_nodes_in_cpumask(node_to_present_cpumask, cpu_present_mask,
|
||||
&nodemsk);
|
||||
|
||||
/*
|
||||
* If the number of nodes in the mask is greater than or equal the
|
||||
@@ -87,7 +144,8 @@ irq_create_affinity_masks(int nvecs, const struct irq_affinity *affd)
|
||||
*/
|
||||
if (affv <= nodes) {
|
||||
for_each_node_mask(n, nodemsk) {
|
||||
cpumask_copy(masks + curvec, cpumask_of_node(n));
|
||||
cpumask_copy(masks + curvec,
|
||||
node_to_present_cpumask[n]);
|
||||
if (++curvec == last_affv)
|
||||
break;
|
||||
}
|
||||
@@ -101,7 +159,7 @@ irq_create_affinity_masks(int nvecs, const struct irq_affinity *affd)
|
||||
vecs_per_node = (affv - (curvec - affd->pre_vectors)) / nodes;
|
||||
|
||||
/* Get the cpus on this node which are in the mask */
|
||||
cpumask_and(nmsk, cpu_online_mask, cpumask_of_node(n));
|
||||
cpumask_and(nmsk, cpu_present_mask, node_to_present_cpumask[n]);
|
||||
|
||||
/* Calculate the number of cpus per vector */
|
||||
ncpus = cpumask_weight(nmsk);
|
||||
@@ -133,6 +191,7 @@ done:
|
||||
/* Fill out vectors at the end that don't need affinity */
|
||||
for (; curvec < nvecs; curvec++)
|
||||
cpumask_copy(masks + curvec, irq_default_affinity);
|
||||
free_node_to_present_cpumask(node_to_present_cpumask);
|
||||
out:
|
||||
free_cpumask_var(nmsk);
|
||||
return masks;
|
||||
@@ -140,19 +199,21 @@ out:
|
||||
|
||||
/**
|
||||
* irq_calc_affinity_vectors - Calculate the optimal number of vectors
|
||||
* @minvec: The minimum number of vectors available
|
||||
* @maxvec: The maximum number of vectors available
|
||||
* @affd: Description of the affinity requirements
|
||||
*/
|
||||
int irq_calc_affinity_vectors(int maxvec, const struct irq_affinity *affd)
|
||||
int irq_calc_affinity_vectors(int minvec, int maxvec, const struct irq_affinity *affd)
|
||||
{
|
||||
int resv = affd->pre_vectors + affd->post_vectors;
|
||||
int vecs = maxvec - resv;
|
||||
int cpus;
|
||||
int ret;
|
||||
|
||||
if (resv > minvec)
|
||||
return 0;
|
||||
|
||||
/* Stabilize the cpumasks */
|
||||
get_online_cpus();
|
||||
cpus = cpumask_weight(cpu_online_mask);
|
||||
ret = min_t(int, cpumask_weight(cpu_present_mask), vecs) + resv;
|
||||
put_online_cpus();
|
||||
|
||||
return min(cpus, vecs) + resv;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ unsigned long probe_irq_on(void)
|
||||
if (desc->irq_data.chip->irq_set_type)
|
||||
desc->irq_data.chip->irq_set_type(&desc->irq_data,
|
||||
IRQ_TYPE_PROBE);
|
||||
irq_startup(desc, false);
|
||||
irq_startup(desc, IRQ_NORESEND, IRQ_START_FORCE);
|
||||
}
|
||||
raw_spin_unlock_irq(&desc->lock);
|
||||
}
|
||||
@@ -70,7 +70,7 @@ unsigned long probe_irq_on(void)
|
||||
raw_spin_lock_irq(&desc->lock);
|
||||
if (!desc->action && irq_settings_can_probe(desc)) {
|
||||
desc->istate |= IRQS_AUTODETECT | IRQS_WAITING;
|
||||
if (irq_startup(desc, false))
|
||||
if (irq_startup(desc, IRQ_NORESEND, IRQ_START_FORCE))
|
||||
desc->istate |= IRQS_PENDING;
|
||||
}
|
||||
raw_spin_unlock_irq(&desc->lock);
|
||||
|
||||
+157
-48
@@ -7,7 +7,7 @@
|
||||
* This file contains the core interrupt handling code, for irq-chip
|
||||
* based architectures.
|
||||
*
|
||||
* Detailed information is available in Documentation/DocBook/genericirq
|
||||
* Detailed information is available in Documentation/core-api/genericirq.rst
|
||||
*/
|
||||
|
||||
#include <linux/irq.h>
|
||||
@@ -170,62 +170,167 @@ static void irq_state_clr_disabled(struct irq_desc *desc)
|
||||
irqd_clear(&desc->irq_data, IRQD_IRQ_DISABLED);
|
||||
}
|
||||
|
||||
static void irq_state_set_disabled(struct irq_desc *desc)
|
||||
{
|
||||
irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED);
|
||||
}
|
||||
|
||||
static void irq_state_clr_masked(struct irq_desc *desc)
|
||||
{
|
||||
irqd_clear(&desc->irq_data, IRQD_IRQ_MASKED);
|
||||
}
|
||||
|
||||
static void irq_state_set_masked(struct irq_desc *desc)
|
||||
static void irq_state_clr_started(struct irq_desc *desc)
|
||||
{
|
||||
irqd_set(&desc->irq_data, IRQD_IRQ_MASKED);
|
||||
irqd_clear(&desc->irq_data, IRQD_IRQ_STARTED);
|
||||
}
|
||||
|
||||
int irq_startup(struct irq_desc *desc, bool resend)
|
||||
static void irq_state_set_started(struct irq_desc *desc)
|
||||
{
|
||||
irqd_set(&desc->irq_data, IRQD_IRQ_STARTED);
|
||||
}
|
||||
|
||||
enum {
|
||||
IRQ_STARTUP_NORMAL,
|
||||
IRQ_STARTUP_MANAGED,
|
||||
IRQ_STARTUP_ABORT,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
static int
|
||||
__irq_startup_managed(struct irq_desc *desc, struct cpumask *aff, bool force)
|
||||
{
|
||||
struct irq_data *d = irq_desc_get_irq_data(desc);
|
||||
|
||||
if (!irqd_affinity_is_managed(d))
|
||||
return IRQ_STARTUP_NORMAL;
|
||||
|
||||
irqd_clr_managed_shutdown(d);
|
||||
|
||||
if (cpumask_any_and(aff, cpu_online_mask) > nr_cpu_ids) {
|
||||
/*
|
||||
* Catch code which fiddles with enable_irq() on a managed
|
||||
* and potentially shutdown IRQ. Chained interrupt
|
||||
* installment or irq auto probing should not happen on
|
||||
* managed irqs either. Emit a warning, break the affinity
|
||||
* and start it up as a normal interrupt.
|
||||
*/
|
||||
if (WARN_ON_ONCE(force))
|
||||
return IRQ_STARTUP_NORMAL;
|
||||
/*
|
||||
* The interrupt was requested, but there is no online CPU
|
||||
* in it's affinity mask. Put it into managed shutdown
|
||||
* state and let the cpu hotplug mechanism start it up once
|
||||
* a CPU in the mask becomes available.
|
||||
*/
|
||||
irqd_set_managed_shutdown(d);
|
||||
return IRQ_STARTUP_ABORT;
|
||||
}
|
||||
return IRQ_STARTUP_MANAGED;
|
||||
}
|
||||
#else
|
||||
static __always_inline int
|
||||
__irq_startup_managed(struct irq_desc *desc, struct cpumask *aff, bool force)
|
||||
{
|
||||
return IRQ_STARTUP_NORMAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int __irq_startup(struct irq_desc *desc)
|
||||
{
|
||||
struct irq_data *d = irq_desc_get_irq_data(desc);
|
||||
int ret = 0;
|
||||
|
||||
irq_state_clr_disabled(desc);
|
||||
desc->depth = 0;
|
||||
|
||||
irq_domain_activate_irq(&desc->irq_data);
|
||||
if (desc->irq_data.chip->irq_startup) {
|
||||
ret = desc->irq_data.chip->irq_startup(&desc->irq_data);
|
||||
irq_domain_activate_irq(d);
|
||||
if (d->chip->irq_startup) {
|
||||
ret = d->chip->irq_startup(d);
|
||||
irq_state_clr_disabled(desc);
|
||||
irq_state_clr_masked(desc);
|
||||
} else {
|
||||
irq_enable(desc);
|
||||
}
|
||||
if (resend)
|
||||
check_irq_resend(desc);
|
||||
irq_state_set_started(desc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int irq_startup(struct irq_desc *desc, bool resend, bool force)
|
||||
{
|
||||
struct irq_data *d = irq_desc_get_irq_data(desc);
|
||||
struct cpumask *aff = irq_data_get_affinity_mask(d);
|
||||
int ret = 0;
|
||||
|
||||
desc->depth = 0;
|
||||
|
||||
if (irqd_is_started(d)) {
|
||||
irq_enable(desc);
|
||||
} else {
|
||||
switch (__irq_startup_managed(desc, aff, force)) {
|
||||
case IRQ_STARTUP_NORMAL:
|
||||
ret = __irq_startup(desc);
|
||||
irq_setup_affinity(desc);
|
||||
break;
|
||||
case IRQ_STARTUP_MANAGED:
|
||||
ret = __irq_startup(desc);
|
||||
irq_set_affinity_locked(d, aff, false);
|
||||
break;
|
||||
case IRQ_STARTUP_ABORT:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (resend)
|
||||
check_irq_resend(desc);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __irq_disable(struct irq_desc *desc, bool mask);
|
||||
|
||||
void irq_shutdown(struct irq_desc *desc)
|
||||
{
|
||||
irq_state_set_disabled(desc);
|
||||
desc->depth = 1;
|
||||
if (desc->irq_data.chip->irq_shutdown)
|
||||
desc->irq_data.chip->irq_shutdown(&desc->irq_data);
|
||||
else if (desc->irq_data.chip->irq_disable)
|
||||
desc->irq_data.chip->irq_disable(&desc->irq_data);
|
||||
else
|
||||
desc->irq_data.chip->irq_mask(&desc->irq_data);
|
||||
if (irqd_is_started(&desc->irq_data)) {
|
||||
desc->depth = 1;
|
||||
if (desc->irq_data.chip->irq_shutdown) {
|
||||
desc->irq_data.chip->irq_shutdown(&desc->irq_data);
|
||||
irq_state_set_disabled(desc);
|
||||
irq_state_set_masked(desc);
|
||||
} else {
|
||||
__irq_disable(desc, true);
|
||||
}
|
||||
irq_state_clr_started(desc);
|
||||
}
|
||||
/*
|
||||
* This must be called even if the interrupt was never started up,
|
||||
* because the activation can happen before the interrupt is
|
||||
* available for request/startup. It has it's own state tracking so
|
||||
* it's safe to call it unconditionally.
|
||||
*/
|
||||
irq_domain_deactivate_irq(&desc->irq_data);
|
||||
irq_state_set_masked(desc);
|
||||
}
|
||||
|
||||
void irq_enable(struct irq_desc *desc)
|
||||
{
|
||||
irq_state_clr_disabled(desc);
|
||||
if (desc->irq_data.chip->irq_enable)
|
||||
desc->irq_data.chip->irq_enable(&desc->irq_data);
|
||||
else
|
||||
desc->irq_data.chip->irq_unmask(&desc->irq_data);
|
||||
irq_state_clr_masked(desc);
|
||||
if (!irqd_irq_disabled(&desc->irq_data)) {
|
||||
unmask_irq(desc);
|
||||
} else {
|
||||
irq_state_clr_disabled(desc);
|
||||
if (desc->irq_data.chip->irq_enable) {
|
||||
desc->irq_data.chip->irq_enable(&desc->irq_data);
|
||||
irq_state_clr_masked(desc);
|
||||
} else {
|
||||
unmask_irq(desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void __irq_disable(struct irq_desc *desc, bool mask)
|
||||
{
|
||||
if (irqd_irq_disabled(&desc->irq_data)) {
|
||||
if (mask)
|
||||
mask_irq(desc);
|
||||
} else {
|
||||
irq_state_set_disabled(desc);
|
||||
if (desc->irq_data.chip->irq_disable) {
|
||||
desc->irq_data.chip->irq_disable(&desc->irq_data);
|
||||
irq_state_set_masked(desc);
|
||||
} else if (mask) {
|
||||
mask_irq(desc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -250,13 +355,7 @@ void irq_enable(struct irq_desc *desc)
|
||||
*/
|
||||
void irq_disable(struct irq_desc *desc)
|
||||
{
|
||||
irq_state_set_disabled(desc);
|
||||
if (desc->irq_data.chip->irq_disable) {
|
||||
desc->irq_data.chip->irq_disable(&desc->irq_data);
|
||||
irq_state_set_masked(desc);
|
||||
} else if (irq_settings_disable_unlazy(desc)) {
|
||||
mask_irq(desc);
|
||||
}
|
||||
__irq_disable(desc, irq_settings_disable_unlazy(desc));
|
||||
}
|
||||
|
||||
void irq_percpu_enable(struct irq_desc *desc, unsigned int cpu)
|
||||
@@ -279,18 +378,21 @@ void irq_percpu_disable(struct irq_desc *desc, unsigned int cpu)
|
||||
|
||||
static inline void mask_ack_irq(struct irq_desc *desc)
|
||||
{
|
||||
if (desc->irq_data.chip->irq_mask_ack)
|
||||
if (desc->irq_data.chip->irq_mask_ack) {
|
||||
desc->irq_data.chip->irq_mask_ack(&desc->irq_data);
|
||||
else {
|
||||
desc->irq_data.chip->irq_mask(&desc->irq_data);
|
||||
irq_state_set_masked(desc);
|
||||
} else {
|
||||
mask_irq(desc);
|
||||
if (desc->irq_data.chip->irq_ack)
|
||||
desc->irq_data.chip->irq_ack(&desc->irq_data);
|
||||
}
|
||||
irq_state_set_masked(desc);
|
||||
}
|
||||
|
||||
void mask_irq(struct irq_desc *desc)
|
||||
{
|
||||
if (irqd_irq_masked(&desc->irq_data))
|
||||
return;
|
||||
|
||||
if (desc->irq_data.chip->irq_mask) {
|
||||
desc->irq_data.chip->irq_mask(&desc->irq_data);
|
||||
irq_state_set_masked(desc);
|
||||
@@ -299,6 +401,9 @@ void mask_irq(struct irq_desc *desc)
|
||||
|
||||
void unmask_irq(struct irq_desc *desc)
|
||||
{
|
||||
if (!irqd_irq_masked(&desc->irq_data))
|
||||
return;
|
||||
|
||||
if (desc->irq_data.chip->irq_unmask) {
|
||||
desc->irq_data.chip->irq_unmask(&desc->irq_data);
|
||||
irq_state_clr_masked(desc);
|
||||
@@ -312,10 +417,7 @@ void unmask_threaded_irq(struct irq_desc *desc)
|
||||
if (chip->flags & IRQCHIP_EOI_THREADED)
|
||||
chip->irq_eoi(&desc->irq_data);
|
||||
|
||||
if (chip->irq_unmask) {
|
||||
chip->irq_unmask(&desc->irq_data);
|
||||
irq_state_clr_masked(desc);
|
||||
}
|
||||
unmask_irq(desc);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -851,7 +953,7 @@ __irq_do_set_handler(struct irq_desc *desc, irq_flow_handler_t handle,
|
||||
irq_settings_set_norequest(desc);
|
||||
irq_settings_set_nothread(desc);
|
||||
desc->action = &chained_action;
|
||||
irq_startup(desc, true);
|
||||
irq_startup(desc, IRQ_RESEND, IRQ_START_FORCE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -903,6 +1005,13 @@ void irq_modify_status(unsigned int irq, unsigned long clr, unsigned long set)
|
||||
|
||||
if (!desc)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Warn when a driver sets the no autoenable flag on an already
|
||||
* active interrupt.
|
||||
*/
|
||||
WARN_ON_ONCE(!desc->depth && (set & _IRQ_NOAUTOEN));
|
||||
|
||||
irq_settings_clr_and_set(desc, clr, set);
|
||||
|
||||
irqd_clear(&desc->irq_data, IRQD_NO_BALANCING | IRQD_PER_CPU |
|
||||
|
||||
+126
-24
@@ -14,37 +14,99 @@
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
/* For !GENERIC_IRQ_EFFECTIVE_AFF_MASK this looks at general affinity mask */
|
||||
static inline bool irq_needs_fixup(struct irq_data *d)
|
||||
{
|
||||
const struct cpumask *m = irq_data_get_effective_affinity_mask(d);
|
||||
|
||||
return cpumask_test_cpu(smp_processor_id(), m);
|
||||
}
|
||||
|
||||
static bool migrate_one_irq(struct irq_desc *desc)
|
||||
{
|
||||
struct irq_data *d = irq_desc_get_irq_data(desc);
|
||||
const struct cpumask *affinity = d->common->affinity;
|
||||
struct irq_chip *c;
|
||||
bool ret = false;
|
||||
struct irq_chip *chip = irq_data_get_irq_chip(d);
|
||||
bool maskchip = !irq_can_move_pcntxt(d) && !irqd_irq_masked(d);
|
||||
const struct cpumask *affinity;
|
||||
bool brokeaff = false;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* If this is a per-CPU interrupt, or the affinity does not
|
||||
* include this CPU, then we have nothing to do.
|
||||
* IRQ chip might be already torn down, but the irq descriptor is
|
||||
* still in the radix tree. Also if the chip has no affinity setter,
|
||||
* nothing can be done here.
|
||||
*/
|
||||
if (irqd_is_per_cpu(d) ||
|
||||
!cpumask_test_cpu(smp_processor_id(), affinity))
|
||||
if (!chip || !chip->irq_set_affinity) {
|
||||
pr_debug("IRQ %u: Unable to migrate away\n", d->irq);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* No move required, if:
|
||||
* - Interrupt is per cpu
|
||||
* - Interrupt is not started
|
||||
* - Affinity mask does not include this CPU.
|
||||
*
|
||||
* Note: Do not check desc->action as this might be a chained
|
||||
* interrupt.
|
||||
*/
|
||||
if (irqd_is_per_cpu(d) || !irqd_is_started(d) || !irq_needs_fixup(d)) {
|
||||
/*
|
||||
* If an irq move is pending, abort it if the dying CPU is
|
||||
* the sole target.
|
||||
*/
|
||||
irq_fixup_move_pending(desc, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Complete an eventually pending irq move cleanup. If this
|
||||
* interrupt was moved in hard irq context, then the vectors need
|
||||
* to be cleaned up. It can't wait until this interrupt actually
|
||||
* happens and this CPU was involved.
|
||||
*/
|
||||
irq_force_complete_move(desc);
|
||||
|
||||
/*
|
||||
* If there is a setaffinity pending, then try to reuse the pending
|
||||
* mask, so the last change of the affinity does not get lost. If
|
||||
* there is no move pending or the pending mask does not contain
|
||||
* any online CPU, use the current affinity mask.
|
||||
*/
|
||||
if (irq_fixup_move_pending(desc, true))
|
||||
affinity = irq_desc_get_pending_mask(desc);
|
||||
else
|
||||
affinity = irq_data_get_affinity_mask(d);
|
||||
|
||||
/* Mask the chip for interrupts which cannot move in process context */
|
||||
if (maskchip && chip->irq_mask)
|
||||
chip->irq_mask(d);
|
||||
|
||||
if (cpumask_any_and(affinity, cpu_online_mask) >= nr_cpu_ids) {
|
||||
/*
|
||||
* If the interrupt is managed, then shut it down and leave
|
||||
* the affinity untouched.
|
||||
*/
|
||||
if (irqd_affinity_is_managed(d)) {
|
||||
irqd_set_managed_shutdown(d);
|
||||
irq_shutdown(desc);
|
||||
return false;
|
||||
}
|
||||
affinity = cpu_online_mask;
|
||||
ret = true;
|
||||
brokeaff = true;
|
||||
}
|
||||
|
||||
c = irq_data_get_irq_chip(d);
|
||||
if (!c->irq_set_affinity) {
|
||||
pr_debug("IRQ%u: unable to set affinity\n", d->irq);
|
||||
} else {
|
||||
int r = irq_do_set_affinity(d, affinity, false);
|
||||
if (r)
|
||||
pr_warn_ratelimited("IRQ%u: set affinity failed(%d).\n",
|
||||
d->irq, r);
|
||||
err = irq_do_set_affinity(d, affinity, true);
|
||||
if (err) {
|
||||
pr_warn_ratelimited("IRQ%u: set affinity failed(%d).\n",
|
||||
d->irq, err);
|
||||
brokeaff = false;
|
||||
}
|
||||
|
||||
return ret;
|
||||
if (maskchip && chip->irq_unmask)
|
||||
chip->irq_unmask(d);
|
||||
|
||||
return brokeaff;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,11 +121,8 @@ static bool migrate_one_irq(struct irq_desc *desc)
|
||||
*/
|
||||
void irq_migrate_all_off_this_cpu(void)
|
||||
{
|
||||
unsigned int irq;
|
||||
struct irq_desc *desc;
|
||||
unsigned long flags;
|
||||
|
||||
local_irq_save(flags);
|
||||
unsigned int irq;
|
||||
|
||||
for_each_active_irq(irq) {
|
||||
bool affinity_broken;
|
||||
@@ -73,10 +132,53 @@ void irq_migrate_all_off_this_cpu(void)
|
||||
affinity_broken = migrate_one_irq(desc);
|
||||
raw_spin_unlock(&desc->lock);
|
||||
|
||||
if (affinity_broken)
|
||||
pr_warn_ratelimited("IRQ%u no longer affine to CPU%u\n",
|
||||
if (affinity_broken) {
|
||||
pr_warn_ratelimited("IRQ %u: no longer affine to CPU%u\n",
|
||||
irq, smp_processor_id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void irq_restore_affinity_of_irq(struct irq_desc *desc, unsigned int cpu)
|
||||
{
|
||||
struct irq_data *data = irq_desc_get_irq_data(desc);
|
||||
const struct cpumask *affinity = irq_data_get_affinity_mask(data);
|
||||
|
||||
if (!irqd_affinity_is_managed(data) || !desc->action ||
|
||||
!irq_data_get_irq_chip(data) || !cpumask_test_cpu(cpu, affinity))
|
||||
return;
|
||||
|
||||
if (irqd_is_managed_and_shutdown(data)) {
|
||||
irq_startup(desc, IRQ_RESEND, IRQ_START_COND);
|
||||
return;
|
||||
}
|
||||
|
||||
local_irq_restore(flags);
|
||||
/*
|
||||
* If the interrupt can only be directed to a single target
|
||||
* CPU then it is already assigned to a CPU in the affinity
|
||||
* mask. No point in trying to move it around.
|
||||
*/
|
||||
if (!irqd_is_single_target(data))
|
||||
irq_set_affinity_locked(data, affinity, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* irq_affinity_online_cpu - Restore affinity for managed interrupts
|
||||
* @cpu: Upcoming CPU for which interrupts should be restored
|
||||
*/
|
||||
int irq_affinity_online_cpu(unsigned int cpu)
|
||||
{
|
||||
struct irq_desc *desc;
|
||||
unsigned int irq;
|
||||
|
||||
irq_lock_sparse();
|
||||
for_each_active_irq(irq) {
|
||||
desc = irq_to_desc(irq);
|
||||
raw_spin_lock_irq(&desc->lock);
|
||||
irq_restore_affinity_of_irq(desc, cpu);
|
||||
raw_spin_unlock_irq(&desc->lock);
|
||||
}
|
||||
irq_unlock_sparse();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* Copyright 2017 Thomas Gleixner <tglx@linutronix.de>
|
||||
*
|
||||
* This file is licensed under the GPL V2.
|
||||
*/
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/irq.h>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
static struct dentry *irq_dir;
|
||||
|
||||
struct irq_bit_descr {
|
||||
unsigned int mask;
|
||||
char *name;
|
||||
};
|
||||
#define BIT_MASK_DESCR(m) { .mask = m, .name = #m }
|
||||
|
||||
static void irq_debug_show_bits(struct seq_file *m, int ind, unsigned int state,
|
||||
const struct irq_bit_descr *sd, int size)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < size; i++, sd++) {
|
||||
if (state & sd->mask)
|
||||
seq_printf(m, "%*s%s\n", ind + 12, "", sd->name);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
static void irq_debug_show_masks(struct seq_file *m, struct irq_desc *desc)
|
||||
{
|
||||
struct irq_data *data = irq_desc_get_irq_data(desc);
|
||||
struct cpumask *msk;
|
||||
|
||||
msk = irq_data_get_affinity_mask(data);
|
||||
seq_printf(m, "affinity: %*pbl\n", cpumask_pr_args(msk));
|
||||
#ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK
|
||||
msk = irq_data_get_effective_affinity_mask(data);
|
||||
seq_printf(m, "effectiv: %*pbl\n", cpumask_pr_args(msk));
|
||||
#endif
|
||||
#ifdef CONFIG_GENERIC_PENDING_IRQ
|
||||
msk = desc->pending_mask;
|
||||
seq_printf(m, "pending: %*pbl\n", cpumask_pr_args(msk));
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
static void irq_debug_show_masks(struct seq_file *m, struct irq_desc *desc) { }
|
||||
#endif
|
||||
|
||||
static const struct irq_bit_descr irqchip_flags[] = {
|
||||
BIT_MASK_DESCR(IRQCHIP_SET_TYPE_MASKED),
|
||||
BIT_MASK_DESCR(IRQCHIP_EOI_IF_HANDLED),
|
||||
BIT_MASK_DESCR(IRQCHIP_MASK_ON_SUSPEND),
|
||||
BIT_MASK_DESCR(IRQCHIP_ONOFFLINE_ENABLED),
|
||||
BIT_MASK_DESCR(IRQCHIP_SKIP_SET_WAKE),
|
||||
BIT_MASK_DESCR(IRQCHIP_ONESHOT_SAFE),
|
||||
BIT_MASK_DESCR(IRQCHIP_EOI_THREADED),
|
||||
};
|
||||
|
||||
static void
|
||||
irq_debug_show_chip(struct seq_file *m, struct irq_data *data, int ind)
|
||||
{
|
||||
struct irq_chip *chip = data->chip;
|
||||
|
||||
if (!chip) {
|
||||
seq_printf(m, "chip: None\n");
|
||||
return;
|
||||
}
|
||||
seq_printf(m, "%*schip: %s\n", ind, "", chip->name);
|
||||
seq_printf(m, "%*sflags: 0x%lx\n", ind + 1, "", chip->flags);
|
||||
irq_debug_show_bits(m, ind, chip->flags, irqchip_flags,
|
||||
ARRAY_SIZE(irqchip_flags));
|
||||
}
|
||||
|
||||
static void
|
||||
irq_debug_show_data(struct seq_file *m, struct irq_data *data, int ind)
|
||||
{
|
||||
seq_printf(m, "%*sdomain: %s\n", ind, "",
|
||||
data->domain ? data->domain->name : "");
|
||||
seq_printf(m, "%*shwirq: 0x%lx\n", ind + 1, "", data->hwirq);
|
||||
irq_debug_show_chip(m, data, ind + 1);
|
||||
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
|
||||
if (!data->parent_data)
|
||||
return;
|
||||
seq_printf(m, "%*sparent:\n", ind + 1, "");
|
||||
irq_debug_show_data(m, data->parent_data, ind + 4);
|
||||
#endif
|
||||
}
|
||||
|
||||
static const struct irq_bit_descr irqdata_states[] = {
|
||||
BIT_MASK_DESCR(IRQ_TYPE_EDGE_RISING),
|
||||
BIT_MASK_DESCR(IRQ_TYPE_EDGE_FALLING),
|
||||
BIT_MASK_DESCR(IRQ_TYPE_LEVEL_HIGH),
|
||||
BIT_MASK_DESCR(IRQ_TYPE_LEVEL_LOW),
|
||||
BIT_MASK_DESCR(IRQD_LEVEL),
|
||||
|
||||
BIT_MASK_DESCR(IRQD_ACTIVATED),
|
||||
BIT_MASK_DESCR(IRQD_IRQ_STARTED),
|
||||
BIT_MASK_DESCR(IRQD_IRQ_DISABLED),
|
||||
BIT_MASK_DESCR(IRQD_IRQ_MASKED),
|
||||
BIT_MASK_DESCR(IRQD_IRQ_INPROGRESS),
|
||||
|
||||
BIT_MASK_DESCR(IRQD_PER_CPU),
|
||||
BIT_MASK_DESCR(IRQD_NO_BALANCING),
|
||||
|
||||
BIT_MASK_DESCR(IRQD_SINGLE_TARGET),
|
||||
BIT_MASK_DESCR(IRQD_MOVE_PCNTXT),
|
||||
BIT_MASK_DESCR(IRQD_AFFINITY_SET),
|
||||
BIT_MASK_DESCR(IRQD_SETAFFINITY_PENDING),
|
||||
BIT_MASK_DESCR(IRQD_AFFINITY_MANAGED),
|
||||
BIT_MASK_DESCR(IRQD_MANAGED_SHUTDOWN),
|
||||
|
||||
BIT_MASK_DESCR(IRQD_FORWARDED_TO_VCPU),
|
||||
|
||||
BIT_MASK_DESCR(IRQD_WAKEUP_STATE),
|
||||
BIT_MASK_DESCR(IRQD_WAKEUP_ARMED),
|
||||
};
|
||||
|
||||
static const struct irq_bit_descr irqdesc_states[] = {
|
||||
BIT_MASK_DESCR(_IRQ_NOPROBE),
|
||||
BIT_MASK_DESCR(_IRQ_NOREQUEST),
|
||||
BIT_MASK_DESCR(_IRQ_NOTHREAD),
|
||||
BIT_MASK_DESCR(_IRQ_NOAUTOEN),
|
||||
BIT_MASK_DESCR(_IRQ_NESTED_THREAD),
|
||||
BIT_MASK_DESCR(_IRQ_PER_CPU_DEVID),
|
||||
BIT_MASK_DESCR(_IRQ_IS_POLLED),
|
||||
BIT_MASK_DESCR(_IRQ_DISABLE_UNLAZY),
|
||||
};
|
||||
|
||||
static const struct irq_bit_descr irqdesc_istates[] = {
|
||||
BIT_MASK_DESCR(IRQS_AUTODETECT),
|
||||
BIT_MASK_DESCR(IRQS_SPURIOUS_DISABLED),
|
||||
BIT_MASK_DESCR(IRQS_POLL_INPROGRESS),
|
||||
BIT_MASK_DESCR(IRQS_ONESHOT),
|
||||
BIT_MASK_DESCR(IRQS_REPLAY),
|
||||
BIT_MASK_DESCR(IRQS_WAITING),
|
||||
BIT_MASK_DESCR(IRQS_PENDING),
|
||||
BIT_MASK_DESCR(IRQS_SUSPENDED),
|
||||
};
|
||||
|
||||
|
||||
static int irq_debug_show(struct seq_file *m, void *p)
|
||||
{
|
||||
struct irq_desc *desc = m->private;
|
||||
struct irq_data *data;
|
||||
|
||||
raw_spin_lock_irq(&desc->lock);
|
||||
data = irq_desc_get_irq_data(desc);
|
||||
seq_printf(m, "handler: %pf\n", desc->handle_irq);
|
||||
seq_printf(m, "status: 0x%08x\n", desc->status_use_accessors);
|
||||
irq_debug_show_bits(m, 0, desc->status_use_accessors, irqdesc_states,
|
||||
ARRAY_SIZE(irqdesc_states));
|
||||
seq_printf(m, "istate: 0x%08x\n", desc->istate);
|
||||
irq_debug_show_bits(m, 0, desc->istate, irqdesc_istates,
|
||||
ARRAY_SIZE(irqdesc_istates));
|
||||
seq_printf(m, "ddepth: %u\n", desc->depth);
|
||||
seq_printf(m, "wdepth: %u\n", desc->wake_depth);
|
||||
seq_printf(m, "dstate: 0x%08x\n", irqd_get(data));
|
||||
irq_debug_show_bits(m, 0, irqd_get(data), irqdata_states,
|
||||
ARRAY_SIZE(irqdata_states));
|
||||
seq_printf(m, "node: %d\n", irq_data_get_node(data));
|
||||
irq_debug_show_masks(m, desc);
|
||||
irq_debug_show_data(m, data, 0);
|
||||
raw_spin_unlock_irq(&desc->lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int irq_debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, irq_debug_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations dfs_irq_ops = {
|
||||
.open = irq_debug_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
void irq_add_debugfs_entry(unsigned int irq, struct irq_desc *desc)
|
||||
{
|
||||
char name [10];
|
||||
|
||||
if (!irq_dir || !desc || desc->debugfs_file)
|
||||
return;
|
||||
|
||||
sprintf(name, "%d", irq);
|
||||
desc->debugfs_file = debugfs_create_file(name, 0444, irq_dir, desc,
|
||||
&dfs_irq_ops);
|
||||
}
|
||||
|
||||
static int __init irq_debugfs_init(void)
|
||||
{
|
||||
struct dentry *root_dir;
|
||||
int irq;
|
||||
|
||||
root_dir = debugfs_create_dir("irq", NULL);
|
||||
if (!root_dir)
|
||||
return -ENOMEM;
|
||||
|
||||
irq_domain_debugfs_init(root_dir);
|
||||
|
||||
irq_dir = debugfs_create_dir("irqs", root_dir);
|
||||
|
||||
irq_lock_sparse();
|
||||
for_each_active_irq(irq)
|
||||
irq_add_debugfs_entry(irq, irq_to_desc(irq));
|
||||
irq_unlock_sparse();
|
||||
|
||||
return 0;
|
||||
}
|
||||
__initcall(irq_debugfs_init);
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <linux/gfp.h>
|
||||
#include <linux/irq.h>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
/*
|
||||
* Device resource management aware IRQ request/free implementation.
|
||||
*/
|
||||
@@ -198,3 +200,87 @@ int __devm_irq_alloc_descs(struct device *dev, int irq, unsigned int from,
|
||||
return base;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__devm_irq_alloc_descs);
|
||||
|
||||
#ifdef CONFIG_GENERIC_IRQ_CHIP
|
||||
/**
|
||||
* devm_irq_alloc_generic_chip - Allocate and initialize a generic chip
|
||||
* for a managed device
|
||||
* @dev: Device to allocate the generic chip for
|
||||
* @name: Name of the irq chip
|
||||
* @num_ct: Number of irq_chip_type instances associated with this
|
||||
* @irq_base: Interrupt base nr for this chip
|
||||
* @reg_base: Register base address (virtual)
|
||||
* @handler: Default flow handler associated with this chip
|
||||
*
|
||||
* Returns an initialized irq_chip_generic structure. The chip defaults
|
||||
* to the primary (index 0) irq_chip_type and @handler
|
||||
*/
|
||||
struct irq_chip_generic *
|
||||
devm_irq_alloc_generic_chip(struct device *dev, const char *name, int num_ct,
|
||||
unsigned int irq_base, void __iomem *reg_base,
|
||||
irq_flow_handler_t handler)
|
||||
{
|
||||
struct irq_chip_generic *gc;
|
||||
unsigned long sz = sizeof(*gc) + num_ct * sizeof(struct irq_chip_type);
|
||||
|
||||
gc = devm_kzalloc(dev, sz, GFP_KERNEL);
|
||||
if (gc)
|
||||
irq_init_generic_chip(gc, name, num_ct,
|
||||
irq_base, reg_base, handler);
|
||||
|
||||
return gc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_irq_alloc_generic_chip);
|
||||
|
||||
struct irq_generic_chip_devres {
|
||||
struct irq_chip_generic *gc;
|
||||
u32 msk;
|
||||
unsigned int clr;
|
||||
unsigned int set;
|
||||
};
|
||||
|
||||
static void devm_irq_remove_generic_chip(struct device *dev, void *res)
|
||||
{
|
||||
struct irq_generic_chip_devres *this = res;
|
||||
|
||||
irq_remove_generic_chip(this->gc, this->msk, this->clr, this->set);
|
||||
}
|
||||
|
||||
/**
|
||||
* devm_irq_setup_generic_chip - Setup a range of interrupts with a generic
|
||||
* chip for a managed device
|
||||
*
|
||||
* @dev: Device to setup the generic chip for
|
||||
* @gc: Generic irq chip holding all data
|
||||
* @msk: Bitmask holding the irqs to initialize relative to gc->irq_base
|
||||
* @flags: Flags for initialization
|
||||
* @clr: IRQ_* bits to clear
|
||||
* @set: IRQ_* bits to set
|
||||
*
|
||||
* Set up max. 32 interrupts starting from gc->irq_base. Note, this
|
||||
* initializes all interrupts to the primary irq_chip_type and its
|
||||
* associated handler.
|
||||
*/
|
||||
int devm_irq_setup_generic_chip(struct device *dev, struct irq_chip_generic *gc,
|
||||
u32 msk, enum irq_gc_flags flags,
|
||||
unsigned int clr, unsigned int set)
|
||||
{
|
||||
struct irq_generic_chip_devres *dr;
|
||||
|
||||
dr = devres_alloc(devm_irq_remove_generic_chip,
|
||||
sizeof(*dr), GFP_KERNEL);
|
||||
if (!dr)
|
||||
return -ENOMEM;
|
||||
|
||||
irq_setup_generic_chip(gc, msk, flags, clr, set);
|
||||
|
||||
dr->gc = gc;
|
||||
dr->msk = msk;
|
||||
dr->clr = clr;
|
||||
dr->set = set;
|
||||
devres_add(dev, dr);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(devm_irq_setup_generic_chip);
|
||||
#endif /* CONFIG_GENERIC_IRQ_CHIP */
|
||||
|
||||
@@ -201,10 +201,9 @@ static void irq_writel_be(u32 val, void __iomem *addr)
|
||||
iowrite32be(val, addr);
|
||||
}
|
||||
|
||||
static void
|
||||
irq_init_generic_chip(struct irq_chip_generic *gc, const char *name,
|
||||
int num_ct, unsigned int irq_base,
|
||||
void __iomem *reg_base, irq_flow_handler_t handler)
|
||||
void irq_init_generic_chip(struct irq_chip_generic *gc, const char *name,
|
||||
int num_ct, unsigned int irq_base,
|
||||
void __iomem *reg_base, irq_flow_handler_t handler)
|
||||
{
|
||||
raw_spin_lock_init(&gc->lock);
|
||||
gc->num_ct = num_ct;
|
||||
|
||||
+3
-1
@@ -6,7 +6,7 @@
|
||||
*
|
||||
* This file contains the core interrupt handling code.
|
||||
*
|
||||
* Detailed information is available in Documentation/DocBook/genericirq
|
||||
* Detailed information is available in Documentation/core-api/genericirq.rst
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -138,6 +138,8 @@ irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags
|
||||
unsigned int irq = desc->irq_data.irq;
|
||||
struct irqaction *action;
|
||||
|
||||
record_irq_time(desc);
|
||||
|
||||
for_each_action_of_desc(desc, action) {
|
||||
irqreturn_t res;
|
||||
|
||||
|
||||
+235
-2
@@ -8,6 +8,7 @@
|
||||
#include <linux/irqdesc.h>
|
||||
#include <linux/kernel_stat.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/sched/clock.h>
|
||||
|
||||
#ifdef CONFIG_SPARSE_IRQ
|
||||
# define IRQ_BITMAP_BITS (NR_IRQS + 8196)
|
||||
@@ -57,6 +58,7 @@ enum {
|
||||
IRQS_WAITING = 0x00000080,
|
||||
IRQS_PENDING = 0x00000200,
|
||||
IRQS_SUSPENDED = 0x00000800,
|
||||
IRQS_TIMINGS = 0x00001000,
|
||||
};
|
||||
|
||||
#include "debug.h"
|
||||
@@ -66,7 +68,14 @@ extern int __irq_set_trigger(struct irq_desc *desc, unsigned long flags);
|
||||
extern void __disable_irq(struct irq_desc *desc);
|
||||
extern void __enable_irq(struct irq_desc *desc);
|
||||
|
||||
extern int irq_startup(struct irq_desc *desc, bool resend);
|
||||
#define IRQ_RESEND true
|
||||
#define IRQ_NORESEND false
|
||||
|
||||
#define IRQ_START_FORCE true
|
||||
#define IRQ_START_COND false
|
||||
|
||||
extern int irq_startup(struct irq_desc *desc, bool resend, bool force);
|
||||
|
||||
extern void irq_shutdown(struct irq_desc *desc);
|
||||
extern void irq_enable(struct irq_desc *desc);
|
||||
extern void irq_disable(struct irq_desc *desc);
|
||||
@@ -109,13 +118,19 @@ static inline void unregister_handler_proc(unsigned int irq,
|
||||
|
||||
extern bool irq_can_set_affinity_usr(unsigned int irq);
|
||||
|
||||
extern int irq_select_affinity_usr(unsigned int irq, struct cpumask *mask);
|
||||
extern int irq_select_affinity_usr(unsigned int irq);
|
||||
|
||||
extern void irq_set_thread_affinity(struct irq_desc *desc);
|
||||
|
||||
extern int irq_do_set_affinity(struct irq_data *data,
|
||||
const struct cpumask *dest, bool force);
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
extern int irq_setup_affinity(struct irq_desc *desc);
|
||||
#else
|
||||
static inline int irq_setup_affinity(struct irq_desc *desc) { return 0; }
|
||||
#endif
|
||||
|
||||
/* Inline functions for support of irq chips on slow busses */
|
||||
static inline void chip_bus_lock(struct irq_desc *desc)
|
||||
{
|
||||
@@ -169,6 +184,11 @@ irq_put_desc_unlock(struct irq_desc *desc, unsigned long flags)
|
||||
|
||||
#define __irqd_to_state(d) ACCESS_PRIVATE((d)->common, state_use_accessors)
|
||||
|
||||
static inline unsigned int irqd_get(struct irq_data *d)
|
||||
{
|
||||
return __irqd_to_state(d);
|
||||
}
|
||||
|
||||
/*
|
||||
* Manipulation functions for irq_data.state
|
||||
*/
|
||||
@@ -182,6 +202,16 @@ static inline void irqd_clr_move_pending(struct irq_data *d)
|
||||
__irqd_to_state(d) &= ~IRQD_SETAFFINITY_PENDING;
|
||||
}
|
||||
|
||||
static inline void irqd_set_managed_shutdown(struct irq_data *d)
|
||||
{
|
||||
__irqd_to_state(d) |= IRQD_MANAGED_SHUTDOWN;
|
||||
}
|
||||
|
||||
static inline void irqd_clr_managed_shutdown(struct irq_data *d)
|
||||
{
|
||||
__irqd_to_state(d) &= ~IRQD_MANAGED_SHUTDOWN;
|
||||
}
|
||||
|
||||
static inline void irqd_clear(struct irq_data *d, unsigned int mask)
|
||||
{
|
||||
__irqd_to_state(d) &= ~mask;
|
||||
@@ -197,6 +227,16 @@ static inline bool irqd_has_set(struct irq_data *d, unsigned int mask)
|
||||
return __irqd_to_state(d) & mask;
|
||||
}
|
||||
|
||||
static inline void irq_state_set_disabled(struct irq_desc *desc)
|
||||
{
|
||||
irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED);
|
||||
}
|
||||
|
||||
static inline void irq_state_set_masked(struct irq_desc *desc)
|
||||
{
|
||||
irqd_set(&desc->irq_data, IRQD_IRQ_MASKED);
|
||||
}
|
||||
|
||||
#undef __irqd_to_state
|
||||
|
||||
static inline void kstat_incr_irqs_this_cpu(struct irq_desc *desc)
|
||||
@@ -226,3 +266,196 @@ irq_pm_install_action(struct irq_desc *desc, struct irqaction *action) { }
|
||||
static inline void
|
||||
irq_pm_remove_action(struct irq_desc *desc, struct irqaction *action) { }
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_IRQ_TIMINGS
|
||||
|
||||
#define IRQ_TIMINGS_SHIFT 5
|
||||
#define IRQ_TIMINGS_SIZE (1 << IRQ_TIMINGS_SHIFT)
|
||||
#define IRQ_TIMINGS_MASK (IRQ_TIMINGS_SIZE - 1)
|
||||
|
||||
/**
|
||||
* struct irq_timings - irq timings storing structure
|
||||
* @values: a circular buffer of u64 encoded <timestamp,irq> values
|
||||
* @count: the number of elements in the array
|
||||
*/
|
||||
struct irq_timings {
|
||||
u64 values[IRQ_TIMINGS_SIZE];
|
||||
int count;
|
||||
};
|
||||
|
||||
DECLARE_PER_CPU(struct irq_timings, irq_timings);
|
||||
|
||||
extern void irq_timings_free(int irq);
|
||||
extern int irq_timings_alloc(int irq);
|
||||
|
||||
static inline void irq_remove_timings(struct irq_desc *desc)
|
||||
{
|
||||
desc->istate &= ~IRQS_TIMINGS;
|
||||
|
||||
irq_timings_free(irq_desc_get_irq(desc));
|
||||
}
|
||||
|
||||
static inline void irq_setup_timings(struct irq_desc *desc, struct irqaction *act)
|
||||
{
|
||||
int irq = irq_desc_get_irq(desc);
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* We don't need the measurement because the idle code already
|
||||
* knows the next expiry event.
|
||||
*/
|
||||
if (act->flags & __IRQF_TIMER)
|
||||
return;
|
||||
|
||||
/*
|
||||
* In case the timing allocation fails, we just want to warn,
|
||||
* not fail, so letting the system boot anyway.
|
||||
*/
|
||||
ret = irq_timings_alloc(irq);
|
||||
if (ret) {
|
||||
pr_warn("Failed to allocate irq timing stats for irq%d (%d)",
|
||||
irq, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
desc->istate |= IRQS_TIMINGS;
|
||||
}
|
||||
|
||||
extern void irq_timings_enable(void);
|
||||
extern void irq_timings_disable(void);
|
||||
|
||||
DECLARE_STATIC_KEY_FALSE(irq_timing_enabled);
|
||||
|
||||
/*
|
||||
* The interrupt number and the timestamp are encoded into a single
|
||||
* u64 variable to optimize the size.
|
||||
* 48 bit time stamp and 16 bit IRQ number is way sufficient.
|
||||
* Who cares an IRQ after 78 hours of idle time?
|
||||
*/
|
||||
static inline u64 irq_timing_encode(u64 timestamp, int irq)
|
||||
{
|
||||
return (timestamp << 16) | irq;
|
||||
}
|
||||
|
||||
static inline int irq_timing_decode(u64 value, u64 *timestamp)
|
||||
{
|
||||
*timestamp = value >> 16;
|
||||
return value & U16_MAX;
|
||||
}
|
||||
|
||||
/*
|
||||
* The function record_irq_time is only called in one place in the
|
||||
* interrupts handler. We want this function always inline so the code
|
||||
* inside is embedded in the function and the static key branching
|
||||
* code can act at the higher level. Without the explicit
|
||||
* __always_inline we can end up with a function call and a small
|
||||
* overhead in the hotpath for nothing.
|
||||
*/
|
||||
static __always_inline void record_irq_time(struct irq_desc *desc)
|
||||
{
|
||||
if (!static_branch_likely(&irq_timing_enabled))
|
||||
return;
|
||||
|
||||
if (desc->istate & IRQS_TIMINGS) {
|
||||
struct irq_timings *timings = this_cpu_ptr(&irq_timings);
|
||||
|
||||
timings->values[timings->count & IRQ_TIMINGS_MASK] =
|
||||
irq_timing_encode(local_clock(),
|
||||
irq_desc_get_irq(desc));
|
||||
|
||||
timings->count++;
|
||||
}
|
||||
}
|
||||
#else
|
||||
static inline void irq_remove_timings(struct irq_desc *desc) {}
|
||||
static inline void irq_setup_timings(struct irq_desc *desc,
|
||||
struct irqaction *act) {};
|
||||
static inline void record_irq_time(struct irq_desc *desc) {}
|
||||
#endif /* CONFIG_IRQ_TIMINGS */
|
||||
|
||||
|
||||
#ifdef CONFIG_GENERIC_IRQ_CHIP
|
||||
void irq_init_generic_chip(struct irq_chip_generic *gc, const char *name,
|
||||
int num_ct, unsigned int irq_base,
|
||||
void __iomem *reg_base, irq_flow_handler_t handler);
|
||||
#else
|
||||
static inline void
|
||||
irq_init_generic_chip(struct irq_chip_generic *gc, const char *name,
|
||||
int num_ct, unsigned int irq_base,
|
||||
void __iomem *reg_base, irq_flow_handler_t handler) { }
|
||||
#endif /* CONFIG_GENERIC_IRQ_CHIP */
|
||||
|
||||
#ifdef CONFIG_GENERIC_PENDING_IRQ
|
||||
static inline bool irq_can_move_pcntxt(struct irq_data *data)
|
||||
{
|
||||
return irqd_can_move_in_process_context(data);
|
||||
}
|
||||
static inline bool irq_move_pending(struct irq_data *data)
|
||||
{
|
||||
return irqd_is_setaffinity_pending(data);
|
||||
}
|
||||
static inline void
|
||||
irq_copy_pending(struct irq_desc *desc, const struct cpumask *mask)
|
||||
{
|
||||
cpumask_copy(desc->pending_mask, mask);
|
||||
}
|
||||
static inline void
|
||||
irq_get_pending(struct cpumask *mask, struct irq_desc *desc)
|
||||
{
|
||||
cpumask_copy(mask, desc->pending_mask);
|
||||
}
|
||||
static inline struct cpumask *irq_desc_get_pending_mask(struct irq_desc *desc)
|
||||
{
|
||||
return desc->pending_mask;
|
||||
}
|
||||
bool irq_fixup_move_pending(struct irq_desc *desc, bool force_clear);
|
||||
#else /* CONFIG_GENERIC_PENDING_IRQ */
|
||||
static inline bool irq_can_move_pcntxt(struct irq_data *data)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
static inline bool irq_move_pending(struct irq_data *data)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
static inline void
|
||||
irq_copy_pending(struct irq_desc *desc, const struct cpumask *mask)
|
||||
{
|
||||
}
|
||||
static inline void
|
||||
irq_get_pending(struct cpumask *mask, struct irq_desc *desc)
|
||||
{
|
||||
}
|
||||
static inline struct cpumask *irq_desc_get_pending_mask(struct irq_desc *desc)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
static inline bool irq_fixup_move_pending(struct irq_desc *desc, bool fclear)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif /* !CONFIG_GENERIC_PENDING_IRQ */
|
||||
|
||||
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
|
||||
#include <linux/debugfs.h>
|
||||
|
||||
void irq_add_debugfs_entry(unsigned int irq, struct irq_desc *desc);
|
||||
static inline void irq_remove_debugfs_entry(struct irq_desc *desc)
|
||||
{
|
||||
debugfs_remove(desc->debugfs_file);
|
||||
}
|
||||
# ifdef CONFIG_IRQ_DOMAIN
|
||||
void irq_domain_debugfs_init(struct dentry *root);
|
||||
# else
|
||||
static inline void irq_domain_debugfs_init(struct dentry *root)
|
||||
{
|
||||
}
|
||||
# endif
|
||||
#else /* CONFIG_GENERIC_IRQ_DEBUGFS */
|
||||
static inline void irq_add_debugfs_entry(unsigned int irq, struct irq_desc *d)
|
||||
{
|
||||
}
|
||||
static inline void irq_remove_debugfs_entry(struct irq_desc *d)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_GENERIC_IRQ_DEBUGFS */
|
||||
|
||||
+28
-11
@@ -4,7 +4,7 @@
|
||||
*
|
||||
* This file contains the interrupt descriptor management code
|
||||
*
|
||||
* Detailed information is available in Documentation/DocBook/genericirq
|
||||
* Detailed information is available in Documentation/core-api/genericirq.rst
|
||||
*
|
||||
*/
|
||||
#include <linux/irq.h>
|
||||
@@ -54,14 +54,25 @@ static void __init init_irq_default_affinity(void)
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
static int alloc_masks(struct irq_desc *desc, gfp_t gfp, int node)
|
||||
static int alloc_masks(struct irq_desc *desc, int node)
|
||||
{
|
||||
if (!zalloc_cpumask_var_node(&desc->irq_common_data.affinity,
|
||||
gfp, node))
|
||||
GFP_KERNEL, node))
|
||||
return -ENOMEM;
|
||||
|
||||
#ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK
|
||||
if (!zalloc_cpumask_var_node(&desc->irq_common_data.effective_affinity,
|
||||
GFP_KERNEL, node)) {
|
||||
free_cpumask_var(desc->irq_common_data.affinity);
|
||||
return -ENOMEM;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_GENERIC_PENDING_IRQ
|
||||
if (!zalloc_cpumask_var_node(&desc->pending_mask, gfp, node)) {
|
||||
if (!zalloc_cpumask_var_node(&desc->pending_mask, GFP_KERNEL, node)) {
|
||||
#ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK
|
||||
free_cpumask_var(desc->irq_common_data.effective_affinity);
|
||||
#endif
|
||||
free_cpumask_var(desc->irq_common_data.affinity);
|
||||
return -ENOMEM;
|
||||
}
|
||||
@@ -86,7 +97,7 @@ static void desc_smp_init(struct irq_desc *desc, int node,
|
||||
|
||||
#else
|
||||
static inline int
|
||||
alloc_masks(struct irq_desc *desc, gfp_t gfp, int node) { return 0; }
|
||||
alloc_masks(struct irq_desc *desc, int node) { return 0; }
|
||||
static inline void
|
||||
desc_smp_init(struct irq_desc *desc, int node, const struct cpumask *affinity) { }
|
||||
#endif
|
||||
@@ -105,6 +116,7 @@ static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node,
|
||||
desc->irq_data.chip_data = NULL;
|
||||
irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS);
|
||||
irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED);
|
||||
irqd_set(&desc->irq_data, IRQD_IRQ_MASKED);
|
||||
desc->handle_irq = handle_bad_irq;
|
||||
desc->depth = 1;
|
||||
desc->irq_count = 0;
|
||||
@@ -324,6 +336,9 @@ static void free_masks(struct irq_desc *desc)
|
||||
free_cpumask_var(desc->pending_mask);
|
||||
#endif
|
||||
free_cpumask_var(desc->irq_common_data.affinity);
|
||||
#ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK
|
||||
free_cpumask_var(desc->irq_common_data.effective_affinity);
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
static inline void free_masks(struct irq_desc *desc) { }
|
||||
@@ -344,9 +359,8 @@ static struct irq_desc *alloc_desc(int irq, int node, unsigned int flags,
|
||||
struct module *owner)
|
||||
{
|
||||
struct irq_desc *desc;
|
||||
gfp_t gfp = GFP_KERNEL;
|
||||
|
||||
desc = kzalloc_node(sizeof(*desc), gfp, node);
|
||||
desc = kzalloc_node(sizeof(*desc), GFP_KERNEL, node);
|
||||
if (!desc)
|
||||
return NULL;
|
||||
/* allocate based on nr_cpu_ids */
|
||||
@@ -354,11 +368,12 @@ static struct irq_desc *alloc_desc(int irq, int node, unsigned int flags,
|
||||
if (!desc->kstat_irqs)
|
||||
goto err_desc;
|
||||
|
||||
if (alloc_masks(desc, gfp, node))
|
||||
if (alloc_masks(desc, node))
|
||||
goto err_kstat;
|
||||
|
||||
raw_spin_lock_init(&desc->lock);
|
||||
lockdep_set_class(&desc->lock, &irq_desc_lock_class);
|
||||
mutex_init(&desc->request_mutex);
|
||||
init_rcu_head(&desc->rcu);
|
||||
|
||||
desc_set_defaults(irq, desc, node, affinity, owner);
|
||||
@@ -394,6 +409,7 @@ static void free_desc(unsigned int irq)
|
||||
{
|
||||
struct irq_desc *desc = irq_to_desc(irq);
|
||||
|
||||
irq_remove_debugfs_entry(desc);
|
||||
unregister_irq_proc(irq, desc);
|
||||
|
||||
/*
|
||||
@@ -480,7 +496,8 @@ int __init early_irq_init(void)
|
||||
|
||||
/* Let arch update nr_irqs and return the nr of preallocated irqs */
|
||||
initcnt = arch_probe_nr_irqs();
|
||||
printk(KERN_INFO "NR_IRQS:%d nr_irqs:%d %d\n", NR_IRQS, nr_irqs, initcnt);
|
||||
printk(KERN_INFO "NR_IRQS: %d, nr_irqs: %d, preallocated irqs: %d\n",
|
||||
NR_IRQS, nr_irqs, initcnt);
|
||||
|
||||
if (WARN_ON(nr_irqs > IRQ_BITMAP_BITS))
|
||||
nr_irqs = IRQ_BITMAP_BITS;
|
||||
@@ -516,14 +533,14 @@ int __init early_irq_init(void)
|
||||
|
||||
init_irq_default_affinity();
|
||||
|
||||
printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);
|
||||
printk(KERN_INFO "NR_IRQS: %d\n", NR_IRQS);
|
||||
|
||||
desc = irq_desc;
|
||||
count = ARRAY_SIZE(irq_desc);
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
desc[i].kstat_irqs = alloc_percpu(unsigned int);
|
||||
alloc_masks(&desc[i], GFP_KERNEL, node);
|
||||
alloc_masks(&desc[i], node);
|
||||
raw_spin_lock_init(&desc[i].lock);
|
||||
lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
|
||||
desc_set_defaults(i, &desc[i], node, NULL, NULL);
|
||||
|
||||
+294
-80
@@ -1,5 +1,6 @@
|
||||
#define pr_fmt(fmt) "irq: " fmt
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/debugfs.h>
|
||||
#include <linux/hardirq.h>
|
||||
#include <linux/interrupt.h>
|
||||
@@ -26,39 +27,69 @@ static struct irq_domain *irq_default_domain;
|
||||
static void irq_domain_check_hierarchy(struct irq_domain *domain);
|
||||
|
||||
struct irqchip_fwid {
|
||||
struct fwnode_handle fwnode;
|
||||
char *name;
|
||||
struct fwnode_handle fwnode;
|
||||
unsigned int type;
|
||||
char *name;
|
||||
void *data;
|
||||
};
|
||||
|
||||
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
|
||||
static void debugfs_add_domain_dir(struct irq_domain *d);
|
||||
static void debugfs_remove_domain_dir(struct irq_domain *d);
|
||||
#else
|
||||
static inline void debugfs_add_domain_dir(struct irq_domain *d) { }
|
||||
static inline void debugfs_remove_domain_dir(struct irq_domain *d) { }
|
||||
#endif
|
||||
|
||||
/**
|
||||
* irq_domain_alloc_fwnode - Allocate a fwnode_handle suitable for
|
||||
* identifying an irq domain
|
||||
* @data: optional user-provided data
|
||||
* @type: Type of irqchip_fwnode. See linux/irqdomain.h
|
||||
* @name: Optional user provided domain name
|
||||
* @id: Optional user provided id if name != NULL
|
||||
* @data: Optional user-provided data
|
||||
*
|
||||
* Allocate a struct device_node, and return a poiner to the embedded
|
||||
* Allocate a struct irqchip_fwid, and return a poiner to the embedded
|
||||
* fwnode_handle (or NULL on failure).
|
||||
*
|
||||
* Note: The types IRQCHIP_FWNODE_NAMED and IRQCHIP_FWNODE_NAMED_ID are
|
||||
* solely to transport name information to irqdomain creation code. The
|
||||
* node is not stored. For other types the pointer is kept in the irq
|
||||
* domain struct.
|
||||
*/
|
||||
struct fwnode_handle *irq_domain_alloc_fwnode(void *data)
|
||||
struct fwnode_handle *__irq_domain_alloc_fwnode(unsigned int type, int id,
|
||||
const char *name, void *data)
|
||||
{
|
||||
struct irqchip_fwid *fwid;
|
||||
char *name;
|
||||
char *n;
|
||||
|
||||
fwid = kzalloc(sizeof(*fwid), GFP_KERNEL);
|
||||
name = kasprintf(GFP_KERNEL, "irqchip@%p", data);
|
||||
|
||||
if (!fwid || !name) {
|
||||
switch (type) {
|
||||
case IRQCHIP_FWNODE_NAMED:
|
||||
n = kasprintf(GFP_KERNEL, "%s", name);
|
||||
break;
|
||||
case IRQCHIP_FWNODE_NAMED_ID:
|
||||
n = kasprintf(GFP_KERNEL, "%s-%d", name, id);
|
||||
break;
|
||||
default:
|
||||
n = kasprintf(GFP_KERNEL, "irqchip@%p", data);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!fwid || !n) {
|
||||
kfree(fwid);
|
||||
kfree(name);
|
||||
kfree(n);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
fwid->name = name;
|
||||
fwid->type = type;
|
||||
fwid->name = n;
|
||||
fwid->data = data;
|
||||
fwid->fwnode.type = FWNODE_IRQCHIP;
|
||||
return &fwid->fwnode;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(irq_domain_alloc_fwnode);
|
||||
EXPORT_SYMBOL_GPL(__irq_domain_alloc_fwnode);
|
||||
|
||||
/**
|
||||
* irq_domain_free_fwnode - Free a non-OF-backed fwnode_handle
|
||||
@@ -97,26 +128,97 @@ struct irq_domain *__irq_domain_add(struct fwnode_handle *fwnode, int size,
|
||||
void *host_data)
|
||||
{
|
||||
struct device_node *of_node = to_of_node(fwnode);
|
||||
struct irqchip_fwid *fwid;
|
||||
struct irq_domain *domain;
|
||||
|
||||
static atomic_t unknown_domains;
|
||||
|
||||
domain = kzalloc_node(sizeof(*domain) + (sizeof(unsigned int) * size),
|
||||
GFP_KERNEL, of_node_to_nid(of_node));
|
||||
if (WARN_ON(!domain))
|
||||
return NULL;
|
||||
|
||||
if (fwnode && is_fwnode_irqchip(fwnode)) {
|
||||
fwid = container_of(fwnode, struct irqchip_fwid, fwnode);
|
||||
|
||||
switch (fwid->type) {
|
||||
case IRQCHIP_FWNODE_NAMED:
|
||||
case IRQCHIP_FWNODE_NAMED_ID:
|
||||
domain->name = kstrdup(fwid->name, GFP_KERNEL);
|
||||
if (!domain->name) {
|
||||
kfree(domain);
|
||||
return NULL;
|
||||
}
|
||||
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
|
||||
break;
|
||||
default:
|
||||
domain->fwnode = fwnode;
|
||||
domain->name = fwid->name;
|
||||
break;
|
||||
}
|
||||
#ifdef CONFIG_ACPI
|
||||
} else if (is_acpi_device_node(fwnode)) {
|
||||
struct acpi_buffer buf = {
|
||||
.length = ACPI_ALLOCATE_BUFFER,
|
||||
};
|
||||
acpi_handle handle;
|
||||
|
||||
handle = acpi_device_handle(to_acpi_device_node(fwnode));
|
||||
if (acpi_get_name(handle, ACPI_FULL_PATHNAME, &buf) == AE_OK) {
|
||||
domain->name = buf.pointer;
|
||||
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
|
||||
}
|
||||
|
||||
domain->fwnode = fwnode;
|
||||
#endif
|
||||
} else if (of_node) {
|
||||
char *name;
|
||||
|
||||
/*
|
||||
* DT paths contain '/', which debugfs is legitimately
|
||||
* unhappy about. Replace them with ':', which does
|
||||
* the trick and is not as offensive as '\'...
|
||||
*/
|
||||
name = kstrdup(of_node_full_name(of_node), GFP_KERNEL);
|
||||
if (!name) {
|
||||
kfree(domain);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
strreplace(name, '/', ':');
|
||||
|
||||
domain->name = name;
|
||||
domain->fwnode = fwnode;
|
||||
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
|
||||
}
|
||||
|
||||
if (!domain->name) {
|
||||
if (fwnode) {
|
||||
pr_err("Invalid fwnode type (%d) for irqdomain\n",
|
||||
fwnode->type);
|
||||
}
|
||||
domain->name = kasprintf(GFP_KERNEL, "unknown-%d",
|
||||
atomic_inc_return(&unknown_domains));
|
||||
if (!domain->name) {
|
||||
kfree(domain);
|
||||
return NULL;
|
||||
}
|
||||
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
|
||||
}
|
||||
|
||||
of_node_get(of_node);
|
||||
|
||||
/* Fill structure */
|
||||
INIT_RADIX_TREE(&domain->revmap_tree, GFP_KERNEL);
|
||||
domain->ops = ops;
|
||||
domain->host_data = host_data;
|
||||
domain->fwnode = fwnode;
|
||||
domain->hwirq_max = hwirq_max;
|
||||
domain->revmap_size = size;
|
||||
domain->revmap_direct_max_irq = direct_max;
|
||||
irq_domain_check_hierarchy(domain);
|
||||
|
||||
mutex_lock(&irq_domain_mutex);
|
||||
debugfs_add_domain_dir(domain);
|
||||
list_add(&domain->link, &irq_domain_list);
|
||||
mutex_unlock(&irq_domain_mutex);
|
||||
|
||||
@@ -136,6 +238,7 @@ EXPORT_SYMBOL_GPL(__irq_domain_add);
|
||||
void irq_domain_remove(struct irq_domain *domain)
|
||||
{
|
||||
mutex_lock(&irq_domain_mutex);
|
||||
debugfs_remove_domain_dir(domain);
|
||||
|
||||
WARN_ON(!radix_tree_empty(&domain->revmap_tree));
|
||||
|
||||
@@ -152,10 +255,43 @@ void irq_domain_remove(struct irq_domain *domain)
|
||||
pr_debug("Removed domain %s\n", domain->name);
|
||||
|
||||
of_node_put(irq_domain_get_of_node(domain));
|
||||
if (domain->flags & IRQ_DOMAIN_NAME_ALLOCATED)
|
||||
kfree(domain->name);
|
||||
kfree(domain);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(irq_domain_remove);
|
||||
|
||||
void irq_domain_update_bus_token(struct irq_domain *domain,
|
||||
enum irq_domain_bus_token bus_token)
|
||||
{
|
||||
char *name;
|
||||
|
||||
if (domain->bus_token == bus_token)
|
||||
return;
|
||||
|
||||
mutex_lock(&irq_domain_mutex);
|
||||
|
||||
domain->bus_token = bus_token;
|
||||
|
||||
name = kasprintf(GFP_KERNEL, "%s-%d", domain->name, bus_token);
|
||||
if (!name) {
|
||||
mutex_unlock(&irq_domain_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
debugfs_remove_domain_dir(domain);
|
||||
|
||||
if (domain->flags & IRQ_DOMAIN_NAME_ALLOCATED)
|
||||
kfree(domain->name);
|
||||
else
|
||||
domain->flags |= IRQ_DOMAIN_NAME_ALLOCATED;
|
||||
|
||||
domain->name = name;
|
||||
debugfs_add_domain_dir(domain);
|
||||
|
||||
mutex_unlock(&irq_domain_mutex);
|
||||
}
|
||||
|
||||
/**
|
||||
* irq_domain_add_simple() - Register an irq_domain and optionally map a range of irqs
|
||||
* @of_node: pointer to interrupt controller's device tree node.
|
||||
@@ -344,6 +480,7 @@ void irq_domain_disassociate(struct irq_domain *domain, unsigned int irq)
|
||||
|
||||
irq_data->domain = NULL;
|
||||
irq_data->hwirq = 0;
|
||||
domain->mapcount--;
|
||||
|
||||
/* Clear reverse map for this hwirq */
|
||||
if (hwirq < domain->revmap_size) {
|
||||
@@ -395,6 +532,7 @@ int irq_domain_associate(struct irq_domain *domain, unsigned int virq,
|
||||
domain->name = irq_data->chip->name;
|
||||
}
|
||||
|
||||
domain->mapcount++;
|
||||
if (hwirq < domain->revmap_size) {
|
||||
domain->linear_revmap[hwirq] = virq;
|
||||
} else {
|
||||
@@ -746,13 +884,54 @@ unsigned int irq_find_mapping(struct irq_domain *domain,
|
||||
EXPORT_SYMBOL_GPL(irq_find_mapping);
|
||||
|
||||
#ifdef CONFIG_IRQ_DOMAIN_DEBUG
|
||||
static void virq_debug_show_one(struct seq_file *m, struct irq_desc *desc)
|
||||
{
|
||||
struct irq_domain *domain;
|
||||
struct irq_data *data;
|
||||
|
||||
domain = desc->irq_data.domain;
|
||||
data = &desc->irq_data;
|
||||
|
||||
while (domain) {
|
||||
unsigned int irq = data->irq;
|
||||
unsigned long hwirq = data->hwirq;
|
||||
struct irq_chip *chip;
|
||||
bool direct;
|
||||
|
||||
if (data == &desc->irq_data)
|
||||
seq_printf(m, "%5d ", irq);
|
||||
else
|
||||
seq_printf(m, "%5d+ ", irq);
|
||||
seq_printf(m, "0x%05lx ", hwirq);
|
||||
|
||||
chip = irq_data_get_irq_chip(data);
|
||||
seq_printf(m, "%-15s ", (chip && chip->name) ? chip->name : "none");
|
||||
|
||||
seq_printf(m, data ? "0x%p " : " %p ",
|
||||
irq_data_get_irq_chip_data(data));
|
||||
|
||||
seq_printf(m, " %c ", (desc->action && desc->action->handler) ? '*' : ' ');
|
||||
direct = (irq == hwirq) && (irq < domain->revmap_direct_max_irq);
|
||||
seq_printf(m, "%6s%-8s ",
|
||||
(hwirq < domain->revmap_size) ? "LINEAR" : "RADIX",
|
||||
direct ? "(DIRECT)" : "");
|
||||
seq_printf(m, "%s\n", domain->name);
|
||||
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
|
||||
domain = domain->parent;
|
||||
data = data->parent_data;
|
||||
#else
|
||||
domain = NULL;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static int virq_debug_show(struct seq_file *m, void *private)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct irq_desc *desc;
|
||||
struct irq_domain *domain;
|
||||
struct radix_tree_iter iter;
|
||||
void *data, **slot;
|
||||
void **slot;
|
||||
int i;
|
||||
|
||||
seq_printf(m, " %-16s %-6s %-10s %-10s %s\n",
|
||||
@@ -760,15 +939,26 @@ static int virq_debug_show(struct seq_file *m, void *private)
|
||||
mutex_lock(&irq_domain_mutex);
|
||||
list_for_each_entry(domain, &irq_domain_list, link) {
|
||||
struct device_node *of_node;
|
||||
const char *name;
|
||||
|
||||
int count = 0;
|
||||
|
||||
of_node = irq_domain_get_of_node(domain);
|
||||
if (of_node)
|
||||
name = of_node_full_name(of_node);
|
||||
else if (is_fwnode_irqchip(domain->fwnode))
|
||||
name = container_of(domain->fwnode, struct irqchip_fwid,
|
||||
fwnode)->name;
|
||||
else
|
||||
name = "";
|
||||
|
||||
radix_tree_for_each_slot(slot, &domain->revmap_tree, &iter, 0)
|
||||
count++;
|
||||
seq_printf(m, "%c%-16s %6u %10u %10u %s\n",
|
||||
domain == irq_default_domain ? '*' : ' ', domain->name,
|
||||
domain->revmap_size + count, domain->revmap_size,
|
||||
domain->revmap_direct_max_irq,
|
||||
of_node ? of_node_full_name(of_node) : "");
|
||||
name);
|
||||
}
|
||||
mutex_unlock(&irq_domain_mutex);
|
||||
|
||||
@@ -782,30 +972,7 @@ static int virq_debug_show(struct seq_file *m, void *private)
|
||||
continue;
|
||||
|
||||
raw_spin_lock_irqsave(&desc->lock, flags);
|
||||
domain = desc->irq_data.domain;
|
||||
|
||||
if (domain) {
|
||||
struct irq_chip *chip;
|
||||
int hwirq = desc->irq_data.hwirq;
|
||||
bool direct;
|
||||
|
||||
seq_printf(m, "%5d ", i);
|
||||
seq_printf(m, "0x%05x ", hwirq);
|
||||
|
||||
chip = irq_desc_get_chip(desc);
|
||||
seq_printf(m, "%-15s ", (chip && chip->name) ? chip->name : "none");
|
||||
|
||||
data = irq_desc_get_chip_data(desc);
|
||||
seq_printf(m, data ? "0x%p " : " %p ", data);
|
||||
|
||||
seq_printf(m, " %c ", (desc->action && desc->action->handler) ? '*' : ' ');
|
||||
direct = (i == hwirq) && (i < domain->revmap_direct_max_irq);
|
||||
seq_printf(m, "%6s%-8s ",
|
||||
(hwirq < domain->revmap_size) ? "LINEAR" : "RADIX",
|
||||
direct ? "(DIRECT)" : "");
|
||||
seq_printf(m, "%s\n", desc->irq_data.domain->name);
|
||||
}
|
||||
|
||||
virq_debug_show_one(m, desc);
|
||||
raw_spin_unlock_irqrestore(&desc->lock, flags);
|
||||
}
|
||||
|
||||
@@ -973,6 +1140,7 @@ static void irq_domain_insert_irq(int virq)
|
||||
struct irq_domain *domain = data->domain;
|
||||
irq_hw_number_t hwirq = data->hwirq;
|
||||
|
||||
domain->mapcount++;
|
||||
if (hwirq < domain->revmap_size) {
|
||||
domain->linear_revmap[hwirq] = virq;
|
||||
} else {
|
||||
@@ -1002,6 +1170,7 @@ static void irq_domain_remove_irq(int virq)
|
||||
struct irq_domain *domain = data->domain;
|
||||
irq_hw_number_t hwirq = data->hwirq;
|
||||
|
||||
domain->mapcount--;
|
||||
if (hwirq < domain->revmap_size) {
|
||||
domain->linear_revmap[hwirq] = 0;
|
||||
} else {
|
||||
@@ -1189,43 +1358,18 @@ void irq_domain_free_irqs_top(struct irq_domain *domain, unsigned int virq,
|
||||
irq_domain_free_irqs_common(domain, virq, nr_irqs);
|
||||
}
|
||||
|
||||
static bool irq_domain_is_auto_recursive(struct irq_domain *domain)
|
||||
{
|
||||
return domain->flags & IRQ_DOMAIN_FLAG_AUTO_RECURSIVE;
|
||||
}
|
||||
|
||||
static void irq_domain_free_irqs_recursive(struct irq_domain *domain,
|
||||
static void irq_domain_free_irqs_hierarchy(struct irq_domain *domain,
|
||||
unsigned int irq_base,
|
||||
unsigned int nr_irqs)
|
||||
{
|
||||
domain->ops->free(domain, irq_base, nr_irqs);
|
||||
if (irq_domain_is_auto_recursive(domain)) {
|
||||
BUG_ON(!domain->parent);
|
||||
irq_domain_free_irqs_recursive(domain->parent, irq_base,
|
||||
nr_irqs);
|
||||
}
|
||||
}
|
||||
|
||||
int irq_domain_alloc_irqs_recursive(struct irq_domain *domain,
|
||||
int irq_domain_alloc_irqs_hierarchy(struct irq_domain *domain,
|
||||
unsigned int irq_base,
|
||||
unsigned int nr_irqs, void *arg)
|
||||
{
|
||||
int ret = 0;
|
||||
struct irq_domain *parent = domain->parent;
|
||||
bool recursive = irq_domain_is_auto_recursive(domain);
|
||||
|
||||
BUG_ON(recursive && !parent);
|
||||
if (recursive)
|
||||
ret = irq_domain_alloc_irqs_recursive(parent, irq_base,
|
||||
nr_irqs, arg);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = domain->ops->alloc(domain, irq_base, nr_irqs, arg);
|
||||
if (ret < 0 && recursive)
|
||||
irq_domain_free_irqs_recursive(parent, irq_base, nr_irqs);
|
||||
|
||||
return ret;
|
||||
return domain->ops->alloc(domain, irq_base, nr_irqs, arg);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1286,7 +1430,7 @@ int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base,
|
||||
}
|
||||
|
||||
mutex_lock(&irq_domain_mutex);
|
||||
ret = irq_domain_alloc_irqs_recursive(domain, virq, nr_irqs, arg);
|
||||
ret = irq_domain_alloc_irqs_hierarchy(domain, virq, nr_irqs, arg);
|
||||
if (ret < 0) {
|
||||
mutex_unlock(&irq_domain_mutex);
|
||||
goto out_free_irq_data;
|
||||
@@ -1321,7 +1465,7 @@ void irq_domain_free_irqs(unsigned int virq, unsigned int nr_irqs)
|
||||
mutex_lock(&irq_domain_mutex);
|
||||
for (i = 0; i < nr_irqs; i++)
|
||||
irq_domain_remove_irq(virq + i);
|
||||
irq_domain_free_irqs_recursive(data->domain, virq, nr_irqs);
|
||||
irq_domain_free_irqs_hierarchy(data->domain, virq, nr_irqs);
|
||||
mutex_unlock(&irq_domain_mutex);
|
||||
|
||||
irq_domain_free_irq_data(virq, nr_irqs);
|
||||
@@ -1341,15 +1485,11 @@ int irq_domain_alloc_irqs_parent(struct irq_domain *domain,
|
||||
unsigned int irq_base, unsigned int nr_irqs,
|
||||
void *arg)
|
||||
{
|
||||
/* irq_domain_alloc_irqs_recursive() has called parent's alloc() */
|
||||
if (irq_domain_is_auto_recursive(domain))
|
||||
return 0;
|
||||
if (!domain->parent)
|
||||
return -ENOSYS;
|
||||
|
||||
domain = domain->parent;
|
||||
if (domain)
|
||||
return irq_domain_alloc_irqs_recursive(domain, irq_base,
|
||||
nr_irqs, arg);
|
||||
return -ENOSYS;
|
||||
return irq_domain_alloc_irqs_hierarchy(domain->parent, irq_base,
|
||||
nr_irqs, arg);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(irq_domain_alloc_irqs_parent);
|
||||
|
||||
@@ -1364,10 +1504,10 @@ EXPORT_SYMBOL_GPL(irq_domain_alloc_irqs_parent);
|
||||
void irq_domain_free_irqs_parent(struct irq_domain *domain,
|
||||
unsigned int irq_base, unsigned int nr_irqs)
|
||||
{
|
||||
/* irq_domain_free_irqs_recursive() will call parent's free */
|
||||
if (!irq_domain_is_auto_recursive(domain) && domain->parent)
|
||||
irq_domain_free_irqs_recursive(domain->parent, irq_base,
|
||||
nr_irqs);
|
||||
if (!domain->parent)
|
||||
return;
|
||||
|
||||
irq_domain_free_irqs_hierarchy(domain->parent, irq_base, nr_irqs);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(irq_domain_free_irqs_parent);
|
||||
|
||||
@@ -1487,3 +1627,77 @@ static void irq_domain_check_hierarchy(struct irq_domain *domain)
|
||||
{
|
||||
}
|
||||
#endif /* CONFIG_IRQ_DOMAIN_HIERARCHY */
|
||||
|
||||
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
|
||||
static struct dentry *domain_dir;
|
||||
|
||||
static void
|
||||
irq_domain_debug_show_one(struct seq_file *m, struct irq_domain *d, int ind)
|
||||
{
|
||||
seq_printf(m, "%*sname: %s\n", ind, "", d->name);
|
||||
seq_printf(m, "%*ssize: %u\n", ind + 1, "",
|
||||
d->revmap_size + d->revmap_direct_max_irq);
|
||||
seq_printf(m, "%*smapped: %u\n", ind + 1, "", d->mapcount);
|
||||
seq_printf(m, "%*sflags: 0x%08x\n", ind +1 , "", d->flags);
|
||||
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY
|
||||
if (!d->parent)
|
||||
return;
|
||||
seq_printf(m, "%*sparent: %s\n", ind + 1, "", d->parent->name);
|
||||
irq_domain_debug_show_one(m, d->parent, ind + 4);
|
||||
#endif
|
||||
}
|
||||
|
||||
static int irq_domain_debug_show(struct seq_file *m, void *p)
|
||||
{
|
||||
struct irq_domain *d = m->private;
|
||||
|
||||
/* Default domain? Might be NULL */
|
||||
if (!d) {
|
||||
if (!irq_default_domain)
|
||||
return 0;
|
||||
d = irq_default_domain;
|
||||
}
|
||||
irq_domain_debug_show_one(m, d, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int irq_domain_debug_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, irq_domain_debug_show, inode->i_private);
|
||||
}
|
||||
|
||||
static const struct file_operations dfs_domain_ops = {
|
||||
.open = irq_domain_debug_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static void debugfs_add_domain_dir(struct irq_domain *d)
|
||||
{
|
||||
if (!d->name || !domain_dir || d->debugfs_file)
|
||||
return;
|
||||
d->debugfs_file = debugfs_create_file(d->name, 0444, domain_dir, d,
|
||||
&dfs_domain_ops);
|
||||
}
|
||||
|
||||
static void debugfs_remove_domain_dir(struct irq_domain *d)
|
||||
{
|
||||
debugfs_remove(d->debugfs_file);
|
||||
}
|
||||
|
||||
void __init irq_domain_debugfs_init(struct dentry *root)
|
||||
{
|
||||
struct irq_domain *d;
|
||||
|
||||
domain_dir = debugfs_create_dir("domains", root);
|
||||
if (!domain_dir)
|
||||
return;
|
||||
|
||||
debugfs_create_file("default", 0444, domain_dir, NULL, &dfs_domain_ops);
|
||||
mutex_lock(&irq_domain_mutex);
|
||||
list_for_each_entry(d, &irq_domain_list, link)
|
||||
debugfs_add_domain_dir(d);
|
||||
mutex_unlock(&irq_domain_mutex);
|
||||
}
|
||||
#endif
|
||||
|
||||
+130
-92
@@ -168,34 +168,6 @@ void irq_set_thread_affinity(struct irq_desc *desc)
|
||||
set_bit(IRQTF_AFFINITY, &action->thread_flags);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_GENERIC_PENDING_IRQ
|
||||
static inline bool irq_can_move_pcntxt(struct irq_data *data)
|
||||
{
|
||||
return irqd_can_move_in_process_context(data);
|
||||
}
|
||||
static inline bool irq_move_pending(struct irq_data *data)
|
||||
{
|
||||
return irqd_is_setaffinity_pending(data);
|
||||
}
|
||||
static inline void
|
||||
irq_copy_pending(struct irq_desc *desc, const struct cpumask *mask)
|
||||
{
|
||||
cpumask_copy(desc->pending_mask, mask);
|
||||
}
|
||||
static inline void
|
||||
irq_get_pending(struct cpumask *mask, struct irq_desc *desc)
|
||||
{
|
||||
cpumask_copy(mask, desc->pending_mask);
|
||||
}
|
||||
#else
|
||||
static inline bool irq_can_move_pcntxt(struct irq_data *data) { return true; }
|
||||
static inline bool irq_move_pending(struct irq_data *data) { return false; }
|
||||
static inline void
|
||||
irq_copy_pending(struct irq_desc *desc, const struct cpumask *mask) { }
|
||||
static inline void
|
||||
irq_get_pending(struct cpumask *mask, struct irq_desc *desc) { }
|
||||
#endif
|
||||
|
||||
int irq_do_set_affinity(struct irq_data *data, const struct cpumask *mask,
|
||||
bool force)
|
||||
{
|
||||
@@ -345,15 +317,18 @@ EXPORT_SYMBOL_GPL(irq_set_affinity_notifier);
|
||||
/*
|
||||
* Generic version of the affinity autoselector.
|
||||
*/
|
||||
static int setup_affinity(struct irq_desc *desc, struct cpumask *mask)
|
||||
int irq_setup_affinity(struct irq_desc *desc)
|
||||
{
|
||||
struct cpumask *set = irq_default_affinity;
|
||||
int node = irq_desc_get_node(desc);
|
||||
int ret, node = irq_desc_get_node(desc);
|
||||
static DEFINE_RAW_SPINLOCK(mask_lock);
|
||||
static struct cpumask mask;
|
||||
|
||||
/* Excludes PER_CPU and NO_BALANCE interrupts */
|
||||
if (!__irq_can_set_affinity(desc))
|
||||
return 0;
|
||||
|
||||
raw_spin_lock(&mask_lock);
|
||||
/*
|
||||
* Preserve the managed affinity setting and a userspace affinity
|
||||
* setup, but make sure that one of the targets is online.
|
||||
@@ -367,46 +342,40 @@ static int setup_affinity(struct irq_desc *desc, struct cpumask *mask)
|
||||
irqd_clear(&desc->irq_data, IRQD_AFFINITY_SET);
|
||||
}
|
||||
|
||||
cpumask_and(mask, cpu_online_mask, set);
|
||||
cpumask_and(&mask, cpu_online_mask, set);
|
||||
if (node != NUMA_NO_NODE) {
|
||||
const struct cpumask *nodemask = cpumask_of_node(node);
|
||||
|
||||
/* make sure at least one of the cpus in nodemask is online */
|
||||
if (cpumask_intersects(mask, nodemask))
|
||||
cpumask_and(mask, mask, nodemask);
|
||||
if (cpumask_intersects(&mask, nodemask))
|
||||
cpumask_and(&mask, &mask, nodemask);
|
||||
}
|
||||
irq_do_set_affinity(&desc->irq_data, mask, false);
|
||||
return 0;
|
||||
ret = irq_do_set_affinity(&desc->irq_data, &mask, false);
|
||||
raw_spin_unlock(&mask_lock);
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
/* Wrapper for ALPHA specific affinity selector magic */
|
||||
static inline int setup_affinity(struct irq_desc *d, struct cpumask *mask)
|
||||
int irq_setup_affinity(struct irq_desc *desc)
|
||||
{
|
||||
return irq_select_affinity(irq_desc_get_irq(d));
|
||||
return irq_select_affinity(irq_desc_get_irq(desc));
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Called when affinity is set via /proc/irq
|
||||
* Called when a bogus affinity is set via /proc/irq
|
||||
*/
|
||||
int irq_select_affinity_usr(unsigned int irq, struct cpumask *mask)
|
||||
int irq_select_affinity_usr(unsigned int irq)
|
||||
{
|
||||
struct irq_desc *desc = irq_to_desc(irq);
|
||||
unsigned long flags;
|
||||
int ret;
|
||||
|
||||
raw_spin_lock_irqsave(&desc->lock, flags);
|
||||
ret = setup_affinity(desc, mask);
|
||||
ret = irq_setup_affinity(desc);
|
||||
raw_spin_unlock_irqrestore(&desc->lock, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#else
|
||||
static inline int
|
||||
setup_affinity(struct irq_desc *desc, struct cpumask *mask)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -533,9 +502,15 @@ void __enable_irq(struct irq_desc *desc)
|
||||
goto err_out;
|
||||
/* Prevent probing on this irq: */
|
||||
irq_settings_set_noprobe(desc);
|
||||
irq_enable(desc);
|
||||
check_irq_resend(desc);
|
||||
/* fall-through */
|
||||
/*
|
||||
* Call irq_startup() not irq_enable() here because the
|
||||
* interrupt might be marked NOAUTOEN. So irq_startup()
|
||||
* needs to be invoked when it gets enabled the first
|
||||
* time. If it was already started up, then irq_startup()
|
||||
* will invoke irq_enable() under the hood.
|
||||
*/
|
||||
irq_startup(desc, IRQ_RESEND, IRQ_START_COND);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
desc->depth--;
|
||||
@@ -1115,6 +1090,16 @@ setup_irq_thread(struct irqaction *new, unsigned int irq, bool secondary)
|
||||
/*
|
||||
* Internal function to register an irqaction - typically used to
|
||||
* allocate special interrupts that are part of the architecture.
|
||||
*
|
||||
* Locking rules:
|
||||
*
|
||||
* desc->request_mutex Provides serialization against a concurrent free_irq()
|
||||
* chip_bus_lock Provides serialization for slow bus operations
|
||||
* desc->lock Provides serialization against hard interrupts
|
||||
*
|
||||
* chip_bus_lock and desc->lock are sufficient for all other management and
|
||||
* interrupt related functions. desc->request_mutex solely serializes
|
||||
* request/free_irq().
|
||||
*/
|
||||
static int
|
||||
__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
|
||||
@@ -1122,7 +1107,6 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
|
||||
struct irqaction *old, **old_ptr;
|
||||
unsigned long flags, thread_mask = 0;
|
||||
int ret, nested, shared = 0;
|
||||
cpumask_var_t mask;
|
||||
|
||||
if (!desc)
|
||||
return -EINVAL;
|
||||
@@ -1181,11 +1165,6 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
|
||||
}
|
||||
}
|
||||
|
||||
if (!alloc_cpumask_var(&mask, GFP_KERNEL)) {
|
||||
ret = -ENOMEM;
|
||||
goto out_thread;
|
||||
}
|
||||
|
||||
/*
|
||||
* Drivers are often written to work w/o knowledge about the
|
||||
* underlying irq chip implementation, so a request for a
|
||||
@@ -1198,8 +1177,35 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
|
||||
if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)
|
||||
new->flags &= ~IRQF_ONESHOT;
|
||||
|
||||
/*
|
||||
* Protects against a concurrent __free_irq() call which might wait
|
||||
* for synchronize_irq() to complete without holding the optional
|
||||
* chip bus lock and desc->lock.
|
||||
*/
|
||||
mutex_lock(&desc->request_mutex);
|
||||
|
||||
/*
|
||||
* Acquire bus lock as the irq_request_resources() callback below
|
||||
* might rely on the serialization or the magic power management
|
||||
* functions which are abusing the irq_bus_lock() callback,
|
||||
*/
|
||||
chip_bus_lock(desc);
|
||||
|
||||
/* First installed action requests resources. */
|
||||
if (!desc->action) {
|
||||
ret = irq_request_resources(desc);
|
||||
if (ret) {
|
||||
pr_err("Failed to request resources for %s (irq %d) on irqchip %s\n",
|
||||
new->name, irq, desc->irq_data.chip->name);
|
||||
goto out_bus_unlock;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The following block of code has to be executed atomically
|
||||
* protected against a concurrent interrupt and any of the other
|
||||
* management calls which are not serialized via
|
||||
* desc->request_mutex or the optional bus lock.
|
||||
*/
|
||||
raw_spin_lock_irqsave(&desc->lock, flags);
|
||||
old_ptr = &desc->action;
|
||||
@@ -1250,7 +1256,7 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
|
||||
*/
|
||||
if (thread_mask == ~0UL) {
|
||||
ret = -EBUSY;
|
||||
goto out_mask;
|
||||
goto out_unlock;
|
||||
}
|
||||
/*
|
||||
* The thread_mask for the action is or'ed to
|
||||
@@ -1294,17 +1300,10 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
|
||||
pr_err("Threaded irq requested with handler=NULL and !ONESHOT for irq %d\n",
|
||||
irq);
|
||||
ret = -EINVAL;
|
||||
goto out_mask;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
if (!shared) {
|
||||
ret = irq_request_resources(desc);
|
||||
if (ret) {
|
||||
pr_err("Failed to request resources for %s (irq %d) on irqchip %s\n",
|
||||
new->name, irq, desc->irq_data.chip->name);
|
||||
goto out_mask;
|
||||
}
|
||||
|
||||
init_waitqueue_head(&desc->wait_for_threads);
|
||||
|
||||
/* Setup the type (level, edge polarity) if configured: */
|
||||
@@ -1313,7 +1312,7 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
|
||||
new->flags & IRQF_TRIGGER_MASK);
|
||||
|
||||
if (ret)
|
||||
goto out_mask;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \
|
||||
@@ -1328,20 +1327,25 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
|
||||
if (new->flags & IRQF_ONESHOT)
|
||||
desc->istate |= IRQS_ONESHOT;
|
||||
|
||||
if (irq_settings_can_autoenable(desc))
|
||||
irq_startup(desc, true);
|
||||
else
|
||||
/* Undo nested disables: */
|
||||
desc->depth = 1;
|
||||
|
||||
/* Exclude IRQ from balancing if requested */
|
||||
if (new->flags & IRQF_NOBALANCING) {
|
||||
irq_settings_set_no_balancing(desc);
|
||||
irqd_set(&desc->irq_data, IRQD_NO_BALANCING);
|
||||
}
|
||||
|
||||
/* Set default affinity mask once everything is setup */
|
||||
setup_affinity(desc, mask);
|
||||
if (irq_settings_can_autoenable(desc)) {
|
||||
irq_startup(desc, IRQ_RESEND, IRQ_START_COND);
|
||||
} else {
|
||||
/*
|
||||
* Shared interrupts do not go well with disabling
|
||||
* auto enable. The sharing interrupt might request
|
||||
* it while it's still disabled and then wait for
|
||||
* interrupts forever.
|
||||
*/
|
||||
WARN_ON_ONCE(new->flags & IRQF_SHARED);
|
||||
/* Undo nested disables: */
|
||||
desc->depth = 1;
|
||||
}
|
||||
|
||||
} else if (new->flags & IRQF_TRIGGER_MASK) {
|
||||
unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK;
|
||||
@@ -1371,6 +1375,10 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
|
||||
}
|
||||
|
||||
raw_spin_unlock_irqrestore(&desc->lock, flags);
|
||||
chip_bus_sync_unlock(desc);
|
||||
mutex_unlock(&desc->request_mutex);
|
||||
|
||||
irq_setup_timings(desc, new);
|
||||
|
||||
/*
|
||||
* Strictly no need to wake it up, but hung_task complains
|
||||
@@ -1382,10 +1390,9 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
|
||||
wake_up_process(new->secondary->thread);
|
||||
|
||||
register_irq_proc(irq, desc);
|
||||
irq_add_debugfs_entry(irq, desc);
|
||||
new->dir = NULL;
|
||||
register_handler_proc(irq, new);
|
||||
free_cpumask_var(mask);
|
||||
|
||||
return 0;
|
||||
|
||||
mismatch:
|
||||
@@ -1398,9 +1405,14 @@ mismatch:
|
||||
}
|
||||
ret = -EBUSY;
|
||||
|
||||
out_mask:
|
||||
out_unlock:
|
||||
raw_spin_unlock_irqrestore(&desc->lock, flags);
|
||||
free_cpumask_var(mask);
|
||||
|
||||
if (!desc->action)
|
||||
irq_release_resources(desc);
|
||||
out_bus_unlock:
|
||||
chip_bus_sync_unlock(desc);
|
||||
mutex_unlock(&desc->request_mutex);
|
||||
|
||||
out_thread:
|
||||
if (new->thread) {
|
||||
@@ -1441,9 +1453,7 @@ int setup_irq(unsigned int irq, struct irqaction *act)
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
chip_bus_lock(desc);
|
||||
retval = __setup_irq(irq, desc, act);
|
||||
chip_bus_sync_unlock(desc);
|
||||
|
||||
if (retval)
|
||||
irq_chip_pm_put(&desc->irq_data);
|
||||
@@ -1467,6 +1477,7 @@ static struct irqaction *__free_irq(unsigned int irq, void *dev_id)
|
||||
if (!desc)
|
||||
return NULL;
|
||||
|
||||
mutex_lock(&desc->request_mutex);
|
||||
chip_bus_lock(desc);
|
||||
raw_spin_lock_irqsave(&desc->lock, flags);
|
||||
|
||||
@@ -1482,6 +1493,7 @@ static struct irqaction *__free_irq(unsigned int irq, void *dev_id)
|
||||
WARN(1, "Trying to free already-free IRQ %d\n", irq);
|
||||
raw_spin_unlock_irqrestore(&desc->lock, flags);
|
||||
chip_bus_sync_unlock(desc);
|
||||
mutex_unlock(&desc->request_mutex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -1499,7 +1511,6 @@ static struct irqaction *__free_irq(unsigned int irq, void *dev_id)
|
||||
if (!desc->action) {
|
||||
irq_settings_clr_disable_unlazy(desc);
|
||||
irq_shutdown(desc);
|
||||
irq_release_resources(desc);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
@@ -1509,6 +1520,20 @@ static struct irqaction *__free_irq(unsigned int irq, void *dev_id)
|
||||
#endif
|
||||
|
||||
raw_spin_unlock_irqrestore(&desc->lock, flags);
|
||||
/*
|
||||
* Drop bus_lock here so the changes which were done in the chip
|
||||
* callbacks above are synced out to the irq chips which hang
|
||||
* behind a slow bus (I2C, SPI) before calling synchronize_irq().
|
||||
*
|
||||
* Aside of that the bus_lock can also be taken from the threaded
|
||||
* handler in irq_finalize_oneshot() which results in a deadlock
|
||||
* because synchronize_irq() would wait forever for the thread to
|
||||
* complete, which is blocked on the bus lock.
|
||||
*
|
||||
* The still held desc->request_mutex() protects against a
|
||||
* concurrent request_irq() of this irq so the release of resources
|
||||
* and timing data is properly serialized.
|
||||
*/
|
||||
chip_bus_sync_unlock(desc);
|
||||
|
||||
unregister_handler_proc(irq, action);
|
||||
@@ -1541,6 +1566,20 @@ static struct irqaction *__free_irq(unsigned int irq, void *dev_id)
|
||||
}
|
||||
}
|
||||
|
||||
/* Last action releases resources */
|
||||
if (!desc->action) {
|
||||
/*
|
||||
* Reaquire bus lock as irq_release_resources() might
|
||||
* require it to deallocate resources over the slow bus.
|
||||
*/
|
||||
chip_bus_lock(desc);
|
||||
irq_release_resources(desc);
|
||||
chip_bus_sync_unlock(desc);
|
||||
irq_remove_timings(desc);
|
||||
}
|
||||
|
||||
mutex_unlock(&desc->request_mutex);
|
||||
|
||||
irq_chip_pm_put(&desc->irq_data);
|
||||
module_put(desc->owner);
|
||||
kfree(action->secondary);
|
||||
@@ -1697,9 +1736,7 @@ int request_threaded_irq(unsigned int irq, irq_handler_t handler,
|
||||
return retval;
|
||||
}
|
||||
|
||||
chip_bus_lock(desc);
|
||||
retval = __setup_irq(irq, desc, action);
|
||||
chip_bus_sync_unlock(desc);
|
||||
|
||||
if (retval) {
|
||||
irq_chip_pm_put(&desc->irq_data);
|
||||
@@ -1947,9 +1984,7 @@ int setup_percpu_irq(unsigned int irq, struct irqaction *act)
|
||||
if (retval < 0)
|
||||
return retval;
|
||||
|
||||
chip_bus_lock(desc);
|
||||
retval = __setup_irq(irq, desc, act);
|
||||
chip_bus_sync_unlock(desc);
|
||||
|
||||
if (retval)
|
||||
irq_chip_pm_put(&desc->irq_data);
|
||||
@@ -1958,9 +1993,10 @@ int setup_percpu_irq(unsigned int irq, struct irqaction *act)
|
||||
}
|
||||
|
||||
/**
|
||||
* request_percpu_irq - allocate a percpu interrupt line
|
||||
* __request_percpu_irq - allocate a percpu interrupt line
|
||||
* @irq: Interrupt line to allocate
|
||||
* @handler: Function to be called when the IRQ occurs.
|
||||
* @flags: Interrupt type flags (IRQF_TIMER only)
|
||||
* @devname: An ascii name for the claiming device
|
||||
* @dev_id: A percpu cookie passed back to the handler function
|
||||
*
|
||||
@@ -1973,8 +2009,9 @@ int setup_percpu_irq(unsigned int irq, struct irqaction *act)
|
||||
* the handler gets called with the interrupted CPU's instance of
|
||||
* that variable.
|
||||
*/
|
||||
int request_percpu_irq(unsigned int irq, irq_handler_t handler,
|
||||
const char *devname, void __percpu *dev_id)
|
||||
int __request_percpu_irq(unsigned int irq, irq_handler_t handler,
|
||||
unsigned long flags, const char *devname,
|
||||
void __percpu *dev_id)
|
||||
{
|
||||
struct irqaction *action;
|
||||
struct irq_desc *desc;
|
||||
@@ -1988,12 +2025,15 @@ int request_percpu_irq(unsigned int irq, irq_handler_t handler,
|
||||
!irq_settings_is_per_cpu_devid(desc))
|
||||
return -EINVAL;
|
||||
|
||||
if (flags && flags != IRQF_TIMER)
|
||||
return -EINVAL;
|
||||
|
||||
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
|
||||
if (!action)
|
||||
return -ENOMEM;
|
||||
|
||||
action->handler = handler;
|
||||
action->flags = IRQF_PERCPU | IRQF_NO_SUSPEND;
|
||||
action->flags = flags | IRQF_PERCPU | IRQF_NO_SUSPEND;
|
||||
action->name = devname;
|
||||
action->percpu_dev_id = dev_id;
|
||||
|
||||
@@ -2003,9 +2043,7 @@ int request_percpu_irq(unsigned int irq, irq_handler_t handler,
|
||||
return retval;
|
||||
}
|
||||
|
||||
chip_bus_lock(desc);
|
||||
retval = __setup_irq(irq, desc, action);
|
||||
chip_bus_sync_unlock(desc);
|
||||
|
||||
if (retval) {
|
||||
irq_chip_pm_put(&desc->irq_data);
|
||||
@@ -2014,7 +2052,7 @@ int request_percpu_irq(unsigned int irq, irq_handler_t handler,
|
||||
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(request_percpu_irq);
|
||||
EXPORT_SYMBOL_GPL(__request_percpu_irq);
|
||||
|
||||
/**
|
||||
* irq_get_irqchip_state - returns the irqchip state of a interrupt.
|
||||
|
||||
@@ -4,6 +4,36 @@
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
/**
|
||||
* irq_fixup_move_pending - Cleanup irq move pending from a dying CPU
|
||||
* @desc: Interrupt descpriptor to clean up
|
||||
* @force_clear: If set clear the move pending bit unconditionally.
|
||||
* If not set, clear it only when the dying CPU is the
|
||||
* last one in the pending mask.
|
||||
*
|
||||
* Returns true if the pending bit was set and the pending mask contains an
|
||||
* online CPU other than the dying CPU.
|
||||
*/
|
||||
bool irq_fixup_move_pending(struct irq_desc *desc, bool force_clear)
|
||||
{
|
||||
struct irq_data *data = irq_desc_get_irq_data(desc);
|
||||
|
||||
if (!irqd_is_setaffinity_pending(data))
|
||||
return false;
|
||||
|
||||
/*
|
||||
* The outgoing CPU might be the last online target in a pending
|
||||
* interrupt move. If that's the case clear the pending move bit.
|
||||
*/
|
||||
if (cpumask_any_and(desc->pending_mask, cpu_online_mask) >= nr_cpu_ids) {
|
||||
irqd_clr_move_pending(data);
|
||||
return false;
|
||||
}
|
||||
if (force_clear)
|
||||
irqd_clr_move_pending(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
void irq_move_masked_irq(struct irq_data *idata)
|
||||
{
|
||||
struct irq_desc *desc = irq_data_to_desc(idata);
|
||||
|
||||
+10
-3
@@ -265,13 +265,20 @@ struct irq_domain *msi_create_irq_domain(struct fwnode_handle *fwnode,
|
||||
struct msi_domain_info *info,
|
||||
struct irq_domain *parent)
|
||||
{
|
||||
struct irq_domain *domain;
|
||||
|
||||
if (info->flags & MSI_FLAG_USE_DEF_DOM_OPS)
|
||||
msi_domain_update_dom_ops(info);
|
||||
if (info->flags & MSI_FLAG_USE_DEF_CHIP_OPS)
|
||||
msi_domain_update_chip_ops(info);
|
||||
|
||||
return irq_domain_create_hierarchy(parent, IRQ_DOMAIN_FLAG_MSI, 0,
|
||||
fwnode, &msi_domain_ops, info);
|
||||
domain = irq_domain_create_hierarchy(parent, IRQ_DOMAIN_FLAG_MSI, 0,
|
||||
fwnode, &msi_domain_ops, info);
|
||||
|
||||
if (domain && !domain->name && info->chip)
|
||||
domain->name = info->chip->name;
|
||||
|
||||
return domain;
|
||||
}
|
||||
|
||||
int msi_domain_prepare_irqs(struct irq_domain *domain, struct device *dev,
|
||||
@@ -308,7 +315,7 @@ int msi_domain_populate_irqs(struct irq_domain *domain, struct device *dev,
|
||||
|
||||
ops->set_desc(arg, desc);
|
||||
/* Assumes the domain mutex is held! */
|
||||
ret = irq_domain_alloc_irqs_recursive(domain, virq, 1, arg);
|
||||
ret = irq_domain_alloc_irqs_hierarchy(domain, virq, 1, arg);
|
||||
if (ret)
|
||||
break;
|
||||
|
||||
|
||||
@@ -149,6 +149,8 @@ static void resume_irq(struct irq_desc *desc)
|
||||
|
||||
/* Pretend that it got disabled ! */
|
||||
desc->depth++;
|
||||
irq_state_set_disabled(desc);
|
||||
irq_state_set_masked(desc);
|
||||
resume:
|
||||
desc->istate &= ~IRQS_SUSPENDED;
|
||||
__enable_irq(desc);
|
||||
|
||||
+94
-16
@@ -37,19 +37,47 @@ static struct proc_dir_entry *root_irq_dir;
|
||||
|
||||
#ifdef CONFIG_SMP
|
||||
|
||||
static int show_irq_affinity(int type, struct seq_file *m, void *v)
|
||||
enum {
|
||||
AFFINITY,
|
||||
AFFINITY_LIST,
|
||||
EFFECTIVE,
|
||||
EFFECTIVE_LIST,
|
||||
};
|
||||
|
||||
static int show_irq_affinity(int type, struct seq_file *m)
|
||||
{
|
||||
struct irq_desc *desc = irq_to_desc((long)m->private);
|
||||
const struct cpumask *mask = desc->irq_common_data.affinity;
|
||||
const struct cpumask *mask;
|
||||
|
||||
switch (type) {
|
||||
case AFFINITY:
|
||||
case AFFINITY_LIST:
|
||||
mask = desc->irq_common_data.affinity;
|
||||
#ifdef CONFIG_GENERIC_PENDING_IRQ
|
||||
if (irqd_is_setaffinity_pending(&desc->irq_data))
|
||||
mask = desc->pending_mask;
|
||||
if (irqd_is_setaffinity_pending(&desc->irq_data))
|
||||
mask = desc->pending_mask;
|
||||
#endif
|
||||
if (type)
|
||||
break;
|
||||
case EFFECTIVE:
|
||||
case EFFECTIVE_LIST:
|
||||
#ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK
|
||||
mask = desc->irq_common_data.effective_affinity;
|
||||
break;
|
||||
#else
|
||||
return -EINVAL;
|
||||
#endif
|
||||
};
|
||||
|
||||
switch (type) {
|
||||
case AFFINITY_LIST:
|
||||
case EFFECTIVE_LIST:
|
||||
seq_printf(m, "%*pbl\n", cpumask_pr_args(mask));
|
||||
else
|
||||
break;
|
||||
case AFFINITY:
|
||||
case EFFECTIVE:
|
||||
seq_printf(m, "%*pb\n", cpumask_pr_args(mask));
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -80,12 +108,12 @@ static int irq_affinity_hint_proc_show(struct seq_file *m, void *v)
|
||||
int no_irq_affinity;
|
||||
static int irq_affinity_proc_show(struct seq_file *m, void *v)
|
||||
{
|
||||
return show_irq_affinity(0, m, v);
|
||||
return show_irq_affinity(AFFINITY, m);
|
||||
}
|
||||
|
||||
static int irq_affinity_list_proc_show(struct seq_file *m, void *v)
|
||||
{
|
||||
return show_irq_affinity(1, m, v);
|
||||
return show_irq_affinity(AFFINITY_LIST, m);
|
||||
}
|
||||
|
||||
|
||||
@@ -120,9 +148,11 @@ static ssize_t write_irq_affinity(int type, struct file *file,
|
||||
* one online CPU still has to be targeted.
|
||||
*/
|
||||
if (!cpumask_intersects(new_value, cpu_online_mask)) {
|
||||
/* Special case for empty set - allow the architecture
|
||||
code to set default SMP affinity. */
|
||||
err = irq_select_affinity_usr(irq, new_value) ? -EINVAL : count;
|
||||
/*
|
||||
* Special case for empty set - allow the architecture code
|
||||
* to set default SMP affinity.
|
||||
*/
|
||||
err = irq_select_affinity_usr(irq) ? -EINVAL : count;
|
||||
} else {
|
||||
irq_set_affinity(irq, new_value);
|
||||
err = count;
|
||||
@@ -183,6 +213,44 @@ static const struct file_operations irq_affinity_list_proc_fops = {
|
||||
.write = irq_affinity_list_proc_write,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK
|
||||
static int irq_effective_aff_proc_show(struct seq_file *m, void *v)
|
||||
{
|
||||
return show_irq_affinity(EFFECTIVE, m);
|
||||
}
|
||||
|
||||
static int irq_effective_aff_list_proc_show(struct seq_file *m, void *v)
|
||||
{
|
||||
return show_irq_affinity(EFFECTIVE_LIST, m);
|
||||
}
|
||||
|
||||
static int irq_effective_aff_proc_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
return single_open(file, irq_effective_aff_proc_show, PDE_DATA(inode));
|
||||
}
|
||||
|
||||
static int irq_effective_aff_list_proc_open(struct inode *inode,
|
||||
struct file *file)
|
||||
{
|
||||
return single_open(file, irq_effective_aff_list_proc_show,
|
||||
PDE_DATA(inode));
|
||||
}
|
||||
|
||||
static const struct file_operations irq_effective_aff_proc_fops = {
|
||||
.open = irq_effective_aff_proc_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
|
||||
static const struct file_operations irq_effective_aff_list_proc_fops = {
|
||||
.open = irq_effective_aff_list_proc_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
};
|
||||
#endif
|
||||
|
||||
static int default_affinity_show(struct seq_file *m, void *v)
|
||||
{
|
||||
seq_printf(m, "%*pb\n", cpumask_pr_args(irq_default_affinity));
|
||||
@@ -324,6 +392,7 @@ void register_handler_proc(unsigned int irq, struct irqaction *action)
|
||||
void register_irq_proc(unsigned int irq, struct irq_desc *desc)
|
||||
{
|
||||
static DEFINE_MUTEX(register_lock);
|
||||
void __maybe_unused *irqp = (void *)(unsigned long) irq;
|
||||
char name [MAX_NAMELEN];
|
||||
|
||||
if (!root_irq_dir || (desc->irq_data.chip == &no_irq_chip))
|
||||
@@ -349,20 +418,25 @@ void register_irq_proc(unsigned int irq, struct irq_desc *desc)
|
||||
#ifdef CONFIG_SMP
|
||||
/* create /proc/irq/<irq>/smp_affinity */
|
||||
proc_create_data("smp_affinity", 0644, desc->dir,
|
||||
&irq_affinity_proc_fops, (void *)(long)irq);
|
||||
&irq_affinity_proc_fops, irqp);
|
||||
|
||||
/* create /proc/irq/<irq>/affinity_hint */
|
||||
proc_create_data("affinity_hint", 0444, desc->dir,
|
||||
&irq_affinity_hint_proc_fops, (void *)(long)irq);
|
||||
&irq_affinity_hint_proc_fops, irqp);
|
||||
|
||||
/* create /proc/irq/<irq>/smp_affinity_list */
|
||||
proc_create_data("smp_affinity_list", 0644, desc->dir,
|
||||
&irq_affinity_list_proc_fops, (void *)(long)irq);
|
||||
&irq_affinity_list_proc_fops, irqp);
|
||||
|
||||
proc_create_data("node", 0444, desc->dir,
|
||||
&irq_node_proc_fops, (void *)(long)irq);
|
||||
&irq_node_proc_fops, irqp);
|
||||
# ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK
|
||||
proc_create_data("effective_affinity", 0444, desc->dir,
|
||||
&irq_effective_aff_proc_fops, irqp);
|
||||
proc_create_data("effective_affinity_list", 0444, desc->dir,
|
||||
&irq_effective_aff_list_proc_fops, irqp);
|
||||
# endif
|
||||
#endif
|
||||
|
||||
proc_create_data("spurious", 0444, desc->dir,
|
||||
&irq_spurious_proc_fops, (void *)(long)irq);
|
||||
|
||||
@@ -381,6 +455,10 @@ void unregister_irq_proc(unsigned int irq, struct irq_desc *desc)
|
||||
remove_proc_entry("affinity_hint", desc->dir);
|
||||
remove_proc_entry("smp_affinity_list", desc->dir);
|
||||
remove_proc_entry("node", desc->dir);
|
||||
# ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK
|
||||
remove_proc_entry("effective_affinity", desc->dir);
|
||||
remove_proc_entry("effective_affinity_list", desc->dir);
|
||||
# endif
|
||||
#endif
|
||||
remove_proc_entry("spurious", desc->dir);
|
||||
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* linux/kernel/irq/timings.c
|
||||
*
|
||||
* Copyright (C) 2016, Linaro Ltd - Daniel Lezcano <daniel.lezcano@linaro.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/percpu.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/static_key.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/math64.h>
|
||||
|
||||
#include <trace/events/irq.h>
|
||||
|
||||
#include "internals.h"
|
||||
|
||||
DEFINE_STATIC_KEY_FALSE(irq_timing_enabled);
|
||||
|
||||
DEFINE_PER_CPU(struct irq_timings, irq_timings);
|
||||
|
||||
struct irqt_stat {
|
||||
u64 next_evt;
|
||||
u64 last_ts;
|
||||
u64 variance;
|
||||
u32 avg;
|
||||
u32 nr_samples;
|
||||
int anomalies;
|
||||
int valid;
|
||||
};
|
||||
|
||||
static DEFINE_IDR(irqt_stats);
|
||||
|
||||
void irq_timings_enable(void)
|
||||
{
|
||||
static_branch_enable(&irq_timing_enabled);
|
||||
}
|
||||
|
||||
void irq_timings_disable(void)
|
||||
{
|
||||
static_branch_disable(&irq_timing_enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* irqs_update - update the irq timing statistics with a new timestamp
|
||||
*
|
||||
* @irqs: an irqt_stat struct pointer
|
||||
* @ts: the new timestamp
|
||||
*
|
||||
* The statistics are computed online, in other words, the code is
|
||||
* designed to compute the statistics on a stream of values rather
|
||||
* than doing multiple passes on the values to compute the average,
|
||||
* then the variance. The integer division introduces a loss of
|
||||
* precision but with an acceptable error margin regarding the results
|
||||
* we would have with the double floating precision: we are dealing
|
||||
* with nanosec, so big numbers, consequently the mantisse is
|
||||
* negligeable, especially when converting the time in usec
|
||||
* afterwards.
|
||||
*
|
||||
* The computation happens at idle time. When the CPU is not idle, the
|
||||
* interrupts' timestamps are stored in the circular buffer, when the
|
||||
* CPU goes idle and this routine is called, all the buffer's values
|
||||
* are injected in the statistical model continuying to extend the
|
||||
* statistics from the previous busy-idle cycle.
|
||||
*
|
||||
* The observations showed a device will trigger a burst of periodic
|
||||
* interrupts followed by one or two peaks of longer time, for
|
||||
* instance when a SD card device flushes its cache, then the periodic
|
||||
* intervals occur again. A one second inactivity period resets the
|
||||
* stats, that gives us the certitude the statistical values won't
|
||||
* exceed 1x10^9, thus the computation won't overflow.
|
||||
*
|
||||
* Basically, the purpose of the algorithm is to watch the periodic
|
||||
* interrupts and eliminate the peaks.
|
||||
*
|
||||
* An interrupt is considered periodically stable if the interval of
|
||||
* its occurences follow the normal distribution, thus the values
|
||||
* comply with:
|
||||
*
|
||||
* avg - 3 x stddev < value < avg + 3 x stddev
|
||||
*
|
||||
* Which can be simplified to:
|
||||
*
|
||||
* -3 x stddev < value - avg < 3 x stddev
|
||||
*
|
||||
* abs(value - avg) < 3 x stddev
|
||||
*
|
||||
* In order to save a costly square root computation, we use the
|
||||
* variance. For the record, stddev = sqrt(variance). The equation
|
||||
* above becomes:
|
||||
*
|
||||
* abs(value - avg) < 3 x sqrt(variance)
|
||||
*
|
||||
* And finally we square it:
|
||||
*
|
||||
* (value - avg) ^ 2 < (3 x sqrt(variance)) ^ 2
|
||||
*
|
||||
* (value - avg) x (value - avg) < 9 x variance
|
||||
*
|
||||
* Statistically speaking, any values out of this interval is
|
||||
* considered as an anomaly and is discarded. However, a normal
|
||||
* distribution appears when the number of samples is 30 (it is the
|
||||
* rule of thumb in statistics, cf. "30 samples" on Internet). When
|
||||
* there are three consecutive anomalies, the statistics are resetted.
|
||||
*
|
||||
*/
|
||||
static void irqs_update(struct irqt_stat *irqs, u64 ts)
|
||||
{
|
||||
u64 old_ts = irqs->last_ts;
|
||||
u64 variance = 0;
|
||||
u64 interval;
|
||||
s64 diff;
|
||||
|
||||
/*
|
||||
* The timestamps are absolute time values, we need to compute
|
||||
* the timing interval between two interrupts.
|
||||
*/
|
||||
irqs->last_ts = ts;
|
||||
|
||||
/*
|
||||
* The interval type is u64 in order to deal with the same
|
||||
* type in our computation, that prevent mindfuck issues with
|
||||
* overflow, sign and division.
|
||||
*/
|
||||
interval = ts - old_ts;
|
||||
|
||||
/*
|
||||
* The interrupt triggered more than one second apart, that
|
||||
* ends the sequence as predictible for our purpose. In this
|
||||
* case, assume we have the beginning of a sequence and the
|
||||
* timestamp is the first value. As it is impossible to
|
||||
* predict anything at this point, return.
|
||||
*
|
||||
* Note the first timestamp of the sequence will always fall
|
||||
* in this test because the old_ts is zero. That is what we
|
||||
* want as we need another timestamp to compute an interval.
|
||||
*/
|
||||
if (interval >= NSEC_PER_SEC) {
|
||||
memset(irqs, 0, sizeof(*irqs));
|
||||
irqs->last_ts = ts;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Pre-compute the delta with the average as the result is
|
||||
* used several times in this function.
|
||||
*/
|
||||
diff = interval - irqs->avg;
|
||||
|
||||
/*
|
||||
* Increment the number of samples.
|
||||
*/
|
||||
irqs->nr_samples++;
|
||||
|
||||
/*
|
||||
* Online variance divided by the number of elements if there
|
||||
* is more than one sample. Normally the formula is division
|
||||
* by nr_samples - 1 but we assume the number of element will be
|
||||
* more than 32 and dividing by 32 instead of 31 is enough
|
||||
* precise.
|
||||
*/
|
||||
if (likely(irqs->nr_samples > 1))
|
||||
variance = irqs->variance >> IRQ_TIMINGS_SHIFT;
|
||||
|
||||
/*
|
||||
* The rule of thumb in statistics for the normal distribution
|
||||
* is having at least 30 samples in order to have the model to
|
||||
* apply. Values outside the interval are considered as an
|
||||
* anomaly.
|
||||
*/
|
||||
if ((irqs->nr_samples >= 30) && ((diff * diff) > (9 * variance))) {
|
||||
/*
|
||||
* After three consecutive anomalies, we reset the
|
||||
* stats as it is no longer stable enough.
|
||||
*/
|
||||
if (irqs->anomalies++ >= 3) {
|
||||
memset(irqs, 0, sizeof(*irqs));
|
||||
irqs->last_ts = ts;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* The anomalies must be consecutives, so at this
|
||||
* point, we reset the anomalies counter.
|
||||
*/
|
||||
irqs->anomalies = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The interrupt is considered stable enough to try to predict
|
||||
* the next event on it.
|
||||
*/
|
||||
irqs->valid = 1;
|
||||
|
||||
/*
|
||||
* Online average algorithm:
|
||||
*
|
||||
* new_average = average + ((value - average) / count)
|
||||
*
|
||||
* The variance computation depends on the new average
|
||||
* to be computed here first.
|
||||
*
|
||||
*/
|
||||
irqs->avg = irqs->avg + (diff >> IRQ_TIMINGS_SHIFT);
|
||||
|
||||
/*
|
||||
* Online variance algorithm:
|
||||
*
|
||||
* new_variance = variance + (value - average) x (value - new_average)
|
||||
*
|
||||
* Warning: irqs->avg is updated with the line above, hence
|
||||
* 'interval - irqs->avg' is no longer equal to 'diff'
|
||||
*/
|
||||
irqs->variance = irqs->variance + (diff * (interval - irqs->avg));
|
||||
|
||||
/*
|
||||
* Update the next event
|
||||
*/
|
||||
irqs->next_evt = ts + irqs->avg;
|
||||
}
|
||||
|
||||
/**
|
||||
* irq_timings_next_event - Return when the next event is supposed to arrive
|
||||
*
|
||||
* During the last busy cycle, the number of interrupts is incremented
|
||||
* and stored in the irq_timings structure. This information is
|
||||
* necessary to:
|
||||
*
|
||||
* - know if the index in the table wrapped up:
|
||||
*
|
||||
* If more than the array size interrupts happened during the
|
||||
* last busy/idle cycle, the index wrapped up and we have to
|
||||
* begin with the next element in the array which is the last one
|
||||
* in the sequence, otherwise it is a the index 0.
|
||||
*
|
||||
* - have an indication of the interrupts activity on this CPU
|
||||
* (eg. irq/sec)
|
||||
*
|
||||
* The values are 'consumed' after inserting in the statistical model,
|
||||
* thus the count is reinitialized.
|
||||
*
|
||||
* The array of values **must** be browsed in the time direction, the
|
||||
* timestamp must increase between an element and the next one.
|
||||
*
|
||||
* Returns a nanosec time based estimation of the earliest interrupt,
|
||||
* U64_MAX otherwise.
|
||||
*/
|
||||
u64 irq_timings_next_event(u64 now)
|
||||
{
|
||||
struct irq_timings *irqts = this_cpu_ptr(&irq_timings);
|
||||
struct irqt_stat *irqs;
|
||||
struct irqt_stat __percpu *s;
|
||||
u64 ts, next_evt = U64_MAX;
|
||||
int i, irq = 0;
|
||||
|
||||
/*
|
||||
* This function must be called with the local irq disabled in
|
||||
* order to prevent the timings circular buffer to be updated
|
||||
* while we are reading it.
|
||||
*/
|
||||
WARN_ON_ONCE(!irqs_disabled());
|
||||
|
||||
/*
|
||||
* Number of elements in the circular buffer: If it happens it
|
||||
* was flushed before, then the number of elements could be
|
||||
* smaller than IRQ_TIMINGS_SIZE, so the count is used,
|
||||
* otherwise the array size is used as we wrapped. The index
|
||||
* begins from zero when we did not wrap. That could be done
|
||||
* in a nicer way with the proper circular array structure
|
||||
* type but with the cost of extra computation in the
|
||||
* interrupt handler hot path. We choose efficiency.
|
||||
*
|
||||
* Inject measured irq/timestamp to the statistical model
|
||||
* while decrementing the counter because we consume the data
|
||||
* from our circular buffer.
|
||||
*/
|
||||
for (i = irqts->count & IRQ_TIMINGS_MASK,
|
||||
irqts->count = min(IRQ_TIMINGS_SIZE, irqts->count);
|
||||
irqts->count > 0; irqts->count--, i = (i + 1) & IRQ_TIMINGS_MASK) {
|
||||
|
||||
irq = irq_timing_decode(irqts->values[i], &ts);
|
||||
|
||||
s = idr_find(&irqt_stats, irq);
|
||||
if (s) {
|
||||
irqs = this_cpu_ptr(s);
|
||||
irqs_update(irqs, ts);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Look in the list of interrupts' statistics, the earliest
|
||||
* next event.
|
||||
*/
|
||||
idr_for_each_entry(&irqt_stats, s, i) {
|
||||
|
||||
irqs = this_cpu_ptr(s);
|
||||
|
||||
if (!irqs->valid)
|
||||
continue;
|
||||
|
||||
if (irqs->next_evt <= now) {
|
||||
irq = i;
|
||||
next_evt = now;
|
||||
|
||||
/*
|
||||
* This interrupt mustn't use in the future
|
||||
* until new events occur and update the
|
||||
* statistics.
|
||||
*/
|
||||
irqs->valid = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (irqs->next_evt < next_evt) {
|
||||
irq = i;
|
||||
next_evt = irqs->next_evt;
|
||||
}
|
||||
}
|
||||
|
||||
return next_evt;
|
||||
}
|
||||
|
||||
void irq_timings_free(int irq)
|
||||
{
|
||||
struct irqt_stat __percpu *s;
|
||||
|
||||
s = idr_find(&irqt_stats, irq);
|
||||
if (s) {
|
||||
free_percpu(s);
|
||||
idr_remove(&irqt_stats, irq);
|
||||
}
|
||||
}
|
||||
|
||||
int irq_timings_alloc(int irq)
|
||||
{
|
||||
struct irqt_stat __percpu *s;
|
||||
int id;
|
||||
|
||||
/*
|
||||
* Some platforms can have the same private interrupt per cpu,
|
||||
* so this function may be be called several times with the
|
||||
* same interrupt number. Just bail out in case the per cpu
|
||||
* stat structure is already allocated.
|
||||
*/
|
||||
s = idr_find(&irqt_stats, irq);
|
||||
if (s)
|
||||
return 0;
|
||||
|
||||
s = alloc_percpu(*s);
|
||||
if (!s)
|
||||
return -ENOMEM;
|
||||
|
||||
idr_preload(GFP_KERNEL);
|
||||
id = idr_alloc(&irqt_stats, s, irq, irq + 1, GFP_NOWAIT);
|
||||
idr_preload_end();
|
||||
|
||||
if (id < 0) {
|
||||
free_percpu(s);
|
||||
return id;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
+14
-6
@@ -15,6 +15,7 @@
|
||||
#include <linux/static_key.h>
|
||||
#include <linux/jump_label_ratelimit.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/cpu.h>
|
||||
|
||||
#ifdef HAVE_JUMP_LABEL
|
||||
|
||||
@@ -124,6 +125,7 @@ void static_key_slow_inc(struct static_key *key)
|
||||
return;
|
||||
}
|
||||
|
||||
cpus_read_lock();
|
||||
jump_label_lock();
|
||||
if (atomic_read(&key->enabled) == 0) {
|
||||
atomic_set(&key->enabled, -1);
|
||||
@@ -133,12 +135,14 @@ void static_key_slow_inc(struct static_key *key)
|
||||
atomic_inc(&key->enabled);
|
||||
}
|
||||
jump_label_unlock();
|
||||
cpus_read_unlock();
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(static_key_slow_inc);
|
||||
|
||||
static void __static_key_slow_dec(struct static_key *key,
|
||||
unsigned long rate_limit, struct delayed_work *work)
|
||||
{
|
||||
cpus_read_lock();
|
||||
/*
|
||||
* The negative count check is valid even when a negative
|
||||
* key->enabled is in use by static_key_slow_inc(); a
|
||||
@@ -149,6 +153,7 @@ static void __static_key_slow_dec(struct static_key *key,
|
||||
if (!atomic_dec_and_mutex_lock(&key->enabled, &jump_label_mutex)) {
|
||||
WARN(atomic_read(&key->enabled) < 0,
|
||||
"jump label: negative count!\n");
|
||||
cpus_read_unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -159,6 +164,7 @@ static void __static_key_slow_dec(struct static_key *key,
|
||||
jump_label_update(key);
|
||||
}
|
||||
jump_label_unlock();
|
||||
cpus_read_unlock();
|
||||
}
|
||||
|
||||
static void jump_label_update_timeout(struct work_struct *work)
|
||||
@@ -334,6 +340,7 @@ void __init jump_label_init(void)
|
||||
if (static_key_initialized)
|
||||
return;
|
||||
|
||||
cpus_read_lock();
|
||||
jump_label_lock();
|
||||
jump_label_sort_entries(iter_start, iter_stop);
|
||||
|
||||
@@ -353,6 +360,7 @@ void __init jump_label_init(void)
|
||||
}
|
||||
static_key_initialized = true;
|
||||
jump_label_unlock();
|
||||
cpus_read_unlock();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MODULES
|
||||
@@ -590,28 +598,28 @@ jump_label_module_notify(struct notifier_block *self, unsigned long val,
|
||||
struct module *mod = data;
|
||||
int ret = 0;
|
||||
|
||||
cpus_read_lock();
|
||||
jump_label_lock();
|
||||
|
||||
switch (val) {
|
||||
case MODULE_STATE_COMING:
|
||||
jump_label_lock();
|
||||
ret = jump_label_add_module(mod);
|
||||
if (ret) {
|
||||
WARN(1, "Failed to allocatote memory: jump_label may not work properly.\n");
|
||||
jump_label_del_module(mod);
|
||||
}
|
||||
jump_label_unlock();
|
||||
break;
|
||||
case MODULE_STATE_GOING:
|
||||
jump_label_lock();
|
||||
jump_label_del_module(mod);
|
||||
jump_label_unlock();
|
||||
break;
|
||||
case MODULE_STATE_LIVE:
|
||||
jump_label_lock();
|
||||
jump_label_invalidate_module_init(mod);
|
||||
jump_label_unlock();
|
||||
break;
|
||||
}
|
||||
|
||||
jump_label_unlock();
|
||||
cpus_read_unlock();
|
||||
|
||||
return notifier_from_errno(ret);
|
||||
}
|
||||
|
||||
|
||||
+2
-8
@@ -28,12 +28,6 @@
|
||||
|
||||
#include <asm/sections.h>
|
||||
|
||||
#ifdef CONFIG_KALLSYMS_ALL
|
||||
#define all_var 1
|
||||
#else
|
||||
#define all_var 0
|
||||
#endif
|
||||
|
||||
/*
|
||||
* These will be re-linked against their real values
|
||||
* during the second link stage.
|
||||
@@ -82,7 +76,7 @@ static inline int is_kernel(unsigned long addr)
|
||||
|
||||
static int is_ksym_addr(unsigned long addr)
|
||||
{
|
||||
if (all_var)
|
||||
if (IS_ENABLED(CONFIG_KALLSYMS_ALL))
|
||||
return is_kernel(addr);
|
||||
|
||||
return is_kernel_text(addr) || is_kernel_inittext(addr);
|
||||
@@ -280,7 +274,7 @@ static unsigned long get_symbol_pos(unsigned long addr,
|
||||
if (!symbol_end) {
|
||||
if (is_kernel_inittext(addr))
|
||||
symbol_end = (unsigned long)_einittext;
|
||||
else if (all_var)
|
||||
else if (IS_ENABLED(CONFIG_KALLSYMS_ALL))
|
||||
symbol_end = (unsigned long)_end;
|
||||
else
|
||||
symbol_end = (unsigned long)_etext;
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
#include <linux/bug.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/kcmp.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/eventpoll.h>
|
||||
#include <linux/file.h>
|
||||
|
||||
#include <asm/unistd.h>
|
||||
|
||||
@@ -94,6 +98,56 @@ static int kcmp_lock(struct mutex *m1, struct mutex *m2)
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EPOLL
|
||||
static int kcmp_epoll_target(struct task_struct *task1,
|
||||
struct task_struct *task2,
|
||||
unsigned long idx1,
|
||||
struct kcmp_epoll_slot __user *uslot)
|
||||
{
|
||||
struct file *filp, *filp_epoll, *filp_tgt;
|
||||
struct kcmp_epoll_slot slot;
|
||||
struct files_struct *files;
|
||||
|
||||
if (copy_from_user(&slot, uslot, sizeof(slot)))
|
||||
return -EFAULT;
|
||||
|
||||
filp = get_file_raw_ptr(task1, idx1);
|
||||
if (!filp)
|
||||
return -EBADF;
|
||||
|
||||
files = get_files_struct(task2);
|
||||
if (!files)
|
||||
return -EBADF;
|
||||
|
||||
spin_lock(&files->file_lock);
|
||||
filp_epoll = fcheck_files(files, slot.efd);
|
||||
if (filp_epoll)
|
||||
get_file(filp_epoll);
|
||||
else
|
||||
filp_tgt = ERR_PTR(-EBADF);
|
||||
spin_unlock(&files->file_lock);
|
||||
put_files_struct(files);
|
||||
|
||||
if (filp_epoll) {
|
||||
filp_tgt = get_epoll_tfile_raw_ptr(filp_epoll, slot.tfd, slot.toff);
|
||||
fput(filp_epoll);
|
||||
} else
|
||||
|
||||
if (IS_ERR(filp_tgt))
|
||||
return PTR_ERR(filp_tgt);
|
||||
|
||||
return kcmp_ptr(filp, filp_tgt, KCMP_FILE);
|
||||
}
|
||||
#else
|
||||
static int kcmp_epoll_target(struct task_struct *task1,
|
||||
struct task_struct *task2,
|
||||
unsigned long idx1,
|
||||
struct kcmp_epoll_slot __user *uslot)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
#endif
|
||||
|
||||
SYSCALL_DEFINE5(kcmp, pid_t, pid1, pid_t, pid2, int, type,
|
||||
unsigned long, idx1, unsigned long, idx2)
|
||||
{
|
||||
@@ -165,6 +219,9 @@ SYSCALL_DEFINE5(kcmp, pid_t, pid1, pid_t, pid2, int, type,
|
||||
ret = -EOPNOTSUPP;
|
||||
#endif
|
||||
break;
|
||||
case KCMP_EPOLL_TFD:
|
||||
ret = kcmp_epoll_target(task1, task2, idx1, (void *)idx2);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
|
||||
@@ -144,6 +144,14 @@ static int do_kexec_load(unsigned long entry, unsigned long nr_segments,
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Some architecture(like S390) may touch the crash memory before
|
||||
* machine_kexec_prepare(), we must copy vmcoreinfo data after it.
|
||||
*/
|
||||
ret = kimage_crash_copy_vmcoreinfo(image);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
for (i = 0; i < nr_segments; i++) {
|
||||
ret = kimage_load_segment(image, &image->segment[i]);
|
||||
if (ret)
|
||||
|
||||
+42
-1
@@ -38,6 +38,7 @@
|
||||
#include <linux/syscore_ops.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/hugetlb.h>
|
||||
#include <linux/frame.h>
|
||||
|
||||
#include <asm/page.h>
|
||||
#include <asm/sections.h>
|
||||
@@ -481,6 +482,40 @@ struct page *kimage_alloc_control_pages(struct kimage *image,
|
||||
return pages;
|
||||
}
|
||||
|
||||
int kimage_crash_copy_vmcoreinfo(struct kimage *image)
|
||||
{
|
||||
struct page *vmcoreinfo_page;
|
||||
void *safecopy;
|
||||
|
||||
if (image->type != KEXEC_TYPE_CRASH)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* For kdump, allocate one vmcoreinfo safe copy from the
|
||||
* crash memory. as we have arch_kexec_protect_crashkres()
|
||||
* after kexec syscall, we naturally protect it from write
|
||||
* (even read) access under kernel direct mapping. But on
|
||||
* the other hand, we still need to operate it when crash
|
||||
* happens to generate vmcoreinfo note, hereby we rely on
|
||||
* vmap for this purpose.
|
||||
*/
|
||||
vmcoreinfo_page = kimage_alloc_control_pages(image, 0);
|
||||
if (!vmcoreinfo_page) {
|
||||
pr_warn("Could not allocate vmcoreinfo buffer\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
safecopy = vmap(&vmcoreinfo_page, 1, VM_MAP, PAGE_KERNEL);
|
||||
if (!safecopy) {
|
||||
pr_warn("Could not vmap vmcoreinfo buffer\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
image->vmcoreinfo_data_copy = safecopy;
|
||||
crash_update_vmcoreinfo_safecopy(safecopy);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int kimage_add_entry(struct kimage *image, kimage_entry_t entry)
|
||||
{
|
||||
if (*image->entry != 0)
|
||||
@@ -568,6 +603,11 @@ void kimage_free(struct kimage *image)
|
||||
if (!image)
|
||||
return;
|
||||
|
||||
if (image->vmcoreinfo_data_copy) {
|
||||
crash_update_vmcoreinfo_safecopy(NULL);
|
||||
vunmap(image->vmcoreinfo_data_copy);
|
||||
}
|
||||
|
||||
kimage_free_extra_pages(image);
|
||||
for_each_kimage_entry(image, ptr, entry) {
|
||||
if (entry & IND_INDIRECTION) {
|
||||
@@ -874,7 +914,7 @@ int kexec_load_disabled;
|
||||
* only when panic_cpu holds the current CPU number; this is the only CPU
|
||||
* which processes crash_kexec routines.
|
||||
*/
|
||||
void __crash_kexec(struct pt_regs *regs)
|
||||
void __noclone __crash_kexec(struct pt_regs *regs)
|
||||
{
|
||||
/* Take the kexec_mutex here to prevent sys_kexec_load
|
||||
* running on one cpu from replacing the crash kernel
|
||||
@@ -896,6 +936,7 @@ void __crash_kexec(struct pt_regs *regs)
|
||||
mutex_unlock(&kexec_mutex);
|
||||
}
|
||||
}
|
||||
STACK_FRAME_NON_STANDARD(__crash_kexec);
|
||||
|
||||
void crash_kexec(struct pt_regs *regs)
|
||||
{
|
||||
|
||||
+12
-17
@@ -26,13 +26,6 @@
|
||||
#include <linux/vmalloc.h>
|
||||
#include "kexec_internal.h"
|
||||
|
||||
/*
|
||||
* Declare these symbols weak so that if architecture provides a purgatory,
|
||||
* these will be overridden.
|
||||
*/
|
||||
char __weak kexec_purgatory[0];
|
||||
size_t __weak kexec_purgatory_size = 0;
|
||||
|
||||
static int kexec_calculate_store_digests(struct kimage *image);
|
||||
|
||||
/* Architectures can provide this probe function */
|
||||
@@ -162,16 +155,10 @@ kimage_file_prepare_segments(struct kimage *image, int kernel_fd, int initrd_fd,
|
||||
}
|
||||
|
||||
if (cmdline_len) {
|
||||
image->cmdline_buf = kzalloc(cmdline_len, GFP_KERNEL);
|
||||
if (!image->cmdline_buf) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = copy_from_user(image->cmdline_buf, cmdline_ptr,
|
||||
cmdline_len);
|
||||
if (ret) {
|
||||
ret = -EFAULT;
|
||||
image->cmdline_buf = memdup_user(cmdline_ptr, cmdline_len);
|
||||
if (IS_ERR(image->cmdline_buf)) {
|
||||
ret = PTR_ERR(image->cmdline_buf);
|
||||
image->cmdline_buf = NULL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
@@ -304,6 +291,14 @@ SYSCALL_DEFINE5(kexec_file_load, int, kernel_fd, int, initrd_fd,
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Some architecture(like S390) may touch the crash memory before
|
||||
* machine_kexec_prepare(), we must copy vmcoreinfo data after it.
|
||||
*/
|
||||
ret = kimage_crash_copy_vmcoreinfo(image);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = kexec_calculate_store_digests(image);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
@@ -17,6 +17,8 @@ extern struct mutex kexec_mutex;
|
||||
#ifdef CONFIG_KEXEC_FILE
|
||||
#include <linux/purgatory.h>
|
||||
void kimage_file_post_load_cleanup(struct kimage *image);
|
||||
extern char kexec_purgatory[];
|
||||
extern size_t kexec_purgatory_size;
|
||||
#else /* CONFIG_KEXEC_FILE */
|
||||
static inline void kimage_file_post_load_cleanup(struct kimage *image) { }
|
||||
#endif /* CONFIG_KEXEC_FILE */
|
||||
|
||||
+25
-31
@@ -45,8 +45,6 @@
|
||||
|
||||
#include <trace/events/module.h>
|
||||
|
||||
extern int max_threads;
|
||||
|
||||
#define CAP_BSET (void *)1
|
||||
#define CAP_PI (void *)2
|
||||
|
||||
@@ -56,6 +54,21 @@ static DEFINE_SPINLOCK(umh_sysctl_lock);
|
||||
static DECLARE_RWSEM(umhelper_sem);
|
||||
|
||||
#ifdef CONFIG_MODULES
|
||||
/*
|
||||
* Assuming:
|
||||
*
|
||||
* threads = div64_u64((u64) totalram_pages * (u64) PAGE_SIZE,
|
||||
* (u64) THREAD_SIZE * 8UL);
|
||||
*
|
||||
* If you need less than 50 threads would mean we're dealing with systems
|
||||
* smaller than 3200 pages. This assuems you are capable of having ~13M memory,
|
||||
* and this would only be an be an upper limit, after which the OOM killer
|
||||
* would take effect. Systems like these are very unlikely if modules are
|
||||
* enabled.
|
||||
*/
|
||||
#define MAX_KMOD_CONCURRENT 50
|
||||
static atomic_t kmod_concurrent_max = ATOMIC_INIT(MAX_KMOD_CONCURRENT);
|
||||
static DECLARE_WAIT_QUEUE_HEAD(kmod_wq);
|
||||
|
||||
/*
|
||||
modprobe_path is set via /proc/sys.
|
||||
@@ -127,11 +140,7 @@ int __request_module(bool wait, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
char module_name[MODULE_NAME_LEN];
|
||||
unsigned int max_modprobes;
|
||||
int ret;
|
||||
static atomic_t kmod_concurrent = ATOMIC_INIT(0);
|
||||
#define MAX_KMOD_CONCURRENT 50 /* Completely arbitrary value - KAO */
|
||||
static int kmod_loop_msg;
|
||||
|
||||
/*
|
||||
* We don't allow synchronous module loading from async. Module
|
||||
@@ -154,40 +163,25 @@ int __request_module(bool wait, const char *fmt, ...)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* If modprobe needs a service that is in a module, we get a recursive
|
||||
* loop. Limit the number of running kmod threads to max_threads/2 or
|
||||
* MAX_KMOD_CONCURRENT, whichever is the smaller. A cleaner method
|
||||
* would be to run the parents of this process, counting how many times
|
||||
* kmod was invoked. That would mean accessing the internals of the
|
||||
* process tables to get the command line, proc_pid_cmdline is static
|
||||
* and it is not worth changing the proc code just to handle this case.
|
||||
* KAO.
|
||||
*
|
||||
* "trace the ppid" is simple, but will fail if someone's
|
||||
* parent exits. I think this is as good as it gets. --RR
|
||||
*/
|
||||
max_modprobes = min(max_threads/2, MAX_KMOD_CONCURRENT);
|
||||
atomic_inc(&kmod_concurrent);
|
||||
if (atomic_read(&kmod_concurrent) > max_modprobes) {
|
||||
/* We may be blaming an innocent here, but unlikely */
|
||||
if (kmod_loop_msg < 5) {
|
||||
printk(KERN_ERR
|
||||
"request_module: runaway loop modprobe %s\n",
|
||||
module_name);
|
||||
kmod_loop_msg++;
|
||||
}
|
||||
atomic_dec(&kmod_concurrent);
|
||||
return -ENOMEM;
|
||||
if (atomic_dec_if_positive(&kmod_concurrent_max) < 0) {
|
||||
pr_warn_ratelimited("request_module: kmod_concurrent_max (%u) close to 0 (max_modprobes: %u), for module %s, throttling...",
|
||||
atomic_read(&kmod_concurrent_max),
|
||||
MAX_KMOD_CONCURRENT, module_name);
|
||||
wait_event_interruptible(kmod_wq,
|
||||
atomic_dec_if_positive(&kmod_concurrent_max) >= 0);
|
||||
}
|
||||
|
||||
trace_module_request(module_name, wait, _RET_IP_);
|
||||
|
||||
ret = call_modprobe(module_name, wait ? UMH_WAIT_PROC : UMH_WAIT_EXEC);
|
||||
|
||||
atomic_dec(&kmod_concurrent);
|
||||
atomic_inc(&kmod_concurrent_max);
|
||||
wake_up(&kmod_wq);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(__request_module);
|
||||
|
||||
#endif /* CONFIG_MODULES */
|
||||
|
||||
static void call_usermodehelper_freeinfo(struct subprocess_info *info)
|
||||
|
||||
+57
-46
@@ -122,7 +122,7 @@ static void *alloc_insn_page(void)
|
||||
return module_alloc(PAGE_SIZE);
|
||||
}
|
||||
|
||||
static void free_insn_page(void *page)
|
||||
void __weak free_insn_page(void *page)
|
||||
{
|
||||
module_memfree(page);
|
||||
}
|
||||
@@ -483,11 +483,6 @@ static DECLARE_DELAYED_WORK(optimizing_work, kprobe_optimizer);
|
||||
*/
|
||||
static void do_optimize_kprobes(void)
|
||||
{
|
||||
/* Optimization never be done when disarmed */
|
||||
if (kprobes_all_disarmed || !kprobes_allow_optimization ||
|
||||
list_empty(&optimizing_list))
|
||||
return;
|
||||
|
||||
/*
|
||||
* The optimization/unoptimization refers online_cpus via
|
||||
* stop_machine() and cpu-hotplug modifies online_cpus.
|
||||
@@ -495,14 +490,19 @@ static void do_optimize_kprobes(void)
|
||||
* This combination can cause a deadlock (cpu-hotplug try to lock
|
||||
* text_mutex but stop_machine can not be done because online_cpus
|
||||
* has been changed)
|
||||
* To avoid this deadlock, we need to call get_online_cpus()
|
||||
* To avoid this deadlock, caller must have locked cpu hotplug
|
||||
* for preventing cpu-hotplug outside of text_mutex locking.
|
||||
*/
|
||||
get_online_cpus();
|
||||
lockdep_assert_cpus_held();
|
||||
|
||||
/* Optimization never be done when disarmed */
|
||||
if (kprobes_all_disarmed || !kprobes_allow_optimization ||
|
||||
list_empty(&optimizing_list))
|
||||
return;
|
||||
|
||||
mutex_lock(&text_mutex);
|
||||
arch_optimize_kprobes(&optimizing_list);
|
||||
mutex_unlock(&text_mutex);
|
||||
put_online_cpus();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -513,12 +513,13 @@ static void do_unoptimize_kprobes(void)
|
||||
{
|
||||
struct optimized_kprobe *op, *tmp;
|
||||
|
||||
/* See comment in do_optimize_kprobes() */
|
||||
lockdep_assert_cpus_held();
|
||||
|
||||
/* Unoptimization must be done anytime */
|
||||
if (list_empty(&unoptimizing_list))
|
||||
return;
|
||||
|
||||
/* Ditto to do_optimize_kprobes */
|
||||
get_online_cpus();
|
||||
mutex_lock(&text_mutex);
|
||||
arch_unoptimize_kprobes(&unoptimizing_list, &freeing_list);
|
||||
/* Loop free_list for disarming */
|
||||
@@ -537,7 +538,6 @@ static void do_unoptimize_kprobes(void)
|
||||
list_del_init(&op->list);
|
||||
}
|
||||
mutex_unlock(&text_mutex);
|
||||
put_online_cpus();
|
||||
}
|
||||
|
||||
/* Reclaim all kprobes on the free_list */
|
||||
@@ -562,6 +562,7 @@ static void kick_kprobe_optimizer(void)
|
||||
static void kprobe_optimizer(struct work_struct *work)
|
||||
{
|
||||
mutex_lock(&kprobe_mutex);
|
||||
cpus_read_lock();
|
||||
/* Lock modules while optimizing kprobes */
|
||||
mutex_lock(&module_mutex);
|
||||
|
||||
@@ -587,6 +588,7 @@ static void kprobe_optimizer(struct work_struct *work)
|
||||
do_free_cleaned_kprobes();
|
||||
|
||||
mutex_unlock(&module_mutex);
|
||||
cpus_read_unlock();
|
||||
mutex_unlock(&kprobe_mutex);
|
||||
|
||||
/* Step 5: Kick optimizer again if needed */
|
||||
@@ -650,9 +652,8 @@ static void optimize_kprobe(struct kprobe *p)
|
||||
/* Short cut to direct unoptimizing */
|
||||
static void force_unoptimize_kprobe(struct optimized_kprobe *op)
|
||||
{
|
||||
get_online_cpus();
|
||||
lockdep_assert_cpus_held();
|
||||
arch_unoptimize_kprobe(op);
|
||||
put_online_cpus();
|
||||
if (kprobe_disabled(&op->kp))
|
||||
arch_disarm_kprobe(&op->kp);
|
||||
}
|
||||
@@ -791,6 +792,7 @@ static void try_to_optimize_kprobe(struct kprobe *p)
|
||||
return;
|
||||
|
||||
/* For preparing optimization, jump_label_text_reserved() is called */
|
||||
cpus_read_lock();
|
||||
jump_label_lock();
|
||||
mutex_lock(&text_mutex);
|
||||
|
||||
@@ -812,6 +814,7 @@ static void try_to_optimize_kprobe(struct kprobe *p)
|
||||
out:
|
||||
mutex_unlock(&text_mutex);
|
||||
jump_label_unlock();
|
||||
cpus_read_unlock();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SYSCTL
|
||||
@@ -826,6 +829,7 @@ static void optimize_all_kprobes(void)
|
||||
if (kprobes_allow_optimization)
|
||||
goto out;
|
||||
|
||||
cpus_read_lock();
|
||||
kprobes_allow_optimization = true;
|
||||
for (i = 0; i < KPROBE_TABLE_SIZE; i++) {
|
||||
head = &kprobe_table[i];
|
||||
@@ -833,6 +837,7 @@ static void optimize_all_kprobes(void)
|
||||
if (!kprobe_disabled(p))
|
||||
optimize_kprobe(p);
|
||||
}
|
||||
cpus_read_unlock();
|
||||
printk(KERN_INFO "Kprobes globally optimized\n");
|
||||
out:
|
||||
mutex_unlock(&kprobe_mutex);
|
||||
@@ -851,6 +856,7 @@ static void unoptimize_all_kprobes(void)
|
||||
return;
|
||||
}
|
||||
|
||||
cpus_read_lock();
|
||||
kprobes_allow_optimization = false;
|
||||
for (i = 0; i < KPROBE_TABLE_SIZE; i++) {
|
||||
head = &kprobe_table[i];
|
||||
@@ -859,6 +865,7 @@ static void unoptimize_all_kprobes(void)
|
||||
unoptimize_kprobe(p, false);
|
||||
}
|
||||
}
|
||||
cpus_read_unlock();
|
||||
mutex_unlock(&kprobe_mutex);
|
||||
|
||||
/* Wait for unoptimizing completion */
|
||||
@@ -1010,14 +1017,11 @@ static void arm_kprobe(struct kprobe *kp)
|
||||
arm_kprobe_ftrace(kp);
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* Here, since __arm_kprobe() doesn't use stop_machine(),
|
||||
* this doesn't cause deadlock on text_mutex. So, we don't
|
||||
* need get_online_cpus().
|
||||
*/
|
||||
cpus_read_lock();
|
||||
mutex_lock(&text_mutex);
|
||||
__arm_kprobe(kp);
|
||||
mutex_unlock(&text_mutex);
|
||||
cpus_read_unlock();
|
||||
}
|
||||
|
||||
/* Disarm a kprobe with text_mutex */
|
||||
@@ -1027,10 +1031,12 @@ static void disarm_kprobe(struct kprobe *kp, bool reopt)
|
||||
disarm_kprobe_ftrace(kp);
|
||||
return;
|
||||
}
|
||||
/* Ditto */
|
||||
|
||||
cpus_read_lock();
|
||||
mutex_lock(&text_mutex);
|
||||
__disarm_kprobe(kp, reopt);
|
||||
mutex_unlock(&text_mutex);
|
||||
cpus_read_unlock();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1298,13 +1304,10 @@ static int register_aggr_kprobe(struct kprobe *orig_p, struct kprobe *p)
|
||||
int ret = 0;
|
||||
struct kprobe *ap = orig_p;
|
||||
|
||||
cpus_read_lock();
|
||||
|
||||
/* For preparing optimization, jump_label_text_reserved() is called */
|
||||
jump_label_lock();
|
||||
/*
|
||||
* Get online CPUs to avoid text_mutex deadlock.with stop machine,
|
||||
* which is invoked by unoptimize_kprobe() in add_new_kprobe()
|
||||
*/
|
||||
get_online_cpus();
|
||||
mutex_lock(&text_mutex);
|
||||
|
||||
if (!kprobe_aggrprobe(orig_p)) {
|
||||
@@ -1352,8 +1355,8 @@ static int register_aggr_kprobe(struct kprobe *orig_p, struct kprobe *p)
|
||||
|
||||
out:
|
||||
mutex_unlock(&text_mutex);
|
||||
put_online_cpus();
|
||||
jump_label_unlock();
|
||||
cpus_read_unlock();
|
||||
|
||||
if (ret == 0 && kprobe_disabled(ap) && !kprobe_disabled(p)) {
|
||||
ap->flags &= ~KPROBE_FLAG_DISABLED;
|
||||
@@ -1555,9 +1558,12 @@ int register_kprobe(struct kprobe *p)
|
||||
goto out;
|
||||
}
|
||||
|
||||
mutex_lock(&text_mutex); /* Avoiding text modification */
|
||||
cpus_read_lock();
|
||||
/* Prevent text modification */
|
||||
mutex_lock(&text_mutex);
|
||||
ret = prepare_kprobe(p);
|
||||
mutex_unlock(&text_mutex);
|
||||
cpus_read_unlock();
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
@@ -1570,7 +1576,6 @@ int register_kprobe(struct kprobe *p)
|
||||
|
||||
/* Try to optimize kprobe */
|
||||
try_to_optimize_kprobe(p);
|
||||
|
||||
out:
|
||||
mutex_unlock(&kprobe_mutex);
|
||||
|
||||
@@ -1766,24 +1771,13 @@ unsigned long __weak arch_deref_entry_point(void *entry)
|
||||
|
||||
int register_jprobes(struct jprobe **jps, int num)
|
||||
{
|
||||
struct jprobe *jp;
|
||||
int ret = 0, i;
|
||||
|
||||
if (num <= 0)
|
||||
return -EINVAL;
|
||||
for (i = 0; i < num; i++) {
|
||||
unsigned long addr, offset;
|
||||
jp = jps[i];
|
||||
addr = arch_deref_entry_point(jp->entry);
|
||||
|
||||
/* Verify probepoint is a function entry point */
|
||||
if (kallsyms_lookup_size_offset(addr, NULL, &offset) &&
|
||||
offset == 0) {
|
||||
jp->kp.pre_handler = setjmp_pre_handler;
|
||||
jp->kp.break_handler = longjmp_break_handler;
|
||||
ret = register_kprobe(&jp->kp);
|
||||
} else
|
||||
ret = -EINVAL;
|
||||
for (i = 0; i < num; i++) {
|
||||
ret = register_jprobe(jps[i]);
|
||||
|
||||
if (ret < 0) {
|
||||
if (i > 0)
|
||||
@@ -1791,13 +1785,30 @@ int register_jprobes(struct jprobe **jps, int num)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(register_jprobes);
|
||||
|
||||
int register_jprobe(struct jprobe *jp)
|
||||
{
|
||||
return register_jprobes(&jp, 1);
|
||||
unsigned long addr, offset;
|
||||
struct kprobe *kp = &jp->kp;
|
||||
|
||||
/*
|
||||
* Verify probepoint as well as the jprobe handler are
|
||||
* valid function entry points.
|
||||
*/
|
||||
addr = arch_deref_entry_point(jp->entry);
|
||||
|
||||
if (kallsyms_lookup_size_offset(addr, NULL, &offset) && offset == 0 &&
|
||||
kprobe_on_func_entry(kp->addr, kp->symbol_name, kp->offset)) {
|
||||
kp->pre_handler = setjmp_pre_handler;
|
||||
kp->break_handler = longjmp_break_handler;
|
||||
return register_kprobe(kp);
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(register_jprobe);
|
||||
|
||||
@@ -1883,12 +1894,12 @@ static int pre_handler_kretprobe(struct kprobe *p, struct pt_regs *regs)
|
||||
}
|
||||
NOKPROBE_SYMBOL(pre_handler_kretprobe);
|
||||
|
||||
bool __weak arch_function_offset_within_entry(unsigned long offset)
|
||||
bool __weak arch_kprobe_on_func_entry(unsigned long offset)
|
||||
{
|
||||
return !offset;
|
||||
}
|
||||
|
||||
bool function_offset_within_entry(kprobe_opcode_t *addr, const char *sym, unsigned long offset)
|
||||
bool kprobe_on_func_entry(kprobe_opcode_t *addr, const char *sym, unsigned long offset)
|
||||
{
|
||||
kprobe_opcode_t *kp_addr = _kprobe_addr(addr, sym, offset);
|
||||
|
||||
@@ -1896,7 +1907,7 @@ bool function_offset_within_entry(kprobe_opcode_t *addr, const char *sym, unsign
|
||||
return false;
|
||||
|
||||
if (!kallsyms_lookup_size_offset((unsigned long)kp_addr, NULL, &offset) ||
|
||||
!arch_function_offset_within_entry(offset))
|
||||
!arch_kprobe_on_func_entry(offset))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
@@ -1909,7 +1920,7 @@ int register_kretprobe(struct kretprobe *rp)
|
||||
int i;
|
||||
void *addr;
|
||||
|
||||
if (!function_offset_within_entry(rp->kp.addr, rp->kp.symbol_name, rp->kp.offset))
|
||||
if (!kprobe_on_func_entry(rp->kp.addr, rp->kp.symbol_name, rp->kp.offset))
|
||||
return -EINVAL;
|
||||
|
||||
if (kretprobe_blacklist_size) {
|
||||
|
||||
+2
-2
@@ -134,7 +134,7 @@ static ssize_t vmcoreinfo_show(struct kobject *kobj,
|
||||
{
|
||||
phys_addr_t vmcore_base = paddr_vmcoreinfo_note();
|
||||
return sprintf(buf, "%pa %x\n", &vmcore_base,
|
||||
(unsigned int)sizeof(vmcoreinfo_note));
|
||||
(unsigned int)VMCOREINFO_NOTE_SIZE);
|
||||
}
|
||||
KERNEL_ATTR_RO(vmcoreinfo);
|
||||
|
||||
@@ -234,7 +234,7 @@ static struct attribute * kernel_attrs[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
static struct attribute_group kernel_attr_group = {
|
||||
static const struct attribute_group kernel_attr_group = {
|
||||
.attrs = kernel_attrs,
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ config LIVEPATCH
|
||||
depends on SYSFS
|
||||
depends on KALLSYMS_ALL
|
||||
depends on HAVE_LIVEPATCH
|
||||
depends on !TRIM_UNUSED_KSYMS
|
||||
help
|
||||
Say Y here if you want to support kernel live patching.
|
||||
This option has no runtime impact until a kernel "patch"
|
||||
|
||||
@@ -59,7 +59,11 @@ static void notrace klp_ftrace_handler(unsigned long ip,
|
||||
|
||||
ops = container_of(fops, struct klp_ops, fops);
|
||||
|
||||
rcu_read_lock();
|
||||
/*
|
||||
* A variant of synchronize_sched() is used to allow patching functions
|
||||
* where RCU is not watching, see klp_synchronize_transition().
|
||||
*/
|
||||
preempt_disable_notrace();
|
||||
|
||||
func = list_first_or_null_rcu(&ops->func_stack, struct klp_func,
|
||||
stack_node);
|
||||
@@ -115,7 +119,7 @@ static void notrace klp_ftrace_handler(unsigned long ip,
|
||||
|
||||
klp_arch_set_pc(regs, (unsigned long)func->new_func);
|
||||
unlock:
|
||||
rcu_read_unlock();
|
||||
preempt_enable_notrace();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -48,6 +48,28 @@ static void klp_transition_work_fn(struct work_struct *work)
|
||||
}
|
||||
static DECLARE_DELAYED_WORK(klp_transition_work, klp_transition_work_fn);
|
||||
|
||||
/*
|
||||
* This function is just a stub to implement a hard force
|
||||
* of synchronize_sched(). This requires synchronizing
|
||||
* tasks even in userspace and idle.
|
||||
*/
|
||||
static void klp_sync(struct work_struct *work)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* We allow to patch also functions where RCU is not watching,
|
||||
* e.g. before user_exit(). We can not rely on the RCU infrastructure
|
||||
* to do the synchronization. Instead hard force the sched synchronization.
|
||||
*
|
||||
* This approach allows to use RCU functions for manipulating func_stack
|
||||
* safely.
|
||||
*/
|
||||
static void klp_synchronize_transition(void)
|
||||
{
|
||||
schedule_on_each_cpu(klp_sync);
|
||||
}
|
||||
|
||||
/*
|
||||
* The transition to the target patch state is complete. Clean up the data
|
||||
* structures.
|
||||
@@ -73,7 +95,7 @@ static void klp_complete_transition(void)
|
||||
* func->transition gets cleared, the handler may choose a
|
||||
* removed function.
|
||||
*/
|
||||
synchronize_rcu();
|
||||
klp_synchronize_transition();
|
||||
}
|
||||
|
||||
if (klp_transition_patch->immediate)
|
||||
@@ -92,7 +114,7 @@ static void klp_complete_transition(void)
|
||||
|
||||
/* Prevent klp_ftrace_handler() from seeing KLP_UNDEFINED state */
|
||||
if (klp_target_state == KLP_PATCHED)
|
||||
synchronize_rcu();
|
||||
klp_synchronize_transition();
|
||||
|
||||
read_lock(&tasklist_lock);
|
||||
for_each_process_thread(g, task) {
|
||||
@@ -136,7 +158,11 @@ void klp_cancel_transition(void)
|
||||
*/
|
||||
void klp_update_patch_state(struct task_struct *task)
|
||||
{
|
||||
rcu_read_lock();
|
||||
/*
|
||||
* A variant of synchronize_sched() is used to allow patching functions
|
||||
* where RCU is not watching, see klp_synchronize_transition().
|
||||
*/
|
||||
preempt_disable_notrace();
|
||||
|
||||
/*
|
||||
* This test_and_clear_tsk_thread_flag() call also serves as a read
|
||||
@@ -153,7 +179,7 @@ void klp_update_patch_state(struct task_struct *task)
|
||||
if (test_and_clear_tsk_thread_flag(task, TIF_PATCH_PENDING))
|
||||
task->patch_state = READ_ONCE(klp_target_state);
|
||||
|
||||
rcu_read_unlock();
|
||||
preempt_enable_notrace();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -539,7 +565,7 @@ void klp_reverse_transition(void)
|
||||
clear_tsk_thread_flag(idle_task(cpu), TIF_PATCH_PENDING);
|
||||
|
||||
/* Let any remaining calls to klp_update_patch_state() complete */
|
||||
synchronize_rcu();
|
||||
klp_synchronize_transition();
|
||||
|
||||
klp_start_transition();
|
||||
}
|
||||
|
||||
+86
-90
@@ -1157,18 +1157,18 @@ print_circular_bug_header(struct lock_list *entry, unsigned int depth,
|
||||
if (debug_locks_silent)
|
||||
return 0;
|
||||
|
||||
printk("\n");
|
||||
pr_warn("\n");
|
||||
pr_warn("======================================================\n");
|
||||
pr_warn("WARNING: possible circular locking dependency detected\n");
|
||||
print_kernel_ident();
|
||||
pr_warn("------------------------------------------------------\n");
|
||||
printk("%s/%d is trying to acquire lock:\n",
|
||||
pr_warn("%s/%d is trying to acquire lock:\n",
|
||||
curr->comm, task_pid_nr(curr));
|
||||
print_lock(check_src);
|
||||
printk("\nbut task is already holding lock:\n");
|
||||
pr_warn("\nbut task is already holding lock:\n");
|
||||
print_lock(check_tgt);
|
||||
printk("\nwhich lock already depends on the new lock.\n\n");
|
||||
printk("\nthe existing dependency chain (in reverse order) is:\n");
|
||||
pr_warn("\nwhich lock already depends on the new lock.\n\n");
|
||||
pr_warn("\nthe existing dependency chain (in reverse order) is:\n");
|
||||
|
||||
print_circular_bug_entry(entry, depth);
|
||||
|
||||
@@ -1495,13 +1495,13 @@ print_bad_irq_dependency(struct task_struct *curr,
|
||||
if (!debug_locks_off_graph_unlock() || debug_locks_silent)
|
||||
return 0;
|
||||
|
||||
printk("\n");
|
||||
pr_warn("\n");
|
||||
pr_warn("=====================================================\n");
|
||||
pr_warn("WARNING: %s-safe -> %s-unsafe lock order detected\n",
|
||||
irqclass, irqclass);
|
||||
print_kernel_ident();
|
||||
pr_warn("-----------------------------------------------------\n");
|
||||
printk("%s/%d [HC%u[%lu]:SC%u[%lu]:HE%u:SE%u] is trying to acquire:\n",
|
||||
pr_warn("%s/%d [HC%u[%lu]:SC%u[%lu]:HE%u:SE%u] is trying to acquire:\n",
|
||||
curr->comm, task_pid_nr(curr),
|
||||
curr->hardirq_context, hardirq_count() >> HARDIRQ_SHIFT,
|
||||
curr->softirq_context, softirq_count() >> SOFTIRQ_SHIFT,
|
||||
@@ -1509,46 +1509,46 @@ print_bad_irq_dependency(struct task_struct *curr,
|
||||
curr->softirqs_enabled);
|
||||
print_lock(next);
|
||||
|
||||
printk("\nand this task is already holding:\n");
|
||||
pr_warn("\nand this task is already holding:\n");
|
||||
print_lock(prev);
|
||||
printk("which would create a new lock dependency:\n");
|
||||
pr_warn("which would create a new lock dependency:\n");
|
||||
print_lock_name(hlock_class(prev));
|
||||
printk(KERN_CONT " ->");
|
||||
pr_cont(" ->");
|
||||
print_lock_name(hlock_class(next));
|
||||
printk(KERN_CONT "\n");
|
||||
pr_cont("\n");
|
||||
|
||||
printk("\nbut this new dependency connects a %s-irq-safe lock:\n",
|
||||
pr_warn("\nbut this new dependency connects a %s-irq-safe lock:\n",
|
||||
irqclass);
|
||||
print_lock_name(backwards_entry->class);
|
||||
printk("\n... which became %s-irq-safe at:\n", irqclass);
|
||||
pr_warn("\n... which became %s-irq-safe at:\n", irqclass);
|
||||
|
||||
print_stack_trace(backwards_entry->class->usage_traces + bit1, 1);
|
||||
|
||||
printk("\nto a %s-irq-unsafe lock:\n", irqclass);
|
||||
pr_warn("\nto a %s-irq-unsafe lock:\n", irqclass);
|
||||
print_lock_name(forwards_entry->class);
|
||||
printk("\n... which became %s-irq-unsafe at:\n", irqclass);
|
||||
printk("...");
|
||||
pr_warn("\n... which became %s-irq-unsafe at:\n", irqclass);
|
||||
pr_warn("...");
|
||||
|
||||
print_stack_trace(forwards_entry->class->usage_traces + bit2, 1);
|
||||
|
||||
printk("\nother info that might help us debug this:\n\n");
|
||||
pr_warn("\nother info that might help us debug this:\n\n");
|
||||
print_irq_lock_scenario(backwards_entry, forwards_entry,
|
||||
hlock_class(prev), hlock_class(next));
|
||||
|
||||
lockdep_print_held_locks(curr);
|
||||
|
||||
printk("\nthe dependencies between %s-irq-safe lock and the holding lock:\n", irqclass);
|
||||
pr_warn("\nthe dependencies between %s-irq-safe lock and the holding lock:\n", irqclass);
|
||||
if (!save_trace(&prev_root->trace))
|
||||
return 0;
|
||||
print_shortest_lock_dependencies(backwards_entry, prev_root);
|
||||
|
||||
printk("\nthe dependencies between the lock to be acquired");
|
||||
printk(" and %s-irq-unsafe lock:\n", irqclass);
|
||||
pr_warn("\nthe dependencies between the lock to be acquired");
|
||||
pr_warn(" and %s-irq-unsafe lock:\n", irqclass);
|
||||
if (!save_trace(&next_root->trace))
|
||||
return 0;
|
||||
print_shortest_lock_dependencies(forwards_entry, next_root);
|
||||
|
||||
printk("\nstack backtrace:\n");
|
||||
pr_warn("\nstack backtrace:\n");
|
||||
dump_stack();
|
||||
|
||||
return 0;
|
||||
@@ -1724,22 +1724,22 @@ print_deadlock_bug(struct task_struct *curr, struct held_lock *prev,
|
||||
if (!debug_locks_off_graph_unlock() || debug_locks_silent)
|
||||
return 0;
|
||||
|
||||
printk("\n");
|
||||
pr_warn("\n");
|
||||
pr_warn("============================================\n");
|
||||
pr_warn("WARNING: possible recursive locking detected\n");
|
||||
print_kernel_ident();
|
||||
pr_warn("--------------------------------------------\n");
|
||||
printk("%s/%d is trying to acquire lock:\n",
|
||||
pr_warn("%s/%d is trying to acquire lock:\n",
|
||||
curr->comm, task_pid_nr(curr));
|
||||
print_lock(next);
|
||||
printk("\nbut task is already holding lock:\n");
|
||||
pr_warn("\nbut task is already holding lock:\n");
|
||||
print_lock(prev);
|
||||
|
||||
printk("\nother info that might help us debug this:\n");
|
||||
pr_warn("\nother info that might help us debug this:\n");
|
||||
print_deadlock_scenario(next, prev);
|
||||
lockdep_print_held_locks(curr);
|
||||
|
||||
printk("\nstack backtrace:\n");
|
||||
pr_warn("\nstack backtrace:\n");
|
||||
dump_stack();
|
||||
|
||||
return 0;
|
||||
@@ -2074,21 +2074,21 @@ static void print_collision(struct task_struct *curr,
|
||||
struct held_lock *hlock_next,
|
||||
struct lock_chain *chain)
|
||||
{
|
||||
printk("\n");
|
||||
pr_warn("\n");
|
||||
pr_warn("============================\n");
|
||||
pr_warn("WARNING: chain_key collision\n");
|
||||
print_kernel_ident();
|
||||
pr_warn("----------------------------\n");
|
||||
printk("%s/%d: ", current->comm, task_pid_nr(current));
|
||||
printk("Hash chain already cached but the contents don't match!\n");
|
||||
pr_warn("%s/%d: ", current->comm, task_pid_nr(current));
|
||||
pr_warn("Hash chain already cached but the contents don't match!\n");
|
||||
|
||||
printk("Held locks:");
|
||||
pr_warn("Held locks:");
|
||||
print_chain_keys_held_locks(curr, hlock_next);
|
||||
|
||||
printk("Locks in cached chain:");
|
||||
pr_warn("Locks in cached chain:");
|
||||
print_chain_keys_chain(chain);
|
||||
|
||||
printk("\nstack backtrace:\n");
|
||||
pr_warn("\nstack backtrace:\n");
|
||||
dump_stack();
|
||||
}
|
||||
#endif
|
||||
@@ -2373,16 +2373,16 @@ print_usage_bug(struct task_struct *curr, struct held_lock *this,
|
||||
if (!debug_locks_off_graph_unlock() || debug_locks_silent)
|
||||
return 0;
|
||||
|
||||
printk("\n");
|
||||
pr_warn("\n");
|
||||
pr_warn("================================\n");
|
||||
pr_warn("WARNING: inconsistent lock state\n");
|
||||
print_kernel_ident();
|
||||
pr_warn("--------------------------------\n");
|
||||
|
||||
printk("inconsistent {%s} -> {%s} usage.\n",
|
||||
pr_warn("inconsistent {%s} -> {%s} usage.\n",
|
||||
usage_str[prev_bit], usage_str[new_bit]);
|
||||
|
||||
printk("%s/%d [HC%u[%lu]:SC%u[%lu]:HE%u:SE%u] takes:\n",
|
||||
pr_warn("%s/%d [HC%u[%lu]:SC%u[%lu]:HE%u:SE%u] takes:\n",
|
||||
curr->comm, task_pid_nr(curr),
|
||||
trace_hardirq_context(curr), hardirq_count() >> HARDIRQ_SHIFT,
|
||||
trace_softirq_context(curr), softirq_count() >> SOFTIRQ_SHIFT,
|
||||
@@ -2390,16 +2390,16 @@ print_usage_bug(struct task_struct *curr, struct held_lock *this,
|
||||
trace_softirqs_enabled(curr));
|
||||
print_lock(this);
|
||||
|
||||
printk("{%s} state was registered at:\n", usage_str[prev_bit]);
|
||||
pr_warn("{%s} state was registered at:\n", usage_str[prev_bit]);
|
||||
print_stack_trace(hlock_class(this)->usage_traces + prev_bit, 1);
|
||||
|
||||
print_irqtrace_events(curr);
|
||||
printk("\nother info that might help us debug this:\n");
|
||||
pr_warn("\nother info that might help us debug this:\n");
|
||||
print_usage_bug_scenario(this);
|
||||
|
||||
lockdep_print_held_locks(curr);
|
||||
|
||||
printk("\nstack backtrace:\n");
|
||||
pr_warn("\nstack backtrace:\n");
|
||||
dump_stack();
|
||||
|
||||
return 0;
|
||||
@@ -2438,28 +2438,28 @@ print_irq_inversion_bug(struct task_struct *curr,
|
||||
if (!debug_locks_off_graph_unlock() || debug_locks_silent)
|
||||
return 0;
|
||||
|
||||
printk("\n");
|
||||
pr_warn("\n");
|
||||
pr_warn("========================================================\n");
|
||||
pr_warn("WARNING: possible irq lock inversion dependency detected\n");
|
||||
print_kernel_ident();
|
||||
pr_warn("--------------------------------------------------------\n");
|
||||
printk("%s/%d just changed the state of lock:\n",
|
||||
pr_warn("%s/%d just changed the state of lock:\n",
|
||||
curr->comm, task_pid_nr(curr));
|
||||
print_lock(this);
|
||||
if (forwards)
|
||||
printk("but this lock took another, %s-unsafe lock in the past:\n", irqclass);
|
||||
pr_warn("but this lock took another, %s-unsafe lock in the past:\n", irqclass);
|
||||
else
|
||||
printk("but this lock was taken by another, %s-safe lock in the past:\n", irqclass);
|
||||
pr_warn("but this lock was taken by another, %s-safe lock in the past:\n", irqclass);
|
||||
print_lock_name(other->class);
|
||||
printk("\n\nand interrupts could create inverse lock ordering between them.\n\n");
|
||||
pr_warn("\n\nand interrupts could create inverse lock ordering between them.\n\n");
|
||||
|
||||
printk("\nother info that might help us debug this:\n");
|
||||
pr_warn("\nother info that might help us debug this:\n");
|
||||
|
||||
/* Find a middle lock (if one exists) */
|
||||
depth = get_lock_depth(other);
|
||||
do {
|
||||
if (depth == 0 && (entry != root)) {
|
||||
printk("lockdep:%s bad path found in chain graph\n", __func__);
|
||||
pr_warn("lockdep:%s bad path found in chain graph\n", __func__);
|
||||
break;
|
||||
}
|
||||
middle = entry;
|
||||
@@ -2475,12 +2475,12 @@ print_irq_inversion_bug(struct task_struct *curr,
|
||||
|
||||
lockdep_print_held_locks(curr);
|
||||
|
||||
printk("\nthe shortest dependencies between 2nd lock and 1st lock:\n");
|
||||
pr_warn("\nthe shortest dependencies between 2nd lock and 1st lock:\n");
|
||||
if (!save_trace(&root->trace))
|
||||
return 0;
|
||||
print_shortest_lock_dependencies(other, root);
|
||||
|
||||
printk("\nstack backtrace:\n");
|
||||
pr_warn("\nstack backtrace:\n");
|
||||
dump_stack();
|
||||
|
||||
return 0;
|
||||
@@ -3189,25 +3189,25 @@ print_lock_nested_lock_not_held(struct task_struct *curr,
|
||||
if (debug_locks_silent)
|
||||
return 0;
|
||||
|
||||
printk("\n");
|
||||
pr_warn("\n");
|
||||
pr_warn("==================================\n");
|
||||
pr_warn("WARNING: Nested lock was not taken\n");
|
||||
print_kernel_ident();
|
||||
pr_warn("----------------------------------\n");
|
||||
|
||||
printk("%s/%d is trying to lock:\n", curr->comm, task_pid_nr(curr));
|
||||
pr_warn("%s/%d is trying to lock:\n", curr->comm, task_pid_nr(curr));
|
||||
print_lock(hlock);
|
||||
|
||||
printk("\nbut this task is not holding:\n");
|
||||
printk("%s\n", hlock->nest_lock->name);
|
||||
pr_warn("\nbut this task is not holding:\n");
|
||||
pr_warn("%s\n", hlock->nest_lock->name);
|
||||
|
||||
printk("\nstack backtrace:\n");
|
||||
pr_warn("\nstack backtrace:\n");
|
||||
dump_stack();
|
||||
|
||||
printk("\nother info that might help us debug this:\n");
|
||||
pr_warn("\nother info that might help us debug this:\n");
|
||||
lockdep_print_held_locks(curr);
|
||||
|
||||
printk("\nstack backtrace:\n");
|
||||
pr_warn("\nstack backtrace:\n");
|
||||
dump_stack();
|
||||
|
||||
return 0;
|
||||
@@ -3402,21 +3402,21 @@ print_unlock_imbalance_bug(struct task_struct *curr, struct lockdep_map *lock,
|
||||
if (debug_locks_silent)
|
||||
return 0;
|
||||
|
||||
printk("\n");
|
||||
pr_warn("\n");
|
||||
pr_warn("=====================================\n");
|
||||
pr_warn("WARNING: bad unlock balance detected!\n");
|
||||
print_kernel_ident();
|
||||
pr_warn("-------------------------------------\n");
|
||||
printk("%s/%d is trying to release lock (",
|
||||
pr_warn("%s/%d is trying to release lock (",
|
||||
curr->comm, task_pid_nr(curr));
|
||||
print_lockdep_cache(lock);
|
||||
printk(KERN_CONT ") at:\n");
|
||||
pr_cont(") at:\n");
|
||||
print_ip_sym(ip);
|
||||
printk("but there are no more locks to release!\n");
|
||||
printk("\nother info that might help us debug this:\n");
|
||||
pr_warn("but there are no more locks to release!\n");
|
||||
pr_warn("\nother info that might help us debug this:\n");
|
||||
lockdep_print_held_locks(curr);
|
||||
|
||||
printk("\nstack backtrace:\n");
|
||||
pr_warn("\nstack backtrace:\n");
|
||||
dump_stack();
|
||||
|
||||
return 0;
|
||||
@@ -3974,21 +3974,21 @@ print_lock_contention_bug(struct task_struct *curr, struct lockdep_map *lock,
|
||||
if (debug_locks_silent)
|
||||
return 0;
|
||||
|
||||
printk("\n");
|
||||
pr_warn("\n");
|
||||
pr_warn("=================================\n");
|
||||
pr_warn("WARNING: bad contention detected!\n");
|
||||
print_kernel_ident();
|
||||
pr_warn("---------------------------------\n");
|
||||
printk("%s/%d is trying to contend lock (",
|
||||
pr_warn("%s/%d is trying to contend lock (",
|
||||
curr->comm, task_pid_nr(curr));
|
||||
print_lockdep_cache(lock);
|
||||
printk(KERN_CONT ") at:\n");
|
||||
pr_cont(") at:\n");
|
||||
print_ip_sym(ip);
|
||||
printk("but there are no locks held!\n");
|
||||
printk("\nother info that might help us debug this:\n");
|
||||
pr_warn("but there are no locks held!\n");
|
||||
pr_warn("\nother info that might help us debug this:\n");
|
||||
lockdep_print_held_locks(curr);
|
||||
|
||||
printk("\nstack backtrace:\n");
|
||||
pr_warn("\nstack backtrace:\n");
|
||||
dump_stack();
|
||||
|
||||
return 0;
|
||||
@@ -4318,17 +4318,17 @@ print_freed_lock_bug(struct task_struct *curr, const void *mem_from,
|
||||
if (debug_locks_silent)
|
||||
return;
|
||||
|
||||
printk("\n");
|
||||
pr_warn("\n");
|
||||
pr_warn("=========================\n");
|
||||
pr_warn("WARNING: held lock freed!\n");
|
||||
print_kernel_ident();
|
||||
pr_warn("-------------------------\n");
|
||||
printk("%s/%d is freeing memory %p-%p, with a lock still held there!\n",
|
||||
pr_warn("%s/%d is freeing memory %p-%p, with a lock still held there!\n",
|
||||
curr->comm, task_pid_nr(curr), mem_from, mem_to-1);
|
||||
print_lock(hlock);
|
||||
lockdep_print_held_locks(curr);
|
||||
|
||||
printk("\nstack backtrace:\n");
|
||||
pr_warn("\nstack backtrace:\n");
|
||||
dump_stack();
|
||||
}
|
||||
|
||||
@@ -4376,14 +4376,14 @@ static void print_held_locks_bug(void)
|
||||
if (debug_locks_silent)
|
||||
return;
|
||||
|
||||
printk("\n");
|
||||
pr_warn("\n");
|
||||
pr_warn("====================================\n");
|
||||
pr_warn("WARNING: %s/%d still has locks held!\n",
|
||||
current->comm, task_pid_nr(current));
|
||||
print_kernel_ident();
|
||||
pr_warn("------------------------------------\n");
|
||||
lockdep_print_held_locks(current);
|
||||
printk("\nstack backtrace:\n");
|
||||
pr_warn("\nstack backtrace:\n");
|
||||
dump_stack();
|
||||
}
|
||||
|
||||
@@ -4402,10 +4402,10 @@ void debug_show_all_locks(void)
|
||||
int unlock = 1;
|
||||
|
||||
if (unlikely(!debug_locks)) {
|
||||
printk("INFO: lockdep is turned off.\n");
|
||||
pr_warn("INFO: lockdep is turned off.\n");
|
||||
return;
|
||||
}
|
||||
printk("\nShowing all locks held in the system:\n");
|
||||
pr_warn("\nShowing all locks held in the system:\n");
|
||||
|
||||
/*
|
||||
* Here we try to get the tasklist_lock as hard as possible,
|
||||
@@ -4416,18 +4416,18 @@ void debug_show_all_locks(void)
|
||||
retry:
|
||||
if (!read_trylock(&tasklist_lock)) {
|
||||
if (count == 10)
|
||||
printk("hm, tasklist_lock locked, retrying... ");
|
||||
pr_warn("hm, tasklist_lock locked, retrying... ");
|
||||
if (count) {
|
||||
count--;
|
||||
printk(" #%d", 10-count);
|
||||
pr_cont(" #%d", 10-count);
|
||||
mdelay(200);
|
||||
goto retry;
|
||||
}
|
||||
printk(" ignoring it.\n");
|
||||
pr_cont(" ignoring it.\n");
|
||||
unlock = 0;
|
||||
} else {
|
||||
if (count != 10)
|
||||
printk(KERN_CONT " locked it.\n");
|
||||
pr_cont(" locked it.\n");
|
||||
}
|
||||
|
||||
do_each_thread(g, p) {
|
||||
@@ -4445,7 +4445,7 @@ retry:
|
||||
unlock = 1;
|
||||
} while_each_thread(g, p);
|
||||
|
||||
printk("\n");
|
||||
pr_warn("\n");
|
||||
pr_warn("=============================================\n\n");
|
||||
|
||||
if (unlock)
|
||||
@@ -4475,12 +4475,12 @@ asmlinkage __visible void lockdep_sys_exit(void)
|
||||
if (unlikely(curr->lockdep_depth)) {
|
||||
if (!debug_locks_off())
|
||||
return;
|
||||
printk("\n");
|
||||
pr_warn("\n");
|
||||
pr_warn("================================================\n");
|
||||
pr_warn("WARNING: lock held when returning to user space!\n");
|
||||
print_kernel_ident();
|
||||
pr_warn("------------------------------------------------\n");
|
||||
printk("%s/%d is leaving the kernel with locks still held!\n",
|
||||
pr_warn("%s/%d is leaving the kernel with locks still held!\n",
|
||||
curr->comm, curr->pid);
|
||||
lockdep_print_held_locks(curr);
|
||||
}
|
||||
@@ -4490,19 +4490,15 @@ void lockdep_rcu_suspicious(const char *file, const int line, const char *s)
|
||||
{
|
||||
struct task_struct *curr = current;
|
||||
|
||||
#ifndef CONFIG_PROVE_RCU_REPEATEDLY
|
||||
if (!debug_locks_off())
|
||||
return;
|
||||
#endif /* #ifdef CONFIG_PROVE_RCU_REPEATEDLY */
|
||||
/* Note: the following can be executed concurrently, so be careful. */
|
||||
printk("\n");
|
||||
pr_warn("\n");
|
||||
pr_warn("=============================\n");
|
||||
pr_warn("WARNING: suspicious RCU usage\n");
|
||||
print_kernel_ident();
|
||||
pr_warn("-----------------------------\n");
|
||||
printk("%s:%d %s!\n", file, line, s);
|
||||
printk("\nother info that might help us debug this:\n\n");
|
||||
printk("\n%srcu_scheduler_active = %d, debug_locks = %d\n",
|
||||
pr_warn("%s:%d %s!\n", file, line, s);
|
||||
pr_warn("\nother info that might help us debug this:\n\n");
|
||||
pr_warn("\n%srcu_scheduler_active = %d, debug_locks = %d\n",
|
||||
!rcu_lockdep_current_cpu_online()
|
||||
? "RCU used illegally from offline CPU!\n"
|
||||
: !rcu_is_watching()
|
||||
@@ -4529,10 +4525,10 @@ void lockdep_rcu_suspicious(const char *file, const int line, const char *s)
|
||||
* rcu_read_lock_bh() and so on from extended quiescent states.
|
||||
*/
|
||||
if (!rcu_is_watching())
|
||||
printk("RCU used illegally from extended quiescent state!\n");
|
||||
pr_warn("RCU used illegally from extended quiescent state!\n");
|
||||
|
||||
lockdep_print_held_locks(curr);
|
||||
printk("\nstack backtrace:\n");
|
||||
pr_warn("\nstack backtrace:\n");
|
||||
dump_stack();
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(lockdep_rcu_suspicious);
|
||||
|
||||
@@ -227,9 +227,9 @@ static void __sched __mutex_lock_slowpath(struct mutex *lock);
|
||||
* (or statically defined) before it can be locked. memset()-ing
|
||||
* the mutex to 0 is not allowed.
|
||||
*
|
||||
* ( The CONFIG_DEBUG_MUTEXES .config option turns on debugging
|
||||
* checks that will enforce the restrictions and will also do
|
||||
* deadlock debugging. )
|
||||
* (The CONFIG_DEBUG_MUTEXES .config option turns on debugging
|
||||
* checks that will enforce the restrictions and will also do
|
||||
* deadlock debugging)
|
||||
*
|
||||
* This function is similar to (but not equivalent to) down().
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/percpu.h>
|
||||
#include <linux/hardirq.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <asm/qrwlock.h>
|
||||
|
||||
/*
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <linux/percpu.h>
|
||||
#include <linux/hardirq.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/prefetch.h>
|
||||
#include <asm/byteorder.h>
|
||||
#include <asm/qspinlock.h>
|
||||
|
||||
|
||||
@@ -193,7 +193,8 @@ void __init __pv_init_lock_hash(void)
|
||||
*/
|
||||
pv_lock_hash = alloc_large_system_hash("PV qspinlock",
|
||||
sizeof(struct pv_hash_entry),
|
||||
pv_hash_size, 0, HASH_EARLY,
|
||||
pv_hash_size, 0,
|
||||
HASH_EARLY | HASH_ZERO,
|
||||
&pv_lock_hash_bits, NULL,
|
||||
pv_hash_size, pv_hash_size);
|
||||
}
|
||||
|
||||
@@ -166,12 +166,16 @@ void debug_rt_mutex_free_waiter(struct rt_mutex_waiter *waiter)
|
||||
memset(waiter, 0x22, sizeof(*waiter));
|
||||
}
|
||||
|
||||
void debug_rt_mutex_init(struct rt_mutex *lock, const char *name)
|
||||
void debug_rt_mutex_init(struct rt_mutex *lock, const char *name, struct lock_class_key *key)
|
||||
{
|
||||
/*
|
||||
* Make sure we are not reinitializing a held lock:
|
||||
*/
|
||||
debug_check_no_locks_freed((void *)lock, sizeof(*lock));
|
||||
lock->name = name;
|
||||
|
||||
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
||||
lockdep_init_map(&lock->dep_map, name, key, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
extern void debug_rt_mutex_init_waiter(struct rt_mutex_waiter *waiter);
|
||||
extern void debug_rt_mutex_free_waiter(struct rt_mutex_waiter *waiter);
|
||||
extern void debug_rt_mutex_init(struct rt_mutex *lock, const char *name);
|
||||
extern void debug_rt_mutex_init(struct rt_mutex *lock, const char *name, struct lock_class_key *key);
|
||||
extern void debug_rt_mutex_lock(struct rt_mutex *lock);
|
||||
extern void debug_rt_mutex_unlock(struct rt_mutex *lock);
|
||||
extern void debug_rt_mutex_proxy_lock(struct rt_mutex *lock,
|
||||
|
||||
+48
-14
@@ -963,7 +963,6 @@ static int task_blocks_on_rt_mutex(struct rt_mutex *lock,
|
||||
return -EDEADLK;
|
||||
|
||||
raw_spin_lock(&task->pi_lock);
|
||||
rt_mutex_adjust_prio(task);
|
||||
waiter->task = task;
|
||||
waiter->lock = lock;
|
||||
waiter->prio = task->prio;
|
||||
@@ -1481,6 +1480,7 @@ void __sched rt_mutex_lock(struct rt_mutex *lock)
|
||||
{
|
||||
might_sleep();
|
||||
|
||||
mutex_acquire(&lock->dep_map, 0, 0, _RET_IP_);
|
||||
rt_mutex_fastlock(lock, TASK_UNINTERRUPTIBLE, rt_mutex_slowlock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rt_mutex_lock);
|
||||
@@ -1496,9 +1496,16 @@ EXPORT_SYMBOL_GPL(rt_mutex_lock);
|
||||
*/
|
||||
int __sched rt_mutex_lock_interruptible(struct rt_mutex *lock)
|
||||
{
|
||||
int ret;
|
||||
|
||||
might_sleep();
|
||||
|
||||
return rt_mutex_fastlock(lock, TASK_INTERRUPTIBLE, rt_mutex_slowlock);
|
||||
mutex_acquire(&lock->dep_map, 0, 0, _RET_IP_);
|
||||
ret = rt_mutex_fastlock(lock, TASK_INTERRUPTIBLE, rt_mutex_slowlock);
|
||||
if (ret)
|
||||
mutex_release(&lock->dep_map, 1, _RET_IP_);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rt_mutex_lock_interruptible);
|
||||
|
||||
@@ -1526,11 +1533,18 @@ int __sched rt_mutex_futex_trylock(struct rt_mutex *lock)
|
||||
int
|
||||
rt_mutex_timed_lock(struct rt_mutex *lock, struct hrtimer_sleeper *timeout)
|
||||
{
|
||||
int ret;
|
||||
|
||||
might_sleep();
|
||||
|
||||
return rt_mutex_timed_fastlock(lock, TASK_INTERRUPTIBLE, timeout,
|
||||
mutex_acquire(&lock->dep_map, 0, 0, _RET_IP_);
|
||||
ret = rt_mutex_timed_fastlock(lock, TASK_INTERRUPTIBLE, timeout,
|
||||
RT_MUTEX_MIN_CHAINWALK,
|
||||
rt_mutex_slowlock);
|
||||
if (ret)
|
||||
mutex_release(&lock->dep_map, 1, _RET_IP_);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rt_mutex_timed_lock);
|
||||
|
||||
@@ -1547,10 +1561,16 @@ EXPORT_SYMBOL_GPL(rt_mutex_timed_lock);
|
||||
*/
|
||||
int __sched rt_mutex_trylock(struct rt_mutex *lock)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (WARN_ON_ONCE(in_irq() || in_nmi() || in_serving_softirq()))
|
||||
return 0;
|
||||
|
||||
return rt_mutex_fasttrylock(lock, rt_mutex_slowtrylock);
|
||||
ret = rt_mutex_fasttrylock(lock, rt_mutex_slowtrylock);
|
||||
if (ret)
|
||||
mutex_acquire(&lock->dep_map, 0, 1, _RET_IP_);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rt_mutex_trylock);
|
||||
|
||||
@@ -1561,6 +1581,7 @@ EXPORT_SYMBOL_GPL(rt_mutex_trylock);
|
||||
*/
|
||||
void __sched rt_mutex_unlock(struct rt_mutex *lock)
|
||||
{
|
||||
mutex_release(&lock->dep_map, 1, _RET_IP_);
|
||||
rt_mutex_fastunlock(lock, rt_mutex_slowunlock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rt_mutex_unlock);
|
||||
@@ -1620,7 +1641,6 @@ void rt_mutex_destroy(struct rt_mutex *lock)
|
||||
lock->magic = NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL_GPL(rt_mutex_destroy);
|
||||
|
||||
/**
|
||||
@@ -1632,14 +1652,16 @@ EXPORT_SYMBOL_GPL(rt_mutex_destroy);
|
||||
*
|
||||
* Initializing of a locked rt lock is not allowed
|
||||
*/
|
||||
void __rt_mutex_init(struct rt_mutex *lock, const char *name)
|
||||
void __rt_mutex_init(struct rt_mutex *lock, const char *name,
|
||||
struct lock_class_key *key)
|
||||
{
|
||||
lock->owner = NULL;
|
||||
raw_spin_lock_init(&lock->wait_lock);
|
||||
lock->waiters = RB_ROOT;
|
||||
lock->waiters_leftmost = NULL;
|
||||
|
||||
debug_rt_mutex_init(lock, name);
|
||||
if (name && key)
|
||||
debug_rt_mutex_init(lock, name, key);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__rt_mutex_init);
|
||||
|
||||
@@ -1660,7 +1682,7 @@ EXPORT_SYMBOL_GPL(__rt_mutex_init);
|
||||
void rt_mutex_init_proxy_locked(struct rt_mutex *lock,
|
||||
struct task_struct *proxy_owner)
|
||||
{
|
||||
__rt_mutex_init(lock, NULL);
|
||||
__rt_mutex_init(lock, NULL, NULL);
|
||||
debug_rt_mutex_proxy_lock(lock, proxy_owner);
|
||||
rt_mutex_set_owner(lock, proxy_owner);
|
||||
}
|
||||
@@ -1785,12 +1807,14 @@ int rt_mutex_wait_proxy_lock(struct rt_mutex *lock,
|
||||
int ret;
|
||||
|
||||
raw_spin_lock_irq(&lock->wait_lock);
|
||||
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
|
||||
/* sleep on the mutex */
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
ret = __rt_mutex_slowlock(lock, TASK_INTERRUPTIBLE, to, waiter);
|
||||
|
||||
/*
|
||||
* try_to_take_rt_mutex() sets the waiter bit unconditionally. We might
|
||||
* have to fix that up.
|
||||
*/
|
||||
fixup_rt_mutex_waiters(lock);
|
||||
raw_spin_unlock_irq(&lock->wait_lock);
|
||||
|
||||
return ret;
|
||||
@@ -1821,16 +1845,26 @@ bool rt_mutex_cleanup_proxy_lock(struct rt_mutex *lock,
|
||||
bool cleanup = false;
|
||||
|
||||
raw_spin_lock_irq(&lock->wait_lock);
|
||||
/*
|
||||
* Do an unconditional try-lock, this deals with the lock stealing
|
||||
* state where __rt_mutex_futex_unlock() -> mark_wakeup_next_waiter()
|
||||
* sets a NULL owner.
|
||||
*
|
||||
* We're not interested in the return value, because the subsequent
|
||||
* test on rt_mutex_owner() will infer that. If the trylock succeeded,
|
||||
* we will own the lock and it will have removed the waiter. If we
|
||||
* failed the trylock, we're still not owner and we need to remove
|
||||
* ourselves.
|
||||
*/
|
||||
try_to_take_rt_mutex(lock, current, waiter);
|
||||
/*
|
||||
* Unless we're the owner; we're still enqueued on the wait_list.
|
||||
* So check if we became owner, if not, take us off the wait_list.
|
||||
*/
|
||||
if (rt_mutex_owner(lock) != current) {
|
||||
remove_waiter(lock, waiter);
|
||||
fixup_rt_mutex_waiters(lock);
|
||||
cleanup = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* try_to_take_rt_mutex() sets the waiter bit unconditionally. We might
|
||||
* have to fix that up.
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#define debug_rt_mutex_proxy_lock(l,p) do { } while (0)
|
||||
#define debug_rt_mutex_proxy_unlock(l) do { } while (0)
|
||||
#define debug_rt_mutex_unlock(l) do { } while (0)
|
||||
#define debug_rt_mutex_init(m, n) do { } while (0)
|
||||
#define debug_rt_mutex_init(m, n, k) do { } while (0)
|
||||
#define debug_rt_mutex_deadlock(d, a ,l) do { } while (0)
|
||||
#define debug_rt_mutex_print_deadlock(w) do { } while (0)
|
||||
#define debug_rt_mutex_reset_waiter(w) do { } while (0)
|
||||
|
||||
@@ -231,8 +231,8 @@ int __sched __down_write_common(struct rw_semaphore *sem, int state)
|
||||
|
||||
out_nolock:
|
||||
list_del(&waiter.list);
|
||||
if (!list_empty(&sem->wait_list))
|
||||
__rwsem_do_wake(sem, 1);
|
||||
if (!list_empty(&sem->wait_list) && sem->count >= 0)
|
||||
__rwsem_do_wake(sem, 0);
|
||||
raw_spin_unlock_irqrestore(&sem->wait_lock, flags);
|
||||
|
||||
return -EINTR;
|
||||
|
||||
+5
-1
@@ -358,7 +358,11 @@ void *devm_memremap_pages(struct device *dev, struct resource *res,
|
||||
goto err_pfn_remap;
|
||||
|
||||
mem_hotplug_begin();
|
||||
error = arch_add_memory(nid, align_start, align_size, true);
|
||||
error = arch_add_memory(nid, align_start, align_size, false);
|
||||
if (!error)
|
||||
move_pfn_range_to_zone(&NODE_DATA(nid)->node_zones[ZONE_DEVICE],
|
||||
align_start >> PAGE_SHIFT,
|
||||
align_size >> PAGE_SHIFT);
|
||||
mem_hotplug_done();
|
||||
if (error)
|
||||
goto err_add_memory;
|
||||
|
||||
+67
-49
@@ -49,9 +49,7 @@
|
||||
#include <linux/rculist.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <asm/cacheflush.h>
|
||||
#ifdef CONFIG_STRICT_MODULE_RWX
|
||||
#include <asm/set_memory.h>
|
||||
#endif
|
||||
#include <linux/set_memory.h>
|
||||
#include <asm/mmu_context.h>
|
||||
#include <linux/license.h>
|
||||
#include <asm/sections.h>
|
||||
@@ -302,6 +300,7 @@ int unregister_module_notifier(struct notifier_block *nb)
|
||||
EXPORT_SYMBOL(unregister_module_notifier);
|
||||
|
||||
struct load_info {
|
||||
const char *name;
|
||||
Elf_Ehdr *hdr;
|
||||
unsigned long len;
|
||||
Elf_Shdr *sechdrs;
|
||||
@@ -602,7 +601,7 @@ static struct module *find_module_all(const char *name, size_t len,
|
||||
|
||||
module_assert_mutex_or_preempt();
|
||||
|
||||
list_for_each_entry(mod, &modules, list) {
|
||||
list_for_each_entry_rcu(mod, &modules, list) {
|
||||
if (!even_unformed && mod->state == MODULE_STATE_UNFORMED)
|
||||
continue;
|
||||
if (strlen(mod->name) == len && !memcmp(mod->name, name, len))
|
||||
@@ -1202,10 +1201,7 @@ static ssize_t store_uevent(struct module_attribute *mattr,
|
||||
struct module_kobject *mk,
|
||||
const char *buffer, size_t count)
|
||||
{
|
||||
enum kobject_action action;
|
||||
|
||||
if (kobject_action_type(buffer, count, &action) == 0)
|
||||
kobject_uevent(&mk->kobj, action);
|
||||
kobject_synth_uevent(&mk->kobj, buffer, count);
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -1278,12 +1274,13 @@ static u32 resolve_rel_crc(const s32 *crc)
|
||||
return *(u32 *)((void *)crc + *crc);
|
||||
}
|
||||
|
||||
static int check_version(Elf_Shdr *sechdrs,
|
||||
unsigned int versindex,
|
||||
static int check_version(const struct load_info *info,
|
||||
const char *symname,
|
||||
struct module *mod,
|
||||
const s32 *crc)
|
||||
{
|
||||
Elf_Shdr *sechdrs = info->sechdrs;
|
||||
unsigned int versindex = info->index.vers;
|
||||
unsigned int i, num_versions;
|
||||
struct modversion_info *versions;
|
||||
|
||||
@@ -1317,17 +1314,16 @@ static int check_version(Elf_Shdr *sechdrs,
|
||||
}
|
||||
|
||||
/* Broken toolchain. Warn once, then let it go.. */
|
||||
pr_warn_once("%s: no symbol version for %s\n", mod->name, symname);
|
||||
pr_warn_once("%s: no symbol version for %s\n", info->name, symname);
|
||||
return 1;
|
||||
|
||||
bad_version:
|
||||
pr_warn("%s: disagrees about version of symbol %s\n",
|
||||
mod->name, symname);
|
||||
info->name, symname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int check_modstruct_version(Elf_Shdr *sechdrs,
|
||||
unsigned int versindex,
|
||||
static inline int check_modstruct_version(const struct load_info *info,
|
||||
struct module *mod)
|
||||
{
|
||||
const s32 *crc;
|
||||
@@ -1343,8 +1339,8 @@ static inline int check_modstruct_version(Elf_Shdr *sechdrs,
|
||||
BUG();
|
||||
}
|
||||
preempt_enable();
|
||||
return check_version(sechdrs, versindex,
|
||||
VMLINUX_SYMBOL_STR(module_layout), mod, crc);
|
||||
return check_version(info, VMLINUX_SYMBOL_STR(module_layout),
|
||||
mod, crc);
|
||||
}
|
||||
|
||||
/* First part is kernel version, which we ignore if module has crcs. */
|
||||
@@ -1358,8 +1354,7 @@ static inline int same_magic(const char *amagic, const char *bmagic,
|
||||
return strcmp(amagic, bmagic) == 0;
|
||||
}
|
||||
#else
|
||||
static inline int check_version(Elf_Shdr *sechdrs,
|
||||
unsigned int versindex,
|
||||
static inline int check_version(const struct load_info *info,
|
||||
const char *symname,
|
||||
struct module *mod,
|
||||
const s32 *crc)
|
||||
@@ -1367,8 +1362,7 @@ static inline int check_version(Elf_Shdr *sechdrs,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline int check_modstruct_version(Elf_Shdr *sechdrs,
|
||||
unsigned int versindex,
|
||||
static inline int check_modstruct_version(const struct load_info *info,
|
||||
struct module *mod)
|
||||
{
|
||||
return 1;
|
||||
@@ -1404,7 +1398,7 @@ static const struct kernel_symbol *resolve_symbol(struct module *mod,
|
||||
if (!sym)
|
||||
goto unlock;
|
||||
|
||||
if (!check_version(info->sechdrs, info->index.vers, name, mod, crc)) {
|
||||
if (!check_version(info, name, mod, crc)) {
|
||||
sym = ERR_PTR(-EINVAL);
|
||||
goto getname;
|
||||
}
|
||||
@@ -1667,21 +1661,6 @@ static inline void remove_notes_attrs(struct module *mod)
|
||||
}
|
||||
#endif /* CONFIG_KALLSYMS */
|
||||
|
||||
static void add_usage_links(struct module *mod)
|
||||
{
|
||||
#ifdef CONFIG_MODULE_UNLOAD
|
||||
struct module_use *use;
|
||||
int nowarn;
|
||||
|
||||
mutex_lock(&module_mutex);
|
||||
list_for_each_entry(use, &mod->target_list, target_list) {
|
||||
nowarn = sysfs_create_link(use->target->holders_dir,
|
||||
&mod->mkobj.kobj, mod->name);
|
||||
}
|
||||
mutex_unlock(&module_mutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void del_usage_links(struct module *mod)
|
||||
{
|
||||
#ifdef CONFIG_MODULE_UNLOAD
|
||||
@@ -1694,6 +1673,26 @@ static void del_usage_links(struct module *mod)
|
||||
#endif
|
||||
}
|
||||
|
||||
static int add_usage_links(struct module *mod)
|
||||
{
|
||||
int ret = 0;
|
||||
#ifdef CONFIG_MODULE_UNLOAD
|
||||
struct module_use *use;
|
||||
|
||||
mutex_lock(&module_mutex);
|
||||
list_for_each_entry(use, &mod->target_list, target_list) {
|
||||
ret = sysfs_create_link(use->target->holders_dir,
|
||||
&mod->mkobj.kobj, mod->name);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
mutex_unlock(&module_mutex);
|
||||
if (ret)
|
||||
del_usage_links(mod);
|
||||
#endif
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int module_add_modinfo_attrs(struct module *mod)
|
||||
{
|
||||
struct module_attribute *attr;
|
||||
@@ -1802,13 +1801,18 @@ static int mod_sysfs_setup(struct module *mod,
|
||||
if (err)
|
||||
goto out_unreg_param;
|
||||
|
||||
add_usage_links(mod);
|
||||
err = add_usage_links(mod);
|
||||
if (err)
|
||||
goto out_unreg_modinfo_attrs;
|
||||
|
||||
add_sect_attrs(mod, info);
|
||||
add_notes_attrs(mod, info);
|
||||
|
||||
kobject_uevent(&mod->mkobj.kobj, KOBJ_ADD);
|
||||
return 0;
|
||||
|
||||
out_unreg_modinfo_attrs:
|
||||
module_remove_modinfo_attrs(mod);
|
||||
out_unreg_param:
|
||||
module_param_sysfs_remove(mod);
|
||||
out_unreg_holders:
|
||||
@@ -2915,9 +2919,15 @@ static int rewrite_section_headers(struct load_info *info, int flags)
|
||||
info->index.vers = 0; /* Pretend no __versions section! */
|
||||
else
|
||||
info->index.vers = find_sec(info, "__versions");
|
||||
info->index.info = find_sec(info, ".modinfo");
|
||||
info->sechdrs[info->index.info].sh_flags &= ~(unsigned long)SHF_ALLOC;
|
||||
info->sechdrs[info->index.vers].sh_flags &= ~(unsigned long)SHF_ALLOC;
|
||||
|
||||
info->index.info = find_sec(info, ".modinfo");
|
||||
if (!info->index.info)
|
||||
info->name = "(missing .modinfo section)";
|
||||
else
|
||||
info->name = get_modinfo(info, "name");
|
||||
info->sechdrs[info->index.info].sh_flags &= ~(unsigned long)SHF_ALLOC;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2957,21 +2967,29 @@ static struct module *setup_load_info(struct load_info *info, int flags)
|
||||
|
||||
info->index.mod = find_sec(info, ".gnu.linkonce.this_module");
|
||||
if (!info->index.mod) {
|
||||
pr_warn("No module found in object\n");
|
||||
pr_warn("%s: No module found in object\n",
|
||||
info->name ?: "(missing .modinfo name field)");
|
||||
return ERR_PTR(-ENOEXEC);
|
||||
}
|
||||
/* This is temporary: point mod into copy of data. */
|
||||
mod = (void *)info->sechdrs[info->index.mod].sh_addr;
|
||||
|
||||
/*
|
||||
* If we didn't load the .modinfo 'name' field, fall back to
|
||||
* on-disk struct mod 'name' field.
|
||||
*/
|
||||
if (!info->name)
|
||||
info->name = mod->name;
|
||||
|
||||
if (info->index.sym == 0) {
|
||||
pr_warn("%s: module has no symbols (stripped?)\n", mod->name);
|
||||
pr_warn("%s: module has no symbols (stripped?)\n", info->name);
|
||||
return ERR_PTR(-ENOEXEC);
|
||||
}
|
||||
|
||||
info->index.pcpu = find_pcpusec(info);
|
||||
|
||||
/* Check module struct version now, before we try to use module. */
|
||||
if (!check_modstruct_version(info->sechdrs, info->index.vers, mod))
|
||||
if (!check_modstruct_version(info, mod))
|
||||
return ERR_PTR(-ENOEXEC);
|
||||
|
||||
return mod;
|
||||
@@ -2992,7 +3010,7 @@ static int check_modinfo(struct module *mod, struct load_info *info, int flags)
|
||||
return err;
|
||||
} else if (!same_magic(modmagic, vermagic, info->index.vers)) {
|
||||
pr_err("%s: version magic '%s' should be '%s'\n",
|
||||
mod->name, modmagic, vermagic);
|
||||
info->name, modmagic, vermagic);
|
||||
return -ENOEXEC;
|
||||
}
|
||||
|
||||
@@ -3077,9 +3095,9 @@ static int find_module_sections(struct module *mod, struct load_info *info)
|
||||
mod->trace_events = section_objs(info, "_ftrace_events",
|
||||
sizeof(*mod->trace_events),
|
||||
&mod->num_trace_events);
|
||||
mod->trace_enums = section_objs(info, "_ftrace_enum_map",
|
||||
sizeof(*mod->trace_enums),
|
||||
&mod->num_trace_enums);
|
||||
mod->trace_evals = section_objs(info, "_ftrace_eval_map",
|
||||
sizeof(*mod->trace_evals),
|
||||
&mod->num_trace_evals);
|
||||
#endif
|
||||
#ifdef CONFIG_TRACING
|
||||
mod->trace_bprintk_fmt_start = section_objs(info, "__trace_printk_fmt",
|
||||
@@ -3242,7 +3260,7 @@ int __weak module_frob_arch_sections(Elf_Ehdr *hdr,
|
||||
|
||||
/* module_blacklist is a comma-separated list of module names */
|
||||
static char *module_blacklist;
|
||||
static bool blacklisted(char *module_name)
|
||||
static bool blacklisted(const char *module_name)
|
||||
{
|
||||
const char *p;
|
||||
size_t len;
|
||||
@@ -3272,7 +3290,7 @@ static struct module *layout_and_allocate(struct load_info *info, int flags)
|
||||
if (IS_ERR(mod))
|
||||
return mod;
|
||||
|
||||
if (blacklisted(mod->name))
|
||||
if (blacklisted(info->name))
|
||||
return ERR_PTR(-EPERM);
|
||||
|
||||
err = check_modinfo(mod, info, flags);
|
||||
@@ -4201,7 +4219,7 @@ const struct exception_table_entry *search_module_extables(unsigned long addr)
|
||||
goto out;
|
||||
|
||||
e = search_extable(mod->extable,
|
||||
mod->extable + mod->num_exentries - 1,
|
||||
mod->num_exentries,
|
||||
addr);
|
||||
out:
|
||||
preempt_enable();
|
||||
|
||||
+22
-21
@@ -933,19 +933,6 @@ static struct kobj_type padata_attr_type = {
|
||||
.release = padata_sysfs_release,
|
||||
};
|
||||
|
||||
/**
|
||||
* padata_alloc_possible - Allocate and initialize padata instance.
|
||||
* Use the cpu_possible_mask for serial and
|
||||
* parallel workers.
|
||||
*
|
||||
* @wq: workqueue to use for the allocated padata instance
|
||||
*/
|
||||
struct padata_instance *padata_alloc_possible(struct workqueue_struct *wq)
|
||||
{
|
||||
return padata_alloc(wq, cpu_possible_mask, cpu_possible_mask);
|
||||
}
|
||||
EXPORT_SYMBOL(padata_alloc_possible);
|
||||
|
||||
/**
|
||||
* padata_alloc - allocate and initialize a padata instance and specify
|
||||
* cpumasks for serial and parallel workers.
|
||||
@@ -953,10 +940,12 @@ EXPORT_SYMBOL(padata_alloc_possible);
|
||||
* @wq: workqueue to use for the allocated padata instance
|
||||
* @pcpumask: cpumask that will be used for padata parallelization
|
||||
* @cbcpumask: cpumask that will be used for padata serialization
|
||||
*
|
||||
* Must be called from a cpus_read_lock() protected region
|
||||
*/
|
||||
struct padata_instance *padata_alloc(struct workqueue_struct *wq,
|
||||
const struct cpumask *pcpumask,
|
||||
const struct cpumask *cbcpumask)
|
||||
static struct padata_instance *padata_alloc(struct workqueue_struct *wq,
|
||||
const struct cpumask *pcpumask,
|
||||
const struct cpumask *cbcpumask)
|
||||
{
|
||||
struct padata_instance *pinst;
|
||||
struct parallel_data *pd = NULL;
|
||||
@@ -965,7 +954,6 @@ struct padata_instance *padata_alloc(struct workqueue_struct *wq,
|
||||
if (!pinst)
|
||||
goto err;
|
||||
|
||||
get_online_cpus();
|
||||
if (!alloc_cpumask_var(&pinst->cpumask.pcpu, GFP_KERNEL))
|
||||
goto err_free_inst;
|
||||
if (!alloc_cpumask_var(&pinst->cpumask.cbcpu, GFP_KERNEL)) {
|
||||
@@ -989,14 +977,12 @@ struct padata_instance *padata_alloc(struct workqueue_struct *wq,
|
||||
|
||||
pinst->flags = 0;
|
||||
|
||||
put_online_cpus();
|
||||
|
||||
BLOCKING_INIT_NOTIFIER_HEAD(&pinst->cpumask_change_notifier);
|
||||
kobject_init(&pinst->kobj, &padata_attr_type);
|
||||
mutex_init(&pinst->lock);
|
||||
|
||||
#ifdef CONFIG_HOTPLUG_CPU
|
||||
cpuhp_state_add_instance_nocalls(hp_online, &pinst->node);
|
||||
cpuhp_state_add_instance_nocalls_cpuslocked(hp_online, &pinst->node);
|
||||
#endif
|
||||
return pinst;
|
||||
|
||||
@@ -1005,11 +991,26 @@ err_free_masks:
|
||||
free_cpumask_var(pinst->cpumask.cbcpu);
|
||||
err_free_inst:
|
||||
kfree(pinst);
|
||||
put_online_cpus();
|
||||
err:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* padata_alloc_possible - Allocate and initialize padata instance.
|
||||
* Use the cpu_possible_mask for serial and
|
||||
* parallel workers.
|
||||
*
|
||||
* @wq: workqueue to use for the allocated padata instance
|
||||
*
|
||||
* Must be called from a cpus_read_lock() protected region
|
||||
*/
|
||||
struct padata_instance *padata_alloc_possible(struct workqueue_struct *wq)
|
||||
{
|
||||
lockdep_assert_cpus_held();
|
||||
return padata_alloc(wq, cpu_possible_mask, cpu_possible_mask);
|
||||
}
|
||||
EXPORT_SYMBOL(padata_alloc_possible);
|
||||
|
||||
/**
|
||||
* padata_free - free a padata instance
|
||||
*
|
||||
|
||||
+2
-5
@@ -575,16 +575,13 @@ struct pid *find_ge_pid(int nr, struct pid_namespace *ns)
|
||||
*/
|
||||
void __init pidhash_init(void)
|
||||
{
|
||||
unsigned int i, pidhash_size;
|
||||
unsigned int pidhash_size;
|
||||
|
||||
pid_hash = alloc_large_system_hash("PID", sizeof(*pid_hash), 0, 18,
|
||||
HASH_EARLY | HASH_SMALL,
|
||||
HASH_EARLY | HASH_SMALL | HASH_ZERO,
|
||||
&pidhash_shift, NULL,
|
||||
0, 4096);
|
||||
pidhash_size = 1U << pidhash_shift;
|
||||
|
||||
for (i = 0; i < pidhash_size; i++)
|
||||
INIT_HLIST_HEAD(&pid_hash[i]);
|
||||
}
|
||||
|
||||
void __init pidmap_init(void)
|
||||
|
||||
@@ -1108,7 +1108,7 @@ static struct attribute * g[] = {
|
||||
};
|
||||
|
||||
|
||||
static struct attribute_group attr_group = {
|
||||
static const struct attribute_group attr_group = {
|
||||
.attrs = g,
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -705,7 +705,7 @@ static struct attribute * g[] = {
|
||||
NULL,
|
||||
};
|
||||
|
||||
static struct attribute_group attr_group = {
|
||||
static const struct attribute_group attr_group = {
|
||||
.attrs = g,
|
||||
};
|
||||
|
||||
|
||||
@@ -30,19 +30,17 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/compiler.h>
|
||||
#include <linux/ktime.h>
|
||||
#include <linux/set_memory.h>
|
||||
|
||||
#include <linux/uaccess.h>
|
||||
#include <asm/mmu_context.h>
|
||||
#include <asm/pgtable.h>
|
||||
#include <asm/tlbflush.h>
|
||||
#include <asm/io.h>
|
||||
#ifdef CONFIG_STRICT_KERNEL_RWX
|
||||
#include <asm/set_memory.h>
|
||||
#endif
|
||||
|
||||
#include "power.h"
|
||||
|
||||
#ifdef CONFIG_STRICT_KERNEL_RWX
|
||||
#if defined(CONFIG_STRICT_KERNEL_RWX) && defined(CONFIG_ARCH_HAS_SET_MEMORY)
|
||||
static bool hibernate_restore_protection;
|
||||
static bool hibernate_restore_protection_active;
|
||||
|
||||
@@ -77,7 +75,7 @@ static inline void hibernate_restore_protection_begin(void) {}
|
||||
static inline void hibernate_restore_protection_end(void) {}
|
||||
static inline void hibernate_restore_protect_page(void *page_address) {}
|
||||
static inline void hibernate_restore_unprotect_page(void *page_address) {}
|
||||
#endif /* CONFIG_STRICT_KERNEL_RWX */
|
||||
#endif /* CONFIG_STRICT_KERNEL_RWX && CONFIG_ARCH_HAS_SET_MEMORY */
|
||||
|
||||
static int swsusp_page_is_free(struct page *);
|
||||
static void swsusp_set_page_forbidden(struct page *);
|
||||
@@ -1425,7 +1423,7 @@ static unsigned int nr_meta_pages;
|
||||
* Numbers of normal and highmem page frames allocated for hibernation image
|
||||
* before suspending devices.
|
||||
*/
|
||||
unsigned int alloc_normal, alloc_highmem;
|
||||
static unsigned int alloc_normal, alloc_highmem;
|
||||
/*
|
||||
* Memory bitmap used for marking saveable pages (during hibernation) or
|
||||
* hibernation image pages (during restore)
|
||||
@@ -1929,8 +1927,7 @@ static inline unsigned int alloc_highmem_pages(struct memory_bitmap *bm,
|
||||
* also be located in the high memory, because of the way in which
|
||||
* copy_data_pages() works.
|
||||
*/
|
||||
static int swsusp_alloc(struct memory_bitmap *orig_bm,
|
||||
struct memory_bitmap *copy_bm,
|
||||
static int swsusp_alloc(struct memory_bitmap *copy_bm,
|
||||
unsigned int nr_pages, unsigned int nr_highmem)
|
||||
{
|
||||
if (nr_highmem > 0) {
|
||||
@@ -1976,7 +1973,7 @@ asmlinkage __visible int swsusp_save(void)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (swsusp_alloc(&orig_bm, ©_bm, nr_pages, nr_highmem)) {
|
||||
if (swsusp_alloc(©_bm, nr_pages, nr_highmem)) {
|
||||
printk(KERN_ERR "PM: Memory allocation failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
@@ -86,11 +86,9 @@ static void freeze_enter(void)
|
||||
|
||||
/* Push all the CPUs into the idle loop. */
|
||||
wake_up_all_idle_cpus();
|
||||
pr_debug("PM: suspend-to-idle\n");
|
||||
/* Make the current CPU wait so it can enter the idle loop too. */
|
||||
wait_event(suspend_freeze_wait_head,
|
||||
suspend_freeze_state == FREEZE_STATE_WAKE);
|
||||
pr_debug("PM: resume from suspend-to-idle\n");
|
||||
|
||||
cpuidle_pause();
|
||||
put_online_cpus();
|
||||
@@ -106,6 +104,8 @@ static void freeze_enter(void)
|
||||
|
||||
static void s2idle_loop(void)
|
||||
{
|
||||
pr_debug("PM: suspend-to-idle\n");
|
||||
|
||||
do {
|
||||
freeze_enter();
|
||||
|
||||
@@ -121,6 +121,8 @@ static void s2idle_loop(void)
|
||||
|
||||
pm_wakeup_clear(false);
|
||||
} while (!dpm_suspend_noirq(PMSG_SUSPEND));
|
||||
|
||||
pr_debug("PM: resume from suspend-to-idle\n");
|
||||
}
|
||||
|
||||
void freeze_wake(void)
|
||||
|
||||
+7
-7
@@ -225,14 +225,14 @@ static struct block_device *hib_resume_bdev;
|
||||
struct hib_bio_batch {
|
||||
atomic_t count;
|
||||
wait_queue_head_t wait;
|
||||
int error;
|
||||
blk_status_t error;
|
||||
};
|
||||
|
||||
static void hib_init_batch(struct hib_bio_batch *hb)
|
||||
{
|
||||
atomic_set(&hb->count, 0);
|
||||
init_waitqueue_head(&hb->wait);
|
||||
hb->error = 0;
|
||||
hb->error = BLK_STS_OK;
|
||||
}
|
||||
|
||||
static void hib_end_io(struct bio *bio)
|
||||
@@ -240,7 +240,7 @@ static void hib_end_io(struct bio *bio)
|
||||
struct hib_bio_batch *hb = bio->bi_private;
|
||||
struct page *page = bio->bi_io_vec[0].bv_page;
|
||||
|
||||
if (bio->bi_error) {
|
||||
if (bio->bi_status) {
|
||||
printk(KERN_ALERT "Read-error on swap-device (%u:%u:%Lu)\n",
|
||||
imajor(bio->bi_bdev->bd_inode),
|
||||
iminor(bio->bi_bdev->bd_inode),
|
||||
@@ -253,8 +253,8 @@ static void hib_end_io(struct bio *bio)
|
||||
flush_icache_range((unsigned long)page_address(page),
|
||||
(unsigned long)page_address(page) + PAGE_SIZE);
|
||||
|
||||
if (bio->bi_error && !hb->error)
|
||||
hb->error = bio->bi_error;
|
||||
if (bio->bi_status && !hb->error)
|
||||
hb->error = bio->bi_status;
|
||||
if (atomic_dec_and_test(&hb->count))
|
||||
wake_up(&hb->wait);
|
||||
|
||||
@@ -293,10 +293,10 @@ static int hib_submit_io(int op, int op_flags, pgoff_t page_off, void *addr,
|
||||
return error;
|
||||
}
|
||||
|
||||
static int hib_wait_io(struct hib_bio_batch *hb)
|
||||
static blk_status_t hib_wait_io(struct hib_bio_batch *hb)
|
||||
{
|
||||
wait_event(hb->wait, atomic_read(&hb->count) == 0);
|
||||
return hb->error;
|
||||
return blk_status_to_errno(hb->error);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -18,12 +18,14 @@
|
||||
|
||||
#ifdef CONFIG_PRINTK
|
||||
|
||||
#define PRINTK_SAFE_CONTEXT_MASK 0x7fffffff
|
||||
#define PRINTK_NMI_CONTEXT_MASK 0x80000000
|
||||
#define PRINTK_SAFE_CONTEXT_MASK 0x3fffffff
|
||||
#define PRINTK_NMI_DEFERRED_CONTEXT_MASK 0x40000000
|
||||
#define PRINTK_NMI_CONTEXT_MASK 0x80000000
|
||||
|
||||
extern raw_spinlock_t logbuf_lock;
|
||||
|
||||
__printf(1, 0) int vprintk_default(const char *fmt, va_list args);
|
||||
__printf(1, 0) int vprintk_deferred(const char *fmt, va_list args);
|
||||
__printf(1, 0) int vprintk_func(const char *fmt, va_list args);
|
||||
void __printk_safe_enter(void);
|
||||
void __printk_safe_exit(void);
|
||||
|
||||
+26
-43
@@ -269,7 +269,6 @@ static struct console *exclusive_console;
|
||||
#define MAX_CMDLINECONSOLES 8
|
||||
|
||||
static struct console_cmdline console_cmdline[MAX_CMDLINECONSOLES];
|
||||
static int console_cmdline_cnt;
|
||||
|
||||
static int preferred_console = -1;
|
||||
int console_set_on_cmdline;
|
||||
@@ -1176,7 +1175,7 @@ static void boot_delay_msec(int level)
|
||||
unsigned long long k;
|
||||
unsigned long timeout;
|
||||
|
||||
if ((boot_delay == 0 || system_state != SYSTEM_BOOTING)
|
||||
if ((boot_delay == 0 || system_state >= SYSTEM_RUNNING)
|
||||
|| suppress_message_printing(level)) {
|
||||
return;
|
||||
}
|
||||
@@ -1906,25 +1905,12 @@ static int __add_preferred_console(char *name, int idx, char *options,
|
||||
* See if this tty is not yet registered, and
|
||||
* if we have a slot free.
|
||||
*/
|
||||
for (i = 0, c = console_cmdline; i < console_cmdline_cnt; i++, c++) {
|
||||
for (i = 0, c = console_cmdline;
|
||||
i < MAX_CMDLINECONSOLES && c->name[0];
|
||||
i++, c++) {
|
||||
if (strcmp(c->name, name) == 0 && c->index == idx) {
|
||||
if (brl_options)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* Maintain an invariant that will help to find if
|
||||
* the matching console is preferred, see
|
||||
* register_console():
|
||||
*
|
||||
* The last non-braille console is always
|
||||
* the preferred one.
|
||||
*/
|
||||
if (i != console_cmdline_cnt - 1)
|
||||
swap(console_cmdline[i],
|
||||
console_cmdline[console_cmdline_cnt - 1]);
|
||||
|
||||
preferred_console = console_cmdline_cnt - 1;
|
||||
|
||||
if (!brl_options)
|
||||
preferred_console = i;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1937,7 +1923,6 @@ static int __add_preferred_console(char *name, int idx, char *options,
|
||||
braille_set_options(c, brl_options);
|
||||
|
||||
c->index = idx;
|
||||
console_cmdline_cnt++;
|
||||
return 0;
|
||||
}
|
||||
/*
|
||||
@@ -2477,23 +2462,12 @@ void register_console(struct console *newcon)
|
||||
}
|
||||
|
||||
/*
|
||||
* See if this console matches one we selected on the command line.
|
||||
*
|
||||
* There may be several entries in the console_cmdline array matching
|
||||
* with the same console, one with newcon->match(), another by
|
||||
* name/index:
|
||||
*
|
||||
* pl011,mmio,0x87e024000000,115200 -- added from SPCR
|
||||
* ttyAMA0 -- added from command line
|
||||
*
|
||||
* Traverse the console_cmdline array in reverse order to be
|
||||
* sure that if this console is preferred then it will be the first
|
||||
* matching entry. We use the invariant that is maintained in
|
||||
* __add_preferred_console().
|
||||
* See if this console matches one we selected on
|
||||
* the command line.
|
||||
*/
|
||||
for (i = console_cmdline_cnt - 1; i >= 0; i--) {
|
||||
c = console_cmdline + i;
|
||||
|
||||
for (i = 0, c = console_cmdline;
|
||||
i < MAX_CMDLINECONSOLES && c->name[0];
|
||||
i++, c++) {
|
||||
if (!newcon->match ||
|
||||
newcon->match(newcon, c->name, c->index, c->options) != 0) {
|
||||
/* default matching */
|
||||
@@ -2746,20 +2720,29 @@ void wake_up_klogd(void)
|
||||
preempt_enable();
|
||||
}
|
||||
|
||||
int vprintk_deferred(const char *fmt, va_list args)
|
||||
{
|
||||
int r;
|
||||
|
||||
r = vprintk_emit(0, LOGLEVEL_SCHED, NULL, 0, fmt, args);
|
||||
|
||||
preempt_disable();
|
||||
__this_cpu_or(printk_pending, PRINTK_PENDING_OUTPUT);
|
||||
irq_work_queue(this_cpu_ptr(&wake_up_klogd_work));
|
||||
preempt_enable();
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int printk_deferred(const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
int r;
|
||||
|
||||
preempt_disable();
|
||||
va_start(args, fmt);
|
||||
r = vprintk_emit(0, LOGLEVEL_SCHED, NULL, 0, fmt, args);
|
||||
r = vprintk_deferred(fmt, args);
|
||||
va_end(args);
|
||||
|
||||
__this_cpu_or(printk_pending, PRINTK_PENDING_OUTPUT);
|
||||
irq_work_queue(this_cpu_ptr(&wake_up_klogd_work));
|
||||
preempt_enable();
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,8 +80,8 @@ static void queue_flush_work(struct printk_safe_seq_buf *s)
|
||||
* happen, printk_safe_log_store() will notice the buffer->len mismatch
|
||||
* and repeat the write.
|
||||
*/
|
||||
static int printk_safe_log_store(struct printk_safe_seq_buf *s,
|
||||
const char *fmt, va_list args)
|
||||
static __printf(2, 0) int printk_safe_log_store(struct printk_safe_seq_buf *s,
|
||||
const char *fmt, va_list args)
|
||||
{
|
||||
int add;
|
||||
size_t len;
|
||||
@@ -299,7 +299,7 @@ void printk_safe_flush_on_panic(void)
|
||||
* one writer running. But the buffer might get flushed from another
|
||||
* CPU, so we need to be careful.
|
||||
*/
|
||||
static int vprintk_nmi(const char *fmt, va_list args)
|
||||
static __printf(1, 0) int vprintk_nmi(const char *fmt, va_list args)
|
||||
{
|
||||
struct printk_safe_seq_buf *s = this_cpu_ptr(&nmi_print_seq);
|
||||
|
||||
@@ -308,17 +308,29 @@ static int vprintk_nmi(const char *fmt, va_list args)
|
||||
|
||||
void printk_nmi_enter(void)
|
||||
{
|
||||
this_cpu_or(printk_context, PRINTK_NMI_CONTEXT_MASK);
|
||||
/*
|
||||
* The size of the extra per-CPU buffer is limited. Use it only when
|
||||
* the main one is locked. If this CPU is not in the safe context,
|
||||
* the lock must be taken on another CPU and we could wait for it.
|
||||
*/
|
||||
if ((this_cpu_read(printk_context) & PRINTK_SAFE_CONTEXT_MASK) &&
|
||||
raw_spin_is_locked(&logbuf_lock)) {
|
||||
this_cpu_or(printk_context, PRINTK_NMI_CONTEXT_MASK);
|
||||
} else {
|
||||
this_cpu_or(printk_context, PRINTK_NMI_DEFERRED_CONTEXT_MASK);
|
||||
}
|
||||
}
|
||||
|
||||
void printk_nmi_exit(void)
|
||||
{
|
||||
this_cpu_and(printk_context, ~PRINTK_NMI_CONTEXT_MASK);
|
||||
this_cpu_and(printk_context,
|
||||
~(PRINTK_NMI_CONTEXT_MASK |
|
||||
PRINTK_NMI_DEFERRED_CONTEXT_MASK));
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static int vprintk_nmi(const char *fmt, va_list args)
|
||||
static __printf(1, 0) int vprintk_nmi(const char *fmt, va_list args)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
@@ -330,7 +342,7 @@ static int vprintk_nmi(const char *fmt, va_list args)
|
||||
* into itself. It uses a per-CPU buffer to store the message, just like
|
||||
* NMI.
|
||||
*/
|
||||
static int vprintk_safe(const char *fmt, va_list args)
|
||||
static __printf(1, 0) int vprintk_safe(const char *fmt, va_list args)
|
||||
{
|
||||
struct printk_safe_seq_buf *s = this_cpu_ptr(&safe_print_seq);
|
||||
|
||||
@@ -351,12 +363,22 @@ void __printk_safe_exit(void)
|
||||
|
||||
__printf(1, 0) int vprintk_func(const char *fmt, va_list args)
|
||||
{
|
||||
/* Use extra buffer in NMI when logbuf_lock is taken or in safe mode. */
|
||||
if (this_cpu_read(printk_context) & PRINTK_NMI_CONTEXT_MASK)
|
||||
return vprintk_nmi(fmt, args);
|
||||
|
||||
/* Use extra buffer to prevent a recursion deadlock in safe mode. */
|
||||
if (this_cpu_read(printk_context) & PRINTK_SAFE_CONTEXT_MASK)
|
||||
return vprintk_safe(fmt, args);
|
||||
|
||||
/*
|
||||
* Use the main logbuf when logbuf_lock is available in NMI.
|
||||
* But avoid calling console drivers that might have their own locks.
|
||||
*/
|
||||
if (this_cpu_read(printk_context) & PRINTK_NMI_DEFERRED_CONTEXT_MASK)
|
||||
return vprintk_deferred(fmt, args);
|
||||
|
||||
/* No obstacles. */
|
||||
return vprintk_default(fmt, args);
|
||||
}
|
||||
|
||||
|
||||
+13
-7
@@ -60,19 +60,25 @@ int ptrace_access_vm(struct task_struct *tsk, unsigned long addr,
|
||||
}
|
||||
|
||||
|
||||
void __ptrace_link(struct task_struct *child, struct task_struct *new_parent,
|
||||
const struct cred *ptracer_cred)
|
||||
{
|
||||
BUG_ON(!list_empty(&child->ptrace_entry));
|
||||
list_add(&child->ptrace_entry, &new_parent->ptraced);
|
||||
child->parent = new_parent;
|
||||
child->ptracer_cred = get_cred(ptracer_cred);
|
||||
}
|
||||
|
||||
/*
|
||||
* ptrace a task: make the debugger its new parent and
|
||||
* move it to the ptrace list.
|
||||
*
|
||||
* Must be called with the tasklist lock write-held.
|
||||
*/
|
||||
void __ptrace_link(struct task_struct *child, struct task_struct *new_parent)
|
||||
static void ptrace_link(struct task_struct *child, struct task_struct *new_parent)
|
||||
{
|
||||
BUG_ON(!list_empty(&child->ptrace_entry));
|
||||
list_add(&child->ptrace_entry, &new_parent->ptraced);
|
||||
child->parent = new_parent;
|
||||
rcu_read_lock();
|
||||
child->ptracer_cred = get_cred(__task_cred(new_parent));
|
||||
__ptrace_link(child, new_parent, __task_cred(new_parent));
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
@@ -386,7 +392,7 @@ static int ptrace_attach(struct task_struct *task, long request,
|
||||
flags |= PT_SEIZED;
|
||||
task->ptrace = flags;
|
||||
|
||||
__ptrace_link(task, current);
|
||||
ptrace_link(task, current);
|
||||
|
||||
/* SEIZE doesn't trap tracee on attach */
|
||||
if (!seize)
|
||||
@@ -459,7 +465,7 @@ static int ptrace_traceme(void)
|
||||
*/
|
||||
if (!ret && !(current->real_parent->flags & PF_EXITING)) {
|
||||
current->ptrace = PT_PTRACED;
|
||||
__ptrace_link(current, current->real_parent);
|
||||
ptrace_link(current, current->real_parent);
|
||||
}
|
||||
}
|
||||
write_unlock_irq(&tasklist_lock);
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
#
|
||||
# RCU-related configuration options
|
||||
#
|
||||
|
||||
menu "RCU Subsystem"
|
||||
|
||||
config TREE_RCU
|
||||
bool
|
||||
default y if !PREEMPT && SMP
|
||||
help
|
||||
This option selects the RCU implementation that is
|
||||
designed for very large SMP system with hundreds or
|
||||
thousands of CPUs. It also scales down nicely to
|
||||
smaller systems.
|
||||
|
||||
config PREEMPT_RCU
|
||||
bool
|
||||
default y if PREEMPT
|
||||
help
|
||||
This option selects the RCU implementation that is
|
||||
designed for very large SMP systems with hundreds or
|
||||
thousands of CPUs, but for which real-time response
|
||||
is also required. It also scales down nicely to
|
||||
smaller systems.
|
||||
|
||||
Select this option if you are unsure.
|
||||
|
||||
config TINY_RCU
|
||||
bool
|
||||
default y if !PREEMPT && !SMP
|
||||
help
|
||||
This option selects the RCU implementation that is
|
||||
designed for UP systems from which real-time response
|
||||
is not required. This option greatly reduces the
|
||||
memory footprint of RCU.
|
||||
|
||||
config RCU_EXPERT
|
||||
bool "Make expert-level adjustments to RCU configuration"
|
||||
default n
|
||||
help
|
||||
This option needs to be enabled if you wish to make
|
||||
expert-level adjustments to RCU configuration. By default,
|
||||
no such adjustments can be made, which has the often-beneficial
|
||||
side-effect of preventing "make oldconfig" from asking you all
|
||||
sorts of detailed questions about how you would like numerous
|
||||
obscure RCU options to be set up.
|
||||
|
||||
Say Y if you need to make expert-level adjustments to RCU.
|
||||
|
||||
Say N if you are unsure.
|
||||
|
||||
config SRCU
|
||||
bool
|
||||
help
|
||||
This option selects the sleepable version of RCU. This version
|
||||
permits arbitrary sleeping or blocking within RCU read-side critical
|
||||
sections.
|
||||
|
||||
config TINY_SRCU
|
||||
bool
|
||||
default y if SRCU && TINY_RCU
|
||||
help
|
||||
This option selects the single-CPU non-preemptible version of SRCU.
|
||||
|
||||
config TREE_SRCU
|
||||
bool
|
||||
default y if SRCU && !TINY_RCU
|
||||
help
|
||||
This option selects the full-fledged version of SRCU.
|
||||
|
||||
config TASKS_RCU
|
||||
bool
|
||||
default n
|
||||
select SRCU
|
||||
help
|
||||
This option enables a task-based RCU implementation that uses
|
||||
only voluntary context switch (not preemption!), idle, and
|
||||
user-mode execution as quiescent states.
|
||||
|
||||
config RCU_STALL_COMMON
|
||||
def_bool ( TREE_RCU || PREEMPT_RCU )
|
||||
help
|
||||
This option enables RCU CPU stall code that is common between
|
||||
the TINY and TREE variants of RCU. The purpose is to allow
|
||||
the tiny variants to disable RCU CPU stall warnings, while
|
||||
making these warnings mandatory for the tree variants.
|
||||
|
||||
config RCU_NEED_SEGCBLIST
|
||||
def_bool ( TREE_RCU || PREEMPT_RCU || TREE_SRCU )
|
||||
|
||||
config CONTEXT_TRACKING
|
||||
bool
|
||||
|
||||
config CONTEXT_TRACKING_FORCE
|
||||
bool "Force context tracking"
|
||||
depends on CONTEXT_TRACKING
|
||||
default y if !NO_HZ_FULL
|
||||
help
|
||||
The major pre-requirement for full dynticks to work is to
|
||||
support the context tracking subsystem. But there are also
|
||||
other dependencies to provide in order to make the full
|
||||
dynticks working.
|
||||
|
||||
This option stands for testing when an arch implements the
|
||||
context tracking backend but doesn't yet fullfill all the
|
||||
requirements to make the full dynticks feature working.
|
||||
Without the full dynticks, there is no way to test the support
|
||||
for context tracking and the subsystems that rely on it: RCU
|
||||
userspace extended quiescent state and tickless cputime
|
||||
accounting. This option copes with the absence of the full
|
||||
dynticks subsystem by forcing the context tracking on all
|
||||
CPUs in the system.
|
||||
|
||||
Say Y only if you're working on the development of an
|
||||
architecture backend for the context tracking.
|
||||
|
||||
Say N otherwise, this option brings an overhead that you
|
||||
don't want in production.
|
||||
|
||||
|
||||
config RCU_FANOUT
|
||||
int "Tree-based hierarchical RCU fanout value"
|
||||
range 2 64 if 64BIT
|
||||
range 2 32 if !64BIT
|
||||
depends on (TREE_RCU || PREEMPT_RCU) && RCU_EXPERT
|
||||
default 64 if 64BIT
|
||||
default 32 if !64BIT
|
||||
help
|
||||
This option controls the fanout of hierarchical implementations
|
||||
of RCU, allowing RCU to work efficiently on machines with
|
||||
large numbers of CPUs. This value must be at least the fourth
|
||||
root of NR_CPUS, which allows NR_CPUS to be insanely large.
|
||||
The default value of RCU_FANOUT should be used for production
|
||||
systems, but if you are stress-testing the RCU implementation
|
||||
itself, small RCU_FANOUT values allow you to test large-system
|
||||
code paths on small(er) systems.
|
||||
|
||||
Select a specific number if testing RCU itself.
|
||||
Take the default if unsure.
|
||||
|
||||
config RCU_FANOUT_LEAF
|
||||
int "Tree-based hierarchical RCU leaf-level fanout value"
|
||||
range 2 64 if 64BIT
|
||||
range 2 32 if !64BIT
|
||||
depends on (TREE_RCU || PREEMPT_RCU) && RCU_EXPERT
|
||||
default 16
|
||||
help
|
||||
This option controls the leaf-level fanout of hierarchical
|
||||
implementations of RCU, and allows trading off cache misses
|
||||
against lock contention. Systems that synchronize their
|
||||
scheduling-clock interrupts for energy-efficiency reasons will
|
||||
want the default because the smaller leaf-level fanout keeps
|
||||
lock contention levels acceptably low. Very large systems
|
||||
(hundreds or thousands of CPUs) will instead want to set this
|
||||
value to the maximum value possible in order to reduce the
|
||||
number of cache misses incurred during RCU's grace-period
|
||||
initialization. These systems tend to run CPU-bound, and thus
|
||||
are not helped by synchronized interrupts, and thus tend to
|
||||
skew them, which reduces lock contention enough that large
|
||||
leaf-level fanouts work well. That said, setting leaf-level
|
||||
fanout to a large number will likely cause problematic
|
||||
lock contention on the leaf-level rcu_node structures unless
|
||||
you boot with the skew_tick kernel parameter.
|
||||
|
||||
Select a specific number if testing RCU itself.
|
||||
|
||||
Select the maximum permissible value for large systems, but
|
||||
please understand that you may also need to set the skew_tick
|
||||
kernel boot parameter to avoid contention on the rcu_node
|
||||
structure's locks.
|
||||
|
||||
Take the default if unsure.
|
||||
|
||||
config RCU_FAST_NO_HZ
|
||||
bool "Accelerate last non-dyntick-idle CPU's grace periods"
|
||||
depends on NO_HZ_COMMON && SMP && RCU_EXPERT
|
||||
default n
|
||||
help
|
||||
This option permits CPUs to enter dynticks-idle state even if
|
||||
they have RCU callbacks queued, and prevents RCU from waking
|
||||
these CPUs up more than roughly once every four jiffies (by
|
||||
default, you can adjust this using the rcutree.rcu_idle_gp_delay
|
||||
parameter), thus improving energy efficiency. On the other
|
||||
hand, this option increases the duration of RCU grace periods,
|
||||
for example, slowing down synchronize_rcu().
|
||||
|
||||
Say Y if energy efficiency is critically important, and you
|
||||
don't care about increased grace-period durations.
|
||||
|
||||
Say N if you are unsure.
|
||||
|
||||
config RCU_BOOST
|
||||
bool "Enable RCU priority boosting"
|
||||
depends on RT_MUTEXES && PREEMPT_RCU && RCU_EXPERT
|
||||
default n
|
||||
help
|
||||
This option boosts the priority of preempted RCU readers that
|
||||
block the current preemptible RCU grace period for too long.
|
||||
This option also prevents heavy loads from blocking RCU
|
||||
callback invocation for all flavors of RCU.
|
||||
|
||||
Say Y here if you are working with real-time apps or heavy loads
|
||||
Say N here if you are unsure.
|
||||
|
||||
config RCU_BOOST_DELAY
|
||||
int "Milliseconds to delay boosting after RCU grace-period start"
|
||||
range 0 3000
|
||||
depends on RCU_BOOST
|
||||
default 500
|
||||
help
|
||||
This option specifies the time to wait after the beginning of
|
||||
a given grace period before priority-boosting preempted RCU
|
||||
readers blocking that grace period. Note that any RCU reader
|
||||
blocking an expedited RCU grace period is boosted immediately.
|
||||
|
||||
Accept the default if unsure.
|
||||
|
||||
config RCU_NOCB_CPU
|
||||
bool "Offload RCU callback processing from boot-selected CPUs"
|
||||
depends on TREE_RCU || PREEMPT_RCU
|
||||
depends on RCU_EXPERT || NO_HZ_FULL
|
||||
default n
|
||||
help
|
||||
Use this option to reduce OS jitter for aggressive HPC or
|
||||
real-time workloads. It can also be used to offload RCU
|
||||
callback invocation to energy-efficient CPUs in battery-powered
|
||||
asymmetric multiprocessors.
|
||||
|
||||
This option offloads callback invocation from the set of
|
||||
CPUs specified at boot time by the rcu_nocbs parameter.
|
||||
For each such CPU, a kthread ("rcuox/N") will be created to
|
||||
invoke callbacks, where the "N" is the CPU being offloaded,
|
||||
and where the "x" is "b" for RCU-bh, "p" for RCU-preempt, and
|
||||
"s" for RCU-sched. Nothing prevents this kthread from running
|
||||
on the specified CPUs, but (1) the kthreads may be preempted
|
||||
between each callback, and (2) affinity or cgroups can be used
|
||||
to force the kthreads to run on whatever set of CPUs is desired.
|
||||
|
||||
Say Y here if you want to help to debug reduced OS jitter.
|
||||
Say N here if you are unsure.
|
||||
|
||||
endmenu # "RCU Subsystem"
|
||||
@@ -0,0 +1,82 @@
|
||||
#
|
||||
# RCU-related debugging configuration options
|
||||
#
|
||||
|
||||
menu "RCU Debugging"
|
||||
|
||||
config PROVE_RCU
|
||||
def_bool PROVE_LOCKING
|
||||
|
||||
config TORTURE_TEST
|
||||
tristate
|
||||
default n
|
||||
|
||||
config RCU_PERF_TEST
|
||||
tristate "performance tests for RCU"
|
||||
depends on DEBUG_KERNEL
|
||||
select TORTURE_TEST
|
||||
select SRCU
|
||||
select TASKS_RCU
|
||||
default n
|
||||
help
|
||||
This option provides a kernel module that runs performance
|
||||
tests on the RCU infrastructure. The kernel module may be built
|
||||
after the fact on the running kernel to be tested, if desired.
|
||||
|
||||
Say Y here if you want RCU performance tests to be built into
|
||||
the kernel.
|
||||
Say M if you want the RCU performance tests to build as a module.
|
||||
Say N if you are unsure.
|
||||
|
||||
config RCU_TORTURE_TEST
|
||||
tristate "torture tests for RCU"
|
||||
depends on DEBUG_KERNEL
|
||||
select TORTURE_TEST
|
||||
select SRCU
|
||||
select TASKS_RCU
|
||||
default n
|
||||
help
|
||||
This option provides a kernel module that runs torture tests
|
||||
on the RCU infrastructure. The kernel module may be built
|
||||
after the fact on the running kernel to be tested, if desired.
|
||||
|
||||
Say Y here if you want RCU torture tests to be built into
|
||||
the kernel.
|
||||
Say M if you want the RCU torture tests to build as a module.
|
||||
Say N if you are unsure.
|
||||
|
||||
config RCU_CPU_STALL_TIMEOUT
|
||||
int "RCU CPU stall timeout in seconds"
|
||||
depends on RCU_STALL_COMMON
|
||||
range 3 300
|
||||
default 21
|
||||
help
|
||||
If a given RCU grace period extends more than the specified
|
||||
number of seconds, a CPU stall warning is printed. If the
|
||||
RCU grace period persists, additional CPU stall warnings are
|
||||
printed at more widely spaced intervals.
|
||||
|
||||
config RCU_TRACE
|
||||
bool "Enable tracing for RCU"
|
||||
depends on DEBUG_KERNEL
|
||||
default y if TREE_RCU
|
||||
select TRACE_CLOCK
|
||||
help
|
||||
This option enables additional tracepoints for ftrace-style
|
||||
event tracing.
|
||||
|
||||
Say Y here if you want to enable RCU tracing
|
||||
Say N if you are unsure.
|
||||
|
||||
config RCU_EQS_DEBUG
|
||||
bool "Provide debugging asserts for adding NO_HZ support to an arch"
|
||||
depends on DEBUG_KERNEL
|
||||
help
|
||||
This option provides consistency checks in RCU's handling of
|
||||
NO_HZ. These checks have proven quite helpful in detecting
|
||||
bugs in arch-specific NO_HZ code.
|
||||
|
||||
Say N here if you need ultimate kernel/user switch latencies
|
||||
Say Y if you are unsure
|
||||
|
||||
endmenu # "RCU Debugging"
|
||||
@@ -3,13 +3,11 @@
|
||||
KCOV_INSTRUMENT := n
|
||||
|
||||
obj-y += update.o sync.o
|
||||
obj-$(CONFIG_CLASSIC_SRCU) += srcu.o
|
||||
obj-$(CONFIG_TREE_SRCU) += srcutree.o
|
||||
obj-$(CONFIG_TINY_SRCU) += srcutiny.o
|
||||
obj-$(CONFIG_RCU_TORTURE_TEST) += rcutorture.o
|
||||
obj-$(CONFIG_RCU_PERF_TEST) += rcuperf.o
|
||||
obj-$(CONFIG_TREE_RCU) += tree.o
|
||||
obj-$(CONFIG_PREEMPT_RCU) += tree.o
|
||||
obj-$(CONFIG_TREE_RCU_TRACE) += tree_trace.o
|
||||
obj-$(CONFIG_TINY_RCU) += tiny.o
|
||||
obj-$(CONFIG_RCU_NEED_SEGCBLIST) += rcu_segcblist.o
|
||||
|
||||
@@ -212,6 +212,18 @@ int rcu_jiffies_till_stall_check(void);
|
||||
*/
|
||||
#define TPS(x) tracepoint_string(x)
|
||||
|
||||
/*
|
||||
* Dump the ftrace buffer, but only one time per callsite per boot.
|
||||
*/
|
||||
#define rcu_ftrace_dump(oops_dump_mode) \
|
||||
do { \
|
||||
static atomic_t ___rfd_beenhere = ATOMIC_INIT(0); \
|
||||
\
|
||||
if (!atomic_read(&___rfd_beenhere) && \
|
||||
!atomic_xchg(&___rfd_beenhere, 1)) \
|
||||
ftrace_dump(oops_dump_mode); \
|
||||
} while (0)
|
||||
|
||||
void rcu_early_boot_tests(void);
|
||||
void rcu_test_sync_prims(void);
|
||||
|
||||
@@ -291,6 +303,271 @@ static inline void rcu_init_levelspread(int *levelspread, const int *levelcnt)
|
||||
cpu <= rnp->grphi; \
|
||||
cpu = cpumask_next((cpu), cpu_possible_mask))
|
||||
|
||||
/*
|
||||
* Wrappers for the rcu_node::lock acquire and release.
|
||||
*
|
||||
* Because the rcu_nodes form a tree, the tree traversal locking will observe
|
||||
* different lock values, this in turn means that an UNLOCK of one level
|
||||
* followed by a LOCK of another level does not imply a full memory barrier;
|
||||
* and most importantly transitivity is lost.
|
||||
*
|
||||
* In order to restore full ordering between tree levels, augment the regular
|
||||
* lock acquire functions with smp_mb__after_unlock_lock().
|
||||
*
|
||||
* As ->lock of struct rcu_node is a __private field, therefore one should use
|
||||
* these wrappers rather than directly call raw_spin_{lock,unlock}* on ->lock.
|
||||
*/
|
||||
#define raw_spin_lock_rcu_node(p) \
|
||||
do { \
|
||||
raw_spin_lock(&ACCESS_PRIVATE(p, lock)); \
|
||||
smp_mb__after_unlock_lock(); \
|
||||
} while (0)
|
||||
|
||||
#define raw_spin_unlock_rcu_node(p) raw_spin_unlock(&ACCESS_PRIVATE(p, lock))
|
||||
|
||||
#define raw_spin_lock_irq_rcu_node(p) \
|
||||
do { \
|
||||
raw_spin_lock_irq(&ACCESS_PRIVATE(p, lock)); \
|
||||
smp_mb__after_unlock_lock(); \
|
||||
} while (0)
|
||||
|
||||
#define raw_spin_unlock_irq_rcu_node(p) \
|
||||
raw_spin_unlock_irq(&ACCESS_PRIVATE(p, lock))
|
||||
|
||||
#define raw_spin_lock_irqsave_rcu_node(p, flags) \
|
||||
do { \
|
||||
raw_spin_lock_irqsave(&ACCESS_PRIVATE(p, lock), flags); \
|
||||
smp_mb__after_unlock_lock(); \
|
||||
} while (0)
|
||||
|
||||
#define raw_spin_unlock_irqrestore_rcu_node(p, flags) \
|
||||
raw_spin_unlock_irqrestore(&ACCESS_PRIVATE(p, lock), flags) \
|
||||
|
||||
#define raw_spin_trylock_rcu_node(p) \
|
||||
({ \
|
||||
bool ___locked = raw_spin_trylock(&ACCESS_PRIVATE(p, lock)); \
|
||||
\
|
||||
if (___locked) \
|
||||
smp_mb__after_unlock_lock(); \
|
||||
___locked; \
|
||||
})
|
||||
|
||||
#endif /* #if defined(SRCU) || !defined(TINY_RCU) */
|
||||
|
||||
#ifdef CONFIG_TINY_RCU
|
||||
/* Tiny RCU doesn't expedite, as its purpose in life is instead to be tiny. */
|
||||
static inline bool rcu_gp_is_normal(void) /* Internal RCU use. */
|
||||
{
|
||||
return true;
|
||||
}
|
||||
static inline bool rcu_gp_is_expedited(void) /* Internal RCU use. */
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void rcu_expedite_gp(void)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void rcu_unexpedite_gp(void)
|
||||
{
|
||||
}
|
||||
#else /* #ifdef CONFIG_TINY_RCU */
|
||||
bool rcu_gp_is_normal(void); /* Internal RCU use. */
|
||||
bool rcu_gp_is_expedited(void); /* Internal RCU use. */
|
||||
void rcu_expedite_gp(void);
|
||||
void rcu_unexpedite_gp(void);
|
||||
void rcupdate_announce_bootup_oddness(void);
|
||||
#endif /* #else #ifdef CONFIG_TINY_RCU */
|
||||
|
||||
#define RCU_SCHEDULER_INACTIVE 0
|
||||
#define RCU_SCHEDULER_INIT 1
|
||||
#define RCU_SCHEDULER_RUNNING 2
|
||||
|
||||
#ifdef CONFIG_TINY_RCU
|
||||
static inline void rcu_request_urgent_qs_task(struct task_struct *t) { }
|
||||
#else /* #ifdef CONFIG_TINY_RCU */
|
||||
void rcu_request_urgent_qs_task(struct task_struct *t);
|
||||
#endif /* #else #ifdef CONFIG_TINY_RCU */
|
||||
|
||||
enum rcutorture_type {
|
||||
RCU_FLAVOR,
|
||||
RCU_BH_FLAVOR,
|
||||
RCU_SCHED_FLAVOR,
|
||||
RCU_TASKS_FLAVOR,
|
||||
SRCU_FLAVOR,
|
||||
INVALID_RCU_FLAVOR
|
||||
};
|
||||
|
||||
#if defined(CONFIG_TREE_RCU) || defined(CONFIG_PREEMPT_RCU)
|
||||
void rcutorture_get_gp_data(enum rcutorture_type test_type, int *flags,
|
||||
unsigned long *gpnum, unsigned long *completed);
|
||||
void rcutorture_record_test_transition(void);
|
||||
void rcutorture_record_progress(unsigned long vernum);
|
||||
void do_trace_rcu_torture_read(const char *rcutorturename,
|
||||
struct rcu_head *rhp,
|
||||
unsigned long secs,
|
||||
unsigned long c_old,
|
||||
unsigned long c);
|
||||
#else
|
||||
static inline void rcutorture_get_gp_data(enum rcutorture_type test_type,
|
||||
int *flags,
|
||||
unsigned long *gpnum,
|
||||
unsigned long *completed)
|
||||
{
|
||||
*flags = 0;
|
||||
*gpnum = 0;
|
||||
*completed = 0;
|
||||
}
|
||||
static inline void rcutorture_record_test_transition(void)
|
||||
{
|
||||
}
|
||||
static inline void rcutorture_record_progress(unsigned long vernum)
|
||||
{
|
||||
}
|
||||
#ifdef CONFIG_RCU_TRACE
|
||||
void do_trace_rcu_torture_read(const char *rcutorturename,
|
||||
struct rcu_head *rhp,
|
||||
unsigned long secs,
|
||||
unsigned long c_old,
|
||||
unsigned long c);
|
||||
#else
|
||||
#define do_trace_rcu_torture_read(rcutorturename, rhp, secs, c_old, c) \
|
||||
do { } while (0)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_TINY_SRCU
|
||||
|
||||
static inline void srcutorture_get_gp_data(enum rcutorture_type test_type,
|
||||
struct srcu_struct *sp, int *flags,
|
||||
unsigned long *gpnum,
|
||||
unsigned long *completed)
|
||||
{
|
||||
if (test_type != SRCU_FLAVOR)
|
||||
return;
|
||||
*flags = 0;
|
||||
*completed = sp->srcu_idx;
|
||||
*gpnum = *completed;
|
||||
}
|
||||
|
||||
#elif defined(CONFIG_TREE_SRCU)
|
||||
|
||||
void srcutorture_get_gp_data(enum rcutorture_type test_type,
|
||||
struct srcu_struct *sp, int *flags,
|
||||
unsigned long *gpnum, unsigned long *completed);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_TINY_RCU
|
||||
|
||||
/*
|
||||
* Return the number of grace periods started.
|
||||
*/
|
||||
static inline unsigned long rcu_batches_started(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the number of bottom-half grace periods started.
|
||||
*/
|
||||
static inline unsigned long rcu_batches_started_bh(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the number of sched grace periods started.
|
||||
*/
|
||||
static inline unsigned long rcu_batches_started_sched(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the number of grace periods completed.
|
||||
*/
|
||||
static inline unsigned long rcu_batches_completed(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the number of bottom-half grace periods completed.
|
||||
*/
|
||||
static inline unsigned long rcu_batches_completed_bh(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the number of sched grace periods completed.
|
||||
*/
|
||||
static inline unsigned long rcu_batches_completed_sched(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the number of expedited grace periods completed.
|
||||
*/
|
||||
static inline unsigned long rcu_exp_batches_completed(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the number of expedited sched grace periods completed.
|
||||
*/
|
||||
static inline unsigned long rcu_exp_batches_completed_sched(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline unsigned long srcu_batches_completed(struct srcu_struct *sp)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline void rcu_force_quiescent_state(void)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void rcu_bh_force_quiescent_state(void)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void rcu_sched_force_quiescent_state(void)
|
||||
{
|
||||
}
|
||||
|
||||
static inline void show_rcu_gp_kthreads(void)
|
||||
{
|
||||
}
|
||||
|
||||
#else /* #ifdef CONFIG_TINY_RCU */
|
||||
extern unsigned long rcutorture_testseq;
|
||||
extern unsigned long rcutorture_vernum;
|
||||
unsigned long rcu_batches_started(void);
|
||||
unsigned long rcu_batches_started_bh(void);
|
||||
unsigned long rcu_batches_started_sched(void);
|
||||
unsigned long rcu_batches_completed(void);
|
||||
unsigned long rcu_batches_completed_bh(void);
|
||||
unsigned long rcu_batches_completed_sched(void);
|
||||
unsigned long rcu_exp_batches_completed(void);
|
||||
unsigned long rcu_exp_batches_completed_sched(void);
|
||||
unsigned long srcu_batches_completed(struct srcu_struct *sp);
|
||||
void show_rcu_gp_kthreads(void);
|
||||
void rcu_force_quiescent_state(void);
|
||||
void rcu_bh_force_quiescent_state(void);
|
||||
void rcu_sched_force_quiescent_state(void);
|
||||
#endif /* #else #ifdef CONFIG_TINY_RCU */
|
||||
|
||||
#ifdef CONFIG_RCU_NOCB_CPU
|
||||
bool rcu_is_nocb_cpu(int cpu);
|
||||
#else
|
||||
static inline bool rcu_is_nocb_cpu(int cpu) { return false; }
|
||||
#endif
|
||||
|
||||
#endif /* __LINUX_RCU_H */
|
||||
|
||||
+111
-18
@@ -48,6 +48,8 @@
|
||||
#include <linux/torture.h>
|
||||
#include <linux/vmalloc.h>
|
||||
|
||||
#include "rcu.h"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Paul E. McKenney <paulmck@linux.vnet.ibm.com>");
|
||||
|
||||
@@ -59,12 +61,16 @@ MODULE_AUTHOR("Paul E. McKenney <paulmck@linux.vnet.ibm.com>");
|
||||
#define VERBOSE_PERFOUT_ERRSTRING(s) \
|
||||
do { if (verbose) pr_alert("%s" PERF_FLAG "!!! %s\n", perf_type, s); } while (0)
|
||||
|
||||
torture_param(bool, gp_async, false, "Use asynchronous GP wait primitives");
|
||||
torture_param(int, gp_async_max, 1000, "Max # outstanding waits per reader");
|
||||
torture_param(bool, gp_exp, false, "Use expedited GP wait primitives");
|
||||
torture_param(int, holdoff, 10, "Holdoff time before test start (s)");
|
||||
torture_param(int, nreaders, -1, "Number of RCU reader threads");
|
||||
torture_param(int, nreaders, 0, "Number of RCU reader threads");
|
||||
torture_param(int, nwriters, -1, "Number of RCU updater threads");
|
||||
torture_param(bool, shutdown, false, "Shutdown at end of performance tests.");
|
||||
torture_param(bool, shutdown, !IS_ENABLED(MODULE),
|
||||
"Shutdown at end of performance tests.");
|
||||
torture_param(bool, verbose, true, "Enable verbose debugging printk()s");
|
||||
torture_param(int, writer_holdoff, 0, "Holdoff (us) between GPs, zero to disable");
|
||||
|
||||
static char *perf_type = "rcu";
|
||||
module_param(perf_type, charp, 0444);
|
||||
@@ -86,13 +92,16 @@ static u64 t_rcu_perf_writer_started;
|
||||
static u64 t_rcu_perf_writer_finished;
|
||||
static unsigned long b_rcu_perf_writer_started;
|
||||
static unsigned long b_rcu_perf_writer_finished;
|
||||
static DEFINE_PER_CPU(atomic_t, n_async_inflight);
|
||||
|
||||
static int rcu_perf_writer_state;
|
||||
#define RTWS_INIT 0
|
||||
#define RTWS_EXP_SYNC 1
|
||||
#define RTWS_SYNC 2
|
||||
#define RTWS_IDLE 2
|
||||
#define RTWS_STOPPING 3
|
||||
#define RTWS_ASYNC 1
|
||||
#define RTWS_BARRIER 2
|
||||
#define RTWS_EXP_SYNC 3
|
||||
#define RTWS_SYNC 4
|
||||
#define RTWS_IDLE 5
|
||||
#define RTWS_STOPPING 6
|
||||
|
||||
#define MAX_MEAS 10000
|
||||
#define MIN_MEAS 100
|
||||
@@ -114,6 +123,8 @@ struct rcu_perf_ops {
|
||||
unsigned long (*started)(void);
|
||||
unsigned long (*completed)(void);
|
||||
unsigned long (*exp_completed)(void);
|
||||
void (*async)(struct rcu_head *head, rcu_callback_t func);
|
||||
void (*gp_barrier)(void);
|
||||
void (*sync)(void);
|
||||
void (*exp_sync)(void);
|
||||
const char *name;
|
||||
@@ -153,6 +164,8 @@ static struct rcu_perf_ops rcu_ops = {
|
||||
.started = rcu_batches_started,
|
||||
.completed = rcu_batches_completed,
|
||||
.exp_completed = rcu_exp_batches_completed,
|
||||
.async = call_rcu,
|
||||
.gp_barrier = rcu_barrier,
|
||||
.sync = synchronize_rcu,
|
||||
.exp_sync = synchronize_rcu_expedited,
|
||||
.name = "rcu"
|
||||
@@ -181,6 +194,8 @@ static struct rcu_perf_ops rcu_bh_ops = {
|
||||
.started = rcu_batches_started_bh,
|
||||
.completed = rcu_batches_completed_bh,
|
||||
.exp_completed = rcu_exp_batches_completed_sched,
|
||||
.async = call_rcu_bh,
|
||||
.gp_barrier = rcu_barrier_bh,
|
||||
.sync = synchronize_rcu_bh,
|
||||
.exp_sync = synchronize_rcu_bh_expedited,
|
||||
.name = "rcu_bh"
|
||||
@@ -208,6 +223,16 @@ static unsigned long srcu_perf_completed(void)
|
||||
return srcu_batches_completed(srcu_ctlp);
|
||||
}
|
||||
|
||||
static void srcu_call_rcu(struct rcu_head *head, rcu_callback_t func)
|
||||
{
|
||||
call_srcu(srcu_ctlp, head, func);
|
||||
}
|
||||
|
||||
static void srcu_rcu_barrier(void)
|
||||
{
|
||||
srcu_barrier(srcu_ctlp);
|
||||
}
|
||||
|
||||
static void srcu_perf_synchronize(void)
|
||||
{
|
||||
synchronize_srcu(srcu_ctlp);
|
||||
@@ -226,11 +251,42 @@ static struct rcu_perf_ops srcu_ops = {
|
||||
.started = NULL,
|
||||
.completed = srcu_perf_completed,
|
||||
.exp_completed = srcu_perf_completed,
|
||||
.async = srcu_call_rcu,
|
||||
.gp_barrier = srcu_rcu_barrier,
|
||||
.sync = srcu_perf_synchronize,
|
||||
.exp_sync = srcu_perf_synchronize_expedited,
|
||||
.name = "srcu"
|
||||
};
|
||||
|
||||
static struct srcu_struct srcud;
|
||||
|
||||
static void srcu_sync_perf_init(void)
|
||||
{
|
||||
srcu_ctlp = &srcud;
|
||||
init_srcu_struct(srcu_ctlp);
|
||||
}
|
||||
|
||||
static void srcu_sync_perf_cleanup(void)
|
||||
{
|
||||
cleanup_srcu_struct(srcu_ctlp);
|
||||
}
|
||||
|
||||
static struct rcu_perf_ops srcud_ops = {
|
||||
.ptype = SRCU_FLAVOR,
|
||||
.init = srcu_sync_perf_init,
|
||||
.cleanup = srcu_sync_perf_cleanup,
|
||||
.readlock = srcu_perf_read_lock,
|
||||
.readunlock = srcu_perf_read_unlock,
|
||||
.started = NULL,
|
||||
.completed = srcu_perf_completed,
|
||||
.exp_completed = srcu_perf_completed,
|
||||
.async = srcu_call_rcu,
|
||||
.gp_barrier = srcu_rcu_barrier,
|
||||
.sync = srcu_perf_synchronize,
|
||||
.exp_sync = srcu_perf_synchronize_expedited,
|
||||
.name = "srcud"
|
||||
};
|
||||
|
||||
/*
|
||||
* Definitions for sched perf testing.
|
||||
*/
|
||||
@@ -254,6 +310,8 @@ static struct rcu_perf_ops sched_ops = {
|
||||
.started = rcu_batches_started_sched,
|
||||
.completed = rcu_batches_completed_sched,
|
||||
.exp_completed = rcu_exp_batches_completed_sched,
|
||||
.async = call_rcu_sched,
|
||||
.gp_barrier = rcu_barrier_sched,
|
||||
.sync = synchronize_sched,
|
||||
.exp_sync = synchronize_sched_expedited,
|
||||
.name = "sched"
|
||||
@@ -281,6 +339,8 @@ static struct rcu_perf_ops tasks_ops = {
|
||||
.readunlock = tasks_perf_read_unlock,
|
||||
.started = rcu_no_completed,
|
||||
.completed = rcu_no_completed,
|
||||
.async = call_rcu_tasks,
|
||||
.gp_barrier = rcu_barrier_tasks,
|
||||
.sync = synchronize_rcu_tasks,
|
||||
.exp_sync = synchronize_rcu_tasks,
|
||||
.name = "tasks"
|
||||
@@ -343,6 +403,15 @@ rcu_perf_reader(void *arg)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Callback function for asynchronous grace periods from rcu_perf_writer().
|
||||
*/
|
||||
static void rcu_perf_async_cb(struct rcu_head *rhp)
|
||||
{
|
||||
atomic_dec(this_cpu_ptr(&n_async_inflight));
|
||||
kfree(rhp);
|
||||
}
|
||||
|
||||
/*
|
||||
* RCU perf writer kthread. Repeatedly does a grace period.
|
||||
*/
|
||||
@@ -352,6 +421,7 @@ rcu_perf_writer(void *arg)
|
||||
int i = 0;
|
||||
int i_max;
|
||||
long me = (long)arg;
|
||||
struct rcu_head *rhp = NULL;
|
||||
struct sched_param sp;
|
||||
bool started = false, done = false, alldone = false;
|
||||
u64 t;
|
||||
@@ -380,9 +450,27 @@ rcu_perf_writer(void *arg)
|
||||
}
|
||||
|
||||
do {
|
||||
if (writer_holdoff)
|
||||
udelay(writer_holdoff);
|
||||
wdp = &wdpp[i];
|
||||
*wdp = ktime_get_mono_fast_ns();
|
||||
if (gp_exp) {
|
||||
if (gp_async) {
|
||||
retry:
|
||||
if (!rhp)
|
||||
rhp = kmalloc(sizeof(*rhp), GFP_KERNEL);
|
||||
if (rhp && atomic_read(this_cpu_ptr(&n_async_inflight)) < gp_async_max) {
|
||||
rcu_perf_writer_state = RTWS_ASYNC;
|
||||
atomic_inc(this_cpu_ptr(&n_async_inflight));
|
||||
cur_ops->async(rhp, rcu_perf_async_cb);
|
||||
rhp = NULL;
|
||||
} else if (!kthread_should_stop()) {
|
||||
rcu_perf_writer_state = RTWS_BARRIER;
|
||||
cur_ops->gp_barrier();
|
||||
goto retry;
|
||||
} else {
|
||||
kfree(rhp); /* Because we are stopping. */
|
||||
}
|
||||
} else if (gp_exp) {
|
||||
rcu_perf_writer_state = RTWS_EXP_SYNC;
|
||||
cur_ops->exp_sync();
|
||||
} else {
|
||||
@@ -429,6 +517,10 @@ rcu_perf_writer(void *arg)
|
||||
i++;
|
||||
rcu_perf_wait_shutdown();
|
||||
} while (!torture_must_stop());
|
||||
if (gp_async) {
|
||||
rcu_perf_writer_state = RTWS_BARRIER;
|
||||
cur_ops->gp_barrier();
|
||||
}
|
||||
rcu_perf_writer_state = RTWS_STOPPING;
|
||||
writer_n_durations[me] = i_max;
|
||||
torture_kthread_stopping("rcu_perf_writer");
|
||||
@@ -452,6 +544,17 @@ rcu_perf_cleanup(void)
|
||||
u64 *wdp;
|
||||
u64 *wdpp;
|
||||
|
||||
/*
|
||||
* Would like warning at start, but everything is expedited
|
||||
* during the mid-boot phase, so have to wait till the end.
|
||||
*/
|
||||
if (rcu_gp_is_expedited() && !rcu_gp_is_normal() && !gp_exp)
|
||||
VERBOSE_PERFOUT_ERRSTRING("All grace periods expedited, no normal ones to measure!");
|
||||
if (rcu_gp_is_normal() && gp_exp)
|
||||
VERBOSE_PERFOUT_ERRSTRING("All grace periods normal, no expedited ones to measure!");
|
||||
if (gp_exp && gp_async)
|
||||
VERBOSE_PERFOUT_ERRSTRING("No expedited async GPs, so went with async!");
|
||||
|
||||
if (torture_cleanup_begin())
|
||||
return;
|
||||
|
||||
@@ -554,7 +657,7 @@ rcu_perf_init(void)
|
||||
long i;
|
||||
int firsterr = 0;
|
||||
static struct rcu_perf_ops *perf_ops[] = {
|
||||
&rcu_ops, &rcu_bh_ops, &srcu_ops, &sched_ops,
|
||||
&rcu_ops, &rcu_bh_ops, &srcu_ops, &srcud_ops, &sched_ops,
|
||||
RCUPERF_TASKS_OPS
|
||||
};
|
||||
|
||||
@@ -624,16 +727,6 @@ rcu_perf_init(void)
|
||||
firsterr = -ENOMEM;
|
||||
goto unwind;
|
||||
}
|
||||
if (rcu_gp_is_expedited() && !rcu_gp_is_normal() && !gp_exp) {
|
||||
VERBOSE_PERFOUT_ERRSTRING("All grace periods expedited, no normal ones to measure!");
|
||||
firsterr = -EINVAL;
|
||||
goto unwind;
|
||||
}
|
||||
if (rcu_gp_is_normal() && gp_exp) {
|
||||
VERBOSE_PERFOUT_ERRSTRING("All grace periods normal, no expedited ones to measure!");
|
||||
firsterr = -EINVAL;
|
||||
goto unwind;
|
||||
}
|
||||
for (i = 0; i < nrealwriters; i++) {
|
||||
writer_durations[i] =
|
||||
kcalloc(MAX_MEAS, sizeof(*writer_durations[i]),
|
||||
|
||||
+3
-18
@@ -52,6 +52,8 @@
|
||||
#include <linux/torture.h>
|
||||
#include <linux/vmalloc.h>
|
||||
|
||||
#include "rcu.h"
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Paul E. McKenney <paulmck@us.ibm.com> and Josh Triplett <josh@joshtriplett.org>");
|
||||
|
||||
@@ -562,31 +564,19 @@ static void srcu_torture_stats(void)
|
||||
int __maybe_unused cpu;
|
||||
int idx;
|
||||
|
||||
#if defined(CONFIG_TREE_SRCU) || defined(CONFIG_CLASSIC_SRCU)
|
||||
#ifdef CONFIG_TREE_SRCU
|
||||
idx = srcu_ctlp->srcu_idx & 0x1;
|
||||
#else /* #ifdef CONFIG_TREE_SRCU */
|
||||
idx = srcu_ctlp->completed & 0x1;
|
||||
#endif /* #else #ifdef CONFIG_TREE_SRCU */
|
||||
pr_alert("%s%s Tree SRCU per-CPU(idx=%d):",
|
||||
torture_type, TORTURE_FLAG, idx);
|
||||
for_each_possible_cpu(cpu) {
|
||||
unsigned long l0, l1;
|
||||
unsigned long u0, u1;
|
||||
long c0, c1;
|
||||
#ifdef CONFIG_TREE_SRCU
|
||||
struct srcu_data *counts;
|
||||
|
||||
counts = per_cpu_ptr(srcu_ctlp->sda, cpu);
|
||||
u0 = counts->srcu_unlock_count[!idx];
|
||||
u1 = counts->srcu_unlock_count[idx];
|
||||
#else /* #ifdef CONFIG_TREE_SRCU */
|
||||
struct srcu_array *counts;
|
||||
|
||||
counts = per_cpu_ptr(srcu_ctlp->per_cpu_ref, cpu);
|
||||
u0 = counts->unlock_count[!idx];
|
||||
u1 = counts->unlock_count[idx];
|
||||
#endif /* #else #ifdef CONFIG_TREE_SRCU */
|
||||
|
||||
/*
|
||||
* Make sure that a lock is always counted if the corresponding
|
||||
@@ -594,13 +584,8 @@ static void srcu_torture_stats(void)
|
||||
*/
|
||||
smp_rmb();
|
||||
|
||||
#ifdef CONFIG_TREE_SRCU
|
||||
l0 = counts->srcu_lock_count[!idx];
|
||||
l1 = counts->srcu_lock_count[idx];
|
||||
#else /* #ifdef CONFIG_TREE_SRCU */
|
||||
l0 = counts->lock_count[!idx];
|
||||
l1 = counts->lock_count[idx];
|
||||
#endif /* #else #ifdef CONFIG_TREE_SRCU */
|
||||
|
||||
c0 = l0 - u0;
|
||||
c1 = l1 - u1;
|
||||
@@ -609,7 +594,7 @@ static void srcu_torture_stats(void)
|
||||
pr_cont("\n");
|
||||
#elif defined(CONFIG_TINY_SRCU)
|
||||
idx = READ_ONCE(srcu_ctlp->srcu_idx) & 0x1;
|
||||
pr_alert("%s%s Tiny SRCU per-CPU(idx=%d): (%d,%d)\n",
|
||||
pr_alert("%s%s Tiny SRCU per-CPU(idx=%d): (%hd,%hd)\n",
|
||||
torture_type, TORTURE_FLAG, idx,
|
||||
READ_ONCE(srcu_ctlp->srcu_lock_nesting[!idx]),
|
||||
READ_ONCE(srcu_ctlp->srcu_lock_nesting[idx]));
|
||||
|
||||
@@ -1,662 +0,0 @@
|
||||
/*
|
||||
* Sleepable Read-Copy Update mechanism for mutual exclusion.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, you can access it online at
|
||||
* http://www.gnu.org/licenses/gpl-2.0.html.
|
||||
*
|
||||
* Copyright (C) IBM Corporation, 2006
|
||||
* Copyright (C) Fujitsu, 2012
|
||||
*
|
||||
* Author: Paul McKenney <paulmck@us.ibm.com>
|
||||
* Lai Jiangshan <laijs@cn.fujitsu.com>
|
||||
*
|
||||
* For detailed explanation of Read-Copy Update mechanism see -
|
||||
* Documentation/RCU/ *.txt
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/export.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/percpu.h>
|
||||
#include <linux/preempt.h>
|
||||
#include <linux/rcupdate_wait.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/smp.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/srcu.h>
|
||||
|
||||
#include "rcu.h"
|
||||
|
||||
/*
|
||||
* Initialize an rcu_batch structure to empty.
|
||||
*/
|
||||
static inline void rcu_batch_init(struct rcu_batch *b)
|
||||
{
|
||||
b->head = NULL;
|
||||
b->tail = &b->head;
|
||||
}
|
||||
|
||||
/*
|
||||
* Enqueue a callback onto the tail of the specified rcu_batch structure.
|
||||
*/
|
||||
static inline void rcu_batch_queue(struct rcu_batch *b, struct rcu_head *head)
|
||||
{
|
||||
*b->tail = head;
|
||||
b->tail = &head->next;
|
||||
}
|
||||
|
||||
/*
|
||||
* Is the specified rcu_batch structure empty?
|
||||
*/
|
||||
static inline bool rcu_batch_empty(struct rcu_batch *b)
|
||||
{
|
||||
return b->tail == &b->head;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the callback at the head of the specified rcu_batch structure
|
||||
* and return a pointer to it, or return NULL if the structure is empty.
|
||||
*/
|
||||
static inline struct rcu_head *rcu_batch_dequeue(struct rcu_batch *b)
|
||||
{
|
||||
struct rcu_head *head;
|
||||
|
||||
if (rcu_batch_empty(b))
|
||||
return NULL;
|
||||
|
||||
head = b->head;
|
||||
b->head = head->next;
|
||||
if (b->tail == &head->next)
|
||||
rcu_batch_init(b);
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
/*
|
||||
* Move all callbacks from the rcu_batch structure specified by "from" to
|
||||
* the structure specified by "to".
|
||||
*/
|
||||
static inline void rcu_batch_move(struct rcu_batch *to, struct rcu_batch *from)
|
||||
{
|
||||
if (!rcu_batch_empty(from)) {
|
||||
*to->tail = from->head;
|
||||
to->tail = from->tail;
|
||||
rcu_batch_init(from);
|
||||
}
|
||||
}
|
||||
|
||||
static int init_srcu_struct_fields(struct srcu_struct *sp)
|
||||
{
|
||||
sp->completed = 0;
|
||||
spin_lock_init(&sp->queue_lock);
|
||||
sp->running = false;
|
||||
rcu_batch_init(&sp->batch_queue);
|
||||
rcu_batch_init(&sp->batch_check0);
|
||||
rcu_batch_init(&sp->batch_check1);
|
||||
rcu_batch_init(&sp->batch_done);
|
||||
INIT_DELAYED_WORK(&sp->work, process_srcu);
|
||||
sp->per_cpu_ref = alloc_percpu(struct srcu_array);
|
||||
return sp->per_cpu_ref ? 0 : -ENOMEM;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
||||
|
||||
int __init_srcu_struct(struct srcu_struct *sp, const char *name,
|
||||
struct lock_class_key *key)
|
||||
{
|
||||
/* Don't re-initialize a lock while it is held. */
|
||||
debug_check_no_locks_freed((void *)sp, sizeof(*sp));
|
||||
lockdep_init_map(&sp->dep_map, name, key, 0);
|
||||
return init_srcu_struct_fields(sp);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__init_srcu_struct);
|
||||
|
||||
#else /* #ifdef CONFIG_DEBUG_LOCK_ALLOC */
|
||||
|
||||
/**
|
||||
* init_srcu_struct - initialize a sleep-RCU structure
|
||||
* @sp: structure to initialize.
|
||||
*
|
||||
* Must invoke this on a given srcu_struct before passing that srcu_struct
|
||||
* to any other function. Each srcu_struct represents a separate domain
|
||||
* of SRCU protection.
|
||||
*/
|
||||
int init_srcu_struct(struct srcu_struct *sp)
|
||||
{
|
||||
return init_srcu_struct_fields(sp);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(init_srcu_struct);
|
||||
|
||||
#endif /* #else #ifdef CONFIG_DEBUG_LOCK_ALLOC */
|
||||
|
||||
/*
|
||||
* Returns approximate total of the readers' ->lock_count[] values for the
|
||||
* rank of per-CPU counters specified by idx.
|
||||
*/
|
||||
static unsigned long srcu_readers_lock_idx(struct srcu_struct *sp, int idx)
|
||||
{
|
||||
int cpu;
|
||||
unsigned long sum = 0;
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
struct srcu_array *cpuc = per_cpu_ptr(sp->per_cpu_ref, cpu);
|
||||
|
||||
sum += READ_ONCE(cpuc->lock_count[idx]);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns approximate total of the readers' ->unlock_count[] values for the
|
||||
* rank of per-CPU counters specified by idx.
|
||||
*/
|
||||
static unsigned long srcu_readers_unlock_idx(struct srcu_struct *sp, int idx)
|
||||
{
|
||||
int cpu;
|
||||
unsigned long sum = 0;
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
struct srcu_array *cpuc = per_cpu_ptr(sp->per_cpu_ref, cpu);
|
||||
|
||||
sum += READ_ONCE(cpuc->unlock_count[idx]);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return true if the number of pre-existing readers is determined to
|
||||
* be zero.
|
||||
*/
|
||||
static bool srcu_readers_active_idx_check(struct srcu_struct *sp, int idx)
|
||||
{
|
||||
unsigned long unlocks;
|
||||
|
||||
unlocks = srcu_readers_unlock_idx(sp, idx);
|
||||
|
||||
/*
|
||||
* Make sure that a lock is always counted if the corresponding unlock
|
||||
* is counted. Needs to be a smp_mb() as the read side may contain a
|
||||
* read from a variable that is written to before the synchronize_srcu()
|
||||
* in the write side. In this case smp_mb()s A and B act like the store
|
||||
* buffering pattern.
|
||||
*
|
||||
* This smp_mb() also pairs with smp_mb() C to prevent accesses after the
|
||||
* synchronize_srcu() from being executed before the grace period ends.
|
||||
*/
|
||||
smp_mb(); /* A */
|
||||
|
||||
/*
|
||||
* If the locks are the same as the unlocks, then there must have
|
||||
* been no readers on this index at some time in between. This does not
|
||||
* mean that there are no more readers, as one could have read the
|
||||
* current index but not have incremented the lock counter yet.
|
||||
*
|
||||
* Possible bug: There is no guarantee that there haven't been ULONG_MAX
|
||||
* increments of ->lock_count[] since the unlocks were counted, meaning
|
||||
* that this could return true even if there are still active readers.
|
||||
* Since there are no memory barriers around srcu_flip(), the CPU is not
|
||||
* required to increment ->completed before running
|
||||
* srcu_readers_unlock_idx(), which means that there could be an
|
||||
* arbitrarily large number of critical sections that execute after
|
||||
* srcu_readers_unlock_idx() but use the old value of ->completed.
|
||||
*/
|
||||
return srcu_readers_lock_idx(sp, idx) == unlocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* srcu_readers_active - returns true if there are readers. and false
|
||||
* otherwise
|
||||
* @sp: which srcu_struct to count active readers (holding srcu_read_lock).
|
||||
*
|
||||
* Note that this is not an atomic primitive, and can therefore suffer
|
||||
* severe errors when invoked on an active srcu_struct. That said, it
|
||||
* can be useful as an error check at cleanup time.
|
||||
*/
|
||||
static bool srcu_readers_active(struct srcu_struct *sp)
|
||||
{
|
||||
int cpu;
|
||||
unsigned long sum = 0;
|
||||
|
||||
for_each_possible_cpu(cpu) {
|
||||
struct srcu_array *cpuc = per_cpu_ptr(sp->per_cpu_ref, cpu);
|
||||
|
||||
sum += READ_ONCE(cpuc->lock_count[0]);
|
||||
sum += READ_ONCE(cpuc->lock_count[1]);
|
||||
sum -= READ_ONCE(cpuc->unlock_count[0]);
|
||||
sum -= READ_ONCE(cpuc->unlock_count[1]);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
/**
|
||||
* cleanup_srcu_struct - deconstruct a sleep-RCU structure
|
||||
* @sp: structure to clean up.
|
||||
*
|
||||
* Must invoke this only after you are finished using a given srcu_struct
|
||||
* that was initialized via init_srcu_struct(). This code does some
|
||||
* probabalistic checking, spotting late uses of srcu_read_lock(),
|
||||
* synchronize_srcu(), synchronize_srcu_expedited(), and call_srcu().
|
||||
* If any such late uses are detected, the per-CPU memory associated with
|
||||
* the srcu_struct is simply leaked and WARN_ON() is invoked. If the
|
||||
* caller frees the srcu_struct itself, a use-after-free crash will likely
|
||||
* ensue, but at least there will be a warning printed.
|
||||
*/
|
||||
void cleanup_srcu_struct(struct srcu_struct *sp)
|
||||
{
|
||||
if (WARN_ON(srcu_readers_active(sp)))
|
||||
return; /* Leakage unless caller handles error. */
|
||||
free_percpu(sp->per_cpu_ref);
|
||||
sp->per_cpu_ref = NULL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cleanup_srcu_struct);
|
||||
|
||||
/*
|
||||
* Counts the new reader in the appropriate per-CPU element of the
|
||||
* srcu_struct. Must be called from process context.
|
||||
* Returns an index that must be passed to the matching srcu_read_unlock().
|
||||
*/
|
||||
int __srcu_read_lock(struct srcu_struct *sp)
|
||||
{
|
||||
int idx;
|
||||
|
||||
idx = READ_ONCE(sp->completed) & 0x1;
|
||||
__this_cpu_inc(sp->per_cpu_ref->lock_count[idx]);
|
||||
smp_mb(); /* B */ /* Avoid leaking the critical section. */
|
||||
return idx;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__srcu_read_lock);
|
||||
|
||||
/*
|
||||
* Removes the count for the old reader from the appropriate per-CPU
|
||||
* element of the srcu_struct. Note that this may well be a different
|
||||
* CPU than that which was incremented by the corresponding srcu_read_lock().
|
||||
* Must be called from process context.
|
||||
*/
|
||||
void __srcu_read_unlock(struct srcu_struct *sp, int idx)
|
||||
{
|
||||
smp_mb(); /* C */ /* Avoid leaking the critical section. */
|
||||
this_cpu_inc(sp->per_cpu_ref->unlock_count[idx]);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__srcu_read_unlock);
|
||||
|
||||
/*
|
||||
* We use an adaptive strategy for synchronize_srcu() and especially for
|
||||
* synchronize_srcu_expedited(). We spin for a fixed time period
|
||||
* (defined below) to allow SRCU readers to exit their read-side critical
|
||||
* sections. If there are still some readers after 10 microseconds,
|
||||
* we repeatedly block for 1-millisecond time periods. This approach
|
||||
* has done well in testing, so there is no need for a config parameter.
|
||||
*/
|
||||
#define SRCU_RETRY_CHECK_DELAY 5
|
||||
#define SYNCHRONIZE_SRCU_TRYCOUNT 2
|
||||
#define SYNCHRONIZE_SRCU_EXP_TRYCOUNT 12
|
||||
|
||||
/*
|
||||
* @@@ Wait until all pre-existing readers complete. Such readers
|
||||
* will have used the index specified by "idx".
|
||||
* the caller should ensures the ->completed is not changed while checking
|
||||
* and idx = (->completed & 1) ^ 1
|
||||
*/
|
||||
static bool try_check_zero(struct srcu_struct *sp, int idx, int trycount)
|
||||
{
|
||||
for (;;) {
|
||||
if (srcu_readers_active_idx_check(sp, idx))
|
||||
return true;
|
||||
if (--trycount <= 0)
|
||||
return false;
|
||||
udelay(SRCU_RETRY_CHECK_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Increment the ->completed counter so that future SRCU readers will
|
||||
* use the other rank of the ->(un)lock_count[] arrays. This allows
|
||||
* us to wait for pre-existing readers in a starvation-free manner.
|
||||
*/
|
||||
static void srcu_flip(struct srcu_struct *sp)
|
||||
{
|
||||
WRITE_ONCE(sp->completed, sp->completed + 1);
|
||||
|
||||
/*
|
||||
* Ensure that if the updater misses an __srcu_read_unlock()
|
||||
* increment, that task's next __srcu_read_lock() will see the
|
||||
* above counter update. Note that both this memory barrier
|
||||
* and the one in srcu_readers_active_idx_check() provide the
|
||||
* guarantee for __srcu_read_lock().
|
||||
*/
|
||||
smp_mb(); /* D */ /* Pairs with C. */
|
||||
}
|
||||
|
||||
/*
|
||||
* Enqueue an SRCU callback on the specified srcu_struct structure,
|
||||
* initiating grace-period processing if it is not already running.
|
||||
*
|
||||
* Note that all CPUs must agree that the grace period extended beyond
|
||||
* all pre-existing SRCU read-side critical section. On systems with
|
||||
* more than one CPU, this means that when "func()" is invoked, each CPU
|
||||
* is guaranteed to have executed a full memory barrier since the end of
|
||||
* its last corresponding SRCU read-side critical section whose beginning
|
||||
* preceded the call to call_rcu(). It also means that each CPU executing
|
||||
* an SRCU read-side critical section that continues beyond the start of
|
||||
* "func()" must have executed a memory barrier after the call_rcu()
|
||||
* but before the beginning of that SRCU read-side critical section.
|
||||
* Note that these guarantees include CPUs that are offline, idle, or
|
||||
* executing in user mode, as well as CPUs that are executing in the kernel.
|
||||
*
|
||||
* Furthermore, if CPU A invoked call_rcu() and CPU B invoked the
|
||||
* resulting SRCU callback function "func()", then both CPU A and CPU
|
||||
* B are guaranteed to execute a full memory barrier during the time
|
||||
* interval between the call to call_rcu() and the invocation of "func()".
|
||||
* This guarantee applies even if CPU A and CPU B are the same CPU (but
|
||||
* again only if the system has more than one CPU).
|
||||
*
|
||||
* Of course, these guarantees apply only for invocations of call_srcu(),
|
||||
* srcu_read_lock(), and srcu_read_unlock() that are all passed the same
|
||||
* srcu_struct structure.
|
||||
*/
|
||||
void call_srcu(struct srcu_struct *sp, struct rcu_head *head,
|
||||
rcu_callback_t func)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
head->next = NULL;
|
||||
head->func = func;
|
||||
spin_lock_irqsave(&sp->queue_lock, flags);
|
||||
smp_mb__after_unlock_lock(); /* Caller's prior accesses before GP. */
|
||||
rcu_batch_queue(&sp->batch_queue, head);
|
||||
if (!sp->running) {
|
||||
sp->running = true;
|
||||
queue_delayed_work(system_power_efficient_wq, &sp->work, 0);
|
||||
}
|
||||
spin_unlock_irqrestore(&sp->queue_lock, flags);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(call_srcu);
|
||||
|
||||
static void srcu_advance_batches(struct srcu_struct *sp, int trycount);
|
||||
static void srcu_reschedule(struct srcu_struct *sp);
|
||||
|
||||
/*
|
||||
* Helper function for synchronize_srcu() and synchronize_srcu_expedited().
|
||||
*/
|
||||
static void __synchronize_srcu(struct srcu_struct *sp, int trycount)
|
||||
{
|
||||
struct rcu_synchronize rcu;
|
||||
struct rcu_head *head = &rcu.head;
|
||||
bool done = false;
|
||||
|
||||
RCU_LOCKDEP_WARN(lock_is_held(&sp->dep_map) ||
|
||||
lock_is_held(&rcu_bh_lock_map) ||
|
||||
lock_is_held(&rcu_lock_map) ||
|
||||
lock_is_held(&rcu_sched_lock_map),
|
||||
"Illegal synchronize_srcu() in same-type SRCU (or in RCU) read-side critical section");
|
||||
|
||||
might_sleep();
|
||||
init_completion(&rcu.completion);
|
||||
|
||||
head->next = NULL;
|
||||
head->func = wakeme_after_rcu;
|
||||
spin_lock_irq(&sp->queue_lock);
|
||||
smp_mb__after_unlock_lock(); /* Caller's prior accesses before GP. */
|
||||
if (!sp->running) {
|
||||
/* steal the processing owner */
|
||||
sp->running = true;
|
||||
rcu_batch_queue(&sp->batch_check0, head);
|
||||
spin_unlock_irq(&sp->queue_lock);
|
||||
|
||||
srcu_advance_batches(sp, trycount);
|
||||
if (!rcu_batch_empty(&sp->batch_done)) {
|
||||
BUG_ON(sp->batch_done.head != head);
|
||||
rcu_batch_dequeue(&sp->batch_done);
|
||||
done = true;
|
||||
}
|
||||
/* give the processing owner to work_struct */
|
||||
srcu_reschedule(sp);
|
||||
} else {
|
||||
rcu_batch_queue(&sp->batch_queue, head);
|
||||
spin_unlock_irq(&sp->queue_lock);
|
||||
}
|
||||
|
||||
if (!done) {
|
||||
wait_for_completion(&rcu.completion);
|
||||
smp_mb(); /* Caller's later accesses after GP. */
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* synchronize_srcu - wait for prior SRCU read-side critical-section completion
|
||||
* @sp: srcu_struct with which to synchronize.
|
||||
*
|
||||
* Wait for the count to drain to zero of both indexes. To avoid the
|
||||
* possible starvation of synchronize_srcu(), it waits for the count of
|
||||
* the index=((->completed & 1) ^ 1) to drain to zero at first,
|
||||
* and then flip the completed and wait for the count of the other index.
|
||||
*
|
||||
* Can block; must be called from process context.
|
||||
*
|
||||
* Note that it is illegal to call synchronize_srcu() from the corresponding
|
||||
* SRCU read-side critical section; doing so will result in deadlock.
|
||||
* However, it is perfectly legal to call synchronize_srcu() on one
|
||||
* srcu_struct from some other srcu_struct's read-side critical section,
|
||||
* as long as the resulting graph of srcu_structs is acyclic.
|
||||
*
|
||||
* There are memory-ordering constraints implied by synchronize_srcu().
|
||||
* On systems with more than one CPU, when synchronize_srcu() returns,
|
||||
* each CPU is guaranteed to have executed a full memory barrier since
|
||||
* the end of its last corresponding SRCU-sched read-side critical section
|
||||
* whose beginning preceded the call to synchronize_srcu(). In addition,
|
||||
* each CPU having an SRCU read-side critical section that extends beyond
|
||||
* the return from synchronize_srcu() is guaranteed to have executed a
|
||||
* full memory barrier after the beginning of synchronize_srcu() and before
|
||||
* the beginning of that SRCU read-side critical section. Note that these
|
||||
* guarantees include CPUs that are offline, idle, or executing in user mode,
|
||||
* as well as CPUs that are executing in the kernel.
|
||||
*
|
||||
* Furthermore, if CPU A invoked synchronize_srcu(), which returned
|
||||
* to its caller on CPU B, then both CPU A and CPU B are guaranteed
|
||||
* to have executed a full memory barrier during the execution of
|
||||
* synchronize_srcu(). This guarantee applies even if CPU A and CPU B
|
||||
* are the same CPU, but again only if the system has more than one CPU.
|
||||
*
|
||||
* Of course, these memory-ordering guarantees apply only when
|
||||
* synchronize_srcu(), srcu_read_lock(), and srcu_read_unlock() are
|
||||
* passed the same srcu_struct structure.
|
||||
*/
|
||||
void synchronize_srcu(struct srcu_struct *sp)
|
||||
{
|
||||
__synchronize_srcu(sp, (rcu_gp_is_expedited() && !rcu_gp_is_normal())
|
||||
? SYNCHRONIZE_SRCU_EXP_TRYCOUNT
|
||||
: SYNCHRONIZE_SRCU_TRYCOUNT);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(synchronize_srcu);
|
||||
|
||||
/**
|
||||
* synchronize_srcu_expedited - Brute-force SRCU grace period
|
||||
* @sp: srcu_struct with which to synchronize.
|
||||
*
|
||||
* Wait for an SRCU grace period to elapse, but be more aggressive about
|
||||
* spinning rather than blocking when waiting.
|
||||
*
|
||||
* Note that synchronize_srcu_expedited() has the same deadlock and
|
||||
* memory-ordering properties as does synchronize_srcu().
|
||||
*/
|
||||
void synchronize_srcu_expedited(struct srcu_struct *sp)
|
||||
{
|
||||
__synchronize_srcu(sp, SYNCHRONIZE_SRCU_EXP_TRYCOUNT);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(synchronize_srcu_expedited);
|
||||
|
||||
/**
|
||||
* srcu_barrier - Wait until all in-flight call_srcu() callbacks complete.
|
||||
* @sp: srcu_struct on which to wait for in-flight callbacks.
|
||||
*/
|
||||
void srcu_barrier(struct srcu_struct *sp)
|
||||
{
|
||||
synchronize_srcu(sp);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(srcu_barrier);
|
||||
|
||||
/**
|
||||
* srcu_batches_completed - return batches completed.
|
||||
* @sp: srcu_struct on which to report batch completion.
|
||||
*
|
||||
* Report the number of batches, correlated with, but not necessarily
|
||||
* precisely the same as, the number of grace periods that have elapsed.
|
||||
*/
|
||||
unsigned long srcu_batches_completed(struct srcu_struct *sp)
|
||||
{
|
||||
return sp->completed;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(srcu_batches_completed);
|
||||
|
||||
#define SRCU_CALLBACK_BATCH 10
|
||||
#define SRCU_INTERVAL 1
|
||||
|
||||
/*
|
||||
* Move any new SRCU callbacks to the first stage of the SRCU grace
|
||||
* period pipeline.
|
||||
*/
|
||||
static void srcu_collect_new(struct srcu_struct *sp)
|
||||
{
|
||||
if (!rcu_batch_empty(&sp->batch_queue)) {
|
||||
spin_lock_irq(&sp->queue_lock);
|
||||
rcu_batch_move(&sp->batch_check0, &sp->batch_queue);
|
||||
spin_unlock_irq(&sp->queue_lock);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Core SRCU state machine. Advance callbacks from ->batch_check0 to
|
||||
* ->batch_check1 and then to ->batch_done as readers drain.
|
||||
*/
|
||||
static void srcu_advance_batches(struct srcu_struct *sp, int trycount)
|
||||
{
|
||||
int idx = 1 ^ (sp->completed & 1);
|
||||
|
||||
/*
|
||||
* Because readers might be delayed for an extended period after
|
||||
* fetching ->completed for their index, at any point in time there
|
||||
* might well be readers using both idx=0 and idx=1. We therefore
|
||||
* need to wait for readers to clear from both index values before
|
||||
* invoking a callback.
|
||||
*/
|
||||
|
||||
if (rcu_batch_empty(&sp->batch_check0) &&
|
||||
rcu_batch_empty(&sp->batch_check1))
|
||||
return; /* no callbacks need to be advanced */
|
||||
|
||||
if (!try_check_zero(sp, idx, trycount))
|
||||
return; /* failed to advance, will try after SRCU_INTERVAL */
|
||||
|
||||
/*
|
||||
* The callbacks in ->batch_check1 have already done with their
|
||||
* first zero check and flip back when they were enqueued on
|
||||
* ->batch_check0 in a previous invocation of srcu_advance_batches().
|
||||
* (Presumably try_check_zero() returned false during that
|
||||
* invocation, leaving the callbacks stranded on ->batch_check1.)
|
||||
* They are therefore ready to invoke, so move them to ->batch_done.
|
||||
*/
|
||||
rcu_batch_move(&sp->batch_done, &sp->batch_check1);
|
||||
|
||||
if (rcu_batch_empty(&sp->batch_check0))
|
||||
return; /* no callbacks need to be advanced */
|
||||
srcu_flip(sp);
|
||||
|
||||
/*
|
||||
* The callbacks in ->batch_check0 just finished their
|
||||
* first check zero and flip, so move them to ->batch_check1
|
||||
* for future checking on the other idx.
|
||||
*/
|
||||
rcu_batch_move(&sp->batch_check1, &sp->batch_check0);
|
||||
|
||||
/*
|
||||
* SRCU read-side critical sections are normally short, so check
|
||||
* at least twice in quick succession after a flip.
|
||||
*/
|
||||
trycount = trycount < 2 ? 2 : trycount;
|
||||
if (!try_check_zero(sp, idx^1, trycount))
|
||||
return; /* failed to advance, will try after SRCU_INTERVAL */
|
||||
|
||||
/*
|
||||
* The callbacks in ->batch_check1 have now waited for all
|
||||
* pre-existing readers using both idx values. They are therefore
|
||||
* ready to invoke, so move them to ->batch_done.
|
||||
*/
|
||||
rcu_batch_move(&sp->batch_done, &sp->batch_check1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Invoke a limited number of SRCU callbacks that have passed through
|
||||
* their grace period. If there are more to do, SRCU will reschedule
|
||||
* the workqueue. Note that needed memory barriers have been executed
|
||||
* in this task's context by srcu_readers_active_idx_check().
|
||||
*/
|
||||
static void srcu_invoke_callbacks(struct srcu_struct *sp)
|
||||
{
|
||||
int i;
|
||||
struct rcu_head *head;
|
||||
|
||||
for (i = 0; i < SRCU_CALLBACK_BATCH; i++) {
|
||||
head = rcu_batch_dequeue(&sp->batch_done);
|
||||
if (!head)
|
||||
break;
|
||||
local_bh_disable();
|
||||
head->func(head);
|
||||
local_bh_enable();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Finished one round of SRCU grace period. Start another if there are
|
||||
* more SRCU callbacks queued, otherwise put SRCU into not-running state.
|
||||
*/
|
||||
static void srcu_reschedule(struct srcu_struct *sp)
|
||||
{
|
||||
bool pending = true;
|
||||
|
||||
if (rcu_batch_empty(&sp->batch_done) &&
|
||||
rcu_batch_empty(&sp->batch_check1) &&
|
||||
rcu_batch_empty(&sp->batch_check0) &&
|
||||
rcu_batch_empty(&sp->batch_queue)) {
|
||||
spin_lock_irq(&sp->queue_lock);
|
||||
if (rcu_batch_empty(&sp->batch_done) &&
|
||||
rcu_batch_empty(&sp->batch_check1) &&
|
||||
rcu_batch_empty(&sp->batch_check0) &&
|
||||
rcu_batch_empty(&sp->batch_queue)) {
|
||||
sp->running = false;
|
||||
pending = false;
|
||||
}
|
||||
spin_unlock_irq(&sp->queue_lock);
|
||||
}
|
||||
|
||||
if (pending)
|
||||
queue_delayed_work(system_power_efficient_wq,
|
||||
&sp->work, SRCU_INTERVAL);
|
||||
}
|
||||
|
||||
/*
|
||||
* This is the work-queue function that handles SRCU grace periods.
|
||||
*/
|
||||
void process_srcu(struct work_struct *work)
|
||||
{
|
||||
struct srcu_struct *sp;
|
||||
|
||||
sp = container_of(work, struct srcu_struct, work.work);
|
||||
|
||||
srcu_collect_new(sp);
|
||||
srcu_advance_batches(sp, 1);
|
||||
srcu_invoke_callbacks(sp);
|
||||
srcu_reschedule(sp);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(process_srcu);
|
||||
+32
-53
@@ -38,8 +38,8 @@ static int init_srcu_struct_fields(struct srcu_struct *sp)
|
||||
sp->srcu_lock_nesting[0] = 0;
|
||||
sp->srcu_lock_nesting[1] = 0;
|
||||
init_swait_queue_head(&sp->srcu_wq);
|
||||
sp->srcu_gp_seq = 0;
|
||||
rcu_segcblist_init(&sp->srcu_cblist);
|
||||
sp->srcu_cb_head = NULL;
|
||||
sp->srcu_cb_tail = &sp->srcu_cb_head;
|
||||
sp->srcu_gp_running = false;
|
||||
sp->srcu_gp_waiting = false;
|
||||
sp->srcu_idx = 0;
|
||||
@@ -88,31 +88,16 @@ void cleanup_srcu_struct(struct srcu_struct *sp)
|
||||
{
|
||||
WARN_ON(sp->srcu_lock_nesting[0] || sp->srcu_lock_nesting[1]);
|
||||
flush_work(&sp->srcu_work);
|
||||
WARN_ON(rcu_seq_state(sp->srcu_gp_seq));
|
||||
WARN_ON(sp->srcu_gp_running);
|
||||
WARN_ON(sp->srcu_gp_waiting);
|
||||
WARN_ON(!rcu_segcblist_empty(&sp->srcu_cblist));
|
||||
WARN_ON(sp->srcu_cb_head);
|
||||
WARN_ON(&sp->srcu_cb_head != sp->srcu_cb_tail);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cleanup_srcu_struct);
|
||||
|
||||
/*
|
||||
* Counts the new reader in the appropriate per-CPU element of the
|
||||
* srcu_struct. Must be called from process context.
|
||||
* Returns an index that must be passed to the matching srcu_read_unlock().
|
||||
*/
|
||||
int __srcu_read_lock(struct srcu_struct *sp)
|
||||
{
|
||||
int idx;
|
||||
|
||||
idx = READ_ONCE(sp->srcu_idx);
|
||||
WRITE_ONCE(sp->srcu_lock_nesting[idx], sp->srcu_lock_nesting[idx] + 1);
|
||||
return idx;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__srcu_read_lock);
|
||||
|
||||
/*
|
||||
* Removes the count for the old reader from the appropriate element of
|
||||
* the srcu_struct. Must be called from process context.
|
||||
* the srcu_struct.
|
||||
*/
|
||||
void __srcu_read_unlock(struct srcu_struct *sp, int idx)
|
||||
{
|
||||
@@ -132,52 +117,44 @@ EXPORT_SYMBOL_GPL(__srcu_read_unlock);
|
||||
void srcu_drive_gp(struct work_struct *wp)
|
||||
{
|
||||
int idx;
|
||||
struct rcu_cblist ready_cbs;
|
||||
struct srcu_struct *sp;
|
||||
struct rcu_head *lh;
|
||||
struct rcu_head *rhp;
|
||||
struct srcu_struct *sp;
|
||||
|
||||
sp = container_of(wp, struct srcu_struct, srcu_work);
|
||||
if (sp->srcu_gp_running || rcu_segcblist_empty(&sp->srcu_cblist))
|
||||
if (sp->srcu_gp_running || !READ_ONCE(sp->srcu_cb_head))
|
||||
return; /* Already running or nothing to do. */
|
||||
|
||||
/* Tag recently arrived callbacks and wait for readers. */
|
||||
/* Remove recently arrived callbacks and wait for readers. */
|
||||
WRITE_ONCE(sp->srcu_gp_running, true);
|
||||
rcu_segcblist_accelerate(&sp->srcu_cblist,
|
||||
rcu_seq_snap(&sp->srcu_gp_seq));
|
||||
rcu_seq_start(&sp->srcu_gp_seq);
|
||||
local_irq_disable();
|
||||
lh = sp->srcu_cb_head;
|
||||
sp->srcu_cb_head = NULL;
|
||||
sp->srcu_cb_tail = &sp->srcu_cb_head;
|
||||
local_irq_enable();
|
||||
idx = sp->srcu_idx;
|
||||
WRITE_ONCE(sp->srcu_idx, !sp->srcu_idx);
|
||||
WRITE_ONCE(sp->srcu_gp_waiting, true); /* srcu_read_unlock() wakes! */
|
||||
swait_event(sp->srcu_wq, !READ_ONCE(sp->srcu_lock_nesting[idx]));
|
||||
WRITE_ONCE(sp->srcu_gp_waiting, false); /* srcu_read_unlock() cheap. */
|
||||
rcu_seq_end(&sp->srcu_gp_seq);
|
||||
|
||||
/* Update callback list based on GP, and invoke ready callbacks. */
|
||||
rcu_segcblist_advance(&sp->srcu_cblist,
|
||||
rcu_seq_current(&sp->srcu_gp_seq));
|
||||
if (rcu_segcblist_ready_cbs(&sp->srcu_cblist)) {
|
||||
rcu_cblist_init(&ready_cbs);
|
||||
local_irq_disable();
|
||||
rcu_segcblist_extract_done_cbs(&sp->srcu_cblist, &ready_cbs);
|
||||
local_irq_enable();
|
||||
rhp = rcu_cblist_dequeue(&ready_cbs);
|
||||
for (; rhp != NULL; rhp = rcu_cblist_dequeue(&ready_cbs)) {
|
||||
local_bh_disable();
|
||||
rhp->func(rhp);
|
||||
local_bh_enable();
|
||||
}
|
||||
local_irq_disable();
|
||||
rcu_segcblist_insert_count(&sp->srcu_cblist, &ready_cbs);
|
||||
local_irq_enable();
|
||||
/* Invoke the callbacks we removed above. */
|
||||
while (lh) {
|
||||
rhp = lh;
|
||||
lh = lh->next;
|
||||
local_bh_disable();
|
||||
rhp->func(rhp);
|
||||
local_bh_enable();
|
||||
}
|
||||
WRITE_ONCE(sp->srcu_gp_running, false);
|
||||
|
||||
/*
|
||||
* If more callbacks, reschedule ourselves. This can race with
|
||||
* a call_srcu() at interrupt level, but the ->srcu_gp_running
|
||||
* checks will straighten that out.
|
||||
* Enable rescheduling, and if there are more callbacks,
|
||||
* reschedule ourselves. This can race with a call_srcu()
|
||||
* at interrupt level, but the ->srcu_gp_running checks will
|
||||
* straighten that out.
|
||||
*/
|
||||
if (!rcu_segcblist_empty(&sp->srcu_cblist))
|
||||
WRITE_ONCE(sp->srcu_gp_running, false);
|
||||
if (READ_ONCE(sp->srcu_cb_head))
|
||||
schedule_work(&sp->srcu_work);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(srcu_drive_gp);
|
||||
@@ -186,14 +163,16 @@ EXPORT_SYMBOL_GPL(srcu_drive_gp);
|
||||
* Enqueue an SRCU callback on the specified srcu_struct structure,
|
||||
* initiating grace-period processing if it is not already running.
|
||||
*/
|
||||
void call_srcu(struct srcu_struct *sp, struct rcu_head *head,
|
||||
void call_srcu(struct srcu_struct *sp, struct rcu_head *rhp,
|
||||
rcu_callback_t func)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
head->func = func;
|
||||
rhp->func = func;
|
||||
rhp->next = NULL;
|
||||
local_irq_save(flags);
|
||||
rcu_segcblist_enqueue(&sp->srcu_cblist, head, false);
|
||||
*sp->srcu_cb_tail = rhp;
|
||||
sp->srcu_cb_tail = &rhp->next;
|
||||
local_irq_restore(flags);
|
||||
if (!READ_ONCE(sp->srcu_gp_running))
|
||||
schedule_work(&sp->srcu_work);
|
||||
|
||||
+132
-60
@@ -40,9 +40,15 @@
|
||||
#include "rcu.h"
|
||||
#include "rcu_segcblist.h"
|
||||
|
||||
ulong exp_holdoff = 25 * 1000; /* Holdoff (ns) for auto-expediting. */
|
||||
/* Holdoff in nanoseconds for auto-expediting. */
|
||||
#define DEFAULT_SRCU_EXP_HOLDOFF (25 * 1000)
|
||||
static ulong exp_holdoff = DEFAULT_SRCU_EXP_HOLDOFF;
|
||||
module_param(exp_holdoff, ulong, 0444);
|
||||
|
||||
/* Overflow-check frequency. N bits roughly says every 2**N grace periods. */
|
||||
static ulong counter_wrap_check = (ULONG_MAX >> 2);
|
||||
module_param(counter_wrap_check, ulong, 0444);
|
||||
|
||||
static void srcu_invoke_callbacks(struct work_struct *work);
|
||||
static void srcu_reschedule(struct srcu_struct *sp, unsigned long delay);
|
||||
|
||||
@@ -70,7 +76,7 @@ static void init_srcu_struct_nodes(struct srcu_struct *sp, bool is_static)
|
||||
|
||||
/* Each pass through this loop initializes one srcu_node structure. */
|
||||
rcu_for_each_node_breadth_first(sp, snp) {
|
||||
spin_lock_init(&snp->lock);
|
||||
raw_spin_lock_init(&ACCESS_PRIVATE(snp, lock));
|
||||
WARN_ON_ONCE(ARRAY_SIZE(snp->srcu_have_cbs) !=
|
||||
ARRAY_SIZE(snp->srcu_data_have_cbs));
|
||||
for (i = 0; i < ARRAY_SIZE(snp->srcu_have_cbs); i++) {
|
||||
@@ -104,7 +110,7 @@ static void init_srcu_struct_nodes(struct srcu_struct *sp, bool is_static)
|
||||
snp_first = sp->level[level];
|
||||
for_each_possible_cpu(cpu) {
|
||||
sdp = per_cpu_ptr(sp->sda, cpu);
|
||||
spin_lock_init(&sdp->lock);
|
||||
raw_spin_lock_init(&ACCESS_PRIVATE(sdp, lock));
|
||||
rcu_segcblist_init(&sdp->srcu_cblist);
|
||||
sdp->srcu_cblist_invoking = false;
|
||||
sdp->srcu_gp_seq_needed = sp->srcu_gp_seq;
|
||||
@@ -163,7 +169,7 @@ int __init_srcu_struct(struct srcu_struct *sp, const char *name,
|
||||
/* Don't re-initialize a lock while it is held. */
|
||||
debug_check_no_locks_freed((void *)sp, sizeof(*sp));
|
||||
lockdep_init_map(&sp->dep_map, name, key, 0);
|
||||
spin_lock_init(&sp->gp_lock);
|
||||
raw_spin_lock_init(&ACCESS_PRIVATE(sp, lock));
|
||||
return init_srcu_struct_fields(sp, false);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__init_srcu_struct);
|
||||
@@ -180,7 +186,7 @@ EXPORT_SYMBOL_GPL(__init_srcu_struct);
|
||||
*/
|
||||
int init_srcu_struct(struct srcu_struct *sp)
|
||||
{
|
||||
spin_lock_init(&sp->gp_lock);
|
||||
raw_spin_lock_init(&ACCESS_PRIVATE(sp, lock));
|
||||
return init_srcu_struct_fields(sp, false);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(init_srcu_struct);
|
||||
@@ -191,7 +197,7 @@ EXPORT_SYMBOL_GPL(init_srcu_struct);
|
||||
* First-use initialization of statically allocated srcu_struct
|
||||
* structure. Wiring up the combining tree is more than can be
|
||||
* done with compile-time initialization, so this check is added
|
||||
* to each update-side SRCU primitive. Use ->gp_lock, which -is-
|
||||
* to each update-side SRCU primitive. Use sp->lock, which -is-
|
||||
* compile-time initialized, to resolve races involving multiple
|
||||
* CPUs trying to garner first-use privileges.
|
||||
*/
|
||||
@@ -203,13 +209,13 @@ static void check_init_srcu_struct(struct srcu_struct *sp)
|
||||
/* The smp_load_acquire() pairs with the smp_store_release(). */
|
||||
if (!rcu_seq_state(smp_load_acquire(&sp->srcu_gp_seq_needed))) /*^^^*/
|
||||
return; /* Already initialized. */
|
||||
spin_lock_irqsave(&sp->gp_lock, flags);
|
||||
raw_spin_lock_irqsave_rcu_node(sp, flags);
|
||||
if (!rcu_seq_state(sp->srcu_gp_seq_needed)) {
|
||||
spin_unlock_irqrestore(&sp->gp_lock, flags);
|
||||
raw_spin_unlock_irqrestore_rcu_node(sp, flags);
|
||||
return;
|
||||
}
|
||||
init_srcu_struct_fields(sp, true);
|
||||
spin_unlock_irqrestore(&sp->gp_lock, flags);
|
||||
raw_spin_unlock_irqrestore_rcu_node(sp, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -275,15 +281,20 @@ static bool srcu_readers_active_idx_check(struct srcu_struct *sp, int idx)
|
||||
* not mean that there are no more readers, as one could have read
|
||||
* the current index but not have incremented the lock counter yet.
|
||||
*
|
||||
* Possible bug: There is no guarantee that there haven't been
|
||||
* ULONG_MAX increments of ->srcu_lock_count[] since the unlocks were
|
||||
* counted, meaning that this could return true even if there are
|
||||
* still active readers. Since there are no memory barriers around
|
||||
* srcu_flip(), the CPU is not required to increment ->srcu_idx
|
||||
* before running srcu_readers_unlock_idx(), which means that there
|
||||
* could be an arbitrarily large number of critical sections that
|
||||
* execute after srcu_readers_unlock_idx() but use the old value
|
||||
* of ->srcu_idx.
|
||||
* So suppose that the updater is preempted here for so long
|
||||
* that more than ULONG_MAX non-nested readers come and go in
|
||||
* the meantime. It turns out that this cannot result in overflow
|
||||
* because if a reader modifies its unlock count after we read it
|
||||
* above, then that reader's next load of ->srcu_idx is guaranteed
|
||||
* to get the new value, which will cause it to operate on the
|
||||
* other bank of counters, where it cannot contribute to the
|
||||
* overflow of these counters. This means that there is a maximum
|
||||
* of 2*NR_CPUS increments, which cannot overflow given current
|
||||
* systems, especially not on 64-bit systems.
|
||||
*
|
||||
* OK, how about nesting? This does impose a limit on nesting
|
||||
* of floor(ULONG_MAX/NR_CPUS/2), which should be sufficient,
|
||||
* especially on 64-bit systems.
|
||||
*/
|
||||
return srcu_readers_lock_idx(sp, idx) == unlocks;
|
||||
}
|
||||
@@ -357,7 +368,7 @@ EXPORT_SYMBOL_GPL(cleanup_srcu_struct);
|
||||
|
||||
/*
|
||||
* Counts the new reader in the appropriate per-CPU element of the
|
||||
* srcu_struct. Must be called from process context.
|
||||
* srcu_struct.
|
||||
* Returns an index that must be passed to the matching srcu_read_unlock().
|
||||
*/
|
||||
int __srcu_read_lock(struct srcu_struct *sp)
|
||||
@@ -365,7 +376,7 @@ int __srcu_read_lock(struct srcu_struct *sp)
|
||||
int idx;
|
||||
|
||||
idx = READ_ONCE(sp->srcu_idx) & 0x1;
|
||||
__this_cpu_inc(sp->sda->srcu_lock_count[idx]);
|
||||
this_cpu_inc(sp->sda->srcu_lock_count[idx]);
|
||||
smp_mb(); /* B */ /* Avoid leaking the critical section. */
|
||||
return idx;
|
||||
}
|
||||
@@ -375,7 +386,6 @@ EXPORT_SYMBOL_GPL(__srcu_read_lock);
|
||||
* Removes the count for the old reader from the appropriate per-CPU
|
||||
* element of the srcu_struct. Note that this may well be a different
|
||||
* CPU than that which was incremented by the corresponding srcu_read_lock().
|
||||
* Must be called from process context.
|
||||
*/
|
||||
void __srcu_read_unlock(struct srcu_struct *sp, int idx)
|
||||
{
|
||||
@@ -401,8 +411,7 @@ static void srcu_gp_start(struct srcu_struct *sp)
|
||||
struct srcu_data *sdp = this_cpu_ptr(sp->sda);
|
||||
int state;
|
||||
|
||||
RCU_LOCKDEP_WARN(!lockdep_is_held(&sp->gp_lock),
|
||||
"Invoked srcu_gp_start() without ->gp_lock!");
|
||||
lockdep_assert_held(&sp->lock);
|
||||
WARN_ON_ONCE(ULONG_CMP_GE(sp->srcu_gp_seq, sp->srcu_gp_seq_needed));
|
||||
rcu_segcblist_advance(&sdp->srcu_cblist,
|
||||
rcu_seq_current(&sp->srcu_gp_seq));
|
||||
@@ -490,17 +499,20 @@ static void srcu_gp_end(struct srcu_struct *sp)
|
||||
{
|
||||
unsigned long cbdelay;
|
||||
bool cbs;
|
||||
int cpu;
|
||||
unsigned long flags;
|
||||
unsigned long gpseq;
|
||||
int idx;
|
||||
int idxnext;
|
||||
unsigned long mask;
|
||||
struct srcu_data *sdp;
|
||||
struct srcu_node *snp;
|
||||
|
||||
/* Prevent more than one additional grace period. */
|
||||
mutex_lock(&sp->srcu_cb_mutex);
|
||||
|
||||
/* End the current grace period. */
|
||||
spin_lock_irq(&sp->gp_lock);
|
||||
raw_spin_lock_irq_rcu_node(sp);
|
||||
idx = rcu_seq_state(sp->srcu_gp_seq);
|
||||
WARN_ON_ONCE(idx != SRCU_STATE_SCAN2);
|
||||
cbdelay = srcu_get_delay(sp);
|
||||
@@ -509,7 +521,7 @@ static void srcu_gp_end(struct srcu_struct *sp)
|
||||
gpseq = rcu_seq_current(&sp->srcu_gp_seq);
|
||||
if (ULONG_CMP_LT(sp->srcu_gp_seq_needed_exp, gpseq))
|
||||
sp->srcu_gp_seq_needed_exp = gpseq;
|
||||
spin_unlock_irq(&sp->gp_lock);
|
||||
raw_spin_unlock_irq_rcu_node(sp);
|
||||
mutex_unlock(&sp->srcu_gp_mutex);
|
||||
/* A new grace period can start at this point. But only one. */
|
||||
|
||||
@@ -517,7 +529,7 @@ static void srcu_gp_end(struct srcu_struct *sp)
|
||||
idx = rcu_seq_ctr(gpseq) % ARRAY_SIZE(snp->srcu_have_cbs);
|
||||
idxnext = (idx + 1) % ARRAY_SIZE(snp->srcu_have_cbs);
|
||||
rcu_for_each_node_breadth_first(sp, snp) {
|
||||
spin_lock_irq(&snp->lock);
|
||||
raw_spin_lock_irq_rcu_node(snp);
|
||||
cbs = false;
|
||||
if (snp >= sp->level[rcu_num_lvls - 1])
|
||||
cbs = snp->srcu_have_cbs[idx] == gpseq;
|
||||
@@ -527,28 +539,37 @@ static void srcu_gp_end(struct srcu_struct *sp)
|
||||
snp->srcu_gp_seq_needed_exp = gpseq;
|
||||
mask = snp->srcu_data_have_cbs[idx];
|
||||
snp->srcu_data_have_cbs[idx] = 0;
|
||||
spin_unlock_irq(&snp->lock);
|
||||
if (cbs) {
|
||||
smp_mb(); /* GP end before CB invocation. */
|
||||
raw_spin_unlock_irq_rcu_node(snp);
|
||||
if (cbs)
|
||||
srcu_schedule_cbs_snp(sp, snp, mask, cbdelay);
|
||||
}
|
||||
|
||||
/* Occasionally prevent srcu_data counter wrap. */
|
||||
if (!(gpseq & counter_wrap_check))
|
||||
for (cpu = snp->grplo; cpu <= snp->grphi; cpu++) {
|
||||
sdp = per_cpu_ptr(sp->sda, cpu);
|
||||
raw_spin_lock_irqsave_rcu_node(sdp, flags);
|
||||
if (ULONG_CMP_GE(gpseq,
|
||||
sdp->srcu_gp_seq_needed + 100))
|
||||
sdp->srcu_gp_seq_needed = gpseq;
|
||||
raw_spin_unlock_irqrestore_rcu_node(sdp, flags);
|
||||
}
|
||||
}
|
||||
|
||||
/* Callback initiation done, allow grace periods after next. */
|
||||
mutex_unlock(&sp->srcu_cb_mutex);
|
||||
|
||||
/* Start a new grace period if needed. */
|
||||
spin_lock_irq(&sp->gp_lock);
|
||||
raw_spin_lock_irq_rcu_node(sp);
|
||||
gpseq = rcu_seq_current(&sp->srcu_gp_seq);
|
||||
if (!rcu_seq_state(gpseq) &&
|
||||
ULONG_CMP_LT(gpseq, sp->srcu_gp_seq_needed)) {
|
||||
srcu_gp_start(sp);
|
||||
spin_unlock_irq(&sp->gp_lock);
|
||||
raw_spin_unlock_irq_rcu_node(sp);
|
||||
/* Throttle expedited grace periods: Should be rare! */
|
||||
srcu_reschedule(sp, rcu_seq_ctr(gpseq) & 0x3ff
|
||||
? 0 : SRCU_INTERVAL);
|
||||
} else {
|
||||
spin_unlock_irq(&sp->gp_lock);
|
||||
raw_spin_unlock_irq_rcu_node(sp);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -568,18 +589,18 @@ static void srcu_funnel_exp_start(struct srcu_struct *sp, struct srcu_node *snp,
|
||||
if (rcu_seq_done(&sp->srcu_gp_seq, s) ||
|
||||
ULONG_CMP_GE(READ_ONCE(snp->srcu_gp_seq_needed_exp), s))
|
||||
return;
|
||||
spin_lock_irqsave(&snp->lock, flags);
|
||||
raw_spin_lock_irqsave_rcu_node(snp, flags);
|
||||
if (ULONG_CMP_GE(snp->srcu_gp_seq_needed_exp, s)) {
|
||||
spin_unlock_irqrestore(&snp->lock, flags);
|
||||
raw_spin_unlock_irqrestore_rcu_node(snp, flags);
|
||||
return;
|
||||
}
|
||||
WRITE_ONCE(snp->srcu_gp_seq_needed_exp, s);
|
||||
spin_unlock_irqrestore(&snp->lock, flags);
|
||||
raw_spin_unlock_irqrestore_rcu_node(snp, flags);
|
||||
}
|
||||
spin_lock_irqsave(&sp->gp_lock, flags);
|
||||
raw_spin_lock_irqsave_rcu_node(sp, flags);
|
||||
if (!ULONG_CMP_LT(sp->srcu_gp_seq_needed_exp, s))
|
||||
sp->srcu_gp_seq_needed_exp = s;
|
||||
spin_unlock_irqrestore(&sp->gp_lock, flags);
|
||||
raw_spin_unlock_irqrestore_rcu_node(sp, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -601,14 +622,13 @@ static void srcu_funnel_gp_start(struct srcu_struct *sp, struct srcu_data *sdp,
|
||||
for (; snp != NULL; snp = snp->srcu_parent) {
|
||||
if (rcu_seq_done(&sp->srcu_gp_seq, s) && snp != sdp->mynode)
|
||||
return; /* GP already done and CBs recorded. */
|
||||
spin_lock_irqsave(&snp->lock, flags);
|
||||
raw_spin_lock_irqsave_rcu_node(snp, flags);
|
||||
if (ULONG_CMP_GE(snp->srcu_have_cbs[idx], s)) {
|
||||
snp_seq = snp->srcu_have_cbs[idx];
|
||||
if (snp == sdp->mynode && snp_seq == s)
|
||||
snp->srcu_data_have_cbs[idx] |= sdp->grpmask;
|
||||
spin_unlock_irqrestore(&snp->lock, flags);
|
||||
raw_spin_unlock_irqrestore_rcu_node(snp, flags);
|
||||
if (snp == sdp->mynode && snp_seq != s) {
|
||||
smp_mb(); /* CBs after GP! */
|
||||
srcu_schedule_cbs_sdp(sdp, do_norm
|
||||
? SRCU_INTERVAL
|
||||
: 0);
|
||||
@@ -623,11 +643,11 @@ static void srcu_funnel_gp_start(struct srcu_struct *sp, struct srcu_data *sdp,
|
||||
snp->srcu_data_have_cbs[idx] |= sdp->grpmask;
|
||||
if (!do_norm && ULONG_CMP_LT(snp->srcu_gp_seq_needed_exp, s))
|
||||
snp->srcu_gp_seq_needed_exp = s;
|
||||
spin_unlock_irqrestore(&snp->lock, flags);
|
||||
raw_spin_unlock_irqrestore_rcu_node(snp, flags);
|
||||
}
|
||||
|
||||
/* Top of tree, must ensure the grace period will be started. */
|
||||
spin_lock_irqsave(&sp->gp_lock, flags);
|
||||
raw_spin_lock_irqsave_rcu_node(sp, flags);
|
||||
if (ULONG_CMP_LT(sp->srcu_gp_seq_needed, s)) {
|
||||
/*
|
||||
* Record need for grace period s. Pair with load
|
||||
@@ -646,7 +666,7 @@ static void srcu_funnel_gp_start(struct srcu_struct *sp, struct srcu_data *sdp,
|
||||
queue_delayed_work(system_power_efficient_wq, &sp->work,
|
||||
srcu_get_delay(sp));
|
||||
}
|
||||
spin_unlock_irqrestore(&sp->gp_lock, flags);
|
||||
raw_spin_unlock_irqrestore_rcu_node(sp, flags);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -672,6 +692,16 @@ static bool try_check_zero(struct srcu_struct *sp, int idx, int trycount)
|
||||
*/
|
||||
static void srcu_flip(struct srcu_struct *sp)
|
||||
{
|
||||
/*
|
||||
* Ensure that if this updater saw a given reader's increment
|
||||
* from __srcu_read_lock(), that reader was using an old value
|
||||
* of ->srcu_idx. Also ensure that if a given reader sees the
|
||||
* new value of ->srcu_idx, this updater's earlier scans cannot
|
||||
* have seen that reader's increments (which is OK, because this
|
||||
* grace period need not wait on that reader).
|
||||
*/
|
||||
smp_mb(); /* E */ /* Pairs with B and C. */
|
||||
|
||||
WRITE_ONCE(sp->srcu_idx, sp->srcu_idx + 1);
|
||||
|
||||
/*
|
||||
@@ -745,6 +775,13 @@ static bool srcu_might_be_idle(struct srcu_struct *sp)
|
||||
return true; /* With reasonable probability, idle! */
|
||||
}
|
||||
|
||||
/*
|
||||
* SRCU callback function to leak a callback.
|
||||
*/
|
||||
static void srcu_leak_callback(struct rcu_head *rhp)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* Enqueue an SRCU callback on the srcu_data structure associated with
|
||||
* the current CPU and the specified srcu_struct structure, initiating
|
||||
@@ -783,10 +820,16 @@ void __call_srcu(struct srcu_struct *sp, struct rcu_head *rhp,
|
||||
struct srcu_data *sdp;
|
||||
|
||||
check_init_srcu_struct(sp);
|
||||
if (debug_rcu_head_queue(rhp)) {
|
||||
/* Probable double call_srcu(), so leak the callback. */
|
||||
WRITE_ONCE(rhp->func, srcu_leak_callback);
|
||||
WARN_ONCE(1, "call_srcu(): Leaked duplicate callback\n");
|
||||
return;
|
||||
}
|
||||
rhp->func = func;
|
||||
local_irq_save(flags);
|
||||
sdp = this_cpu_ptr(sp->sda);
|
||||
spin_lock(&sdp->lock);
|
||||
raw_spin_lock_rcu_node(sdp);
|
||||
rcu_segcblist_enqueue(&sdp->srcu_cblist, rhp, false);
|
||||
rcu_segcblist_advance(&sdp->srcu_cblist,
|
||||
rcu_seq_current(&sp->srcu_gp_seq));
|
||||
@@ -800,13 +843,30 @@ void __call_srcu(struct srcu_struct *sp, struct rcu_head *rhp,
|
||||
sdp->srcu_gp_seq_needed_exp = s;
|
||||
needexp = true;
|
||||
}
|
||||
spin_unlock_irqrestore(&sdp->lock, flags);
|
||||
raw_spin_unlock_irqrestore_rcu_node(sdp, flags);
|
||||
if (needgp)
|
||||
srcu_funnel_gp_start(sp, sdp, s, do_norm);
|
||||
else if (needexp)
|
||||
srcu_funnel_exp_start(sp, sdp->mynode, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* call_srcu() - Queue a callback for invocation after an SRCU grace period
|
||||
* @sp: srcu_struct in queue the callback
|
||||
* @head: structure to be used for queueing the SRCU callback.
|
||||
* @func: function to be invoked after the SRCU grace period
|
||||
*
|
||||
* The callback function will be invoked some time after a full SRCU
|
||||
* grace period elapses, in other words after all pre-existing SRCU
|
||||
* read-side critical sections have completed. However, the callback
|
||||
* function might well execute concurrently with other SRCU read-side
|
||||
* critical sections that started after call_srcu() was invoked. SRCU
|
||||
* read-side critical sections are delimited by srcu_read_lock() and
|
||||
* srcu_read_unlock(), and may be nested.
|
||||
*
|
||||
* The callback will be invoked from process context, but must nevertheless
|
||||
* be fast and must not block.
|
||||
*/
|
||||
void call_srcu(struct srcu_struct *sp, struct rcu_head *rhp,
|
||||
rcu_callback_t func)
|
||||
{
|
||||
@@ -954,13 +1014,16 @@ void srcu_barrier(struct srcu_struct *sp)
|
||||
*/
|
||||
for_each_possible_cpu(cpu) {
|
||||
sdp = per_cpu_ptr(sp->sda, cpu);
|
||||
spin_lock_irq(&sdp->lock);
|
||||
raw_spin_lock_irq_rcu_node(sdp);
|
||||
atomic_inc(&sp->srcu_barrier_cpu_cnt);
|
||||
sdp->srcu_barrier_head.func = srcu_barrier_cb;
|
||||
debug_rcu_head_queue(&sdp->srcu_barrier_head);
|
||||
if (!rcu_segcblist_entrain(&sdp->srcu_cblist,
|
||||
&sdp->srcu_barrier_head, 0))
|
||||
&sdp->srcu_barrier_head, 0)) {
|
||||
debug_rcu_head_unqueue(&sdp->srcu_barrier_head);
|
||||
atomic_dec(&sp->srcu_barrier_cpu_cnt);
|
||||
spin_unlock_irq(&sdp->lock);
|
||||
}
|
||||
raw_spin_unlock_irq_rcu_node(sdp);
|
||||
}
|
||||
|
||||
/* Remove the initial count, at which point reaching zero can happen. */
|
||||
@@ -1009,17 +1072,17 @@ static void srcu_advance_state(struct srcu_struct *sp)
|
||||
*/
|
||||
idx = rcu_seq_state(smp_load_acquire(&sp->srcu_gp_seq)); /* ^^^ */
|
||||
if (idx == SRCU_STATE_IDLE) {
|
||||
spin_lock_irq(&sp->gp_lock);
|
||||
raw_spin_lock_irq_rcu_node(sp);
|
||||
if (ULONG_CMP_GE(sp->srcu_gp_seq, sp->srcu_gp_seq_needed)) {
|
||||
WARN_ON_ONCE(rcu_seq_state(sp->srcu_gp_seq));
|
||||
spin_unlock_irq(&sp->gp_lock);
|
||||
raw_spin_unlock_irq_rcu_node(sp);
|
||||
mutex_unlock(&sp->srcu_gp_mutex);
|
||||
return;
|
||||
}
|
||||
idx = rcu_seq_state(READ_ONCE(sp->srcu_gp_seq));
|
||||
if (idx == SRCU_STATE_IDLE)
|
||||
srcu_gp_start(sp);
|
||||
spin_unlock_irq(&sp->gp_lock);
|
||||
raw_spin_unlock_irq_rcu_node(sp);
|
||||
if (idx != SRCU_STATE_IDLE) {
|
||||
mutex_unlock(&sp->srcu_gp_mutex);
|
||||
return; /* Someone else started the grace period. */
|
||||
@@ -1068,22 +1131,22 @@ static void srcu_invoke_callbacks(struct work_struct *work)
|
||||
sdp = container_of(work, struct srcu_data, work.work);
|
||||
sp = sdp->sp;
|
||||
rcu_cblist_init(&ready_cbs);
|
||||
spin_lock_irq(&sdp->lock);
|
||||
smp_mb(); /* Old grace periods before callback invocation! */
|
||||
raw_spin_lock_irq_rcu_node(sdp);
|
||||
rcu_segcblist_advance(&sdp->srcu_cblist,
|
||||
rcu_seq_current(&sp->srcu_gp_seq));
|
||||
if (sdp->srcu_cblist_invoking ||
|
||||
!rcu_segcblist_ready_cbs(&sdp->srcu_cblist)) {
|
||||
spin_unlock_irq(&sdp->lock);
|
||||
raw_spin_unlock_irq_rcu_node(sdp);
|
||||
return; /* Someone else on the job or nothing to do. */
|
||||
}
|
||||
|
||||
/* We are on the job! Extract and invoke ready callbacks. */
|
||||
sdp->srcu_cblist_invoking = true;
|
||||
rcu_segcblist_extract_done_cbs(&sdp->srcu_cblist, &ready_cbs);
|
||||
spin_unlock_irq(&sdp->lock);
|
||||
raw_spin_unlock_irq_rcu_node(sdp);
|
||||
rhp = rcu_cblist_dequeue(&ready_cbs);
|
||||
for (; rhp != NULL; rhp = rcu_cblist_dequeue(&ready_cbs)) {
|
||||
debug_rcu_head_unqueue(rhp);
|
||||
local_bh_disable();
|
||||
rhp->func(rhp);
|
||||
local_bh_enable();
|
||||
@@ -1093,13 +1156,13 @@ static void srcu_invoke_callbacks(struct work_struct *work)
|
||||
* Update counts, accelerate new callbacks, and if needed,
|
||||
* schedule another round of callback invocation.
|
||||
*/
|
||||
spin_lock_irq(&sdp->lock);
|
||||
raw_spin_lock_irq_rcu_node(sdp);
|
||||
rcu_segcblist_insert_count(&sdp->srcu_cblist, &ready_cbs);
|
||||
(void)rcu_segcblist_accelerate(&sdp->srcu_cblist,
|
||||
rcu_seq_snap(&sp->srcu_gp_seq));
|
||||
sdp->srcu_cblist_invoking = false;
|
||||
more = rcu_segcblist_ready_cbs(&sdp->srcu_cblist);
|
||||
spin_unlock_irq(&sdp->lock);
|
||||
raw_spin_unlock_irq_rcu_node(sdp);
|
||||
if (more)
|
||||
srcu_schedule_cbs_sdp(sdp, 0);
|
||||
}
|
||||
@@ -1112,7 +1175,7 @@ static void srcu_reschedule(struct srcu_struct *sp, unsigned long delay)
|
||||
{
|
||||
bool pushgp = true;
|
||||
|
||||
spin_lock_irq(&sp->gp_lock);
|
||||
raw_spin_lock_irq_rcu_node(sp);
|
||||
if (ULONG_CMP_GE(sp->srcu_gp_seq, sp->srcu_gp_seq_needed)) {
|
||||
if (!WARN_ON_ONCE(rcu_seq_state(sp->srcu_gp_seq))) {
|
||||
/* All requests fulfilled, time to go idle. */
|
||||
@@ -1122,7 +1185,7 @@ static void srcu_reschedule(struct srcu_struct *sp, unsigned long delay)
|
||||
/* Outstanding request and no GP. Start one. */
|
||||
srcu_gp_start(sp);
|
||||
}
|
||||
spin_unlock_irq(&sp->gp_lock);
|
||||
raw_spin_unlock_irq_rcu_node(sp);
|
||||
|
||||
if (pushgp)
|
||||
queue_delayed_work(system_power_efficient_wq, &sp->work, delay);
|
||||
@@ -1153,3 +1216,12 @@ void srcutorture_get_gp_data(enum rcutorture_type test_type,
|
||||
*gpnum = rcu_seq_ctr(sp->srcu_gp_seq_needed);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(srcutorture_get_gp_data);
|
||||
|
||||
static int __init srcu_bootup_announce(void)
|
||||
{
|
||||
pr_info("Hierarchical SRCU implementation.\n");
|
||||
if (exp_holdoff != DEFAULT_SRCU_EXP_HOLDOFF)
|
||||
pr_info("\tNon-default auto-expedite holdoff of %lu ns.\n", exp_holdoff);
|
||||
return 0;
|
||||
}
|
||||
early_initcall(srcu_bootup_announce);
|
||||
|
||||
+18
-36
@@ -35,15 +35,26 @@
|
||||
#include <linux/time.h>
|
||||
#include <linux/cpu.h>
|
||||
#include <linux/prefetch.h>
|
||||
#include <linux/trace_events.h>
|
||||
|
||||
#include "rcu.h"
|
||||
|
||||
/* Forward declarations for tiny_plugin.h. */
|
||||
struct rcu_ctrlblk;
|
||||
static void __call_rcu(struct rcu_head *head,
|
||||
rcu_callback_t func,
|
||||
struct rcu_ctrlblk *rcp);
|
||||
/* Global control variables for rcupdate callback mechanism. */
|
||||
struct rcu_ctrlblk {
|
||||
struct rcu_head *rcucblist; /* List of pending callbacks (CBs). */
|
||||
struct rcu_head **donetail; /* ->next pointer of last "done" CB. */
|
||||
struct rcu_head **curtail; /* ->next pointer of last CB. */
|
||||
};
|
||||
|
||||
/* Definition for rcupdate control block. */
|
||||
static struct rcu_ctrlblk rcu_sched_ctrlblk = {
|
||||
.donetail = &rcu_sched_ctrlblk.rcucblist,
|
||||
.curtail = &rcu_sched_ctrlblk.rcucblist,
|
||||
};
|
||||
|
||||
static struct rcu_ctrlblk rcu_bh_ctrlblk = {
|
||||
.donetail = &rcu_bh_ctrlblk.rcucblist,
|
||||
.curtail = &rcu_bh_ctrlblk.rcucblist,
|
||||
};
|
||||
|
||||
#include "tiny_plugin.h"
|
||||
|
||||
@@ -59,19 +70,6 @@ void rcu_barrier_sched(void)
|
||||
}
|
||||
EXPORT_SYMBOL(rcu_barrier_sched);
|
||||
|
||||
#if defined(CONFIG_DEBUG_LOCK_ALLOC) || defined(CONFIG_RCU_TRACE)
|
||||
|
||||
/*
|
||||
* Test whether RCU thinks that the current CPU is idle.
|
||||
*/
|
||||
bool notrace __rcu_is_watching(void)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
EXPORT_SYMBOL(__rcu_is_watching);
|
||||
|
||||
#endif /* defined(CONFIG_DEBUG_LOCK_ALLOC) || defined(CONFIG_RCU_TRACE) */
|
||||
|
||||
/*
|
||||
* Helper function for rcu_sched_qs() and rcu_bh_qs().
|
||||
* Also irqs are disabled to avoid confusion due to interrupt handlers
|
||||
@@ -79,7 +77,6 @@ EXPORT_SYMBOL(__rcu_is_watching);
|
||||
*/
|
||||
static int rcu_qsctr_help(struct rcu_ctrlblk *rcp)
|
||||
{
|
||||
RCU_TRACE(reset_cpu_stall_ticks(rcp);)
|
||||
if (rcp->donetail != rcp->curtail) {
|
||||
rcp->donetail = rcp->curtail;
|
||||
return 1;
|
||||
@@ -125,7 +122,6 @@ void rcu_bh_qs(void)
|
||||
*/
|
||||
void rcu_check_callbacks(int user)
|
||||
{
|
||||
RCU_TRACE(check_cpu_stalls();)
|
||||
if (user)
|
||||
rcu_sched_qs();
|
||||
else if (!in_softirq())
|
||||
@@ -140,10 +136,8 @@ void rcu_check_callbacks(int user)
|
||||
*/
|
||||
static void __rcu_process_callbacks(struct rcu_ctrlblk *rcp)
|
||||
{
|
||||
const char *rn = NULL;
|
||||
struct rcu_head *next, *list;
|
||||
unsigned long flags;
|
||||
RCU_TRACE(int cb_count = 0;)
|
||||
|
||||
/* Move the ready-to-invoke callbacks to a local list. */
|
||||
local_irq_save(flags);
|
||||
@@ -152,7 +146,6 @@ static void __rcu_process_callbacks(struct rcu_ctrlblk *rcp)
|
||||
local_irq_restore(flags);
|
||||
return;
|
||||
}
|
||||
RCU_TRACE(trace_rcu_batch_start(rcp->name, 0, rcp->qlen, -1);)
|
||||
list = rcp->rcucblist;
|
||||
rcp->rcucblist = *rcp->donetail;
|
||||
*rcp->donetail = NULL;
|
||||
@@ -162,22 +155,15 @@ static void __rcu_process_callbacks(struct rcu_ctrlblk *rcp)
|
||||
local_irq_restore(flags);
|
||||
|
||||
/* Invoke the callbacks on the local list. */
|
||||
RCU_TRACE(rn = rcp->name;)
|
||||
while (list) {
|
||||
next = list->next;
|
||||
prefetch(next);
|
||||
debug_rcu_head_unqueue(list);
|
||||
local_bh_disable();
|
||||
__rcu_reclaim(rn, list);
|
||||
__rcu_reclaim("", list);
|
||||
local_bh_enable();
|
||||
list = next;
|
||||
RCU_TRACE(cb_count++;)
|
||||
}
|
||||
RCU_TRACE(rcu_trace_sub_qlen(rcp, cb_count);)
|
||||
RCU_TRACE(trace_rcu_batch_end(rcp->name,
|
||||
cb_count, 0, need_resched(),
|
||||
is_idle_task(current),
|
||||
false));
|
||||
}
|
||||
|
||||
static __latent_entropy void rcu_process_callbacks(struct softirq_action *unused)
|
||||
@@ -221,7 +207,6 @@ static void __call_rcu(struct rcu_head *head,
|
||||
local_irq_save(flags);
|
||||
*rcp->curtail = head;
|
||||
rcp->curtail = &head->next;
|
||||
RCU_TRACE(rcp->qlen++;)
|
||||
local_irq_restore(flags);
|
||||
|
||||
if (unlikely(is_idle_task(current))) {
|
||||
@@ -254,8 +239,5 @@ EXPORT_SYMBOL_GPL(call_rcu_bh);
|
||||
void __init rcu_init(void)
|
||||
{
|
||||
open_softirq(RCU_SOFTIRQ, rcu_process_callbacks);
|
||||
RCU_TRACE(reset_cpu_stall_ticks(&rcu_sched_ctrlblk);)
|
||||
RCU_TRACE(reset_cpu_stall_ticks(&rcu_bh_ctrlblk);)
|
||||
|
||||
rcu_early_boot_tests();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user