Merge branch 'for-5.8' into for-linus
This commit is contained in:
+1
-3
@@ -1,6 +1,4 @@
|
||||
#
|
||||
# Generated files
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
kheaders.md5
|
||||
timeconst.h
|
||||
hz.bc
|
||||
|
||||
@@ -27,6 +27,7 @@ KCOV_INSTRUMENT_softirq.o := n
|
||||
# and produce insane amounts of uninteresting coverage.
|
||||
KCOV_INSTRUMENT_module.o := n
|
||||
KCOV_INSTRUMENT_extable.o := n
|
||||
KCOV_INSTRUMENT_stacktrace.o := n
|
||||
# Don't self-instrument.
|
||||
KCOV_INSTRUMENT_kcov.o := n
|
||||
KASAN_SANITIZE_kcov.o := n
|
||||
|
||||
+3
-1
@@ -416,6 +416,7 @@ static void fill_ac(acct_t *ac)
|
||||
{
|
||||
struct pacct_struct *pacct = ¤t->signal->pacct;
|
||||
u64 elapsed, run_time;
|
||||
time64_t btime;
|
||||
struct tty_struct *tty;
|
||||
|
||||
/*
|
||||
@@ -448,7 +449,8 @@ static void fill_ac(acct_t *ac)
|
||||
}
|
||||
#endif
|
||||
do_div(elapsed, AHZ);
|
||||
ac->ac_btime = get_seconds() - elapsed;
|
||||
btime = ktime_get_real_seconds() - elapsed;
|
||||
ac->ac_btime = clamp_t(time64_t, btime, 0, U32_MAX);
|
||||
#if ACCT_VERSION==2
|
||||
ac->ac_ahz = AHZ;
|
||||
#endif
|
||||
|
||||
+22
-19
@@ -1101,13 +1101,11 @@ static void audit_log_feature_change(int which, u32 old_feature, u32 new_feature
|
||||
audit_log_end(ab);
|
||||
}
|
||||
|
||||
static int audit_set_feature(struct sk_buff *skb)
|
||||
static int audit_set_feature(struct audit_features *uaf)
|
||||
{
|
||||
struct audit_features *uaf;
|
||||
int i;
|
||||
|
||||
BUILD_BUG_ON(AUDIT_LAST_FEATURE + 1 > ARRAY_SIZE(audit_feature_names));
|
||||
uaf = nlmsg_data(nlmsg_hdr(skb));
|
||||
|
||||
/* if there is ever a version 2 we should handle that here */
|
||||
|
||||
@@ -1175,6 +1173,7 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
|
||||
{
|
||||
u32 seq;
|
||||
void *data;
|
||||
int data_len;
|
||||
int err;
|
||||
struct audit_buffer *ab;
|
||||
u16 msg_type = nlh->nlmsg_type;
|
||||
@@ -1188,6 +1187,7 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
|
||||
|
||||
seq = nlh->nlmsg_seq;
|
||||
data = nlmsg_data(nlh);
|
||||
data_len = nlmsg_len(nlh);
|
||||
|
||||
switch (msg_type) {
|
||||
case AUDIT_GET: {
|
||||
@@ -1211,7 +1211,7 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
|
||||
struct audit_status s;
|
||||
memset(&s, 0, sizeof(s));
|
||||
/* guard against past and future API changes */
|
||||
memcpy(&s, data, min_t(size_t, sizeof(s), nlmsg_len(nlh)));
|
||||
memcpy(&s, data, min_t(size_t, sizeof(s), data_len));
|
||||
if (s.mask & AUDIT_STATUS_ENABLED) {
|
||||
err = audit_set_enabled(s.enabled);
|
||||
if (err < 0)
|
||||
@@ -1315,7 +1315,9 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
|
||||
return err;
|
||||
break;
|
||||
case AUDIT_SET_FEATURE:
|
||||
err = audit_set_feature(skb);
|
||||
if (data_len < sizeof(struct audit_features))
|
||||
return -EINVAL;
|
||||
err = audit_set_feature(data);
|
||||
if (err)
|
||||
return err;
|
||||
break;
|
||||
@@ -1327,6 +1329,8 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
|
||||
|
||||
err = audit_filter(msg_type, AUDIT_FILTER_USER);
|
||||
if (err == 1) { /* match or error */
|
||||
char *str = data;
|
||||
|
||||
err = 0;
|
||||
if (msg_type == AUDIT_USER_TTY) {
|
||||
err = tty_audit_push();
|
||||
@@ -1334,26 +1338,24 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
|
||||
break;
|
||||
}
|
||||
audit_log_user_recv_msg(&ab, msg_type);
|
||||
if (msg_type != AUDIT_USER_TTY)
|
||||
if (msg_type != AUDIT_USER_TTY) {
|
||||
/* ensure NULL termination */
|
||||
str[data_len - 1] = '\0';
|
||||
audit_log_format(ab, " msg='%.*s'",
|
||||
AUDIT_MESSAGE_TEXT_MAX,
|
||||
(char *)data);
|
||||
else {
|
||||
int size;
|
||||
|
||||
str);
|
||||
} else {
|
||||
audit_log_format(ab, " data=");
|
||||
size = nlmsg_len(nlh);
|
||||
if (size > 0 &&
|
||||
((unsigned char *)data)[size - 1] == '\0')
|
||||
size--;
|
||||
audit_log_n_untrustedstring(ab, data, size);
|
||||
if (data_len > 0 && str[data_len - 1] == '\0')
|
||||
data_len--;
|
||||
audit_log_n_untrustedstring(ab, str, data_len);
|
||||
}
|
||||
audit_log_end(ab);
|
||||
}
|
||||
break;
|
||||
case AUDIT_ADD_RULE:
|
||||
case AUDIT_DEL_RULE:
|
||||
if (nlmsg_len(nlh) < sizeof(struct audit_rule_data))
|
||||
if (data_len < sizeof(struct audit_rule_data))
|
||||
return -EINVAL;
|
||||
if (audit_enabled == AUDIT_LOCKED) {
|
||||
audit_log_common_recv_msg(audit_context(), &ab,
|
||||
@@ -1365,7 +1367,7 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
|
||||
audit_log_end(ab);
|
||||
return -EPERM;
|
||||
}
|
||||
err = audit_rule_change(msg_type, seq, data, nlmsg_len(nlh));
|
||||
err = audit_rule_change(msg_type, seq, data, data_len);
|
||||
break;
|
||||
case AUDIT_LIST_RULES:
|
||||
err = audit_list_rules_send(skb, seq);
|
||||
@@ -1380,7 +1382,7 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
|
||||
case AUDIT_MAKE_EQUIV: {
|
||||
void *bufp = data;
|
||||
u32 sizes[2];
|
||||
size_t msglen = nlmsg_len(nlh);
|
||||
size_t msglen = data_len;
|
||||
char *old, *new;
|
||||
|
||||
err = -EINVAL;
|
||||
@@ -1456,7 +1458,7 @@ static int audit_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
|
||||
|
||||
memset(&s, 0, sizeof(s));
|
||||
/* guard against past and future API changes */
|
||||
memcpy(&s, data, min_t(size_t, sizeof(s), nlmsg_len(nlh)));
|
||||
memcpy(&s, data, min_t(size_t, sizeof(s), data_len));
|
||||
/* check if new data is valid */
|
||||
if ((s.enabled != 0 && s.enabled != 1) ||
|
||||
(s.log_passwd != 0 && s.log_passwd != 1))
|
||||
@@ -1798,6 +1800,7 @@ struct audit_buffer *audit_log_start(struct audit_context *ctx, gfp_t gfp_mask,
|
||||
}
|
||||
|
||||
audit_get_stamp(ab->ctx, &t, &serial);
|
||||
audit_clear_dummy(ab->ctx);
|
||||
audit_log_format(ab, "audit(%llu.%03lu:%u): ",
|
||||
(unsigned long long)t.tv_sec, t.tv_nsec/1000000, serial);
|
||||
|
||||
|
||||
@@ -290,6 +290,13 @@ extern int audit_signal_info_syscall(struct task_struct *t);
|
||||
extern void audit_filter_inodes(struct task_struct *tsk,
|
||||
struct audit_context *ctx);
|
||||
extern struct list_head *audit_killed_trees(void);
|
||||
|
||||
static inline void audit_clear_dummy(struct audit_context *ctx)
|
||||
{
|
||||
if (ctx)
|
||||
ctx->dummy = 0;
|
||||
}
|
||||
|
||||
#else /* CONFIG_AUDITSYSCALL */
|
||||
#define auditsc_get_stamp(c, t, s) 0
|
||||
#define audit_put_watch(w) {}
|
||||
@@ -323,6 +330,7 @@ static inline int audit_signal_info_syscall(struct task_struct *t)
|
||||
}
|
||||
|
||||
#define audit_filter_inodes(t, c) AUDIT_DISABLED
|
||||
#define audit_clear_dummy(c) {}
|
||||
#endif /* CONFIG_AUDITSYSCALL */
|
||||
|
||||
extern char *audit_unpack_string(void **bufp, size_t *remain, size_t len);
|
||||
|
||||
+2
-11
@@ -160,23 +160,14 @@ static int audit_mark_handle_event(struct fsnotify_group *group,
|
||||
{
|
||||
struct fsnotify_mark *inode_mark = fsnotify_iter_inode_mark(iter_info);
|
||||
struct audit_fsnotify_mark *audit_mark;
|
||||
const struct inode *inode = NULL;
|
||||
const struct inode *inode = fsnotify_data_inode(data, data_type);
|
||||
|
||||
audit_mark = container_of(inode_mark, struct audit_fsnotify_mark, mark);
|
||||
|
||||
BUG_ON(group != audit_fsnotify_group);
|
||||
|
||||
switch (data_type) {
|
||||
case (FSNOTIFY_EVENT_PATH):
|
||||
inode = ((const struct path *)data)->dentry->d_inode;
|
||||
break;
|
||||
case (FSNOTIFY_EVENT_INODE):
|
||||
inode = (const struct inode *)data;
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
if (WARN_ON(!inode))
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mask & (FS_CREATE|FS_MOVED_TO|FS_DELETE|FS_MOVED_FROM)) {
|
||||
if (audit_compare_dname_path(dname, audit_mark->path, AUDIT_NAME_FULL))
|
||||
|
||||
+2
-16
@@ -302,8 +302,6 @@ static void audit_update_watch(struct audit_parent *parent,
|
||||
if (oentry->rule.exe)
|
||||
audit_remove_mark(oentry->rule.exe);
|
||||
|
||||
audit_watch_log_rule_change(r, owatch, "updated_rules");
|
||||
|
||||
call_rcu(&oentry->rcu, audit_free_rule_rcu);
|
||||
}
|
||||
|
||||
@@ -473,25 +471,13 @@ static int audit_watch_handle_event(struct fsnotify_group *group,
|
||||
struct fsnotify_iter_info *iter_info)
|
||||
{
|
||||
struct fsnotify_mark *inode_mark = fsnotify_iter_inode_mark(iter_info);
|
||||
const struct inode *inode;
|
||||
const struct inode *inode = fsnotify_data_inode(data, data_type);
|
||||
struct audit_parent *parent;
|
||||
|
||||
parent = container_of(inode_mark, struct audit_parent, mark);
|
||||
|
||||
BUG_ON(group != audit_watch_group);
|
||||
|
||||
switch (data_type) {
|
||||
case (FSNOTIFY_EVENT_PATH):
|
||||
inode = d_backing_inode(((const struct path *)data)->dentry);
|
||||
break;
|
||||
case (FSNOTIFY_EVENT_INODE):
|
||||
inode = (const struct inode *)data;
|
||||
break;
|
||||
default:
|
||||
BUG();
|
||||
inode = NULL;
|
||||
break;
|
||||
}
|
||||
WARN_ON(!inode);
|
||||
|
||||
if (mask & (FS_CREATE|FS_MOVED_TO) && inode)
|
||||
audit_update_watch(parent, dname, inode->i_sb->s_dev, inode->i_ino, 0);
|
||||
|
||||
+44
-37
@@ -456,6 +456,7 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
|
||||
bufp = data->buf;
|
||||
for (i = 0; i < data->field_count; i++) {
|
||||
struct audit_field *f = &entry->rule.fields[i];
|
||||
u32 f_val;
|
||||
|
||||
err = -EINVAL;
|
||||
|
||||
@@ -464,12 +465,12 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
|
||||
goto exit_free;
|
||||
|
||||
f->type = data->fields[i];
|
||||
f->val = data->values[i];
|
||||
f_val = data->values[i];
|
||||
|
||||
/* Support legacy tests for a valid loginuid */
|
||||
if ((f->type == AUDIT_LOGINUID) && (f->val == AUDIT_UID_UNSET)) {
|
||||
if ((f->type == AUDIT_LOGINUID) && (f_val == AUDIT_UID_UNSET)) {
|
||||
f->type = AUDIT_LOGINUID_SET;
|
||||
f->val = 0;
|
||||
f_val = 0;
|
||||
entry->rule.pflags |= AUDIT_LOGINUID_LEGACY;
|
||||
}
|
||||
|
||||
@@ -485,7 +486,7 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
|
||||
case AUDIT_SUID:
|
||||
case AUDIT_FSUID:
|
||||
case AUDIT_OBJ_UID:
|
||||
f->uid = make_kuid(current_user_ns(), f->val);
|
||||
f->uid = make_kuid(current_user_ns(), f_val);
|
||||
if (!uid_valid(f->uid))
|
||||
goto exit_free;
|
||||
break;
|
||||
@@ -494,11 +495,12 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
|
||||
case AUDIT_SGID:
|
||||
case AUDIT_FSGID:
|
||||
case AUDIT_OBJ_GID:
|
||||
f->gid = make_kgid(current_user_ns(), f->val);
|
||||
f->gid = make_kgid(current_user_ns(), f_val);
|
||||
if (!gid_valid(f->gid))
|
||||
goto exit_free;
|
||||
break;
|
||||
case AUDIT_ARCH:
|
||||
f->val = f_val;
|
||||
entry->rule.arch_f = f;
|
||||
break;
|
||||
case AUDIT_SUBJ_USER:
|
||||
@@ -511,11 +513,13 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
|
||||
case AUDIT_OBJ_TYPE:
|
||||
case AUDIT_OBJ_LEV_LOW:
|
||||
case AUDIT_OBJ_LEV_HIGH:
|
||||
str = audit_unpack_string(&bufp, &remain, f->val);
|
||||
if (IS_ERR(str))
|
||||
str = audit_unpack_string(&bufp, &remain, f_val);
|
||||
if (IS_ERR(str)) {
|
||||
err = PTR_ERR(str);
|
||||
goto exit_free;
|
||||
entry->rule.buflen += f->val;
|
||||
|
||||
}
|
||||
entry->rule.buflen += f_val;
|
||||
f->lsm_str = str;
|
||||
err = security_audit_rule_init(f->type, f->op, str,
|
||||
(void **)&f->lsm_rule);
|
||||
/* Keep currently invalid fields around in case they
|
||||
@@ -524,68 +528,71 @@ static struct audit_entry *audit_data_to_entry(struct audit_rule_data *data,
|
||||
pr_warn("audit rule for LSM \'%s\' is invalid\n",
|
||||
str);
|
||||
err = 0;
|
||||
}
|
||||
if (err) {
|
||||
kfree(str);
|
||||
} else if (err)
|
||||
goto exit_free;
|
||||
} else
|
||||
f->lsm_str = str;
|
||||
break;
|
||||
case AUDIT_WATCH:
|
||||
str = audit_unpack_string(&bufp, &remain, f->val);
|
||||
if (IS_ERR(str))
|
||||
str = audit_unpack_string(&bufp, &remain, f_val);
|
||||
if (IS_ERR(str)) {
|
||||
err = PTR_ERR(str);
|
||||
goto exit_free;
|
||||
entry->rule.buflen += f->val;
|
||||
|
||||
err = audit_to_watch(&entry->rule, str, f->val, f->op);
|
||||
}
|
||||
err = audit_to_watch(&entry->rule, str, f_val, f->op);
|
||||
if (err) {
|
||||
kfree(str);
|
||||
goto exit_free;
|
||||
}
|
||||
entry->rule.buflen += f_val;
|
||||
break;
|
||||
case AUDIT_DIR:
|
||||
str = audit_unpack_string(&bufp, &remain, f->val);
|
||||
if (IS_ERR(str))
|
||||
str = audit_unpack_string(&bufp, &remain, f_val);
|
||||
if (IS_ERR(str)) {
|
||||
err = PTR_ERR(str);
|
||||
goto exit_free;
|
||||
entry->rule.buflen += f->val;
|
||||
|
||||
}
|
||||
err = audit_make_tree(&entry->rule, str, f->op);
|
||||
kfree(str);
|
||||
if (err)
|
||||
goto exit_free;
|
||||
entry->rule.buflen += f_val;
|
||||
break;
|
||||
case AUDIT_INODE:
|
||||
f->val = f_val;
|
||||
err = audit_to_inode(&entry->rule, f);
|
||||
if (err)
|
||||
goto exit_free;
|
||||
break;
|
||||
case AUDIT_FILTERKEY:
|
||||
if (entry->rule.filterkey || f->val > AUDIT_MAX_KEY_LEN)
|
||||
if (entry->rule.filterkey || f_val > AUDIT_MAX_KEY_LEN)
|
||||
goto exit_free;
|
||||
str = audit_unpack_string(&bufp, &remain, f->val);
|
||||
if (IS_ERR(str))
|
||||
goto exit_free;
|
||||
entry->rule.buflen += f->val;
|
||||
entry->rule.filterkey = str;
|
||||
break;
|
||||
case AUDIT_EXE:
|
||||
if (entry->rule.exe || f->val > PATH_MAX)
|
||||
goto exit_free;
|
||||
str = audit_unpack_string(&bufp, &remain, f->val);
|
||||
str = audit_unpack_string(&bufp, &remain, f_val);
|
||||
if (IS_ERR(str)) {
|
||||
err = PTR_ERR(str);
|
||||
goto exit_free;
|
||||
}
|
||||
entry->rule.buflen += f->val;
|
||||
|
||||
audit_mark = audit_alloc_mark(&entry->rule, str, f->val);
|
||||
entry->rule.buflen += f_val;
|
||||
entry->rule.filterkey = str;
|
||||
break;
|
||||
case AUDIT_EXE:
|
||||
if (entry->rule.exe || f_val > PATH_MAX)
|
||||
goto exit_free;
|
||||
str = audit_unpack_string(&bufp, &remain, f_val);
|
||||
if (IS_ERR(str)) {
|
||||
err = PTR_ERR(str);
|
||||
goto exit_free;
|
||||
}
|
||||
audit_mark = audit_alloc_mark(&entry->rule, str, f_val);
|
||||
if (IS_ERR(audit_mark)) {
|
||||
kfree(str);
|
||||
err = PTR_ERR(audit_mark);
|
||||
goto exit_free;
|
||||
}
|
||||
entry->rule.buflen += f_val;
|
||||
entry->rule.exe = audit_mark;
|
||||
break;
|
||||
default:
|
||||
f->val = f_val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1406,9 +1406,6 @@ static void audit_log_proctitle(void)
|
||||
struct audit_context *context = audit_context();
|
||||
struct audit_buffer *ab;
|
||||
|
||||
if (!context || context->dummy)
|
||||
return;
|
||||
|
||||
ab = audit_log_start(context, GFP_KERNEL, AUDIT_PROCTITLE);
|
||||
if (!ab)
|
||||
return; /* audit_panic or being filtered */
|
||||
|
||||
@@ -29,4 +29,5 @@ obj-$(CONFIG_DEBUG_INFO_BTF) += sysfs_btf.o
|
||||
endif
|
||||
ifeq ($(CONFIG_BPF_JIT),y)
|
||||
obj-$(CONFIG_BPF_SYSCALL) += bpf_struct_ops.o
|
||||
obj-${CONFIG_BPF_LSM} += bpf_lsm.o
|
||||
endif
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
/*
|
||||
* Copyright (C) 2020 Google LLC.
|
||||
*/
|
||||
|
||||
#include <linux/filter.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/btf.h>
|
||||
#include <linux/lsm_hooks.h>
|
||||
#include <linux/bpf_lsm.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/bpf_verifier.h>
|
||||
|
||||
/* For every LSM hook that allows attachment of BPF programs, declare a nop
|
||||
* function where a BPF program can be attached.
|
||||
*/
|
||||
#define LSM_HOOK(RET, DEFAULT, NAME, ...) \
|
||||
noinline RET bpf_lsm_##NAME(__VA_ARGS__) \
|
||||
{ \
|
||||
return DEFAULT; \
|
||||
}
|
||||
|
||||
#include <linux/lsm_hook_defs.h>
|
||||
#undef LSM_HOOK
|
||||
|
||||
#define BPF_LSM_SYM_PREFX "bpf_lsm_"
|
||||
|
||||
int bpf_lsm_verify_prog(struct bpf_verifier_log *vlog,
|
||||
const struct bpf_prog *prog)
|
||||
{
|
||||
if (!prog->gpl_compatible) {
|
||||
bpf_log(vlog,
|
||||
"LSM programs must have a GPL compatible license\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (strncmp(BPF_LSM_SYM_PREFX, prog->aux->attach_func_name,
|
||||
sizeof(BPF_LSM_SYM_PREFX) - 1)) {
|
||||
bpf_log(vlog, "attach_btf_id %u points to wrong type name %s\n",
|
||||
prog->aux->attach_btf_id, prog->aux->attach_func_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const struct bpf_prog_ops lsm_prog_ops = {
|
||||
};
|
||||
|
||||
const struct bpf_verifier_ops lsm_verifier_ops = {
|
||||
.get_func_proto = bpf_tracing_func_proto,
|
||||
.is_valid_access = btf_ctx_access,
|
||||
};
|
||||
@@ -23,7 +23,7 @@ enum bpf_struct_ops_state {
|
||||
|
||||
struct bpf_struct_ops_value {
|
||||
BPF_STRUCT_OPS_COMMON_VALUE;
|
||||
char data[0] ____cacheline_aligned_in_smp;
|
||||
char data[] ____cacheline_aligned_in_smp;
|
||||
};
|
||||
|
||||
struct bpf_struct_ops_map {
|
||||
@@ -96,12 +96,11 @@ const struct bpf_prog_ops bpf_struct_ops_prog_ops = {
|
||||
|
||||
static const struct btf_type *module_type;
|
||||
|
||||
void bpf_struct_ops_init(struct btf *btf)
|
||||
void bpf_struct_ops_init(struct btf *btf, struct bpf_verifier_log *log)
|
||||
{
|
||||
s32 type_id, value_id, module_id;
|
||||
const struct btf_member *member;
|
||||
struct bpf_struct_ops *st_ops;
|
||||
struct bpf_verifier_log log = {};
|
||||
const struct btf_type *t;
|
||||
char value_name[128];
|
||||
const char *mname;
|
||||
@@ -172,7 +171,7 @@ void bpf_struct_ops_init(struct btf *btf)
|
||||
member->type,
|
||||
NULL);
|
||||
if (func_proto &&
|
||||
btf_distill_func_proto(&log, btf,
|
||||
btf_distill_func_proto(log, btf,
|
||||
func_proto, mname,
|
||||
&st_ops->func_models[j])) {
|
||||
pr_warn("Error in parsing func ptr %s in struct %s\n",
|
||||
@@ -321,6 +320,7 @@ static int bpf_struct_ops_map_update_elem(struct bpf_map *map, void *key,
|
||||
struct bpf_struct_ops_value *uvalue, *kvalue;
|
||||
const struct btf_member *member;
|
||||
const struct btf_type *t = st_ops->type;
|
||||
struct bpf_tramp_progs *tprogs = NULL;
|
||||
void *udata, *kdata;
|
||||
int prog_fd, err = 0;
|
||||
void *image;
|
||||
@@ -344,6 +344,10 @@ static int bpf_struct_ops_map_update_elem(struct bpf_map *map, void *key,
|
||||
if (uvalue->state || refcount_read(&uvalue->refcnt))
|
||||
return -EINVAL;
|
||||
|
||||
tprogs = kcalloc(BPF_TRAMP_MAX, sizeof(*tprogs), GFP_KERNEL);
|
||||
if (!tprogs)
|
||||
return -ENOMEM;
|
||||
|
||||
uvalue = (struct bpf_struct_ops_value *)st_map->uvalue;
|
||||
kvalue = (struct bpf_struct_ops_value *)&st_map->kvalue;
|
||||
|
||||
@@ -426,10 +430,12 @@ static int bpf_struct_ops_map_update_elem(struct bpf_map *map, void *key,
|
||||
goto reset_unlock;
|
||||
}
|
||||
|
||||
tprogs[BPF_TRAMP_FENTRY].progs[0] = prog;
|
||||
tprogs[BPF_TRAMP_FENTRY].nr_progs = 1;
|
||||
err = arch_prepare_bpf_trampoline(image,
|
||||
st_map->image + PAGE_SIZE,
|
||||
&st_ops->func_models[i], 0,
|
||||
&prog, 1, NULL, 0, NULL);
|
||||
tprogs, NULL);
|
||||
if (err < 0)
|
||||
goto reset_unlock;
|
||||
|
||||
@@ -470,6 +476,7 @@ reset_unlock:
|
||||
memset(uvalue, 0, map->value_size);
|
||||
memset(kvalue, 0, map->value_size);
|
||||
unlock:
|
||||
kfree(tprogs);
|
||||
mutex_unlock(&st_map->lock);
|
||||
return err;
|
||||
}
|
||||
@@ -483,13 +490,21 @@ static int bpf_struct_ops_map_delete_elem(struct bpf_map *map, void *key)
|
||||
prev_state = cmpxchg(&st_map->kvalue.state,
|
||||
BPF_STRUCT_OPS_STATE_INUSE,
|
||||
BPF_STRUCT_OPS_STATE_TOBEFREE);
|
||||
if (prev_state == BPF_STRUCT_OPS_STATE_INUSE) {
|
||||
switch (prev_state) {
|
||||
case BPF_STRUCT_OPS_STATE_INUSE:
|
||||
st_map->st_ops->unreg(&st_map->kvalue.data);
|
||||
if (refcount_dec_and_test(&st_map->kvalue.refcnt))
|
||||
bpf_map_put(map);
|
||||
return 0;
|
||||
case BPF_STRUCT_OPS_STATE_TOBEFREE:
|
||||
return -EINPROGRESS;
|
||||
case BPF_STRUCT_OPS_STATE_INIT:
|
||||
return -ENOENT;
|
||||
default:
|
||||
WARN_ON_ONCE(1);
|
||||
/* Should never happen. Treat it as not found. */
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bpf_struct_ops_map_seq_show_elem(struct bpf_map *map, void *key,
|
||||
|
||||
+59
-24
@@ -2418,7 +2418,7 @@ static int btf_enum_check_member(struct btf_verifier_env *env,
|
||||
|
||||
struct_size = struct_type->size;
|
||||
bytes_offset = BITS_ROUNDDOWN_BYTES(struct_bits_off);
|
||||
if (struct_size - bytes_offset < sizeof(int)) {
|
||||
if (struct_size - bytes_offset < member_type->size) {
|
||||
btf_verifier_log_member(env, struct_type, member,
|
||||
"Member exceeds struct_size");
|
||||
return -EINVAL;
|
||||
@@ -3477,8 +3477,8 @@ errout:
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
extern char __weak _binary__btf_vmlinux_bin_start[];
|
||||
extern char __weak _binary__btf_vmlinux_bin_end[];
|
||||
extern char __weak __start_BTF[];
|
||||
extern char __weak __stop_BTF[];
|
||||
extern struct btf *btf_vmlinux;
|
||||
|
||||
#define BPF_MAP_TYPE(_id, _ops)
|
||||
@@ -3605,9 +3605,8 @@ struct btf *btf_parse_vmlinux(void)
|
||||
}
|
||||
env->btf = btf;
|
||||
|
||||
btf->data = _binary__btf_vmlinux_bin_start;
|
||||
btf->data_size = _binary__btf_vmlinux_bin_end -
|
||||
_binary__btf_vmlinux_bin_start;
|
||||
btf->data = __start_BTF;
|
||||
btf->data_size = __stop_BTF - __start_BTF;
|
||||
|
||||
err = btf_parse_hdr(env);
|
||||
if (err)
|
||||
@@ -3643,7 +3642,7 @@ struct btf *btf_parse_vmlinux(void)
|
||||
goto errout;
|
||||
}
|
||||
|
||||
bpf_struct_ops_init(btf);
|
||||
bpf_struct_ops_init(btf, log);
|
||||
|
||||
btf_verifier_env_free(env);
|
||||
refcount_set(&btf->refcnt, 1);
|
||||
@@ -3710,23 +3709,60 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
|
||||
nr_args--;
|
||||
}
|
||||
|
||||
if (prog->expected_attach_type == BPF_TRACE_FEXIT &&
|
||||
arg == nr_args) {
|
||||
if (!t)
|
||||
/* Default prog with 5 args. 6th arg is retval. */
|
||||
return true;
|
||||
/* function return type */
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
} else if (arg >= nr_args) {
|
||||
if (arg > nr_args) {
|
||||
bpf_log(log, "func '%s' doesn't have %d-th argument\n",
|
||||
tname, arg + 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (arg == nr_args) {
|
||||
switch (prog->expected_attach_type) {
|
||||
case BPF_LSM_MAC:
|
||||
case BPF_TRACE_FEXIT:
|
||||
/* When LSM programs are attached to void LSM hooks
|
||||
* they use FEXIT trampolines and when attached to
|
||||
* int LSM hooks, they use MODIFY_RETURN trampolines.
|
||||
*
|
||||
* While the LSM programs are BPF_MODIFY_RETURN-like
|
||||
* the check:
|
||||
*
|
||||
* if (ret_type != 'int')
|
||||
* return -EINVAL;
|
||||
*
|
||||
* is _not_ done here. This is still safe as LSM hooks
|
||||
* have only void and int return types.
|
||||
*/
|
||||
if (!t)
|
||||
return true;
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
break;
|
||||
case BPF_MODIFY_RETURN:
|
||||
/* For now the BPF_MODIFY_RETURN can only be attached to
|
||||
* functions that return an int.
|
||||
*/
|
||||
if (!t)
|
||||
return false;
|
||||
|
||||
t = btf_type_skip_modifiers(btf, t->type, NULL);
|
||||
if (!btf_type_is_int(t)) {
|
||||
bpf_log(log,
|
||||
"ret type %s not allowed for fmod_ret\n",
|
||||
btf_kind_str[BTF_INFO_KIND(t->info)]);
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
bpf_log(log, "func '%s' doesn't have %d-th argument\n",
|
||||
tname, arg + 1);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!t)
|
||||
/* Default prog with 5 args */
|
||||
return true;
|
||||
t = btf_type_by_id(btf, args[arg].type);
|
||||
}
|
||||
|
||||
/* skip modifiers */
|
||||
while (btf_type_is_modifier(t))
|
||||
t = btf_type_by_id(btf, t->type);
|
||||
@@ -3931,6 +3967,7 @@ again:
|
||||
|
||||
if (btf_type_is_ptr(mtype)) {
|
||||
const struct btf_type *stype;
|
||||
u32 id;
|
||||
|
||||
if (msize != size || off != moff) {
|
||||
bpf_log(log,
|
||||
@@ -3939,12 +3976,9 @@ again:
|
||||
return -EACCES;
|
||||
}
|
||||
|
||||
stype = btf_type_by_id(btf_vmlinux, mtype->type);
|
||||
/* skip modifiers */
|
||||
while (btf_type_is_modifier(stype))
|
||||
stype = btf_type_by_id(btf_vmlinux, stype->type);
|
||||
stype = btf_type_skip_modifiers(btf_vmlinux, mtype->type, &id);
|
||||
if (btf_type_is_struct(stype)) {
|
||||
*next_btf_id = mtype->type;
|
||||
*next_btf_id = id;
|
||||
return PTR_TO_BTF_ID;
|
||||
}
|
||||
}
|
||||
@@ -4144,9 +4178,9 @@ int btf_distill_func_proto(struct bpf_verifier_log *log,
|
||||
* EFAULT - verifier bug
|
||||
* 0 - 99% match. The last 1% is validated by the verifier.
|
||||
*/
|
||||
int btf_check_func_type_match(struct bpf_verifier_log *log,
|
||||
struct btf *btf1, const struct btf_type *t1,
|
||||
struct btf *btf2, const struct btf_type *t2)
|
||||
static int btf_check_func_type_match(struct bpf_verifier_log *log,
|
||||
struct btf *btf1, const struct btf_type *t1,
|
||||
struct btf *btf2, const struct btf_type *t2)
|
||||
{
|
||||
const struct btf_param *args1, *args2;
|
||||
const char *fn1, *fn2, *s1, *s2;
|
||||
@@ -4566,7 +4600,7 @@ int btf_get_info_by_fd(const struct btf *btf,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
struct bpf_btf_info __user *uinfo;
|
||||
struct bpf_btf_info info = {};
|
||||
struct bpf_btf_info info;
|
||||
u32 info_copy, btf_copy;
|
||||
void __user *ubtf;
|
||||
u32 uinfo_len;
|
||||
@@ -4575,6 +4609,7 @@ int btf_get_info_by_fd(const struct btf *btf,
|
||||
uinfo_len = attr->info.info_len;
|
||||
|
||||
info_copy = min_t(u32, uinfo_len, sizeof(info));
|
||||
memset(&info, 0, sizeof(info));
|
||||
if (copy_from_user(&info, uinfo, info_copy))
|
||||
return -EFAULT;
|
||||
|
||||
|
||||
+396
-124
@@ -28,6 +28,69 @@ void cgroup_bpf_offline(struct cgroup *cgrp)
|
||||
percpu_ref_kill(&cgrp->bpf.refcnt);
|
||||
}
|
||||
|
||||
static void bpf_cgroup_storages_free(struct bpf_cgroup_storage *storages[])
|
||||
{
|
||||
enum bpf_cgroup_storage_type stype;
|
||||
|
||||
for_each_cgroup_storage_type(stype)
|
||||
bpf_cgroup_storage_free(storages[stype]);
|
||||
}
|
||||
|
||||
static int bpf_cgroup_storages_alloc(struct bpf_cgroup_storage *storages[],
|
||||
struct bpf_prog *prog)
|
||||
{
|
||||
enum bpf_cgroup_storage_type stype;
|
||||
|
||||
for_each_cgroup_storage_type(stype) {
|
||||
storages[stype] = bpf_cgroup_storage_alloc(prog, stype);
|
||||
if (IS_ERR(storages[stype])) {
|
||||
storages[stype] = NULL;
|
||||
bpf_cgroup_storages_free(storages);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void bpf_cgroup_storages_assign(struct bpf_cgroup_storage *dst[],
|
||||
struct bpf_cgroup_storage *src[])
|
||||
{
|
||||
enum bpf_cgroup_storage_type stype;
|
||||
|
||||
for_each_cgroup_storage_type(stype)
|
||||
dst[stype] = src[stype];
|
||||
}
|
||||
|
||||
static void bpf_cgroup_storages_link(struct bpf_cgroup_storage *storages[],
|
||||
struct cgroup* cgrp,
|
||||
enum bpf_attach_type attach_type)
|
||||
{
|
||||
enum bpf_cgroup_storage_type stype;
|
||||
|
||||
for_each_cgroup_storage_type(stype)
|
||||
bpf_cgroup_storage_link(storages[stype], cgrp, attach_type);
|
||||
}
|
||||
|
||||
static void bpf_cgroup_storages_unlink(struct bpf_cgroup_storage *storages[])
|
||||
{
|
||||
enum bpf_cgroup_storage_type stype;
|
||||
|
||||
for_each_cgroup_storage_type(stype)
|
||||
bpf_cgroup_storage_unlink(storages[stype]);
|
||||
}
|
||||
|
||||
/* Called when bpf_cgroup_link is auto-detached from dying cgroup.
|
||||
* It drops cgroup and bpf_prog refcounts, and marks bpf_link as defunct. It
|
||||
* doesn't free link memory, which will eventually be done by bpf_link's
|
||||
* release() callback, when its last FD is closed.
|
||||
*/
|
||||
static void bpf_cgroup_link_auto_detach(struct bpf_cgroup_link *link)
|
||||
{
|
||||
cgroup_put(link->cgroup);
|
||||
link->cgroup = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* cgroup_bpf_release() - put references of all bpf programs and
|
||||
* release all cgroup bpf data
|
||||
@@ -37,7 +100,6 @@ static void cgroup_bpf_release(struct work_struct *work)
|
||||
{
|
||||
struct cgroup *p, *cgrp = container_of(work, struct cgroup,
|
||||
bpf.release_work);
|
||||
enum bpf_cgroup_storage_type stype;
|
||||
struct bpf_prog_array *old_array;
|
||||
unsigned int type;
|
||||
|
||||
@@ -49,11 +111,12 @@ static void cgroup_bpf_release(struct work_struct *work)
|
||||
|
||||
list_for_each_entry_safe(pl, tmp, progs, node) {
|
||||
list_del(&pl->node);
|
||||
bpf_prog_put(pl->prog);
|
||||
for_each_cgroup_storage_type(stype) {
|
||||
bpf_cgroup_storage_unlink(pl->storage[stype]);
|
||||
bpf_cgroup_storage_free(pl->storage[stype]);
|
||||
}
|
||||
if (pl->prog)
|
||||
bpf_prog_put(pl->prog);
|
||||
if (pl->link)
|
||||
bpf_cgroup_link_auto_detach(pl->link);
|
||||
bpf_cgroup_storages_unlink(pl->storage);
|
||||
bpf_cgroup_storages_free(pl->storage);
|
||||
kfree(pl);
|
||||
static_branch_dec(&cgroup_bpf_enabled_key);
|
||||
}
|
||||
@@ -85,6 +148,18 @@ static void cgroup_bpf_release_fn(struct percpu_ref *ref)
|
||||
queue_work(system_wq, &cgrp->bpf.release_work);
|
||||
}
|
||||
|
||||
/* Get underlying bpf_prog of bpf_prog_list entry, regardless if it's through
|
||||
* link or direct prog.
|
||||
*/
|
||||
static struct bpf_prog *prog_list_prog(struct bpf_prog_list *pl)
|
||||
{
|
||||
if (pl->prog)
|
||||
return pl->prog;
|
||||
if (pl->link)
|
||||
return pl->link->link.prog;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* count number of elements in the list.
|
||||
* it's slow but the list cannot be long
|
||||
*/
|
||||
@@ -94,7 +169,7 @@ static u32 prog_list_length(struct list_head *head)
|
||||
u32 cnt = 0;
|
||||
|
||||
list_for_each_entry(pl, head, node) {
|
||||
if (!pl->prog)
|
||||
if (!prog_list_prog(pl))
|
||||
continue;
|
||||
cnt++;
|
||||
}
|
||||
@@ -138,7 +213,7 @@ static int compute_effective_progs(struct cgroup *cgrp,
|
||||
enum bpf_attach_type type,
|
||||
struct bpf_prog_array **array)
|
||||
{
|
||||
enum bpf_cgroup_storage_type stype;
|
||||
struct bpf_prog_array_item *item;
|
||||
struct bpf_prog_array *progs;
|
||||
struct bpf_prog_list *pl;
|
||||
struct cgroup *p = cgrp;
|
||||
@@ -163,13 +238,13 @@ static int compute_effective_progs(struct cgroup *cgrp,
|
||||
continue;
|
||||
|
||||
list_for_each_entry(pl, &p->bpf.progs[type], node) {
|
||||
if (!pl->prog)
|
||||
if (!prog_list_prog(pl))
|
||||
continue;
|
||||
|
||||
progs->items[cnt].prog = pl->prog;
|
||||
for_each_cgroup_storage_type(stype)
|
||||
progs->items[cnt].cgroup_storage[stype] =
|
||||
pl->storage[stype];
|
||||
item = &progs->items[cnt];
|
||||
item->prog = prog_list_prog(pl);
|
||||
bpf_cgroup_storages_assign(item->cgroup_storage,
|
||||
pl->storage);
|
||||
cnt++;
|
||||
}
|
||||
} while ((p = cgroup_parent(p)));
|
||||
@@ -227,6 +302,9 @@ cleanup:
|
||||
for (i = 0; i < NR; i++)
|
||||
bpf_prog_array_free(arrays[i]);
|
||||
|
||||
for (p = cgroup_parent(cgrp); p; p = cgroup_parent(p))
|
||||
cgroup_bpf_put(p);
|
||||
|
||||
percpu_ref_exit(&cgrp->bpf.refcnt);
|
||||
|
||||
return -ENOMEM;
|
||||
@@ -284,34 +362,80 @@ cleanup:
|
||||
|
||||
#define BPF_CGROUP_MAX_PROGS 64
|
||||
|
||||
static struct bpf_prog_list *find_attach_entry(struct list_head *progs,
|
||||
struct bpf_prog *prog,
|
||||
struct bpf_cgroup_link *link,
|
||||
struct bpf_prog *replace_prog,
|
||||
bool allow_multi)
|
||||
{
|
||||
struct bpf_prog_list *pl;
|
||||
|
||||
/* single-attach case */
|
||||
if (!allow_multi) {
|
||||
if (list_empty(progs))
|
||||
return NULL;
|
||||
return list_first_entry(progs, typeof(*pl), node);
|
||||
}
|
||||
|
||||
list_for_each_entry(pl, progs, node) {
|
||||
if (prog && pl->prog == prog)
|
||||
/* disallow attaching the same prog twice */
|
||||
return ERR_PTR(-EINVAL);
|
||||
if (link && pl->link == link)
|
||||
/* disallow attaching the same link twice */
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
/* direct prog multi-attach w/ replacement case */
|
||||
if (replace_prog) {
|
||||
list_for_each_entry(pl, progs, node) {
|
||||
if (pl->prog == replace_prog)
|
||||
/* a match found */
|
||||
return pl;
|
||||
}
|
||||
/* prog to replace not found for cgroup */
|
||||
return ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* __cgroup_bpf_attach() - Attach the program to a cgroup, and
|
||||
* __cgroup_bpf_attach() - Attach the program or the link to a cgroup, and
|
||||
* propagate the change to descendants
|
||||
* @cgrp: The cgroup which descendants to traverse
|
||||
* @prog: A program to attach
|
||||
* @link: A link to attach
|
||||
* @replace_prog: Previously attached program to replace if BPF_F_REPLACE is set
|
||||
* @type: Type of attach operation
|
||||
* @flags: Option flags
|
||||
*
|
||||
* Exactly one of @prog or @link can be non-null.
|
||||
* Must be called with cgroup_mutex held.
|
||||
*/
|
||||
int __cgroup_bpf_attach(struct cgroup *cgrp, struct bpf_prog *prog,
|
||||
struct bpf_prog *replace_prog,
|
||||
int __cgroup_bpf_attach(struct cgroup *cgrp,
|
||||
struct bpf_prog *prog, struct bpf_prog *replace_prog,
|
||||
struct bpf_cgroup_link *link,
|
||||
enum bpf_attach_type type, u32 flags)
|
||||
{
|
||||
u32 saved_flags = (flags & (BPF_F_ALLOW_OVERRIDE | BPF_F_ALLOW_MULTI));
|
||||
struct list_head *progs = &cgrp->bpf.progs[type];
|
||||
struct bpf_prog *old_prog = NULL;
|
||||
struct bpf_cgroup_storage *storage[MAX_BPF_CGROUP_STORAGE_TYPE],
|
||||
*old_storage[MAX_BPF_CGROUP_STORAGE_TYPE] = {NULL};
|
||||
struct bpf_prog_list *pl, *replace_pl = NULL;
|
||||
enum bpf_cgroup_storage_type stype;
|
||||
struct bpf_cgroup_storage *storage[MAX_BPF_CGROUP_STORAGE_TYPE] = {};
|
||||
struct bpf_cgroup_storage *old_storage[MAX_BPF_CGROUP_STORAGE_TYPE] = {};
|
||||
struct bpf_prog_list *pl;
|
||||
int err;
|
||||
|
||||
if (((flags & BPF_F_ALLOW_OVERRIDE) && (flags & BPF_F_ALLOW_MULTI)) ||
|
||||
((flags & BPF_F_REPLACE) && !(flags & BPF_F_ALLOW_MULTI)))
|
||||
/* invalid combination */
|
||||
return -EINVAL;
|
||||
if (link && (prog || replace_prog))
|
||||
/* only either link or prog/replace_prog can be specified */
|
||||
return -EINVAL;
|
||||
if (!!replace_prog != !!(flags & BPF_F_REPLACE))
|
||||
/* replace_prog implies BPF_F_REPLACE, and vice versa */
|
||||
return -EINVAL;
|
||||
|
||||
if (!hierarchy_allows_attach(cgrp, type))
|
||||
return -EPERM;
|
||||
@@ -326,140 +450,203 @@ int __cgroup_bpf_attach(struct cgroup *cgrp, struct bpf_prog *prog,
|
||||
if (prog_list_length(progs) >= BPF_CGROUP_MAX_PROGS)
|
||||
return -E2BIG;
|
||||
|
||||
if (flags & BPF_F_ALLOW_MULTI) {
|
||||
list_for_each_entry(pl, progs, node) {
|
||||
if (pl->prog == prog)
|
||||
/* disallow attaching the same prog twice */
|
||||
return -EINVAL;
|
||||
if (pl->prog == replace_prog)
|
||||
replace_pl = pl;
|
||||
}
|
||||
if ((flags & BPF_F_REPLACE) && !replace_pl)
|
||||
/* prog to replace not found for cgroup */
|
||||
return -ENOENT;
|
||||
} else if (!list_empty(progs)) {
|
||||
replace_pl = list_first_entry(progs, typeof(*pl), node);
|
||||
}
|
||||
pl = find_attach_entry(progs, prog, link, replace_prog,
|
||||
flags & BPF_F_ALLOW_MULTI);
|
||||
if (IS_ERR(pl))
|
||||
return PTR_ERR(pl);
|
||||
|
||||
for_each_cgroup_storage_type(stype) {
|
||||
storage[stype] = bpf_cgroup_storage_alloc(prog, stype);
|
||||
if (IS_ERR(storage[stype])) {
|
||||
storage[stype] = NULL;
|
||||
for_each_cgroup_storage_type(stype)
|
||||
bpf_cgroup_storage_free(storage[stype]);
|
||||
return -ENOMEM;
|
||||
}
|
||||
}
|
||||
if (bpf_cgroup_storages_alloc(storage, prog ? : link->link.prog))
|
||||
return -ENOMEM;
|
||||
|
||||
if (replace_pl) {
|
||||
pl = replace_pl;
|
||||
if (pl) {
|
||||
old_prog = pl->prog;
|
||||
for_each_cgroup_storage_type(stype) {
|
||||
old_storage[stype] = pl->storage[stype];
|
||||
bpf_cgroup_storage_unlink(old_storage[stype]);
|
||||
}
|
||||
bpf_cgroup_storages_unlink(pl->storage);
|
||||
bpf_cgroup_storages_assign(old_storage, pl->storage);
|
||||
} else {
|
||||
pl = kmalloc(sizeof(*pl), GFP_KERNEL);
|
||||
if (!pl) {
|
||||
for_each_cgroup_storage_type(stype)
|
||||
bpf_cgroup_storage_free(storage[stype]);
|
||||
bpf_cgroup_storages_free(storage);
|
||||
return -ENOMEM;
|
||||
}
|
||||
list_add_tail(&pl->node, progs);
|
||||
}
|
||||
|
||||
pl->prog = prog;
|
||||
for_each_cgroup_storage_type(stype)
|
||||
pl->storage[stype] = storage[stype];
|
||||
|
||||
pl->link = link;
|
||||
bpf_cgroup_storages_assign(pl->storage, storage);
|
||||
cgrp->bpf.flags[type] = saved_flags;
|
||||
|
||||
err = update_effective_progs(cgrp, type);
|
||||
if (err)
|
||||
goto cleanup;
|
||||
|
||||
static_branch_inc(&cgroup_bpf_enabled_key);
|
||||
for_each_cgroup_storage_type(stype) {
|
||||
if (!old_storage[stype])
|
||||
continue;
|
||||
bpf_cgroup_storage_free(old_storage[stype]);
|
||||
}
|
||||
if (old_prog) {
|
||||
bpf_cgroup_storages_free(old_storage);
|
||||
if (old_prog)
|
||||
bpf_prog_put(old_prog);
|
||||
static_branch_dec(&cgroup_bpf_enabled_key);
|
||||
}
|
||||
for_each_cgroup_storage_type(stype)
|
||||
bpf_cgroup_storage_link(storage[stype], cgrp, type);
|
||||
else
|
||||
static_branch_inc(&cgroup_bpf_enabled_key);
|
||||
bpf_cgroup_storages_link(pl->storage, cgrp, type);
|
||||
return 0;
|
||||
|
||||
cleanup:
|
||||
/* and cleanup the prog list */
|
||||
pl->prog = old_prog;
|
||||
for_each_cgroup_storage_type(stype) {
|
||||
bpf_cgroup_storage_free(pl->storage[stype]);
|
||||
pl->storage[stype] = old_storage[stype];
|
||||
bpf_cgroup_storage_link(old_storage[stype], cgrp, type);
|
||||
if (old_prog) {
|
||||
pl->prog = old_prog;
|
||||
pl->link = NULL;
|
||||
}
|
||||
if (!replace_pl) {
|
||||
bpf_cgroup_storages_free(pl->storage);
|
||||
bpf_cgroup_storages_assign(pl->storage, old_storage);
|
||||
bpf_cgroup_storages_link(pl->storage, cgrp, type);
|
||||
if (!old_prog) {
|
||||
list_del(&pl->node);
|
||||
kfree(pl);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Swap updated BPF program for given link in effective program arrays across
|
||||
* all descendant cgroups. This function is guaranteed to succeed.
|
||||
*/
|
||||
static void replace_effective_prog(struct cgroup *cgrp,
|
||||
enum bpf_attach_type type,
|
||||
struct bpf_cgroup_link *link)
|
||||
{
|
||||
struct bpf_prog_array_item *item;
|
||||
struct cgroup_subsys_state *css;
|
||||
struct bpf_prog_array *progs;
|
||||
struct bpf_prog_list *pl;
|
||||
struct list_head *head;
|
||||
struct cgroup *cg;
|
||||
int pos;
|
||||
|
||||
css_for_each_descendant_pre(css, &cgrp->self) {
|
||||
struct cgroup *desc = container_of(css, struct cgroup, self);
|
||||
|
||||
if (percpu_ref_is_zero(&desc->bpf.refcnt))
|
||||
continue;
|
||||
|
||||
/* find position of link in effective progs array */
|
||||
for (pos = 0, cg = desc; cg; cg = cgroup_parent(cg)) {
|
||||
if (pos && !(cg->bpf.flags[type] & BPF_F_ALLOW_MULTI))
|
||||
continue;
|
||||
|
||||
head = &cg->bpf.progs[type];
|
||||
list_for_each_entry(pl, head, node) {
|
||||
if (!prog_list_prog(pl))
|
||||
continue;
|
||||
if (pl->link == link)
|
||||
goto found;
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
found:
|
||||
BUG_ON(!cg);
|
||||
progs = rcu_dereference_protected(
|
||||
desc->bpf.effective[type],
|
||||
lockdep_is_held(&cgroup_mutex));
|
||||
item = &progs->items[pos];
|
||||
WRITE_ONCE(item->prog, link->link.prog);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* __cgroup_bpf_detach() - Detach the program from a cgroup, and
|
||||
* propagate the change to descendants
|
||||
* __cgroup_bpf_replace() - Replace link's program and propagate the change
|
||||
* to descendants
|
||||
* @cgrp: The cgroup which descendants to traverse
|
||||
* @prog: A program to detach or NULL
|
||||
* @type: Type of detach operation
|
||||
* @link: A link for which to replace BPF program
|
||||
* @type: Type of attach operation
|
||||
*
|
||||
* Must be called with cgroup_mutex held.
|
||||
*/
|
||||
int __cgroup_bpf_detach(struct cgroup *cgrp, struct bpf_prog *prog,
|
||||
enum bpf_attach_type type)
|
||||
int __cgroup_bpf_replace(struct cgroup *cgrp, struct bpf_cgroup_link *link,
|
||||
struct bpf_prog *new_prog)
|
||||
{
|
||||
struct list_head *progs = &cgrp->bpf.progs[type];
|
||||
enum bpf_cgroup_storage_type stype;
|
||||
u32 flags = cgrp->bpf.flags[type];
|
||||
struct bpf_prog *old_prog = NULL;
|
||||
struct list_head *progs = &cgrp->bpf.progs[link->type];
|
||||
struct bpf_prog *old_prog;
|
||||
struct bpf_prog_list *pl;
|
||||
int err;
|
||||
bool found = false;
|
||||
|
||||
if (flags & BPF_F_ALLOW_MULTI) {
|
||||
if (!prog)
|
||||
/* to detach MULTI prog the user has to specify valid FD
|
||||
* of the program to be detached
|
||||
*/
|
||||
return -EINVAL;
|
||||
} else {
|
||||
if (list_empty(progs))
|
||||
/* report error when trying to detach and nothing is attached */
|
||||
return -ENOENT;
|
||||
}
|
||||
if (link->link.prog->type != new_prog->type)
|
||||
return -EINVAL;
|
||||
|
||||
if (flags & BPF_F_ALLOW_MULTI) {
|
||||
/* find the prog and detach it */
|
||||
list_for_each_entry(pl, progs, node) {
|
||||
if (pl->prog != prog)
|
||||
continue;
|
||||
old_prog = prog;
|
||||
/* mark it deleted, so it's ignored while
|
||||
* recomputing effective
|
||||
*/
|
||||
pl->prog = NULL;
|
||||
list_for_each_entry(pl, progs, node) {
|
||||
if (pl->link == link) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if (!old_prog)
|
||||
return -ENOENT;
|
||||
} else {
|
||||
/* to maintain backward compatibility NONE and OVERRIDE cgroups
|
||||
* allow detaching with invalid FD (prog==NULL)
|
||||
*/
|
||||
pl = list_first_entry(progs, typeof(*pl), node);
|
||||
old_prog = pl->prog;
|
||||
pl->prog = NULL;
|
||||
}
|
||||
if (!found)
|
||||
return -ENOENT;
|
||||
|
||||
old_prog = xchg(&link->link.prog, new_prog);
|
||||
replace_effective_prog(cgrp, link->type, link);
|
||||
bpf_prog_put(old_prog);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct bpf_prog_list *find_detach_entry(struct list_head *progs,
|
||||
struct bpf_prog *prog,
|
||||
struct bpf_cgroup_link *link,
|
||||
bool allow_multi)
|
||||
{
|
||||
struct bpf_prog_list *pl;
|
||||
|
||||
if (!allow_multi) {
|
||||
if (list_empty(progs))
|
||||
/* report error when trying to detach and nothing is attached */
|
||||
return ERR_PTR(-ENOENT);
|
||||
|
||||
/* to maintain backward compatibility NONE and OVERRIDE cgroups
|
||||
* allow detaching with invalid FD (prog==NULL) in legacy mode
|
||||
*/
|
||||
return list_first_entry(progs, typeof(*pl), node);
|
||||
}
|
||||
|
||||
if (!prog && !link)
|
||||
/* to detach MULTI prog the user has to specify valid FD
|
||||
* of the program or link to be detached
|
||||
*/
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
/* find the prog or link and detach it */
|
||||
list_for_each_entry(pl, progs, node) {
|
||||
if (pl->prog == prog && pl->link == link)
|
||||
return pl;
|
||||
}
|
||||
return ERR_PTR(-ENOENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* __cgroup_bpf_detach() - Detach the program or link from a cgroup, and
|
||||
* propagate the change to descendants
|
||||
* @cgrp: The cgroup which descendants to traverse
|
||||
* @prog: A program to detach or NULL
|
||||
* @prog: A link to detach or NULL
|
||||
* @type: Type of detach operation
|
||||
*
|
||||
* At most one of @prog or @link can be non-NULL.
|
||||
* Must be called with cgroup_mutex held.
|
||||
*/
|
||||
int __cgroup_bpf_detach(struct cgroup *cgrp, struct bpf_prog *prog,
|
||||
struct bpf_cgroup_link *link, enum bpf_attach_type type)
|
||||
{
|
||||
struct list_head *progs = &cgrp->bpf.progs[type];
|
||||
u32 flags = cgrp->bpf.flags[type];
|
||||
struct bpf_prog_list *pl;
|
||||
struct bpf_prog *old_prog;
|
||||
int err;
|
||||
|
||||
if (prog && link)
|
||||
/* only one of prog or link can be specified */
|
||||
return -EINVAL;
|
||||
|
||||
pl = find_detach_entry(progs, prog, link, flags & BPF_F_ALLOW_MULTI);
|
||||
if (IS_ERR(pl))
|
||||
return PTR_ERR(pl);
|
||||
|
||||
/* mark it deleted, so it's ignored while recomputing effective */
|
||||
old_prog = pl->prog;
|
||||
pl->prog = NULL;
|
||||
pl->link = NULL;
|
||||
|
||||
err = update_effective_progs(cgrp, type);
|
||||
if (err)
|
||||
@@ -467,22 +654,21 @@ int __cgroup_bpf_detach(struct cgroup *cgrp, struct bpf_prog *prog,
|
||||
|
||||
/* now can actually delete it from this cgroup list */
|
||||
list_del(&pl->node);
|
||||
for_each_cgroup_storage_type(stype) {
|
||||
bpf_cgroup_storage_unlink(pl->storage[stype]);
|
||||
bpf_cgroup_storage_free(pl->storage[stype]);
|
||||
}
|
||||
bpf_cgroup_storages_unlink(pl->storage);
|
||||
bpf_cgroup_storages_free(pl->storage);
|
||||
kfree(pl);
|
||||
if (list_empty(progs))
|
||||
/* last program was detached, reset flags to zero */
|
||||
cgrp->bpf.flags[type] = 0;
|
||||
|
||||
bpf_prog_put(old_prog);
|
||||
if (old_prog)
|
||||
bpf_prog_put(old_prog);
|
||||
static_branch_dec(&cgroup_bpf_enabled_key);
|
||||
return 0;
|
||||
|
||||
cleanup:
|
||||
/* and restore back old_prog */
|
||||
/* restore back prog or link */
|
||||
pl->prog = old_prog;
|
||||
pl->link = link;
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -495,6 +681,7 @@ int __cgroup_bpf_query(struct cgroup *cgrp, const union bpf_attr *attr,
|
||||
struct list_head *progs = &cgrp->bpf.progs[type];
|
||||
u32 flags = cgrp->bpf.flags[type];
|
||||
struct bpf_prog_array *effective;
|
||||
struct bpf_prog *prog;
|
||||
int cnt, ret = 0, i;
|
||||
|
||||
effective = rcu_dereference_protected(cgrp->bpf.effective[type],
|
||||
@@ -525,7 +712,8 @@ int __cgroup_bpf_query(struct cgroup *cgrp, const union bpf_attr *attr,
|
||||
|
||||
i = 0;
|
||||
list_for_each_entry(pl, progs, node) {
|
||||
id = pl->prog->aux->id;
|
||||
prog = prog_list_prog(pl);
|
||||
id = prog->aux->id;
|
||||
if (copy_to_user(prog_ids + i, &id, sizeof(id)))
|
||||
return -EFAULT;
|
||||
if (++i == cnt)
|
||||
@@ -555,8 +743,8 @@ int cgroup_bpf_prog_attach(const union bpf_attr *attr,
|
||||
}
|
||||
}
|
||||
|
||||
ret = cgroup_bpf_attach(cgrp, prog, replace_prog, attr->attach_type,
|
||||
attr->attach_flags);
|
||||
ret = cgroup_bpf_attach(cgrp, prog, replace_prog, NULL,
|
||||
attr->attach_type, attr->attach_flags);
|
||||
|
||||
if (replace_prog)
|
||||
bpf_prog_put(replace_prog);
|
||||
@@ -578,7 +766,7 @@ int cgroup_bpf_prog_detach(const union bpf_attr *attr, enum bpf_prog_type ptype)
|
||||
if (IS_ERR(prog))
|
||||
prog = NULL;
|
||||
|
||||
ret = cgroup_bpf_detach(cgrp, prog, attr->attach_type, 0);
|
||||
ret = cgroup_bpf_detach(cgrp, prog, attr->attach_type);
|
||||
if (prog)
|
||||
bpf_prog_put(prog);
|
||||
|
||||
@@ -586,6 +774,90 @@ int cgroup_bpf_prog_detach(const union bpf_attr *attr, enum bpf_prog_type ptype)
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void bpf_cgroup_link_release(struct bpf_link *link)
|
||||
{
|
||||
struct bpf_cgroup_link *cg_link =
|
||||
container_of(link, struct bpf_cgroup_link, link);
|
||||
|
||||
/* link might have been auto-detached by dying cgroup already,
|
||||
* in that case our work is done here
|
||||
*/
|
||||
if (!cg_link->cgroup)
|
||||
return;
|
||||
|
||||
mutex_lock(&cgroup_mutex);
|
||||
|
||||
/* re-check cgroup under lock again */
|
||||
if (!cg_link->cgroup) {
|
||||
mutex_unlock(&cgroup_mutex);
|
||||
return;
|
||||
}
|
||||
|
||||
WARN_ON(__cgroup_bpf_detach(cg_link->cgroup, NULL, cg_link,
|
||||
cg_link->type));
|
||||
|
||||
mutex_unlock(&cgroup_mutex);
|
||||
cgroup_put(cg_link->cgroup);
|
||||
}
|
||||
|
||||
static void bpf_cgroup_link_dealloc(struct bpf_link *link)
|
||||
{
|
||||
struct bpf_cgroup_link *cg_link =
|
||||
container_of(link, struct bpf_cgroup_link, link);
|
||||
|
||||
kfree(cg_link);
|
||||
}
|
||||
|
||||
const struct bpf_link_ops bpf_cgroup_link_lops = {
|
||||
.release = bpf_cgroup_link_release,
|
||||
.dealloc = bpf_cgroup_link_dealloc,
|
||||
};
|
||||
|
||||
int cgroup_bpf_link_attach(const union bpf_attr *attr, struct bpf_prog *prog)
|
||||
{
|
||||
struct bpf_cgroup_link *link;
|
||||
struct file *link_file;
|
||||
struct cgroup *cgrp;
|
||||
int err, link_fd;
|
||||
|
||||
if (attr->link_create.flags)
|
||||
return -EINVAL;
|
||||
|
||||
cgrp = cgroup_get_from_fd(attr->link_create.target_fd);
|
||||
if (IS_ERR(cgrp))
|
||||
return PTR_ERR(cgrp);
|
||||
|
||||
link = kzalloc(sizeof(*link), GFP_USER);
|
||||
if (!link) {
|
||||
err = -ENOMEM;
|
||||
goto out_put_cgroup;
|
||||
}
|
||||
bpf_link_init(&link->link, &bpf_cgroup_link_lops, prog);
|
||||
link->cgroup = cgrp;
|
||||
link->type = attr->link_create.attach_type;
|
||||
|
||||
link_file = bpf_link_new_file(&link->link, &link_fd);
|
||||
if (IS_ERR(link_file)) {
|
||||
kfree(link);
|
||||
err = PTR_ERR(link_file);
|
||||
goto out_put_cgroup;
|
||||
}
|
||||
|
||||
err = cgroup_bpf_attach(cgrp, NULL, NULL, link, link->type,
|
||||
BPF_F_ALLOW_MULTI);
|
||||
if (err) {
|
||||
bpf_link_cleanup(&link->link, link_file, link_fd);
|
||||
goto out_put_cgroup;
|
||||
}
|
||||
|
||||
fd_install(link_fd, link_file);
|
||||
return link_fd;
|
||||
|
||||
out_put_cgroup:
|
||||
cgroup_put(cgrp);
|
||||
return err;
|
||||
}
|
||||
|
||||
int cgroup_bpf_prog_query(const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
|
||||
+66
-56
@@ -97,7 +97,7 @@ struct bpf_prog *bpf_prog_alloc_no_stats(unsigned int size, gfp_t gfp_extra_flag
|
||||
fp->aux->prog = fp;
|
||||
fp->jit_requested = ebpf_jit_enabled();
|
||||
|
||||
INIT_LIST_HEAD_RCU(&fp->aux->ksym_lnode);
|
||||
INIT_LIST_HEAD_RCU(&fp->aux->ksym.lnode);
|
||||
|
||||
return fp;
|
||||
}
|
||||
@@ -523,22 +523,22 @@ int bpf_jit_kallsyms __read_mostly = IS_BUILTIN(CONFIG_BPF_JIT_DEFAULT_ON);
|
||||
int bpf_jit_harden __read_mostly;
|
||||
long bpf_jit_limit __read_mostly;
|
||||
|
||||
static __always_inline void
|
||||
bpf_get_prog_addr_region(const struct bpf_prog *prog,
|
||||
unsigned long *symbol_start,
|
||||
unsigned long *symbol_end)
|
||||
static void
|
||||
bpf_prog_ksym_set_addr(struct bpf_prog *prog)
|
||||
{
|
||||
const struct bpf_binary_header *hdr = bpf_jit_binary_hdr(prog);
|
||||
unsigned long addr = (unsigned long)hdr;
|
||||
|
||||
WARN_ON_ONCE(!bpf_prog_ebpf_jited(prog));
|
||||
|
||||
*symbol_start = addr;
|
||||
*symbol_end = addr + hdr->pages * PAGE_SIZE;
|
||||
prog->aux->ksym.start = (unsigned long) prog->bpf_func;
|
||||
prog->aux->ksym.end = addr + hdr->pages * PAGE_SIZE;
|
||||
}
|
||||
|
||||
void bpf_get_prog_name(const struct bpf_prog *prog, char *sym)
|
||||
static void
|
||||
bpf_prog_ksym_set_name(struct bpf_prog *prog)
|
||||
{
|
||||
char *sym = prog->aux->ksym.name;
|
||||
const char *end = sym + KSYM_NAME_LEN;
|
||||
const struct btf_type *type;
|
||||
const char *func_name;
|
||||
@@ -572,36 +572,27 @@ void bpf_get_prog_name(const struct bpf_prog *prog, char *sym)
|
||||
*sym = 0;
|
||||
}
|
||||
|
||||
static __always_inline unsigned long
|
||||
bpf_get_prog_addr_start(struct latch_tree_node *n)
|
||||
static unsigned long bpf_get_ksym_start(struct latch_tree_node *n)
|
||||
{
|
||||
unsigned long symbol_start, symbol_end;
|
||||
const struct bpf_prog_aux *aux;
|
||||
|
||||
aux = container_of(n, struct bpf_prog_aux, ksym_tnode);
|
||||
bpf_get_prog_addr_region(aux->prog, &symbol_start, &symbol_end);
|
||||
|
||||
return symbol_start;
|
||||
return container_of(n, struct bpf_ksym, tnode)->start;
|
||||
}
|
||||
|
||||
static __always_inline bool bpf_tree_less(struct latch_tree_node *a,
|
||||
struct latch_tree_node *b)
|
||||
{
|
||||
return bpf_get_prog_addr_start(a) < bpf_get_prog_addr_start(b);
|
||||
return bpf_get_ksym_start(a) < bpf_get_ksym_start(b);
|
||||
}
|
||||
|
||||
static __always_inline int bpf_tree_comp(void *key, struct latch_tree_node *n)
|
||||
{
|
||||
unsigned long val = (unsigned long)key;
|
||||
unsigned long symbol_start, symbol_end;
|
||||
const struct bpf_prog_aux *aux;
|
||||
const struct bpf_ksym *ksym;
|
||||
|
||||
aux = container_of(n, struct bpf_prog_aux, ksym_tnode);
|
||||
bpf_get_prog_addr_region(aux->prog, &symbol_start, &symbol_end);
|
||||
ksym = container_of(n, struct bpf_ksym, tnode);
|
||||
|
||||
if (val < symbol_start)
|
||||
if (val < ksym->start)
|
||||
return -1;
|
||||
if (val >= symbol_end)
|
||||
if (val >= ksym->end)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
@@ -616,20 +607,29 @@ static DEFINE_SPINLOCK(bpf_lock);
|
||||
static LIST_HEAD(bpf_kallsyms);
|
||||
static struct latch_tree_root bpf_tree __cacheline_aligned;
|
||||
|
||||
static void bpf_prog_ksym_node_add(struct bpf_prog_aux *aux)
|
||||
void bpf_ksym_add(struct bpf_ksym *ksym)
|
||||
{
|
||||
WARN_ON_ONCE(!list_empty(&aux->ksym_lnode));
|
||||
list_add_tail_rcu(&aux->ksym_lnode, &bpf_kallsyms);
|
||||
latch_tree_insert(&aux->ksym_tnode, &bpf_tree, &bpf_tree_ops);
|
||||
spin_lock_bh(&bpf_lock);
|
||||
WARN_ON_ONCE(!list_empty(&ksym->lnode));
|
||||
list_add_tail_rcu(&ksym->lnode, &bpf_kallsyms);
|
||||
latch_tree_insert(&ksym->tnode, &bpf_tree, &bpf_tree_ops);
|
||||
spin_unlock_bh(&bpf_lock);
|
||||
}
|
||||
|
||||
static void bpf_prog_ksym_node_del(struct bpf_prog_aux *aux)
|
||||
static void __bpf_ksym_del(struct bpf_ksym *ksym)
|
||||
{
|
||||
if (list_empty(&aux->ksym_lnode))
|
||||
if (list_empty(&ksym->lnode))
|
||||
return;
|
||||
|
||||
latch_tree_erase(&aux->ksym_tnode, &bpf_tree, &bpf_tree_ops);
|
||||
list_del_rcu(&aux->ksym_lnode);
|
||||
latch_tree_erase(&ksym->tnode, &bpf_tree, &bpf_tree_ops);
|
||||
list_del_rcu(&ksym->lnode);
|
||||
}
|
||||
|
||||
void bpf_ksym_del(struct bpf_ksym *ksym)
|
||||
{
|
||||
spin_lock_bh(&bpf_lock);
|
||||
__bpf_ksym_del(ksym);
|
||||
spin_unlock_bh(&bpf_lock);
|
||||
}
|
||||
|
||||
static bool bpf_prog_kallsyms_candidate(const struct bpf_prog *fp)
|
||||
@@ -639,8 +639,8 @@ static bool bpf_prog_kallsyms_candidate(const struct bpf_prog *fp)
|
||||
|
||||
static bool bpf_prog_kallsyms_verify_off(const struct bpf_prog *fp)
|
||||
{
|
||||
return list_empty(&fp->aux->ksym_lnode) ||
|
||||
fp->aux->ksym_lnode.prev == LIST_POISON2;
|
||||
return list_empty(&fp->aux->ksym.lnode) ||
|
||||
fp->aux->ksym.lnode.prev == LIST_POISON2;
|
||||
}
|
||||
|
||||
void bpf_prog_kallsyms_add(struct bpf_prog *fp)
|
||||
@@ -649,9 +649,11 @@ void bpf_prog_kallsyms_add(struct bpf_prog *fp)
|
||||
!capable(CAP_SYS_ADMIN))
|
||||
return;
|
||||
|
||||
spin_lock_bh(&bpf_lock);
|
||||
bpf_prog_ksym_node_add(fp->aux);
|
||||
spin_unlock_bh(&bpf_lock);
|
||||
bpf_prog_ksym_set_addr(fp);
|
||||
bpf_prog_ksym_set_name(fp);
|
||||
fp->aux->ksym.prog = true;
|
||||
|
||||
bpf_ksym_add(&fp->aux->ksym);
|
||||
}
|
||||
|
||||
void bpf_prog_kallsyms_del(struct bpf_prog *fp)
|
||||
@@ -659,33 +661,30 @@ void bpf_prog_kallsyms_del(struct bpf_prog *fp)
|
||||
if (!bpf_prog_kallsyms_candidate(fp))
|
||||
return;
|
||||
|
||||
spin_lock_bh(&bpf_lock);
|
||||
bpf_prog_ksym_node_del(fp->aux);
|
||||
spin_unlock_bh(&bpf_lock);
|
||||
bpf_ksym_del(&fp->aux->ksym);
|
||||
}
|
||||
|
||||
static struct bpf_prog *bpf_prog_kallsyms_find(unsigned long addr)
|
||||
static struct bpf_ksym *bpf_ksym_find(unsigned long addr)
|
||||
{
|
||||
struct latch_tree_node *n;
|
||||
|
||||
n = latch_tree_find((void *)addr, &bpf_tree, &bpf_tree_ops);
|
||||
return n ?
|
||||
container_of(n, struct bpf_prog_aux, ksym_tnode)->prog :
|
||||
NULL;
|
||||
return n ? container_of(n, struct bpf_ksym, tnode) : NULL;
|
||||
}
|
||||
|
||||
const char *__bpf_address_lookup(unsigned long addr, unsigned long *size,
|
||||
unsigned long *off, char *sym)
|
||||
{
|
||||
unsigned long symbol_start, symbol_end;
|
||||
struct bpf_prog *prog;
|
||||
struct bpf_ksym *ksym;
|
||||
char *ret = NULL;
|
||||
|
||||
rcu_read_lock();
|
||||
prog = bpf_prog_kallsyms_find(addr);
|
||||
if (prog) {
|
||||
bpf_get_prog_addr_region(prog, &symbol_start, &symbol_end);
|
||||
bpf_get_prog_name(prog, sym);
|
||||
ksym = bpf_ksym_find(addr);
|
||||
if (ksym) {
|
||||
unsigned long symbol_start = ksym->start;
|
||||
unsigned long symbol_end = ksym->end;
|
||||
|
||||
strncpy(sym, ksym->name, KSYM_NAME_LEN);
|
||||
|
||||
ret = sym;
|
||||
if (size)
|
||||
@@ -703,19 +702,28 @@ bool is_bpf_text_address(unsigned long addr)
|
||||
bool ret;
|
||||
|
||||
rcu_read_lock();
|
||||
ret = bpf_prog_kallsyms_find(addr) != NULL;
|
||||
ret = bpf_ksym_find(addr) != NULL;
|
||||
rcu_read_unlock();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct bpf_prog *bpf_prog_ksym_find(unsigned long addr)
|
||||
{
|
||||
struct bpf_ksym *ksym = bpf_ksym_find(addr);
|
||||
|
||||
return ksym && ksym->prog ?
|
||||
container_of(ksym, struct bpf_prog_aux, ksym)->prog :
|
||||
NULL;
|
||||
}
|
||||
|
||||
const struct exception_table_entry *search_bpf_extables(unsigned long addr)
|
||||
{
|
||||
const struct exception_table_entry *e = NULL;
|
||||
struct bpf_prog *prog;
|
||||
|
||||
rcu_read_lock();
|
||||
prog = bpf_prog_kallsyms_find(addr);
|
||||
prog = bpf_prog_ksym_find(addr);
|
||||
if (!prog)
|
||||
goto out;
|
||||
if (!prog->aux->num_exentries)
|
||||
@@ -730,7 +738,7 @@ out:
|
||||
int bpf_get_kallsym(unsigned int symnum, unsigned long *value, char *type,
|
||||
char *sym)
|
||||
{
|
||||
struct bpf_prog_aux *aux;
|
||||
struct bpf_ksym *ksym;
|
||||
unsigned int it = 0;
|
||||
int ret = -ERANGE;
|
||||
|
||||
@@ -738,13 +746,13 @@ int bpf_get_kallsym(unsigned int symnum, unsigned long *value, char *type,
|
||||
return ret;
|
||||
|
||||
rcu_read_lock();
|
||||
list_for_each_entry_rcu(aux, &bpf_kallsyms, ksym_lnode) {
|
||||
list_for_each_entry_rcu(ksym, &bpf_kallsyms, lnode) {
|
||||
if (it++ != symnum)
|
||||
continue;
|
||||
|
||||
bpf_get_prog_name(aux->prog, sym);
|
||||
strncpy(sym, ksym->name, KSYM_NAME_LEN);
|
||||
|
||||
*value = (unsigned long)aux->prog->bpf_func;
|
||||
*value = ksym->start;
|
||||
*type = BPF_SYM_ELF_TYPE;
|
||||
|
||||
ret = 0;
|
||||
@@ -2148,7 +2156,9 @@ const struct bpf_func_proto bpf_get_current_pid_tgid_proto __weak;
|
||||
const struct bpf_func_proto bpf_get_current_uid_gid_proto __weak;
|
||||
const struct bpf_func_proto bpf_get_current_comm_proto __weak;
|
||||
const struct bpf_func_proto bpf_get_current_cgroup_id_proto __weak;
|
||||
const struct bpf_func_proto bpf_get_current_ancestor_cgroup_id_proto __weak;
|
||||
const struct bpf_func_proto bpf_get_local_storage_proto __weak;
|
||||
const struct bpf_func_proto bpf_get_ns_current_pid_tgid_proto __weak;
|
||||
|
||||
const struct bpf_func_proto * __weak bpf_get_trace_printk_proto(void)
|
||||
{
|
||||
|
||||
@@ -113,7 +113,7 @@ static void bpf_dispatcher_update(struct bpf_dispatcher *d, int prev_num_progs)
|
||||
noff = 0;
|
||||
} else {
|
||||
old = d->image + d->image_off;
|
||||
noff = d->image_off ^ (BPF_IMAGE_SIZE / 2);
|
||||
noff = d->image_off ^ (PAGE_SIZE / 2);
|
||||
}
|
||||
|
||||
new = d->num_progs ? d->image + noff : NULL;
|
||||
@@ -140,9 +140,10 @@ void bpf_dispatcher_change_prog(struct bpf_dispatcher *d, struct bpf_prog *from,
|
||||
|
||||
mutex_lock(&d->mutex);
|
||||
if (!d->image) {
|
||||
d->image = bpf_image_alloc();
|
||||
d->image = bpf_jit_alloc_exec_page();
|
||||
if (!d->image)
|
||||
goto out;
|
||||
bpf_image_ksym_add(d->image, &d->ksym);
|
||||
}
|
||||
|
||||
prev_num_progs = d->num_progs;
|
||||
|
||||
+176
-54
@@ -27,9 +27,62 @@
|
||||
.map_delete_batch = \
|
||||
generic_map_delete_batch
|
||||
|
||||
/*
|
||||
* The bucket lock has two protection scopes:
|
||||
*
|
||||
* 1) Serializing concurrent operations from BPF programs on differrent
|
||||
* CPUs
|
||||
*
|
||||
* 2) Serializing concurrent operations from BPF programs and sys_bpf()
|
||||
*
|
||||
* BPF programs can execute in any context including perf, kprobes and
|
||||
* tracing. As there are almost no limits where perf, kprobes and tracing
|
||||
* can be invoked from the lock operations need to be protected against
|
||||
* deadlocks. Deadlocks can be caused by recursion and by an invocation in
|
||||
* the lock held section when functions which acquire this lock are invoked
|
||||
* from sys_bpf(). BPF recursion is prevented by incrementing the per CPU
|
||||
* variable bpf_prog_active, which prevents BPF programs attached to perf
|
||||
* events, kprobes and tracing to be invoked before the prior invocation
|
||||
* from one of these contexts completed. sys_bpf() uses the same mechanism
|
||||
* by pinning the task to the current CPU and incrementing the recursion
|
||||
* protection accross the map operation.
|
||||
*
|
||||
* This has subtle implications on PREEMPT_RT. PREEMPT_RT forbids certain
|
||||
* operations like memory allocations (even with GFP_ATOMIC) from atomic
|
||||
* contexts. This is required because even with GFP_ATOMIC the memory
|
||||
* allocator calls into code pathes which acquire locks with long held lock
|
||||
* sections. To ensure the deterministic behaviour these locks are regular
|
||||
* spinlocks, which are converted to 'sleepable' spinlocks on RT. The only
|
||||
* true atomic contexts on an RT kernel are the low level hardware
|
||||
* handling, scheduling, low level interrupt handling, NMIs etc. None of
|
||||
* these contexts should ever do memory allocations.
|
||||
*
|
||||
* As regular device interrupt handlers and soft interrupts are forced into
|
||||
* thread context, the existing code which does
|
||||
* spin_lock*(); alloc(GPF_ATOMIC); spin_unlock*();
|
||||
* just works.
|
||||
*
|
||||
* In theory the BPF locks could be converted to regular spinlocks as well,
|
||||
* but the bucket locks and percpu_freelist locks can be taken from
|
||||
* arbitrary contexts (perf, kprobes, tracepoints) which are required to be
|
||||
* atomic contexts even on RT. These mechanisms require preallocated maps,
|
||||
* so there is no need to invoke memory allocations within the lock held
|
||||
* sections.
|
||||
*
|
||||
* BPF maps which need dynamic allocation are only used from (forced)
|
||||
* thread context on RT and can therefore use regular spinlocks which in
|
||||
* turn allows to invoke memory allocations from the lock held section.
|
||||
*
|
||||
* On a non RT kernel this distinction is neither possible nor required.
|
||||
* spinlock maps to raw_spinlock and the extra code is optimized out by the
|
||||
* compiler.
|
||||
*/
|
||||
struct bucket {
|
||||
struct hlist_nulls_head head;
|
||||
raw_spinlock_t lock;
|
||||
union {
|
||||
raw_spinlock_t raw_lock;
|
||||
spinlock_t lock;
|
||||
};
|
||||
};
|
||||
|
||||
struct bpf_htab {
|
||||
@@ -56,6 +109,7 @@ struct htab_elem {
|
||||
union {
|
||||
struct bpf_htab *htab;
|
||||
struct pcpu_freelist_node fnode;
|
||||
struct htab_elem *batch_flink;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -64,9 +118,54 @@ struct htab_elem {
|
||||
struct bpf_lru_node lru_node;
|
||||
};
|
||||
u32 hash;
|
||||
char key[0] __aligned(8);
|
||||
char key[] __aligned(8);
|
||||
};
|
||||
|
||||
static inline bool htab_is_prealloc(const struct bpf_htab *htab)
|
||||
{
|
||||
return !(htab->map.map_flags & BPF_F_NO_PREALLOC);
|
||||
}
|
||||
|
||||
static inline bool htab_use_raw_lock(const struct bpf_htab *htab)
|
||||
{
|
||||
return (!IS_ENABLED(CONFIG_PREEMPT_RT) || htab_is_prealloc(htab));
|
||||
}
|
||||
|
||||
static void htab_init_buckets(struct bpf_htab *htab)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; i < htab->n_buckets; i++) {
|
||||
INIT_HLIST_NULLS_HEAD(&htab->buckets[i].head, i);
|
||||
if (htab_use_raw_lock(htab))
|
||||
raw_spin_lock_init(&htab->buckets[i].raw_lock);
|
||||
else
|
||||
spin_lock_init(&htab->buckets[i].lock);
|
||||
}
|
||||
}
|
||||
|
||||
static inline unsigned long htab_lock_bucket(const struct bpf_htab *htab,
|
||||
struct bucket *b)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
if (htab_use_raw_lock(htab))
|
||||
raw_spin_lock_irqsave(&b->raw_lock, flags);
|
||||
else
|
||||
spin_lock_irqsave(&b->lock, flags);
|
||||
return flags;
|
||||
}
|
||||
|
||||
static inline void htab_unlock_bucket(const struct bpf_htab *htab,
|
||||
struct bucket *b,
|
||||
unsigned long flags)
|
||||
{
|
||||
if (htab_use_raw_lock(htab))
|
||||
raw_spin_unlock_irqrestore(&b->raw_lock, flags);
|
||||
else
|
||||
spin_unlock_irqrestore(&b->lock, flags);
|
||||
}
|
||||
|
||||
static bool htab_lru_map_delete_node(void *arg, struct bpf_lru_node *node);
|
||||
|
||||
static bool htab_is_lru(const struct bpf_htab *htab)
|
||||
@@ -81,11 +180,6 @@ static bool htab_is_percpu(const struct bpf_htab *htab)
|
||||
htab->map.map_type == BPF_MAP_TYPE_LRU_PERCPU_HASH;
|
||||
}
|
||||
|
||||
static bool htab_is_prealloc(const struct bpf_htab *htab)
|
||||
{
|
||||
return !(htab->map.map_flags & BPF_F_NO_PREALLOC);
|
||||
}
|
||||
|
||||
static inline void htab_elem_set_ptr(struct htab_elem *l, u32 key_size,
|
||||
void __percpu *pptr)
|
||||
{
|
||||
@@ -126,6 +220,17 @@ free_elems:
|
||||
bpf_map_area_free(htab->elems);
|
||||
}
|
||||
|
||||
/* The LRU list has a lock (lru_lock). Each htab bucket has a lock
|
||||
* (bucket_lock). If both locks need to be acquired together, the lock
|
||||
* order is always lru_lock -> bucket_lock and this only happens in
|
||||
* bpf_lru_list.c logic. For example, certain code path of
|
||||
* bpf_lru_pop_free(), which is called by function prealloc_lru_pop(),
|
||||
* will acquire lru_lock first followed by acquiring bucket_lock.
|
||||
*
|
||||
* In hashtab.c, to avoid deadlock, lock acquisition of
|
||||
* bucket_lock followed by lru_lock is not allowed. In such cases,
|
||||
* bucket_lock needs to be released first before acquiring lru_lock.
|
||||
*/
|
||||
static struct htab_elem *prealloc_lru_pop(struct bpf_htab *htab, void *key,
|
||||
u32 hash)
|
||||
{
|
||||
@@ -316,8 +421,8 @@ static struct bpf_map *htab_map_alloc(union bpf_attr *attr)
|
||||
bool percpu_lru = (attr->map_flags & BPF_F_NO_COMMON_LRU);
|
||||
bool prealloc = !(attr->map_flags & BPF_F_NO_PREALLOC);
|
||||
struct bpf_htab *htab;
|
||||
int err, i;
|
||||
u64 cost;
|
||||
int err;
|
||||
|
||||
htab = kzalloc(sizeof(*htab), GFP_USER);
|
||||
if (!htab)
|
||||
@@ -379,10 +484,7 @@ static struct bpf_map *htab_map_alloc(union bpf_attr *attr)
|
||||
else
|
||||
htab->hashrnd = get_random_int();
|
||||
|
||||
for (i = 0; i < htab->n_buckets; i++) {
|
||||
INIT_HLIST_NULLS_HEAD(&htab->buckets[i].head, i);
|
||||
raw_spin_lock_init(&htab->buckets[i].lock);
|
||||
}
|
||||
htab_init_buckets(htab);
|
||||
|
||||
if (prealloc) {
|
||||
err = prealloc_init(htab);
|
||||
@@ -590,7 +692,7 @@ static bool htab_lru_map_delete_node(void *arg, struct bpf_lru_node *node)
|
||||
b = __select_bucket(htab, tgt_l->hash);
|
||||
head = &b->head;
|
||||
|
||||
raw_spin_lock_irqsave(&b->lock, flags);
|
||||
flags = htab_lock_bucket(htab, b);
|
||||
|
||||
hlist_nulls_for_each_entry_rcu(l, n, head, hash_node)
|
||||
if (l == tgt_l) {
|
||||
@@ -598,7 +700,7 @@ static bool htab_lru_map_delete_node(void *arg, struct bpf_lru_node *node)
|
||||
break;
|
||||
}
|
||||
|
||||
raw_spin_unlock_irqrestore(&b->lock, flags);
|
||||
htab_unlock_bucket(htab, b, flags);
|
||||
|
||||
return l == tgt_l;
|
||||
}
|
||||
@@ -674,15 +776,7 @@ static void htab_elem_free_rcu(struct rcu_head *head)
|
||||
struct htab_elem *l = container_of(head, struct htab_elem, rcu);
|
||||
struct bpf_htab *htab = l->htab;
|
||||
|
||||
/* must increment bpf_prog_active to avoid kprobe+bpf triggering while
|
||||
* we're calling kfree, otherwise deadlock is possible if kprobes
|
||||
* are placed somewhere inside of slub
|
||||
*/
|
||||
preempt_disable();
|
||||
__this_cpu_inc(bpf_prog_active);
|
||||
htab_elem_free(htab, l);
|
||||
__this_cpu_dec(bpf_prog_active);
|
||||
preempt_enable();
|
||||
}
|
||||
|
||||
static void free_htab_elem(struct bpf_htab *htab, struct htab_elem *l)
|
||||
@@ -872,8 +966,7 @@ static int htab_map_update_elem(struct bpf_map *map, void *key, void *value,
|
||||
*/
|
||||
}
|
||||
|
||||
/* bpf_map_update_elem() can be called in_irq() */
|
||||
raw_spin_lock_irqsave(&b->lock, flags);
|
||||
flags = htab_lock_bucket(htab, b);
|
||||
|
||||
l_old = lookup_elem_raw(head, hash, key, key_size);
|
||||
|
||||
@@ -914,7 +1007,7 @@ static int htab_map_update_elem(struct bpf_map *map, void *key, void *value,
|
||||
}
|
||||
ret = 0;
|
||||
err:
|
||||
raw_spin_unlock_irqrestore(&b->lock, flags);
|
||||
htab_unlock_bucket(htab, b, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -952,8 +1045,7 @@ static int htab_lru_map_update_elem(struct bpf_map *map, void *key, void *value,
|
||||
return -ENOMEM;
|
||||
memcpy(l_new->key + round_up(map->key_size, 8), value, map->value_size);
|
||||
|
||||
/* bpf_map_update_elem() can be called in_irq() */
|
||||
raw_spin_lock_irqsave(&b->lock, flags);
|
||||
flags = htab_lock_bucket(htab, b);
|
||||
|
||||
l_old = lookup_elem_raw(head, hash, key, key_size);
|
||||
|
||||
@@ -972,7 +1064,7 @@ static int htab_lru_map_update_elem(struct bpf_map *map, void *key, void *value,
|
||||
ret = 0;
|
||||
|
||||
err:
|
||||
raw_spin_unlock_irqrestore(&b->lock, flags);
|
||||
htab_unlock_bucket(htab, b, flags);
|
||||
|
||||
if (ret)
|
||||
bpf_lru_push_free(&htab->lru, &l_new->lru_node);
|
||||
@@ -1007,8 +1099,7 @@ static int __htab_percpu_map_update_elem(struct bpf_map *map, void *key,
|
||||
b = __select_bucket(htab, hash);
|
||||
head = &b->head;
|
||||
|
||||
/* bpf_map_update_elem() can be called in_irq() */
|
||||
raw_spin_lock_irqsave(&b->lock, flags);
|
||||
flags = htab_lock_bucket(htab, b);
|
||||
|
||||
l_old = lookup_elem_raw(head, hash, key, key_size);
|
||||
|
||||
@@ -1031,7 +1122,7 @@ static int __htab_percpu_map_update_elem(struct bpf_map *map, void *key,
|
||||
}
|
||||
ret = 0;
|
||||
err:
|
||||
raw_spin_unlock_irqrestore(&b->lock, flags);
|
||||
htab_unlock_bucket(htab, b, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1071,8 +1162,7 @@ static int __htab_lru_percpu_map_update_elem(struct bpf_map *map, void *key,
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* bpf_map_update_elem() can be called in_irq() */
|
||||
raw_spin_lock_irqsave(&b->lock, flags);
|
||||
flags = htab_lock_bucket(htab, b);
|
||||
|
||||
l_old = lookup_elem_raw(head, hash, key, key_size);
|
||||
|
||||
@@ -1094,7 +1184,7 @@ static int __htab_lru_percpu_map_update_elem(struct bpf_map *map, void *key,
|
||||
}
|
||||
ret = 0;
|
||||
err:
|
||||
raw_spin_unlock_irqrestore(&b->lock, flags);
|
||||
htab_unlock_bucket(htab, b, flags);
|
||||
if (l_new)
|
||||
bpf_lru_push_free(&htab->lru, &l_new->lru_node);
|
||||
return ret;
|
||||
@@ -1132,7 +1222,7 @@ static int htab_map_delete_elem(struct bpf_map *map, void *key)
|
||||
b = __select_bucket(htab, hash);
|
||||
head = &b->head;
|
||||
|
||||
raw_spin_lock_irqsave(&b->lock, flags);
|
||||
flags = htab_lock_bucket(htab, b);
|
||||
|
||||
l = lookup_elem_raw(head, hash, key, key_size);
|
||||
|
||||
@@ -1142,7 +1232,7 @@ static int htab_map_delete_elem(struct bpf_map *map, void *key)
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
raw_spin_unlock_irqrestore(&b->lock, flags);
|
||||
htab_unlock_bucket(htab, b, flags);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -1164,7 +1254,7 @@ static int htab_lru_map_delete_elem(struct bpf_map *map, void *key)
|
||||
b = __select_bucket(htab, hash);
|
||||
head = &b->head;
|
||||
|
||||
raw_spin_lock_irqsave(&b->lock, flags);
|
||||
flags = htab_lock_bucket(htab, b);
|
||||
|
||||
l = lookup_elem_raw(head, hash, key, key_size);
|
||||
|
||||
@@ -1173,7 +1263,7 @@ static int htab_lru_map_delete_elem(struct bpf_map *map, void *key)
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
raw_spin_unlock_irqrestore(&b->lock, flags);
|
||||
htab_unlock_bucket(htab, b, flags);
|
||||
if (l)
|
||||
bpf_lru_push_free(&htab->lru, &l->lru_node);
|
||||
return ret;
|
||||
@@ -1256,10 +1346,12 @@ __htab_map_lookup_and_delete_batch(struct bpf_map *map,
|
||||
void __user *ukeys = u64_to_user_ptr(attr->batch.keys);
|
||||
void *ubatch = u64_to_user_ptr(attr->batch.in_batch);
|
||||
u32 batch, max_count, size, bucket_size;
|
||||
struct htab_elem *node_to_free = NULL;
|
||||
u64 elem_map_flags, map_flags;
|
||||
struct hlist_nulls_head *head;
|
||||
struct hlist_nulls_node *n;
|
||||
unsigned long flags;
|
||||
unsigned long flags = 0;
|
||||
bool locked = false;
|
||||
struct htab_elem *l;
|
||||
struct bucket *b;
|
||||
int ret = 0;
|
||||
@@ -1311,41 +1403,55 @@ alloc:
|
||||
}
|
||||
|
||||
again:
|
||||
preempt_disable();
|
||||
this_cpu_inc(bpf_prog_active);
|
||||
bpf_disable_instrumentation();
|
||||
rcu_read_lock();
|
||||
again_nocopy:
|
||||
dst_key = keys;
|
||||
dst_val = values;
|
||||
b = &htab->buckets[batch];
|
||||
head = &b->head;
|
||||
raw_spin_lock_irqsave(&b->lock, flags);
|
||||
/* do not grab the lock unless need it (bucket_cnt > 0). */
|
||||
if (locked)
|
||||
flags = htab_lock_bucket(htab, b);
|
||||
|
||||
bucket_cnt = 0;
|
||||
hlist_nulls_for_each_entry_rcu(l, n, head, hash_node)
|
||||
bucket_cnt++;
|
||||
|
||||
if (bucket_cnt && !locked) {
|
||||
locked = true;
|
||||
goto again_nocopy;
|
||||
}
|
||||
|
||||
if (bucket_cnt > (max_count - total)) {
|
||||
if (total == 0)
|
||||
ret = -ENOSPC;
|
||||
raw_spin_unlock_irqrestore(&b->lock, flags);
|
||||
/* Note that since bucket_cnt > 0 here, it is implicit
|
||||
* that the locked was grabbed, so release it.
|
||||
*/
|
||||
htab_unlock_bucket(htab, b, flags);
|
||||
rcu_read_unlock();
|
||||
this_cpu_dec(bpf_prog_active);
|
||||
preempt_enable();
|
||||
bpf_enable_instrumentation();
|
||||
goto after_loop;
|
||||
}
|
||||
|
||||
if (bucket_cnt > bucket_size) {
|
||||
bucket_size = bucket_cnt;
|
||||
raw_spin_unlock_irqrestore(&b->lock, flags);
|
||||
/* Note that since bucket_cnt > 0 here, it is implicit
|
||||
* that the locked was grabbed, so release it.
|
||||
*/
|
||||
htab_unlock_bucket(htab, b, flags);
|
||||
rcu_read_unlock();
|
||||
this_cpu_dec(bpf_prog_active);
|
||||
preempt_enable();
|
||||
bpf_enable_instrumentation();
|
||||
kvfree(keys);
|
||||
kvfree(values);
|
||||
goto alloc;
|
||||
}
|
||||
|
||||
/* Next block is only safe to run if you have grabbed the lock */
|
||||
if (!locked)
|
||||
goto next_batch;
|
||||
|
||||
hlist_nulls_for_each_entry_safe(l, n, head, hash_node) {
|
||||
memcpy(dst_key, l->key, key_size);
|
||||
|
||||
@@ -1370,16 +1476,33 @@ again_nocopy:
|
||||
}
|
||||
if (do_delete) {
|
||||
hlist_nulls_del_rcu(&l->hash_node);
|
||||
if (is_lru_map)
|
||||
bpf_lru_push_free(&htab->lru, &l->lru_node);
|
||||
else
|
||||
|
||||
/* bpf_lru_push_free() will acquire lru_lock, which
|
||||
* may cause deadlock. See comments in function
|
||||
* prealloc_lru_pop(). Let us do bpf_lru_push_free()
|
||||
* after releasing the bucket lock.
|
||||
*/
|
||||
if (is_lru_map) {
|
||||
l->batch_flink = node_to_free;
|
||||
node_to_free = l;
|
||||
} else {
|
||||
free_htab_elem(htab, l);
|
||||
}
|
||||
}
|
||||
dst_key += key_size;
|
||||
dst_val += value_size;
|
||||
}
|
||||
|
||||
raw_spin_unlock_irqrestore(&b->lock, flags);
|
||||
htab_unlock_bucket(htab, b, flags);
|
||||
locked = false;
|
||||
|
||||
while (node_to_free) {
|
||||
l = node_to_free;
|
||||
node_to_free = node_to_free->batch_flink;
|
||||
bpf_lru_push_free(&htab->lru, &l->lru_node);
|
||||
}
|
||||
|
||||
next_batch:
|
||||
/* If we are not copying data, we can go to next bucket and avoid
|
||||
* unlocking the rcu.
|
||||
*/
|
||||
@@ -1389,8 +1512,7 @@ again_nocopy:
|
||||
}
|
||||
|
||||
rcu_read_unlock();
|
||||
this_cpu_dec(bpf_prog_active);
|
||||
preempt_enable();
|
||||
bpf_enable_instrumentation();
|
||||
if (bucket_cnt && (copy_to_user(ukeys + total * key_size, keys,
|
||||
key_size * bucket_cnt) ||
|
||||
copy_to_user(uvalues + total * value_size, values,
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
#include <linux/filter.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/pid_namespace.h>
|
||||
#include <linux/proc_ns.h>
|
||||
|
||||
#include "../../lib/kstrtox.h"
|
||||
|
||||
@@ -338,6 +340,24 @@ const struct bpf_func_proto bpf_get_current_cgroup_id_proto = {
|
||||
.ret_type = RET_INTEGER,
|
||||
};
|
||||
|
||||
BPF_CALL_1(bpf_get_current_ancestor_cgroup_id, int, ancestor_level)
|
||||
{
|
||||
struct cgroup *cgrp = task_dfl_cgroup(current);
|
||||
struct cgroup *ancestor;
|
||||
|
||||
ancestor = cgroup_ancestor(cgrp, ancestor_level);
|
||||
if (!ancestor)
|
||||
return 0;
|
||||
return cgroup_id(ancestor);
|
||||
}
|
||||
|
||||
const struct bpf_func_proto bpf_get_current_ancestor_cgroup_id_proto = {
|
||||
.func = bpf_get_current_ancestor_cgroup_id,
|
||||
.gpl_only = false,
|
||||
.ret_type = RET_INTEGER,
|
||||
.arg1_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_CGROUP_BPF
|
||||
DECLARE_PER_CPU(struct bpf_cgroup_storage*,
|
||||
bpf_cgroup_storage[MAX_BPF_CGROUP_STORAGE_TYPE]);
|
||||
@@ -499,3 +519,46 @@ const struct bpf_func_proto bpf_strtoul_proto = {
|
||||
.arg4_type = ARG_PTR_TO_LONG,
|
||||
};
|
||||
#endif
|
||||
|
||||
BPF_CALL_4(bpf_get_ns_current_pid_tgid, u64, dev, u64, ino,
|
||||
struct bpf_pidns_info *, nsdata, u32, size)
|
||||
{
|
||||
struct task_struct *task = current;
|
||||
struct pid_namespace *pidns;
|
||||
int err = -EINVAL;
|
||||
|
||||
if (unlikely(size != sizeof(struct bpf_pidns_info)))
|
||||
goto clear;
|
||||
|
||||
if (unlikely((u64)(dev_t)dev != dev))
|
||||
goto clear;
|
||||
|
||||
if (unlikely(!task))
|
||||
goto clear;
|
||||
|
||||
pidns = task_active_pid_ns(task);
|
||||
if (unlikely(!pidns)) {
|
||||
err = -ENOENT;
|
||||
goto clear;
|
||||
}
|
||||
|
||||
if (!ns_match(&pidns->ns, (dev_t)dev, ino))
|
||||
goto clear;
|
||||
|
||||
nsdata->pid = task_pid_nr_ns(task, pidns);
|
||||
nsdata->tgid = task_tgid_nr_ns(task, pidns);
|
||||
return 0;
|
||||
clear:
|
||||
memset((void *)nsdata, 0, (size_t) size);
|
||||
return err;
|
||||
}
|
||||
|
||||
const struct bpf_func_proto bpf_get_ns_current_pid_tgid_proto = {
|
||||
.func = bpf_get_ns_current_pid_tgid,
|
||||
.gpl_only = false,
|
||||
.ret_type = RET_INTEGER,
|
||||
.arg1_type = ARG_ANYTHING,
|
||||
.arg2_type = ARG_ANYTHING,
|
||||
.arg3_type = ARG_PTR_TO_UNINIT_MEM,
|
||||
.arg4_type = ARG_CONST_SIZE,
|
||||
};
|
||||
|
||||
+42
-13
@@ -25,6 +25,7 @@ enum bpf_type {
|
||||
BPF_TYPE_UNSPEC = 0,
|
||||
BPF_TYPE_PROG,
|
||||
BPF_TYPE_MAP,
|
||||
BPF_TYPE_LINK,
|
||||
};
|
||||
|
||||
static void *bpf_any_get(void *raw, enum bpf_type type)
|
||||
@@ -36,6 +37,9 @@ static void *bpf_any_get(void *raw, enum bpf_type type)
|
||||
case BPF_TYPE_MAP:
|
||||
bpf_map_inc_with_uref(raw);
|
||||
break;
|
||||
case BPF_TYPE_LINK:
|
||||
bpf_link_inc(raw);
|
||||
break;
|
||||
default:
|
||||
WARN_ON_ONCE(1);
|
||||
break;
|
||||
@@ -53,6 +57,9 @@ static void bpf_any_put(void *raw, enum bpf_type type)
|
||||
case BPF_TYPE_MAP:
|
||||
bpf_map_put_with_uref(raw);
|
||||
break;
|
||||
case BPF_TYPE_LINK:
|
||||
bpf_link_put(raw);
|
||||
break;
|
||||
default:
|
||||
WARN_ON_ONCE(1);
|
||||
break;
|
||||
@@ -63,20 +70,32 @@ static void *bpf_fd_probe_obj(u32 ufd, enum bpf_type *type)
|
||||
{
|
||||
void *raw;
|
||||
|
||||
*type = BPF_TYPE_MAP;
|
||||
raw = bpf_map_get_with_uref(ufd);
|
||||
if (IS_ERR(raw)) {
|
||||
*type = BPF_TYPE_PROG;
|
||||
raw = bpf_prog_get(ufd);
|
||||
if (!IS_ERR(raw)) {
|
||||
*type = BPF_TYPE_MAP;
|
||||
return raw;
|
||||
}
|
||||
|
||||
return raw;
|
||||
raw = bpf_prog_get(ufd);
|
||||
if (!IS_ERR(raw)) {
|
||||
*type = BPF_TYPE_PROG;
|
||||
return raw;
|
||||
}
|
||||
|
||||
raw = bpf_link_get_from_fd(ufd);
|
||||
if (!IS_ERR(raw)) {
|
||||
*type = BPF_TYPE_LINK;
|
||||
return raw;
|
||||
}
|
||||
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
static const struct inode_operations bpf_dir_iops;
|
||||
|
||||
static const struct inode_operations bpf_prog_iops = { };
|
||||
static const struct inode_operations bpf_map_iops = { };
|
||||
static const struct inode_operations bpf_link_iops = { };
|
||||
|
||||
static struct inode *bpf_get_inode(struct super_block *sb,
|
||||
const struct inode *dir,
|
||||
@@ -114,6 +133,8 @@ static int bpf_inode_type(const struct inode *inode, enum bpf_type *type)
|
||||
*type = BPF_TYPE_PROG;
|
||||
else if (inode->i_op == &bpf_map_iops)
|
||||
*type = BPF_TYPE_MAP;
|
||||
else if (inode->i_op == &bpf_link_iops)
|
||||
*type = BPF_TYPE_LINK;
|
||||
else
|
||||
return -EACCES;
|
||||
|
||||
@@ -335,6 +356,12 @@ static int bpf_mkmap(struct dentry *dentry, umode_t mode, void *arg)
|
||||
&bpffs_map_fops : &bpffs_obj_fops);
|
||||
}
|
||||
|
||||
static int bpf_mklink(struct dentry *dentry, umode_t mode, void *arg)
|
||||
{
|
||||
return bpf_mkobj_ops(dentry, mode, arg, &bpf_link_iops,
|
||||
&bpffs_obj_fops);
|
||||
}
|
||||
|
||||
static struct dentry *
|
||||
bpf_lookup(struct inode *dir, struct dentry *dentry, unsigned flags)
|
||||
{
|
||||
@@ -411,6 +438,9 @@ static int bpf_obj_do_pin(const char __user *pathname, void *raw,
|
||||
case BPF_TYPE_MAP:
|
||||
ret = vfs_mkobj(dentry, mode, bpf_mkmap, raw);
|
||||
break;
|
||||
case BPF_TYPE_LINK:
|
||||
ret = vfs_mkobj(dentry, mode, bpf_mklink, raw);
|
||||
break;
|
||||
default:
|
||||
ret = -EPERM;
|
||||
}
|
||||
@@ -487,6 +517,8 @@ int bpf_obj_get_user(const char __user *pathname, int flags)
|
||||
ret = bpf_prog_new_fd(raw);
|
||||
else if (type == BPF_TYPE_MAP)
|
||||
ret = bpf_map_new_fd(raw, f_flags);
|
||||
else if (type == BPF_TYPE_LINK)
|
||||
ret = bpf_link_new_fd(raw);
|
||||
else
|
||||
return -ENOENT;
|
||||
|
||||
@@ -504,6 +536,8 @@ static struct bpf_prog *__get_prog_inode(struct inode *inode, enum bpf_prog_type
|
||||
|
||||
if (inode->i_op == &bpf_map_iops)
|
||||
return ERR_PTR(-EINVAL);
|
||||
if (inode->i_op == &bpf_link_iops)
|
||||
return ERR_PTR(-EINVAL);
|
||||
if (inode->i_op != &bpf_prog_iops)
|
||||
return ERR_PTR(-EACCES);
|
||||
|
||||
@@ -569,16 +603,11 @@ enum {
|
||||
OPT_MODE,
|
||||
};
|
||||
|
||||
static const struct fs_parameter_spec bpf_param_specs[] = {
|
||||
static const struct fs_parameter_spec bpf_fs_parameters[] = {
|
||||
fsparam_u32oct ("mode", OPT_MODE),
|
||||
{}
|
||||
};
|
||||
|
||||
static const struct fs_parameter_description bpf_fs_parameters = {
|
||||
.name = "bpf",
|
||||
.specs = bpf_param_specs,
|
||||
};
|
||||
|
||||
struct bpf_mount_opts {
|
||||
umode_t mode;
|
||||
};
|
||||
@@ -589,7 +618,7 @@ static int bpf_parse_param(struct fs_context *fc, struct fs_parameter *param)
|
||||
struct fs_parse_result result;
|
||||
int opt;
|
||||
|
||||
opt = fs_parse(fc, &bpf_fs_parameters, param, &result);
|
||||
opt = fs_parse(fc, bpf_fs_parameters, param, &result);
|
||||
if (opt < 0)
|
||||
/* We might like to report bad mount options here, but
|
||||
* traditionally we've ignored all mount options, so we'd
|
||||
@@ -665,7 +694,7 @@ static struct file_system_type bpf_fs_type = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "bpf",
|
||||
.init_fs_context = bpf_init_fs_context,
|
||||
.parameters = &bpf_fs_parameters,
|
||||
.parameters = bpf_fs_parameters,
|
||||
.kill_sb = kill_litter_super,
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ struct lpm_trie_node {
|
||||
struct lpm_trie_node __rcu *child[2];
|
||||
u32 prefixlen;
|
||||
u32 flags;
|
||||
u8 data[0];
|
||||
u8 data[];
|
||||
};
|
||||
|
||||
struct lpm_trie {
|
||||
@@ -34,7 +34,7 @@ struct lpm_trie {
|
||||
size_t n_entries;
|
||||
size_t max_prefixlen;
|
||||
size_t data_size;
|
||||
raw_spinlock_t lock;
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
/* This trie implements a longest prefix match algorithm that can be used to
|
||||
@@ -315,7 +315,7 @@ static int trie_update_elem(struct bpf_map *map,
|
||||
if (key->prefixlen > trie->max_prefixlen)
|
||||
return -EINVAL;
|
||||
|
||||
raw_spin_lock_irqsave(&trie->lock, irq_flags);
|
||||
spin_lock_irqsave(&trie->lock, irq_flags);
|
||||
|
||||
/* Allocate and fill a new node */
|
||||
|
||||
@@ -422,7 +422,7 @@ out:
|
||||
kfree(im_node);
|
||||
}
|
||||
|
||||
raw_spin_unlock_irqrestore(&trie->lock, irq_flags);
|
||||
spin_unlock_irqrestore(&trie->lock, irq_flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -442,7 +442,7 @@ static int trie_delete_elem(struct bpf_map *map, void *_key)
|
||||
if (key->prefixlen > trie->max_prefixlen)
|
||||
return -EINVAL;
|
||||
|
||||
raw_spin_lock_irqsave(&trie->lock, irq_flags);
|
||||
spin_lock_irqsave(&trie->lock, irq_flags);
|
||||
|
||||
/* Walk the tree looking for an exact key/length match and keeping
|
||||
* track of the path we traverse. We will need to know the node
|
||||
@@ -518,7 +518,7 @@ static int trie_delete_elem(struct bpf_map *map, void *_key)
|
||||
kfree_rcu(node, rcu);
|
||||
|
||||
out:
|
||||
raw_spin_unlock_irqrestore(&trie->lock, irq_flags);
|
||||
spin_unlock_irqrestore(&trie->lock, irq_flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
@@ -575,7 +575,7 @@ static struct bpf_map *trie_alloc(union bpf_attr *attr)
|
||||
if (ret)
|
||||
goto out_err;
|
||||
|
||||
raw_spin_lock_init(&trie->lock);
|
||||
spin_lock_init(&trie->lock);
|
||||
|
||||
return &trie->map;
|
||||
out_err:
|
||||
|
||||
@@ -321,7 +321,7 @@ int bpf_prog_offload_info_fill(struct bpf_prog_info *info,
|
||||
|
||||
ulen = info->jited_prog_len;
|
||||
info->jited_prog_len = aux->offload->jited_len;
|
||||
if (info->jited_prog_len & ulen) {
|
||||
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, aux->offload->jited_image, ulen)) {
|
||||
|
||||
@@ -25,12 +25,18 @@ void pcpu_freelist_destroy(struct pcpu_freelist *s)
|
||||
free_percpu(s->freelist);
|
||||
}
|
||||
|
||||
static inline void pcpu_freelist_push_node(struct pcpu_freelist_head *head,
|
||||
struct pcpu_freelist_node *node)
|
||||
{
|
||||
node->next = head->first;
|
||||
head->first = node;
|
||||
}
|
||||
|
||||
static inline void ___pcpu_freelist_push(struct pcpu_freelist_head *head,
|
||||
struct pcpu_freelist_node *node)
|
||||
{
|
||||
raw_spin_lock(&head->lock);
|
||||
node->next = head->first;
|
||||
head->first = node;
|
||||
pcpu_freelist_push_node(head, node);
|
||||
raw_spin_unlock(&head->lock);
|
||||
}
|
||||
|
||||
@@ -56,21 +62,16 @@ void pcpu_freelist_populate(struct pcpu_freelist *s, void *buf, u32 elem_size,
|
||||
u32 nr_elems)
|
||||
{
|
||||
struct pcpu_freelist_head *head;
|
||||
unsigned long flags;
|
||||
int i, cpu, pcpu_entries;
|
||||
|
||||
pcpu_entries = nr_elems / num_possible_cpus() + 1;
|
||||
i = 0;
|
||||
|
||||
/* disable irq to workaround lockdep false positive
|
||||
* in bpf usage pcpu_freelist_populate() will never race
|
||||
* with pcpu_freelist_push()
|
||||
*/
|
||||
local_irq_save(flags);
|
||||
for_each_possible_cpu(cpu) {
|
||||
again:
|
||||
head = per_cpu_ptr(s->freelist, cpu);
|
||||
___pcpu_freelist_push(head, buf);
|
||||
/* No locking required as this is not visible yet. */
|
||||
pcpu_freelist_push_node(head, buf);
|
||||
i++;
|
||||
buf += elem_size;
|
||||
if (i == nr_elems)
|
||||
@@ -78,7 +79,6 @@ again:
|
||||
if (i % pcpu_entries)
|
||||
goto again;
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
struct pcpu_freelist_node *__pcpu_freelist_pop(struct pcpu_freelist *s)
|
||||
|
||||
@@ -305,11 +305,6 @@ int bpf_fd_reuseport_array_update_elem(struct bpf_map *map, void *key,
|
||||
if (err)
|
||||
goto put_file_unlock;
|
||||
|
||||
/* Ensure reuse->reuseport_id is set */
|
||||
err = reuseport_get_id(reuse);
|
||||
if (err < 0)
|
||||
goto put_file_unlock;
|
||||
|
||||
WRITE_ONCE(nsk->sk_user_data, &array->ptrs[index]);
|
||||
rcu_assign_pointer(array->ptrs[index], nsk);
|
||||
free_osk = osk;
|
||||
|
||||
+15
-3
@@ -40,6 +40,9 @@ static void do_up_read(struct irq_work *entry)
|
||||
{
|
||||
struct stack_map_irq_work *work;
|
||||
|
||||
if (WARN_ON_ONCE(IS_ENABLED(CONFIG_PREEMPT_RT)))
|
||||
return;
|
||||
|
||||
work = container_of(entry, struct stack_map_irq_work, irq_work);
|
||||
up_read_non_owner(work->sem);
|
||||
work->sem = NULL;
|
||||
@@ -288,10 +291,19 @@ static void stack_map_get_build_id_offset(struct bpf_stack_build_id *id_offs,
|
||||
struct stack_map_irq_work *work = NULL;
|
||||
|
||||
if (irqs_disabled()) {
|
||||
work = this_cpu_ptr(&up_read_work);
|
||||
if (atomic_read(&work->irq_work.flags) & IRQ_WORK_BUSY)
|
||||
/* cannot queue more up_read, fallback */
|
||||
if (!IS_ENABLED(CONFIG_PREEMPT_RT)) {
|
||||
work = this_cpu_ptr(&up_read_work);
|
||||
if (atomic_read(&work->irq_work.flags) & IRQ_WORK_BUSY) {
|
||||
/* cannot queue more up_read, fallback */
|
||||
irq_work_busy = true;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* PREEMPT_RT does not allow to trylock mmap sem in
|
||||
* interrupt disabled context. Force the fallback code.
|
||||
*/
|
||||
irq_work_busy = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+514
-201
@@ -25,6 +25,7 @@
|
||||
#include <linux/nospec.h>
|
||||
#include <linux/audit.h>
|
||||
#include <uapi/linux/btf.h>
|
||||
#include <linux/bpf_lsm.h>
|
||||
|
||||
#define IS_FD_ARRAY(map) ((map)->map_type == BPF_MAP_TYPE_PERF_EVENT_ARRAY || \
|
||||
(map)->map_type == BPF_MAP_TYPE_CGROUP_ARRAY || \
|
||||
@@ -171,11 +172,7 @@ static int bpf_map_update_value(struct bpf_map *map, struct fd f, void *key,
|
||||
flags);
|
||||
}
|
||||
|
||||
/* must increment bpf_prog_active to avoid kprobe+bpf triggering from
|
||||
* inside bpf map update or delete otherwise deadlocks are possible
|
||||
*/
|
||||
preempt_disable();
|
||||
__this_cpu_inc(bpf_prog_active);
|
||||
bpf_disable_instrumentation();
|
||||
if (map->map_type == BPF_MAP_TYPE_PERCPU_HASH ||
|
||||
map->map_type == BPF_MAP_TYPE_LRU_PERCPU_HASH) {
|
||||
err = bpf_percpu_hash_update(map, key, value, flags);
|
||||
@@ -206,8 +203,7 @@ static int bpf_map_update_value(struct bpf_map *map, struct fd f, void *key,
|
||||
err = map->ops->map_update_elem(map, key, value, flags);
|
||||
rcu_read_unlock();
|
||||
}
|
||||
__this_cpu_dec(bpf_prog_active);
|
||||
preempt_enable();
|
||||
bpf_enable_instrumentation();
|
||||
maybe_wait_bpf_programs(map);
|
||||
|
||||
return err;
|
||||
@@ -222,8 +218,7 @@ static int bpf_map_copy_value(struct bpf_map *map, void *key, void *value,
|
||||
if (bpf_map_is_dev_bound(map))
|
||||
return bpf_map_offload_lookup_elem(map, key, value);
|
||||
|
||||
preempt_disable();
|
||||
this_cpu_inc(bpf_prog_active);
|
||||
bpf_disable_instrumentation();
|
||||
if (map->map_type == BPF_MAP_TYPE_PERCPU_HASH ||
|
||||
map->map_type == BPF_MAP_TYPE_LRU_PERCPU_HASH) {
|
||||
err = bpf_percpu_hash_copy(map, key, value);
|
||||
@@ -268,8 +263,7 @@ static int bpf_map_copy_value(struct bpf_map *map, void *key, void *value,
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
this_cpu_dec(bpf_prog_active);
|
||||
preempt_enable();
|
||||
bpf_enable_instrumentation();
|
||||
maybe_wait_bpf_programs(map);
|
||||
|
||||
return err;
|
||||
@@ -696,14 +690,15 @@ int bpf_get_file_flag(int flags)
|
||||
offsetof(union bpf_attr, CMD##_LAST_FIELD) - \
|
||||
sizeof(attr->CMD##_LAST_FIELD)) != NULL
|
||||
|
||||
/* dst and src must have at least BPF_OBJ_NAME_LEN number of bytes.
|
||||
* Return 0 on success and < 0 on error.
|
||||
/* dst and src must have at least "size" number of bytes.
|
||||
* Return strlen on success and < 0 on error.
|
||||
*/
|
||||
static int bpf_obj_name_cpy(char *dst, const char *src)
|
||||
int bpf_obj_name_cpy(char *dst, const char *src, unsigned int size)
|
||||
{
|
||||
const char *end = src + BPF_OBJ_NAME_LEN;
|
||||
const char *end = src + size;
|
||||
const char *orig_src = src;
|
||||
|
||||
memset(dst, 0, BPF_OBJ_NAME_LEN);
|
||||
memset(dst, 0, size);
|
||||
/* Copy all isalnum(), '_' and '.' chars. */
|
||||
while (src < end && *src) {
|
||||
if (!isalnum(*src) &&
|
||||
@@ -712,11 +707,11 @@ static int bpf_obj_name_cpy(char *dst, const char *src)
|
||||
*dst++ = *src++;
|
||||
}
|
||||
|
||||
/* No '\0' found in BPF_OBJ_NAME_LEN number of bytes */
|
||||
/* No '\0' found in "size" number of bytes */
|
||||
if (src == end)
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
return src - orig_src;
|
||||
}
|
||||
|
||||
int map_check_no_btf(const struct bpf_map *map,
|
||||
@@ -810,8 +805,9 @@ static int map_create(union bpf_attr *attr)
|
||||
if (IS_ERR(map))
|
||||
return PTR_ERR(map);
|
||||
|
||||
err = bpf_obj_name_cpy(map->name, attr->map_name);
|
||||
if (err)
|
||||
err = bpf_obj_name_cpy(map->name, attr->map_name,
|
||||
sizeof(attr->map_name));
|
||||
if (err < 0)
|
||||
goto free_map;
|
||||
|
||||
atomic64_set(&map->refcnt, 1);
|
||||
@@ -909,6 +905,21 @@ void bpf_map_inc_with_uref(struct bpf_map *map)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(bpf_map_inc_with_uref);
|
||||
|
||||
struct bpf_map *bpf_map_get(u32 ufd)
|
||||
{
|
||||
struct fd f = fdget(ufd);
|
||||
struct bpf_map *map;
|
||||
|
||||
map = __bpf_map_get(f);
|
||||
if (IS_ERR(map))
|
||||
return map;
|
||||
|
||||
bpf_map_inc(map);
|
||||
fdput(f);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
struct bpf_map *bpf_map_get_with_uref(u32 ufd)
|
||||
{
|
||||
struct fd f = fdget(ufd);
|
||||
@@ -1136,13 +1147,11 @@ static int map_delete_elem(union bpf_attr *attr)
|
||||
goto out;
|
||||
}
|
||||
|
||||
preempt_disable();
|
||||
__this_cpu_inc(bpf_prog_active);
|
||||
bpf_disable_instrumentation();
|
||||
rcu_read_lock();
|
||||
err = map->ops->map_delete_elem(map, key);
|
||||
rcu_read_unlock();
|
||||
__this_cpu_dec(bpf_prog_active);
|
||||
preempt_enable();
|
||||
bpf_enable_instrumentation();
|
||||
maybe_wait_bpf_programs(map);
|
||||
out:
|
||||
kfree(key);
|
||||
@@ -1254,13 +1263,11 @@ int generic_map_delete_batch(struct bpf_map *map,
|
||||
break;
|
||||
}
|
||||
|
||||
preempt_disable();
|
||||
__this_cpu_inc(bpf_prog_active);
|
||||
bpf_disable_instrumentation();
|
||||
rcu_read_lock();
|
||||
err = map->ops->map_delete_elem(map, key);
|
||||
rcu_read_unlock();
|
||||
__this_cpu_dec(bpf_prog_active);
|
||||
preempt_enable();
|
||||
bpf_enable_instrumentation();
|
||||
maybe_wait_bpf_programs(map);
|
||||
if (err)
|
||||
break;
|
||||
@@ -1510,6 +1517,11 @@ static int map_freeze(const union bpf_attr *attr)
|
||||
if (IS_ERR(map))
|
||||
return PTR_ERR(map);
|
||||
|
||||
if (map->map_type == BPF_MAP_TYPE_STRUCT_OPS) {
|
||||
fdput(f);
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
mutex_lock(&map->freeze_mutex);
|
||||
|
||||
if (map->writecnt) {
|
||||
@@ -1931,6 +1943,7 @@ bpf_prog_load_check_attach(enum bpf_prog_type prog_type,
|
||||
|
||||
switch (prog_type) {
|
||||
case BPF_PROG_TYPE_TRACING:
|
||||
case BPF_PROG_TYPE_LSM:
|
||||
case BPF_PROG_TYPE_STRUCT_OPS:
|
||||
case BPF_PROG_TYPE_EXT:
|
||||
break;
|
||||
@@ -2093,8 +2106,9 @@ static int bpf_prog_load(union bpf_attr *attr, union bpf_attr __user *uattr)
|
||||
goto free_prog;
|
||||
|
||||
prog->aux->load_time = ktime_get_boottime_ns();
|
||||
err = bpf_obj_name_cpy(prog->aux->name, attr->prog_name);
|
||||
if (err)
|
||||
err = bpf_obj_name_cpy(prog->aux->name, attr->prog_name,
|
||||
sizeof(attr->prog_name));
|
||||
if (err < 0)
|
||||
goto free_prog;
|
||||
|
||||
/* run eBPF verifier */
|
||||
@@ -2169,84 +2183,288 @@ static int bpf_obj_get(const union bpf_attr *attr)
|
||||
attr->file_flags);
|
||||
}
|
||||
|
||||
static int bpf_tracing_prog_release(struct inode *inode, struct file *filp)
|
||||
void bpf_link_init(struct bpf_link *link, const struct bpf_link_ops *ops,
|
||||
struct bpf_prog *prog)
|
||||
{
|
||||
struct bpf_prog *prog = filp->private_data;
|
||||
atomic64_set(&link->refcnt, 1);
|
||||
link->ops = ops;
|
||||
link->prog = prog;
|
||||
}
|
||||
|
||||
WARN_ON_ONCE(bpf_trampoline_unlink_prog(prog));
|
||||
bpf_prog_put(prog);
|
||||
/* Clean up bpf_link and corresponding anon_inode file and FD. After
|
||||
* anon_inode is created, bpf_link can't be just kfree()'d due to deferred
|
||||
* anon_inode's release() call. This helper manages marking bpf_link as
|
||||
* defunct, releases anon_inode file and puts reserved FD.
|
||||
*/
|
||||
void bpf_link_cleanup(struct bpf_link *link, struct file *link_file,
|
||||
int link_fd)
|
||||
{
|
||||
link->prog = NULL;
|
||||
fput(link_file);
|
||||
put_unused_fd(link_fd);
|
||||
}
|
||||
|
||||
void bpf_link_inc(struct bpf_link *link)
|
||||
{
|
||||
atomic64_inc(&link->refcnt);
|
||||
}
|
||||
|
||||
/* bpf_link_free is guaranteed to be called from process context */
|
||||
static void bpf_link_free(struct bpf_link *link)
|
||||
{
|
||||
if (link->prog) {
|
||||
/* detach BPF program, clean up used resources */
|
||||
link->ops->release(link);
|
||||
bpf_prog_put(link->prog);
|
||||
}
|
||||
/* free bpf_link and its containing memory */
|
||||
link->ops->dealloc(link);
|
||||
}
|
||||
|
||||
static void bpf_link_put_deferred(struct work_struct *work)
|
||||
{
|
||||
struct bpf_link *link = container_of(work, struct bpf_link, work);
|
||||
|
||||
bpf_link_free(link);
|
||||
}
|
||||
|
||||
/* bpf_link_put can be called from atomic context, but ensures that resources
|
||||
* are freed from process context
|
||||
*/
|
||||
void bpf_link_put(struct bpf_link *link)
|
||||
{
|
||||
if (!atomic64_dec_and_test(&link->refcnt))
|
||||
return;
|
||||
|
||||
if (in_atomic()) {
|
||||
INIT_WORK(&link->work, bpf_link_put_deferred);
|
||||
schedule_work(&link->work);
|
||||
} else {
|
||||
bpf_link_free(link);
|
||||
}
|
||||
}
|
||||
|
||||
static int bpf_link_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct bpf_link *link = filp->private_data;
|
||||
|
||||
bpf_link_put(link);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations bpf_tracing_prog_fops = {
|
||||
.release = bpf_tracing_prog_release,
|
||||
#ifdef CONFIG_PROC_FS
|
||||
static const struct bpf_link_ops bpf_raw_tp_lops;
|
||||
static const struct bpf_link_ops bpf_tracing_link_lops;
|
||||
|
||||
static void bpf_link_show_fdinfo(struct seq_file *m, struct file *filp)
|
||||
{
|
||||
const struct bpf_link *link = filp->private_data;
|
||||
const struct bpf_prog *prog = link->prog;
|
||||
char prog_tag[sizeof(prog->tag) * 2 + 1] = { };
|
||||
const char *link_type;
|
||||
|
||||
if (link->ops == &bpf_raw_tp_lops)
|
||||
link_type = "raw_tracepoint";
|
||||
else if (link->ops == &bpf_tracing_link_lops)
|
||||
link_type = "tracing";
|
||||
#ifdef CONFIG_CGROUP_BPF
|
||||
else if (link->ops == &bpf_cgroup_link_lops)
|
||||
link_type = "cgroup";
|
||||
#endif
|
||||
else
|
||||
link_type = "unknown";
|
||||
|
||||
bin2hex(prog_tag, prog->tag, sizeof(prog->tag));
|
||||
seq_printf(m,
|
||||
"link_type:\t%s\n"
|
||||
"prog_tag:\t%s\n"
|
||||
"prog_id:\t%u\n",
|
||||
link_type,
|
||||
prog_tag,
|
||||
prog->aux->id);
|
||||
}
|
||||
#endif
|
||||
|
||||
const struct file_operations bpf_link_fops = {
|
||||
#ifdef CONFIG_PROC_FS
|
||||
.show_fdinfo = bpf_link_show_fdinfo,
|
||||
#endif
|
||||
.release = bpf_link_release,
|
||||
.read = bpf_dummy_read,
|
||||
.write = bpf_dummy_write,
|
||||
};
|
||||
|
||||
int bpf_link_new_fd(struct bpf_link *link)
|
||||
{
|
||||
return anon_inode_getfd("bpf-link", &bpf_link_fops, link, O_CLOEXEC);
|
||||
}
|
||||
|
||||
/* Similar to bpf_link_new_fd, create anon_inode for given bpf_link, but
|
||||
* instead of immediately installing fd in fdtable, just reserve it and
|
||||
* return. Caller then need to either install it with fd_install(fd, file) or
|
||||
* release with put_unused_fd(fd).
|
||||
* This is useful for cases when bpf_link attachment/detachment are
|
||||
* complicated and expensive operations and should be delayed until all the fd
|
||||
* reservation and anon_inode creation succeeds.
|
||||
*/
|
||||
struct file *bpf_link_new_file(struct bpf_link *link, int *reserved_fd)
|
||||
{
|
||||
struct file *file;
|
||||
int fd;
|
||||
|
||||
fd = get_unused_fd_flags(O_CLOEXEC);
|
||||
if (fd < 0)
|
||||
return ERR_PTR(fd);
|
||||
|
||||
file = anon_inode_getfile("bpf_link", &bpf_link_fops, link, O_CLOEXEC);
|
||||
if (IS_ERR(file)) {
|
||||
put_unused_fd(fd);
|
||||
return file;
|
||||
}
|
||||
|
||||
*reserved_fd = fd;
|
||||
return file;
|
||||
}
|
||||
|
||||
struct bpf_link *bpf_link_get_from_fd(u32 ufd)
|
||||
{
|
||||
struct fd f = fdget(ufd);
|
||||
struct bpf_link *link;
|
||||
|
||||
if (!f.file)
|
||||
return ERR_PTR(-EBADF);
|
||||
if (f.file->f_op != &bpf_link_fops) {
|
||||
fdput(f);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
link = f.file->private_data;
|
||||
bpf_link_inc(link);
|
||||
fdput(f);
|
||||
|
||||
return link;
|
||||
}
|
||||
|
||||
struct bpf_tracing_link {
|
||||
struct bpf_link link;
|
||||
};
|
||||
|
||||
static void bpf_tracing_link_release(struct bpf_link *link)
|
||||
{
|
||||
WARN_ON_ONCE(bpf_trampoline_unlink_prog(link->prog));
|
||||
}
|
||||
|
||||
static void bpf_tracing_link_dealloc(struct bpf_link *link)
|
||||
{
|
||||
struct bpf_tracing_link *tr_link =
|
||||
container_of(link, struct bpf_tracing_link, link);
|
||||
|
||||
kfree(tr_link);
|
||||
}
|
||||
|
||||
static const struct bpf_link_ops bpf_tracing_link_lops = {
|
||||
.release = bpf_tracing_link_release,
|
||||
.dealloc = bpf_tracing_link_dealloc,
|
||||
};
|
||||
|
||||
static int bpf_tracing_prog_attach(struct bpf_prog *prog)
|
||||
{
|
||||
int tr_fd, err;
|
||||
struct bpf_tracing_link *link;
|
||||
struct file *link_file;
|
||||
int link_fd, err;
|
||||
|
||||
if (prog->expected_attach_type != BPF_TRACE_FENTRY &&
|
||||
prog->expected_attach_type != BPF_TRACE_FEXIT &&
|
||||
prog->type != BPF_PROG_TYPE_EXT) {
|
||||
switch (prog->type) {
|
||||
case BPF_PROG_TYPE_TRACING:
|
||||
if (prog->expected_attach_type != BPF_TRACE_FENTRY &&
|
||||
prog->expected_attach_type != BPF_TRACE_FEXIT &&
|
||||
prog->expected_attach_type != BPF_MODIFY_RETURN) {
|
||||
err = -EINVAL;
|
||||
goto out_put_prog;
|
||||
}
|
||||
break;
|
||||
case BPF_PROG_TYPE_EXT:
|
||||
if (prog->expected_attach_type != 0) {
|
||||
err = -EINVAL;
|
||||
goto out_put_prog;
|
||||
}
|
||||
break;
|
||||
case BPF_PROG_TYPE_LSM:
|
||||
if (prog->expected_attach_type != BPF_LSM_MAC) {
|
||||
err = -EINVAL;
|
||||
goto out_put_prog;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
goto out_put_prog;
|
||||
}
|
||||
|
||||
err = bpf_trampoline_link_prog(prog);
|
||||
if (err)
|
||||
goto out_put_prog;
|
||||
|
||||
tr_fd = anon_inode_getfd("bpf-tracing-prog", &bpf_tracing_prog_fops,
|
||||
prog, O_CLOEXEC);
|
||||
if (tr_fd < 0) {
|
||||
WARN_ON_ONCE(bpf_trampoline_unlink_prog(prog));
|
||||
err = tr_fd;
|
||||
link = kzalloc(sizeof(*link), GFP_USER);
|
||||
if (!link) {
|
||||
err = -ENOMEM;
|
||||
goto out_put_prog;
|
||||
}
|
||||
return tr_fd;
|
||||
bpf_link_init(&link->link, &bpf_tracing_link_lops, prog);
|
||||
|
||||
link_file = bpf_link_new_file(&link->link, &link_fd);
|
||||
if (IS_ERR(link_file)) {
|
||||
kfree(link);
|
||||
err = PTR_ERR(link_file);
|
||||
goto out_put_prog;
|
||||
}
|
||||
|
||||
err = bpf_trampoline_link_prog(prog);
|
||||
if (err) {
|
||||
bpf_link_cleanup(&link->link, link_file, link_fd);
|
||||
goto out_put_prog;
|
||||
}
|
||||
|
||||
fd_install(link_fd, link_file);
|
||||
return link_fd;
|
||||
|
||||
out_put_prog:
|
||||
bpf_prog_put(prog);
|
||||
return err;
|
||||
}
|
||||
|
||||
struct bpf_raw_tracepoint {
|
||||
struct bpf_raw_tp_link {
|
||||
struct bpf_link link;
|
||||
struct bpf_raw_event_map *btp;
|
||||
struct bpf_prog *prog;
|
||||
};
|
||||
|
||||
static int bpf_raw_tracepoint_release(struct inode *inode, struct file *filp)
|
||||
static void bpf_raw_tp_link_release(struct bpf_link *link)
|
||||
{
|
||||
struct bpf_raw_tracepoint *raw_tp = filp->private_data;
|
||||
struct bpf_raw_tp_link *raw_tp =
|
||||
container_of(link, struct bpf_raw_tp_link, link);
|
||||
|
||||
if (raw_tp->prog) {
|
||||
bpf_probe_unregister(raw_tp->btp, raw_tp->prog);
|
||||
bpf_prog_put(raw_tp->prog);
|
||||
}
|
||||
bpf_probe_unregister(raw_tp->btp, raw_tp->link.prog);
|
||||
bpf_put_raw_tracepoint(raw_tp->btp);
|
||||
kfree(raw_tp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations bpf_raw_tp_fops = {
|
||||
.release = bpf_raw_tracepoint_release,
|
||||
.read = bpf_dummy_read,
|
||||
.write = bpf_dummy_write,
|
||||
static void bpf_raw_tp_link_dealloc(struct bpf_link *link)
|
||||
{
|
||||
struct bpf_raw_tp_link *raw_tp =
|
||||
container_of(link, struct bpf_raw_tp_link, link);
|
||||
|
||||
kfree(raw_tp);
|
||||
}
|
||||
|
||||
static const struct bpf_link_ops bpf_raw_tp_lops = {
|
||||
.release = bpf_raw_tp_link_release,
|
||||
.dealloc = bpf_raw_tp_link_dealloc,
|
||||
};
|
||||
|
||||
#define BPF_RAW_TRACEPOINT_OPEN_LAST_FIELD raw_tracepoint.prog_fd
|
||||
|
||||
static int bpf_raw_tracepoint_open(const union bpf_attr *attr)
|
||||
{
|
||||
struct bpf_raw_tracepoint *raw_tp;
|
||||
struct bpf_raw_tp_link *link;
|
||||
struct bpf_raw_event_map *btp;
|
||||
struct file *link_file;
|
||||
struct bpf_prog *prog;
|
||||
const char *tp_name;
|
||||
char buf[128];
|
||||
int tp_fd, err;
|
||||
int link_fd, err;
|
||||
|
||||
if (CHECK_ATTR(BPF_RAW_TRACEPOINT_OPEN))
|
||||
return -EINVAL;
|
||||
@@ -2255,16 +2473,10 @@ static int bpf_raw_tracepoint_open(const union bpf_attr *attr)
|
||||
if (IS_ERR(prog))
|
||||
return PTR_ERR(prog);
|
||||
|
||||
if (prog->type != BPF_PROG_TYPE_RAW_TRACEPOINT &&
|
||||
prog->type != BPF_PROG_TYPE_TRACING &&
|
||||
prog->type != BPF_PROG_TYPE_EXT &&
|
||||
prog->type != BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE) {
|
||||
err = -EINVAL;
|
||||
goto out_put_prog;
|
||||
}
|
||||
|
||||
if (prog->type == BPF_PROG_TYPE_TRACING ||
|
||||
prog->type == BPF_PROG_TYPE_EXT) {
|
||||
switch (prog->type) {
|
||||
case BPF_PROG_TYPE_TRACING:
|
||||
case BPF_PROG_TYPE_EXT:
|
||||
case BPF_PROG_TYPE_LSM:
|
||||
if (attr->raw_tracepoint.name) {
|
||||
/* The attach point for this category of programs
|
||||
* should be specified via btf_id during program load.
|
||||
@@ -2272,11 +2484,14 @@ static int bpf_raw_tracepoint_open(const union bpf_attr *attr)
|
||||
err = -EINVAL;
|
||||
goto out_put_prog;
|
||||
}
|
||||
if (prog->expected_attach_type == BPF_TRACE_RAW_TP)
|
||||
if (prog->type == BPF_PROG_TYPE_TRACING &&
|
||||
prog->expected_attach_type == BPF_TRACE_RAW_TP) {
|
||||
tp_name = prog->aux->attach_func_name;
|
||||
else
|
||||
return bpf_tracing_prog_attach(prog);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
return bpf_tracing_prog_attach(prog);
|
||||
case BPF_PROG_TYPE_RAW_TRACEPOINT:
|
||||
case BPF_PROG_TYPE_RAW_TRACEPOINT_WRITABLE:
|
||||
if (strncpy_from_user(buf,
|
||||
u64_to_user_ptr(attr->raw_tracepoint.name),
|
||||
sizeof(buf) - 1) < 0) {
|
||||
@@ -2285,6 +2500,10 @@ static int bpf_raw_tracepoint_open(const union bpf_attr *attr)
|
||||
}
|
||||
buf[sizeof(buf) - 1] = 0;
|
||||
tp_name = buf;
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
goto out_put_prog;
|
||||
}
|
||||
|
||||
btp = bpf_get_raw_tracepoint(tp_name);
|
||||
@@ -2293,29 +2512,30 @@ static int bpf_raw_tracepoint_open(const union bpf_attr *attr)
|
||||
goto out_put_prog;
|
||||
}
|
||||
|
||||
raw_tp = kzalloc(sizeof(*raw_tp), GFP_USER);
|
||||
if (!raw_tp) {
|
||||
link = kzalloc(sizeof(*link), GFP_USER);
|
||||
if (!link) {
|
||||
err = -ENOMEM;
|
||||
goto out_put_btp;
|
||||
}
|
||||
raw_tp->btp = btp;
|
||||
raw_tp->prog = prog;
|
||||
bpf_link_init(&link->link, &bpf_raw_tp_lops, prog);
|
||||
link->btp = btp;
|
||||
|
||||
err = bpf_probe_register(raw_tp->btp, prog);
|
||||
if (err)
|
||||
goto out_free_tp;
|
||||
|
||||
tp_fd = anon_inode_getfd("bpf-raw-tracepoint", &bpf_raw_tp_fops, raw_tp,
|
||||
O_CLOEXEC);
|
||||
if (tp_fd < 0) {
|
||||
bpf_probe_unregister(raw_tp->btp, prog);
|
||||
err = tp_fd;
|
||||
goto out_free_tp;
|
||||
link_file = bpf_link_new_file(&link->link, &link_fd);
|
||||
if (IS_ERR(link_file)) {
|
||||
kfree(link);
|
||||
err = PTR_ERR(link_file);
|
||||
goto out_put_btp;
|
||||
}
|
||||
return tp_fd;
|
||||
|
||||
out_free_tp:
|
||||
kfree(raw_tp);
|
||||
err = bpf_probe_register(link->btp, prog);
|
||||
if (err) {
|
||||
bpf_link_cleanup(&link->link, link_file, link_fd);
|
||||
goto out_put_btp;
|
||||
}
|
||||
|
||||
fd_install(link_fd, link_file);
|
||||
return link_fd;
|
||||
|
||||
out_put_btp:
|
||||
bpf_put_raw_tracepoint(btp);
|
||||
out_put_prog:
|
||||
@@ -2340,6 +2560,50 @@ static int bpf_prog_attach_check_attach_type(const struct bpf_prog *prog,
|
||||
}
|
||||
}
|
||||
|
||||
static enum bpf_prog_type
|
||||
attach_type_to_prog_type(enum bpf_attach_type attach_type)
|
||||
{
|
||||
switch (attach_type) {
|
||||
case BPF_CGROUP_INET_INGRESS:
|
||||
case BPF_CGROUP_INET_EGRESS:
|
||||
return BPF_PROG_TYPE_CGROUP_SKB;
|
||||
break;
|
||||
case BPF_CGROUP_INET_SOCK_CREATE:
|
||||
case BPF_CGROUP_INET4_POST_BIND:
|
||||
case BPF_CGROUP_INET6_POST_BIND:
|
||||
return BPF_PROG_TYPE_CGROUP_SOCK;
|
||||
case BPF_CGROUP_INET4_BIND:
|
||||
case BPF_CGROUP_INET6_BIND:
|
||||
case BPF_CGROUP_INET4_CONNECT:
|
||||
case BPF_CGROUP_INET6_CONNECT:
|
||||
case BPF_CGROUP_UDP4_SENDMSG:
|
||||
case BPF_CGROUP_UDP6_SENDMSG:
|
||||
case BPF_CGROUP_UDP4_RECVMSG:
|
||||
case BPF_CGROUP_UDP6_RECVMSG:
|
||||
return BPF_PROG_TYPE_CGROUP_SOCK_ADDR;
|
||||
case BPF_CGROUP_SOCK_OPS:
|
||||
return BPF_PROG_TYPE_SOCK_OPS;
|
||||
case BPF_CGROUP_DEVICE:
|
||||
return BPF_PROG_TYPE_CGROUP_DEVICE;
|
||||
case BPF_SK_MSG_VERDICT:
|
||||
return BPF_PROG_TYPE_SK_MSG;
|
||||
case BPF_SK_SKB_STREAM_PARSER:
|
||||
case BPF_SK_SKB_STREAM_VERDICT:
|
||||
return BPF_PROG_TYPE_SK_SKB;
|
||||
case BPF_LIRC_MODE2:
|
||||
return BPF_PROG_TYPE_LIRC_MODE2;
|
||||
case BPF_FLOW_DISSECTOR:
|
||||
return BPF_PROG_TYPE_FLOW_DISSECTOR;
|
||||
case BPF_CGROUP_SYSCTL:
|
||||
return BPF_PROG_TYPE_CGROUP_SYSCTL;
|
||||
case BPF_CGROUP_GETSOCKOPT:
|
||||
case BPF_CGROUP_SETSOCKOPT:
|
||||
return BPF_PROG_TYPE_CGROUP_SOCKOPT;
|
||||
default:
|
||||
return BPF_PROG_TYPE_UNSPEC;
|
||||
}
|
||||
}
|
||||
|
||||
#define BPF_PROG_ATTACH_LAST_FIELD replace_bpf_fd
|
||||
|
||||
#define BPF_F_ATTACH_MASK \
|
||||
@@ -2360,55 +2624,9 @@ static int bpf_prog_attach(const union bpf_attr *attr)
|
||||
if (attr->attach_flags & ~BPF_F_ATTACH_MASK)
|
||||
return -EINVAL;
|
||||
|
||||
switch (attr->attach_type) {
|
||||
case BPF_CGROUP_INET_INGRESS:
|
||||
case BPF_CGROUP_INET_EGRESS:
|
||||
ptype = BPF_PROG_TYPE_CGROUP_SKB;
|
||||
break;
|
||||
case BPF_CGROUP_INET_SOCK_CREATE:
|
||||
case BPF_CGROUP_INET4_POST_BIND:
|
||||
case BPF_CGROUP_INET6_POST_BIND:
|
||||
ptype = BPF_PROG_TYPE_CGROUP_SOCK;
|
||||
break;
|
||||
case BPF_CGROUP_INET4_BIND:
|
||||
case BPF_CGROUP_INET6_BIND:
|
||||
case BPF_CGROUP_INET4_CONNECT:
|
||||
case BPF_CGROUP_INET6_CONNECT:
|
||||
case BPF_CGROUP_UDP4_SENDMSG:
|
||||
case BPF_CGROUP_UDP6_SENDMSG:
|
||||
case BPF_CGROUP_UDP4_RECVMSG:
|
||||
case BPF_CGROUP_UDP6_RECVMSG:
|
||||
ptype = BPF_PROG_TYPE_CGROUP_SOCK_ADDR;
|
||||
break;
|
||||
case BPF_CGROUP_SOCK_OPS:
|
||||
ptype = BPF_PROG_TYPE_SOCK_OPS;
|
||||
break;
|
||||
case BPF_CGROUP_DEVICE:
|
||||
ptype = BPF_PROG_TYPE_CGROUP_DEVICE;
|
||||
break;
|
||||
case BPF_SK_MSG_VERDICT:
|
||||
ptype = BPF_PROG_TYPE_SK_MSG;
|
||||
break;
|
||||
case BPF_SK_SKB_STREAM_PARSER:
|
||||
case BPF_SK_SKB_STREAM_VERDICT:
|
||||
ptype = BPF_PROG_TYPE_SK_SKB;
|
||||
break;
|
||||
case BPF_LIRC_MODE2:
|
||||
ptype = BPF_PROG_TYPE_LIRC_MODE2;
|
||||
break;
|
||||
case BPF_FLOW_DISSECTOR:
|
||||
ptype = BPF_PROG_TYPE_FLOW_DISSECTOR;
|
||||
break;
|
||||
case BPF_CGROUP_SYSCTL:
|
||||
ptype = BPF_PROG_TYPE_CGROUP_SYSCTL;
|
||||
break;
|
||||
case BPF_CGROUP_GETSOCKOPT:
|
||||
case BPF_CGROUP_SETSOCKOPT:
|
||||
ptype = BPF_PROG_TYPE_CGROUP_SOCKOPT;
|
||||
break;
|
||||
default:
|
||||
ptype = attach_type_to_prog_type(attr->attach_type);
|
||||
if (ptype == BPF_PROG_TYPE_UNSPEC)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
prog = bpf_prog_get_type(attr->attach_bpf_fd, ptype);
|
||||
if (IS_ERR(prog))
|
||||
@@ -2430,8 +2648,17 @@ static int bpf_prog_attach(const union bpf_attr *attr)
|
||||
case BPF_PROG_TYPE_FLOW_DISSECTOR:
|
||||
ret = skb_flow_dissector_bpf_prog_attach(attr, prog);
|
||||
break;
|
||||
default:
|
||||
case BPF_PROG_TYPE_CGROUP_DEVICE:
|
||||
case BPF_PROG_TYPE_CGROUP_SKB:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCK:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCK_ADDR:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCKOPT:
|
||||
case BPF_PROG_TYPE_CGROUP_SYSCTL:
|
||||
case BPF_PROG_TYPE_SOCK_OPS:
|
||||
ret = cgroup_bpf_prog_attach(attr, ptype, prog);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
@@ -2451,53 +2678,27 @@ static int bpf_prog_detach(const union bpf_attr *attr)
|
||||
if (CHECK_ATTR(BPF_PROG_DETACH))
|
||||
return -EINVAL;
|
||||
|
||||
switch (attr->attach_type) {
|
||||
case BPF_CGROUP_INET_INGRESS:
|
||||
case BPF_CGROUP_INET_EGRESS:
|
||||
ptype = BPF_PROG_TYPE_CGROUP_SKB;
|
||||
break;
|
||||
case BPF_CGROUP_INET_SOCK_CREATE:
|
||||
case BPF_CGROUP_INET4_POST_BIND:
|
||||
case BPF_CGROUP_INET6_POST_BIND:
|
||||
ptype = BPF_PROG_TYPE_CGROUP_SOCK;
|
||||
break;
|
||||
case BPF_CGROUP_INET4_BIND:
|
||||
case BPF_CGROUP_INET6_BIND:
|
||||
case BPF_CGROUP_INET4_CONNECT:
|
||||
case BPF_CGROUP_INET6_CONNECT:
|
||||
case BPF_CGROUP_UDP4_SENDMSG:
|
||||
case BPF_CGROUP_UDP6_SENDMSG:
|
||||
case BPF_CGROUP_UDP4_RECVMSG:
|
||||
case BPF_CGROUP_UDP6_RECVMSG:
|
||||
ptype = BPF_PROG_TYPE_CGROUP_SOCK_ADDR;
|
||||
break;
|
||||
case BPF_CGROUP_SOCK_OPS:
|
||||
ptype = BPF_PROG_TYPE_SOCK_OPS;
|
||||
break;
|
||||
case BPF_CGROUP_DEVICE:
|
||||
ptype = BPF_PROG_TYPE_CGROUP_DEVICE;
|
||||
break;
|
||||
case BPF_SK_MSG_VERDICT:
|
||||
ptype = attach_type_to_prog_type(attr->attach_type);
|
||||
|
||||
switch (ptype) {
|
||||
case BPF_PROG_TYPE_SK_MSG:
|
||||
case BPF_PROG_TYPE_SK_SKB:
|
||||
return sock_map_get_from_fd(attr, NULL);
|
||||
case BPF_SK_SKB_STREAM_PARSER:
|
||||
case BPF_SK_SKB_STREAM_VERDICT:
|
||||
return sock_map_get_from_fd(attr, NULL);
|
||||
case BPF_LIRC_MODE2:
|
||||
case BPF_PROG_TYPE_LIRC_MODE2:
|
||||
return lirc_prog_detach(attr);
|
||||
case BPF_FLOW_DISSECTOR:
|
||||
case BPF_PROG_TYPE_FLOW_DISSECTOR:
|
||||
return skb_flow_dissector_bpf_prog_detach(attr);
|
||||
case BPF_CGROUP_SYSCTL:
|
||||
ptype = BPF_PROG_TYPE_CGROUP_SYSCTL;
|
||||
break;
|
||||
case BPF_CGROUP_GETSOCKOPT:
|
||||
case BPF_CGROUP_SETSOCKOPT:
|
||||
ptype = BPF_PROG_TYPE_CGROUP_SOCKOPT;
|
||||
break;
|
||||
case BPF_PROG_TYPE_CGROUP_DEVICE:
|
||||
case BPF_PROG_TYPE_CGROUP_SKB:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCK:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCK_ADDR:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCKOPT:
|
||||
case BPF_PROG_TYPE_CGROUP_SYSCTL:
|
||||
case BPF_PROG_TYPE_SOCK_OPS:
|
||||
return cgroup_bpf_prog_detach(attr, ptype);
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return cgroup_bpf_prog_detach(attr, ptype);
|
||||
}
|
||||
|
||||
#define BPF_PROG_QUERY_LAST_FIELD query.prog_cnt
|
||||
@@ -2531,7 +2732,7 @@ static int bpf_prog_query(const union bpf_attr *attr,
|
||||
case BPF_CGROUP_SYSCTL:
|
||||
case BPF_CGROUP_GETSOCKOPT:
|
||||
case BPF_CGROUP_SETSOCKOPT:
|
||||
break;
|
||||
return cgroup_bpf_prog_query(attr, uattr);
|
||||
case BPF_LIRC_MODE2:
|
||||
return lirc_prog_query(attr, uattr);
|
||||
case BPF_FLOW_DISSECTOR:
|
||||
@@ -2539,8 +2740,6 @@ static int bpf_prog_query(const union bpf_attr *attr,
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return cgroup_bpf_prog_query(attr, uattr);
|
||||
}
|
||||
|
||||
#define BPF_PROG_TEST_RUN_LAST_FIELD test.ctx_out
|
||||
@@ -2787,7 +2986,7 @@ static int bpf_prog_get_info_by_fd(struct bpf_prog *prog,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
struct bpf_prog_info __user *uinfo = u64_to_user_ptr(attr->info.info);
|
||||
struct bpf_prog_info info = {};
|
||||
struct bpf_prog_info info;
|
||||
u32 info_len = attr->info.info_len;
|
||||
struct bpf_prog_stats stats;
|
||||
char __user *uinsns;
|
||||
@@ -2799,6 +2998,7 @@ static int bpf_prog_get_info_by_fd(struct bpf_prog *prog,
|
||||
return err;
|
||||
info_len = min_t(u32, sizeof(info), info_len);
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
if (copy_from_user(&info, uinfo, info_len))
|
||||
return -EFAULT;
|
||||
|
||||
@@ -3062,7 +3262,7 @@ static int bpf_map_get_info_by_fd(struct bpf_map *map,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
struct bpf_map_info __user *uinfo = u64_to_user_ptr(attr->info.info);
|
||||
struct bpf_map_info info = {};
|
||||
struct bpf_map_info info;
|
||||
u32 info_len = attr->info.info_len;
|
||||
int err;
|
||||
|
||||
@@ -3071,6 +3271,7 @@ static int bpf_map_get_info_by_fd(struct bpf_map *map,
|
||||
return err;
|
||||
info_len = min_t(u32, sizeof(info), info_len);
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.type = map->map_type;
|
||||
info.id = map->id;
|
||||
info.key_size = map->key_size;
|
||||
@@ -3262,15 +3463,21 @@ static int bpf_task_fd_query(const union bpf_attr *attr,
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
if (file->f_op == &bpf_raw_tp_fops) {
|
||||
struct bpf_raw_tracepoint *raw_tp = file->private_data;
|
||||
struct bpf_raw_event_map *btp = raw_tp->btp;
|
||||
if (file->f_op == &bpf_link_fops) {
|
||||
struct bpf_link *link = file->private_data;
|
||||
|
||||
err = bpf_task_fd_query_copy(attr, uattr,
|
||||
raw_tp->prog->aux->id,
|
||||
BPF_FD_TYPE_RAW_TRACEPOINT,
|
||||
btp->tp->name, 0, 0);
|
||||
goto put_file;
|
||||
if (link->ops == &bpf_raw_tp_lops) {
|
||||
struct bpf_raw_tp_link *raw_tp =
|
||||
container_of(link, struct bpf_raw_tp_link, link);
|
||||
struct bpf_raw_event_map *btp = raw_tp->btp;
|
||||
|
||||
err = bpf_task_fd_query_copy(attr, uattr,
|
||||
raw_tp->link.prog->aux->id,
|
||||
BPF_FD_TYPE_RAW_TRACEPOINT,
|
||||
btp->tp->name, 0, 0);
|
||||
goto put_file;
|
||||
}
|
||||
goto out_not_supp;
|
||||
}
|
||||
|
||||
event = perf_get_event(file);
|
||||
@@ -3290,6 +3497,7 @@ static int bpf_task_fd_query(const union bpf_attr *attr,
|
||||
goto put_file;
|
||||
}
|
||||
|
||||
out_not_supp:
|
||||
err = -ENOTSUPP;
|
||||
put_file:
|
||||
fput(file);
|
||||
@@ -3352,9 +3560,107 @@ err_put:
|
||||
return err;
|
||||
}
|
||||
|
||||
#define BPF_LINK_CREATE_LAST_FIELD link_create.flags
|
||||
static int link_create(union bpf_attr *attr)
|
||||
{
|
||||
enum bpf_prog_type ptype;
|
||||
struct bpf_prog *prog;
|
||||
int ret;
|
||||
|
||||
if (!capable(CAP_NET_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (CHECK_ATTR(BPF_LINK_CREATE))
|
||||
return -EINVAL;
|
||||
|
||||
ptype = attach_type_to_prog_type(attr->link_create.attach_type);
|
||||
if (ptype == BPF_PROG_TYPE_UNSPEC)
|
||||
return -EINVAL;
|
||||
|
||||
prog = bpf_prog_get_type(attr->link_create.prog_fd, ptype);
|
||||
if (IS_ERR(prog))
|
||||
return PTR_ERR(prog);
|
||||
|
||||
ret = bpf_prog_attach_check_attach_type(prog,
|
||||
attr->link_create.attach_type);
|
||||
if (ret)
|
||||
goto err_out;
|
||||
|
||||
switch (ptype) {
|
||||
case BPF_PROG_TYPE_CGROUP_SKB:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCK:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCK_ADDR:
|
||||
case BPF_PROG_TYPE_SOCK_OPS:
|
||||
case BPF_PROG_TYPE_CGROUP_DEVICE:
|
||||
case BPF_PROG_TYPE_CGROUP_SYSCTL:
|
||||
case BPF_PROG_TYPE_CGROUP_SOCKOPT:
|
||||
ret = cgroup_bpf_link_attach(attr, prog);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
}
|
||||
|
||||
err_out:
|
||||
if (ret < 0)
|
||||
bpf_prog_put(prog);
|
||||
return ret;
|
||||
}
|
||||
|
||||
#define BPF_LINK_UPDATE_LAST_FIELD link_update.old_prog_fd
|
||||
|
||||
static int link_update(union bpf_attr *attr)
|
||||
{
|
||||
struct bpf_prog *old_prog = NULL, *new_prog;
|
||||
struct bpf_link *link;
|
||||
u32 flags;
|
||||
int ret;
|
||||
|
||||
if (!capable(CAP_NET_ADMIN))
|
||||
return -EPERM;
|
||||
|
||||
if (CHECK_ATTR(BPF_LINK_UPDATE))
|
||||
return -EINVAL;
|
||||
|
||||
flags = attr->link_update.flags;
|
||||
if (flags & ~BPF_F_REPLACE)
|
||||
return -EINVAL;
|
||||
|
||||
link = bpf_link_get_from_fd(attr->link_update.link_fd);
|
||||
if (IS_ERR(link))
|
||||
return PTR_ERR(link);
|
||||
|
||||
new_prog = bpf_prog_get(attr->link_update.new_prog_fd);
|
||||
if (IS_ERR(new_prog))
|
||||
return PTR_ERR(new_prog);
|
||||
|
||||
if (flags & BPF_F_REPLACE) {
|
||||
old_prog = bpf_prog_get(attr->link_update.old_prog_fd);
|
||||
if (IS_ERR(old_prog)) {
|
||||
ret = PTR_ERR(old_prog);
|
||||
old_prog = NULL;
|
||||
goto out_put_progs;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CGROUP_BPF
|
||||
if (link->ops == &bpf_cgroup_link_lops) {
|
||||
ret = cgroup_bpf_replace(link, old_prog, new_prog);
|
||||
goto out_put_progs;
|
||||
}
|
||||
#endif
|
||||
ret = -EINVAL;
|
||||
|
||||
out_put_progs:
|
||||
if (old_prog)
|
||||
bpf_prog_put(old_prog);
|
||||
if (ret)
|
||||
bpf_prog_put(new_prog);
|
||||
return ret;
|
||||
}
|
||||
|
||||
SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, size)
|
||||
{
|
||||
union bpf_attr attr = {};
|
||||
union bpf_attr attr;
|
||||
int err;
|
||||
|
||||
if (sysctl_unprivileged_bpf_disabled && !capable(CAP_SYS_ADMIN))
|
||||
@@ -3366,6 +3672,7 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz
|
||||
size = min_t(u32, size, sizeof(attr));
|
||||
|
||||
/* copy attributes from user space, may be less than sizeof(bpf_attr) */
|
||||
memset(&attr, 0, sizeof(attr));
|
||||
if (copy_from_user(&attr, uattr, size) != 0)
|
||||
return -EFAULT;
|
||||
|
||||
@@ -3462,6 +3769,12 @@ SYSCALL_DEFINE3(bpf, int, cmd, union bpf_attr __user *, uattr, unsigned int, siz
|
||||
case BPF_MAP_DELETE_BATCH:
|
||||
err = bpf_map_do_batch(&attr, uattr, BPF_MAP_DELETE_BATCH);
|
||||
break;
|
||||
case BPF_LINK_CREATE:
|
||||
err = link_create(&attr);
|
||||
break;
|
||||
case BPF_LINK_UPDATE:
|
||||
err = link_update(&attr);
|
||||
break;
|
||||
default:
|
||||
err = -EINVAL;
|
||||
break;
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
#include <linux/sysfs.h>
|
||||
|
||||
/* See scripts/link-vmlinux.sh, gen_btf() func for details */
|
||||
extern char __weak _binary__btf_vmlinux_bin_start[];
|
||||
extern char __weak _binary__btf_vmlinux_bin_end[];
|
||||
extern char __weak __start_BTF[];
|
||||
extern char __weak __stop_BTF[];
|
||||
|
||||
static ssize_t
|
||||
btf_vmlinux_read(struct file *file, struct kobject *kobj,
|
||||
struct bin_attribute *bin_attr,
|
||||
char *buf, loff_t off, size_t len)
|
||||
{
|
||||
memcpy(buf, _binary__btf_vmlinux_bin_start + off, len);
|
||||
memcpy(buf, __start_BTF + off, len);
|
||||
return len;
|
||||
}
|
||||
|
||||
@@ -30,15 +30,14 @@ static struct kobject *btf_kobj;
|
||||
|
||||
static int __init btf_vmlinux_init(void)
|
||||
{
|
||||
if (!_binary__btf_vmlinux_bin_start)
|
||||
if (!__start_BTF)
|
||||
return 0;
|
||||
|
||||
btf_kobj = kobject_create_and_add("btf", kernel_kobj);
|
||||
if (!btf_kobj)
|
||||
return -ENOMEM;
|
||||
|
||||
bin_attr_btf_vmlinux.size = _binary__btf_vmlinux_bin_end -
|
||||
_binary__btf_vmlinux_bin_start;
|
||||
bin_attr_btf_vmlinux.size = __stop_BTF - __start_BTF;
|
||||
|
||||
return sysfs_create_bin_file(btf_kobj, &bin_attr_btf_vmlinux);
|
||||
}
|
||||
|
||||
@@ -194,3 +194,18 @@ int tnum_sbin(char *str, size_t size, struct tnum a)
|
||||
str[min(size - 1, (size_t)64)] = 0;
|
||||
return 64;
|
||||
}
|
||||
|
||||
struct tnum tnum_subreg(struct tnum a)
|
||||
{
|
||||
return tnum_cast(a, 4);
|
||||
}
|
||||
|
||||
struct tnum tnum_clear_subreg(struct tnum a)
|
||||
{
|
||||
return tnum_lshift(tnum_rshift(a, 32), 32);
|
||||
}
|
||||
|
||||
struct tnum tnum_const_subreg(struct tnum a, u32 value)
|
||||
{
|
||||
return tnum_or(tnum_clear_subreg(a), tnum_const(value));
|
||||
}
|
||||
|
||||
+83
-95
@@ -5,6 +5,8 @@
|
||||
#include <linux/filter.h>
|
||||
#include <linux/ftrace.h>
|
||||
#include <linux/rbtree_latch.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <linux/btf.h>
|
||||
|
||||
/* dummy _ops. The verifier will operate on target program's ops. */
|
||||
const struct bpf_verifier_ops bpf_extension_verifier_ops = {
|
||||
@@ -17,12 +19,11 @@ const struct bpf_prog_ops bpf_extension_prog_ops = {
|
||||
#define TRAMPOLINE_TABLE_SIZE (1 << TRAMPOLINE_HASH_BITS)
|
||||
|
||||
static struct hlist_head trampoline_table[TRAMPOLINE_TABLE_SIZE];
|
||||
static struct latch_tree_root image_tree __cacheline_aligned;
|
||||
|
||||
/* serializes access to trampoline_table and image_tree */
|
||||
/* serializes access to trampoline_table */
|
||||
static DEFINE_MUTEX(trampoline_mutex);
|
||||
|
||||
static void *bpf_jit_alloc_exec_page(void)
|
||||
void *bpf_jit_alloc_exec_page(void)
|
||||
{
|
||||
void *image;
|
||||
|
||||
@@ -38,62 +39,28 @@ static void *bpf_jit_alloc_exec_page(void)
|
||||
return image;
|
||||
}
|
||||
|
||||
static __always_inline bool image_tree_less(struct latch_tree_node *a,
|
||||
struct latch_tree_node *b)
|
||||
void bpf_image_ksym_add(void *data, struct bpf_ksym *ksym)
|
||||
{
|
||||
struct bpf_image *ia = container_of(a, struct bpf_image, tnode);
|
||||
struct bpf_image *ib = container_of(b, struct bpf_image, tnode);
|
||||
|
||||
return ia < ib;
|
||||
ksym->start = (unsigned long) data;
|
||||
ksym->end = ksym->start + PAGE_SIZE;
|
||||
bpf_ksym_add(ksym);
|
||||
perf_event_ksymbol(PERF_RECORD_KSYMBOL_TYPE_BPF, ksym->start,
|
||||
PAGE_SIZE, false, ksym->name);
|
||||
}
|
||||
|
||||
static __always_inline int image_tree_comp(void *addr, struct latch_tree_node *n)
|
||||
void bpf_image_ksym_del(struct bpf_ksym *ksym)
|
||||
{
|
||||
void *image = container_of(n, struct bpf_image, tnode);
|
||||
|
||||
if (addr < image)
|
||||
return -1;
|
||||
if (addr >= image + PAGE_SIZE)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
bpf_ksym_del(ksym);
|
||||
perf_event_ksymbol(PERF_RECORD_KSYMBOL_TYPE_BPF, ksym->start,
|
||||
PAGE_SIZE, true, ksym->name);
|
||||
}
|
||||
|
||||
static const struct latch_tree_ops image_tree_ops = {
|
||||
.less = image_tree_less,
|
||||
.comp = image_tree_comp,
|
||||
};
|
||||
|
||||
static void *__bpf_image_alloc(bool lock)
|
||||
static void bpf_trampoline_ksym_add(struct bpf_trampoline *tr)
|
||||
{
|
||||
struct bpf_image *image;
|
||||
struct bpf_ksym *ksym = &tr->ksym;
|
||||
|
||||
image = bpf_jit_alloc_exec_page();
|
||||
if (!image)
|
||||
return NULL;
|
||||
|
||||
if (lock)
|
||||
mutex_lock(&trampoline_mutex);
|
||||
latch_tree_insert(&image->tnode, &image_tree, &image_tree_ops);
|
||||
if (lock)
|
||||
mutex_unlock(&trampoline_mutex);
|
||||
return image->data;
|
||||
}
|
||||
|
||||
void *bpf_image_alloc(void)
|
||||
{
|
||||
return __bpf_image_alloc(true);
|
||||
}
|
||||
|
||||
bool is_bpf_image_address(unsigned long addr)
|
||||
{
|
||||
bool ret;
|
||||
|
||||
rcu_read_lock();
|
||||
ret = latch_tree_find((void *) addr, &image_tree, &image_tree_ops) != NULL;
|
||||
rcu_read_unlock();
|
||||
|
||||
return ret;
|
||||
snprintf(ksym->name, KSYM_NAME_LEN, "bpf_trampoline_%llu", tr->key);
|
||||
bpf_image_ksym_add(tr->image, ksym);
|
||||
}
|
||||
|
||||
struct bpf_trampoline *bpf_trampoline_lookup(u64 key)
|
||||
@@ -116,7 +83,7 @@ struct bpf_trampoline *bpf_trampoline_lookup(u64 key)
|
||||
goto out;
|
||||
|
||||
/* is_root was checked earlier. No need for bpf_jit_charge_modmem() */
|
||||
image = __bpf_image_alloc(false);
|
||||
image = bpf_jit_alloc_exec_page();
|
||||
if (!image) {
|
||||
kfree(tr);
|
||||
tr = NULL;
|
||||
@@ -131,6 +98,8 @@ struct bpf_trampoline *bpf_trampoline_lookup(u64 key)
|
||||
for (i = 0; i < BPF_TRAMP_MAX; i++)
|
||||
INIT_HLIST_HEAD(&tr->progs_hlist[i]);
|
||||
tr->image = image;
|
||||
INIT_LIST_HEAD_RCU(&tr->ksym.lnode);
|
||||
bpf_trampoline_ksym_add(tr);
|
||||
out:
|
||||
mutex_unlock(&trampoline_mutex);
|
||||
return tr;
|
||||
@@ -190,40 +159,50 @@ static int register_fentry(struct bpf_trampoline *tr, void *new_addr)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Each call __bpf_prog_enter + call bpf_func + call __bpf_prog_exit is ~50
|
||||
* bytes on x86. Pick a number to fit into BPF_IMAGE_SIZE / 2
|
||||
*/
|
||||
#define BPF_MAX_TRAMP_PROGS 40
|
||||
static struct bpf_tramp_progs *
|
||||
bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total)
|
||||
{
|
||||
const struct bpf_prog_aux *aux;
|
||||
struct bpf_tramp_progs *tprogs;
|
||||
struct bpf_prog **progs;
|
||||
int kind;
|
||||
|
||||
*total = 0;
|
||||
tprogs = kcalloc(BPF_TRAMP_MAX, sizeof(*tprogs), GFP_KERNEL);
|
||||
if (!tprogs)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
for (kind = 0; kind < BPF_TRAMP_MAX; kind++) {
|
||||
tprogs[kind].nr_progs = tr->progs_cnt[kind];
|
||||
*total += tr->progs_cnt[kind];
|
||||
progs = tprogs[kind].progs;
|
||||
|
||||
hlist_for_each_entry(aux, &tr->progs_hlist[kind], tramp_hlist)
|
||||
*progs++ = aux->prog;
|
||||
}
|
||||
return tprogs;
|
||||
}
|
||||
|
||||
static int bpf_trampoline_update(struct bpf_trampoline *tr)
|
||||
{
|
||||
void *old_image = tr->image + ((tr->selector + 1) & 1) * BPF_IMAGE_SIZE/2;
|
||||
void *new_image = tr->image + (tr->selector & 1) * BPF_IMAGE_SIZE/2;
|
||||
struct bpf_prog *progs_to_run[BPF_MAX_TRAMP_PROGS];
|
||||
int fentry_cnt = tr->progs_cnt[BPF_TRAMP_FENTRY];
|
||||
int fexit_cnt = tr->progs_cnt[BPF_TRAMP_FEXIT];
|
||||
struct bpf_prog **progs, **fentry, **fexit;
|
||||
void *old_image = tr->image + ((tr->selector + 1) & 1) * PAGE_SIZE/2;
|
||||
void *new_image = tr->image + (tr->selector & 1) * PAGE_SIZE/2;
|
||||
struct bpf_tramp_progs *tprogs;
|
||||
u32 flags = BPF_TRAMP_F_RESTORE_REGS;
|
||||
struct bpf_prog_aux *aux;
|
||||
int err;
|
||||
int err, total;
|
||||
|
||||
if (fentry_cnt + fexit_cnt == 0) {
|
||||
tprogs = bpf_trampoline_get_progs(tr, &total);
|
||||
if (IS_ERR(tprogs))
|
||||
return PTR_ERR(tprogs);
|
||||
|
||||
if (total == 0) {
|
||||
err = unregister_fentry(tr, old_image);
|
||||
tr->selector = 0;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* populate fentry progs */
|
||||
fentry = progs = progs_to_run;
|
||||
hlist_for_each_entry(aux, &tr->progs_hlist[BPF_TRAMP_FENTRY], tramp_hlist)
|
||||
*progs++ = aux->prog;
|
||||
|
||||
/* populate fexit progs */
|
||||
fexit = progs;
|
||||
hlist_for_each_entry(aux, &tr->progs_hlist[BPF_TRAMP_FEXIT], tramp_hlist)
|
||||
*progs++ = aux->prog;
|
||||
|
||||
if (fexit_cnt)
|
||||
if (tprogs[BPF_TRAMP_FEXIT].nr_progs ||
|
||||
tprogs[BPF_TRAMP_MODIFY_RETURN].nr_progs)
|
||||
flags = BPF_TRAMP_F_CALL_ORIG | BPF_TRAMP_F_SKIP_FRAME;
|
||||
|
||||
/* Though the second half of trampoline page is unused a task could be
|
||||
@@ -232,12 +211,11 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr)
|
||||
* preempted task. Hence wait for tasks to voluntarily schedule or go
|
||||
* to userspace.
|
||||
*/
|
||||
|
||||
synchronize_rcu_tasks();
|
||||
|
||||
err = arch_prepare_bpf_trampoline(new_image, new_image + BPF_IMAGE_SIZE / 2,
|
||||
&tr->func.model, flags,
|
||||
fentry, fentry_cnt,
|
||||
fexit, fexit_cnt,
|
||||
err = arch_prepare_bpf_trampoline(new_image, new_image + PAGE_SIZE / 2,
|
||||
&tr->func.model, flags, tprogs,
|
||||
tr->func.addr);
|
||||
if (err < 0)
|
||||
goto out;
|
||||
@@ -252,16 +230,27 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr)
|
||||
goto out;
|
||||
tr->selector++;
|
||||
out:
|
||||
kfree(tprogs);
|
||||
return err;
|
||||
}
|
||||
|
||||
static enum bpf_tramp_prog_type bpf_attach_type_to_tramp(enum bpf_attach_type t)
|
||||
static enum bpf_tramp_prog_type bpf_attach_type_to_tramp(struct bpf_prog *prog)
|
||||
{
|
||||
switch (t) {
|
||||
switch (prog->expected_attach_type) {
|
||||
case BPF_TRACE_FENTRY:
|
||||
return BPF_TRAMP_FENTRY;
|
||||
case BPF_MODIFY_RETURN:
|
||||
return BPF_TRAMP_MODIFY_RETURN;
|
||||
case BPF_TRACE_FEXIT:
|
||||
return BPF_TRAMP_FEXIT;
|
||||
case BPF_LSM_MAC:
|
||||
if (!prog->aux->attach_func_proto->type)
|
||||
/* The function returns void, we cannot modify its
|
||||
* return value.
|
||||
*/
|
||||
return BPF_TRAMP_FEXIT;
|
||||
else
|
||||
return BPF_TRAMP_MODIFY_RETURN;
|
||||
default:
|
||||
return BPF_TRAMP_REPLACE;
|
||||
}
|
||||
@@ -275,7 +264,7 @@ int bpf_trampoline_link_prog(struct bpf_prog *prog)
|
||||
int cnt;
|
||||
|
||||
tr = prog->aux->trampoline;
|
||||
kind = bpf_attach_type_to_tramp(prog->expected_attach_type);
|
||||
kind = bpf_attach_type_to_tramp(prog);
|
||||
mutex_lock(&tr->mutex);
|
||||
if (tr->extension_prog) {
|
||||
/* cannot attach fentry/fexit if extension prog is attached.
|
||||
@@ -325,7 +314,7 @@ int bpf_trampoline_unlink_prog(struct bpf_prog *prog)
|
||||
int err;
|
||||
|
||||
tr = prog->aux->trampoline;
|
||||
kind = bpf_attach_type_to_tramp(prog->expected_attach_type);
|
||||
kind = bpf_attach_type_to_tramp(prog);
|
||||
mutex_lock(&tr->mutex);
|
||||
if (kind == BPF_TRAMP_REPLACE) {
|
||||
WARN_ON_ONCE(!tr->extension_prog);
|
||||
@@ -344,8 +333,6 @@ out:
|
||||
|
||||
void bpf_trampoline_put(struct bpf_trampoline *tr)
|
||||
{
|
||||
struct bpf_image *image;
|
||||
|
||||
if (!tr)
|
||||
return;
|
||||
mutex_lock(&trampoline_mutex);
|
||||
@@ -356,35 +343,37 @@ void bpf_trampoline_put(struct bpf_trampoline *tr)
|
||||
goto out;
|
||||
if (WARN_ON_ONCE(!hlist_empty(&tr->progs_hlist[BPF_TRAMP_FEXIT])))
|
||||
goto out;
|
||||
image = container_of(tr->image, struct bpf_image, data);
|
||||
latch_tree_erase(&image->tnode, &image_tree, &image_tree_ops);
|
||||
bpf_image_ksym_del(&tr->ksym);
|
||||
/* wait for tasks to get out of trampoline before freeing it */
|
||||
synchronize_rcu_tasks();
|
||||
bpf_jit_free_exec(image);
|
||||
bpf_jit_free_exec(tr->image);
|
||||
hlist_del(&tr->hlist);
|
||||
kfree(tr);
|
||||
out:
|
||||
mutex_unlock(&trampoline_mutex);
|
||||
}
|
||||
|
||||
/* The logic is similar to BPF_PROG_RUN, but with explicit rcu and preempt that
|
||||
* are needed for trampoline. The macro is split into
|
||||
/* The logic is similar to BPF_PROG_RUN, but with an explicit
|
||||
* rcu_read_lock() and migrate_disable() which are required
|
||||
* for the trampoline. The macro is split into
|
||||
* call _bpf_prog_enter
|
||||
* call prog->bpf_func
|
||||
* call __bpf_prog_exit
|
||||
*/
|
||||
u64 notrace __bpf_prog_enter(void)
|
||||
__acquires(RCU)
|
||||
{
|
||||
u64 start = 0;
|
||||
|
||||
rcu_read_lock();
|
||||
preempt_disable();
|
||||
migrate_disable();
|
||||
if (static_branch_unlikely(&bpf_stats_enabled_key))
|
||||
start = sched_clock();
|
||||
return start;
|
||||
}
|
||||
|
||||
void notrace __bpf_prog_exit(struct bpf_prog *prog, u64 start)
|
||||
__releases(RCU)
|
||||
{
|
||||
struct bpf_prog_stats *stats;
|
||||
|
||||
@@ -401,15 +390,14 @@ void notrace __bpf_prog_exit(struct bpf_prog *prog, u64 start)
|
||||
stats->nsecs += sched_clock() - start;
|
||||
u64_stats_update_end(&stats->syncp);
|
||||
}
|
||||
preempt_enable();
|
||||
migrate_enable();
|
||||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
int __weak
|
||||
arch_prepare_bpf_trampoline(void *image, void *image_end,
|
||||
const struct btf_func_model *m, u32 flags,
|
||||
struct bpf_prog **fentry_progs, int fentry_cnt,
|
||||
struct bpf_prog **fexit_progs, int fexit_cnt,
|
||||
struct bpf_tramp_progs *tprogs,
|
||||
void *orig_call)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
|
||||
+1155
-498
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/refcount.h>
|
||||
#include <linux/fs_context.h>
|
||||
#include <linux/fs_parser.h>
|
||||
|
||||
#define TRACE_CGROUP_PATH_LEN 1024
|
||||
extern spinlock_t trace_cgroup_path_lock;
|
||||
@@ -265,7 +265,7 @@ extern const struct proc_ns_operations cgroupns_operations;
|
||||
*/
|
||||
extern struct cftype cgroup1_base_files[];
|
||||
extern struct kernfs_syscall_ops cgroup1_kf_syscall_ops;
|
||||
extern const struct fs_parameter_description cgroup1_fs_parameters;
|
||||
extern const struct fs_parameter_spec cgroup1_fs_parameters[];
|
||||
|
||||
int proc_cgroupstats_show(struct seq_file *m, void *v);
|
||||
bool cgroup1_ssid_disabled(int ssid);
|
||||
|
||||
+31
-37
@@ -18,8 +18,6 @@
|
||||
|
||||
#include <trace/events/cgroup.h>
|
||||
|
||||
#define cg_invalf(fc, fmt, ...) invalf(fc, fmt, ## __VA_ARGS__)
|
||||
|
||||
/*
|
||||
* pidlists linger the following amount before being destroyed. The goal
|
||||
* is avoiding frequent destruction in the middle of consecutive read calls
|
||||
@@ -40,10 +38,7 @@ static bool cgroup_no_v1_named;
|
||||
*/
|
||||
static struct workqueue_struct *cgroup_pidlist_destroy_wq;
|
||||
|
||||
/*
|
||||
* Protects cgroup_subsys->release_agent_path. Modifying it also requires
|
||||
* cgroup_mutex. Reading requires either cgroup_mutex or this spinlock.
|
||||
*/
|
||||
/* protects cgroup_subsys->release_agent_path */
|
||||
static DEFINE_SPINLOCK(release_agent_path_lock);
|
||||
|
||||
bool cgroup1_ssid_disabled(int ssid)
|
||||
@@ -473,6 +468,7 @@ static void *cgroup_pidlist_next(struct seq_file *s, void *v, loff_t *pos)
|
||||
*/
|
||||
p++;
|
||||
if (p >= end) {
|
||||
(*pos)++;
|
||||
return NULL;
|
||||
} else {
|
||||
*pos = *p;
|
||||
@@ -776,22 +772,29 @@ void cgroup1_release_agent(struct work_struct *work)
|
||||
{
|
||||
struct cgroup *cgrp =
|
||||
container_of(work, struct cgroup, release_agent_work);
|
||||
char *pathbuf = NULL, *agentbuf = NULL;
|
||||
char *pathbuf, *agentbuf;
|
||||
char *argv[3], *envp[3];
|
||||
int ret;
|
||||
|
||||
mutex_lock(&cgroup_mutex);
|
||||
/* snoop agent path and exit early if empty */
|
||||
if (!cgrp->root->release_agent_path[0])
|
||||
return;
|
||||
|
||||
/* prepare argument buffers */
|
||||
pathbuf = kmalloc(PATH_MAX, GFP_KERNEL);
|
||||
agentbuf = kstrdup(cgrp->root->release_agent_path, GFP_KERNEL);
|
||||
agentbuf = kmalloc(PATH_MAX, GFP_KERNEL);
|
||||
if (!pathbuf || !agentbuf)
|
||||
goto out;
|
||||
goto out_free;
|
||||
|
||||
spin_lock_irq(&css_set_lock);
|
||||
ret = cgroup_path_ns_locked(cgrp, pathbuf, PATH_MAX, &init_cgroup_ns);
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
spin_lock(&release_agent_path_lock);
|
||||
strlcpy(agentbuf, cgrp->root->release_agent_path, PATH_MAX);
|
||||
spin_unlock(&release_agent_path_lock);
|
||||
if (!agentbuf[0])
|
||||
goto out_free;
|
||||
|
||||
ret = cgroup_path_ns(cgrp, pathbuf, PATH_MAX, &init_cgroup_ns);
|
||||
if (ret < 0 || ret >= PATH_MAX)
|
||||
goto out;
|
||||
goto out_free;
|
||||
|
||||
argv[0] = agentbuf;
|
||||
argv[1] = pathbuf;
|
||||
@@ -802,11 +805,7 @@ void cgroup1_release_agent(struct work_struct *work)
|
||||
envp[1] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
|
||||
envp[2] = NULL;
|
||||
|
||||
mutex_unlock(&cgroup_mutex);
|
||||
call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
|
||||
goto out_free;
|
||||
out:
|
||||
mutex_unlock(&cgroup_mutex);
|
||||
out_free:
|
||||
kfree(agentbuf);
|
||||
kfree(pathbuf);
|
||||
@@ -887,7 +886,7 @@ enum cgroup1_param {
|
||||
Opt_xattr,
|
||||
};
|
||||
|
||||
static const struct fs_parameter_spec cgroup1_param_specs[] = {
|
||||
const struct fs_parameter_spec cgroup1_fs_parameters[] = {
|
||||
fsparam_flag ("all", Opt_all),
|
||||
fsparam_flag ("clone_children", Opt_clone_children),
|
||||
fsparam_flag ("cpuset_v2_mode", Opt_cpuset_v2_mode),
|
||||
@@ -899,11 +898,6 @@ static const struct fs_parameter_spec cgroup1_param_specs[] = {
|
||||
{}
|
||||
};
|
||||
|
||||
const struct fs_parameter_description cgroup1_fs_parameters = {
|
||||
.name = "cgroup1",
|
||||
.specs = cgroup1_param_specs,
|
||||
};
|
||||
|
||||
int cgroup1_parse_param(struct fs_context *fc, struct fs_parameter *param)
|
||||
{
|
||||
struct cgroup_fs_context *ctx = cgroup_fc2context(fc);
|
||||
@@ -911,7 +905,7 @@ int cgroup1_parse_param(struct fs_context *fc, struct fs_parameter *param)
|
||||
struct fs_parse_result result;
|
||||
int opt, i;
|
||||
|
||||
opt = fs_parse(fc, &cgroup1_fs_parameters, param, &result);
|
||||
opt = fs_parse(fc, cgroup1_fs_parameters, param, &result);
|
||||
if (opt == -ENOPARAM) {
|
||||
if (strcmp(param->key, "source") == 0) {
|
||||
fc->source = param->string;
|
||||
@@ -924,7 +918,7 @@ int cgroup1_parse_param(struct fs_context *fc, struct fs_parameter *param)
|
||||
ctx->subsys_mask |= (1 << i);
|
||||
return 0;
|
||||
}
|
||||
return cg_invalf(fc, "cgroup1: Unknown subsys name '%s'", param->key);
|
||||
return invalfc(fc, "Unknown subsys name '%s'", param->key);
|
||||
}
|
||||
if (opt < 0)
|
||||
return opt;
|
||||
@@ -952,7 +946,7 @@ int cgroup1_parse_param(struct fs_context *fc, struct fs_parameter *param)
|
||||
case Opt_release_agent:
|
||||
/* Specifying two release agents is forbidden */
|
||||
if (ctx->release_agent)
|
||||
return cg_invalf(fc, "cgroup1: release_agent respecified");
|
||||
return invalfc(fc, "release_agent respecified");
|
||||
ctx->release_agent = param->string;
|
||||
param->string = NULL;
|
||||
break;
|
||||
@@ -962,9 +956,9 @@ int cgroup1_parse_param(struct fs_context *fc, struct fs_parameter *param)
|
||||
return -ENOENT;
|
||||
/* Can't specify an empty name */
|
||||
if (!param->size)
|
||||
return cg_invalf(fc, "cgroup1: Empty name");
|
||||
return invalfc(fc, "Empty name");
|
||||
if (param->size > MAX_CGROUP_ROOT_NAMELEN - 1)
|
||||
return cg_invalf(fc, "cgroup1: Name too long");
|
||||
return invalfc(fc, "Name too long");
|
||||
/* Must match [\w.-]+ */
|
||||
for (i = 0; i < param->size; i++) {
|
||||
char c = param->string[i];
|
||||
@@ -972,11 +966,11 @@ int cgroup1_parse_param(struct fs_context *fc, struct fs_parameter *param)
|
||||
continue;
|
||||
if ((c == '.') || (c == '-') || (c == '_'))
|
||||
continue;
|
||||
return cg_invalf(fc, "cgroup1: Invalid name");
|
||||
return invalfc(fc, "Invalid name");
|
||||
}
|
||||
/* Specifying two names is forbidden */
|
||||
if (ctx->name)
|
||||
return cg_invalf(fc, "cgroup1: name respecified");
|
||||
return invalfc(fc, "name respecified");
|
||||
ctx->name = param->string;
|
||||
param->string = NULL;
|
||||
break;
|
||||
@@ -1011,7 +1005,7 @@ static int check_cgroupfs_options(struct fs_context *fc)
|
||||
if (ctx->all_ss) {
|
||||
/* Mutually exclusive option 'all' + subsystem name */
|
||||
if (ctx->subsys_mask)
|
||||
return cg_invalf(fc, "cgroup1: subsys name conflicts with all");
|
||||
return invalfc(fc, "subsys name conflicts with all");
|
||||
/* 'all' => select all the subsystems */
|
||||
ctx->subsys_mask = enabled;
|
||||
}
|
||||
@@ -1021,7 +1015,7 @@ static int check_cgroupfs_options(struct fs_context *fc)
|
||||
* empty hierarchies must have a name).
|
||||
*/
|
||||
if (!ctx->subsys_mask && !ctx->name)
|
||||
return cg_invalf(fc, "cgroup1: Need name or subsystem set");
|
||||
return invalfc(fc, "Need name or subsystem set");
|
||||
|
||||
/*
|
||||
* Option noprefix was introduced just for backward compatibility
|
||||
@@ -1029,11 +1023,11 @@ static int check_cgroupfs_options(struct fs_context *fc)
|
||||
* the cpuset subsystem.
|
||||
*/
|
||||
if ((ctx->flags & CGRP_ROOT_NOPREFIX) && (ctx->subsys_mask & mask))
|
||||
return cg_invalf(fc, "cgroup1: noprefix used incorrectly");
|
||||
return invalfc(fc, "noprefix used incorrectly");
|
||||
|
||||
/* Can't specify "none" and some subsystems */
|
||||
if (ctx->subsys_mask && ctx->none)
|
||||
return cg_invalf(fc, "cgroup1: none used incorrectly");
|
||||
return invalfc(fc, "none used incorrectly");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1063,7 +1057,7 @@ int cgroup1_reconfigure(struct fs_context *fc)
|
||||
/* Don't allow flags or name to change at remount */
|
||||
if ((ctx->flags ^ root->flags) ||
|
||||
(ctx->name && strcmp(ctx->name, root->name))) {
|
||||
cg_invalf(fc, "option or name mismatch, new: 0x%x \"%s\", old: 0x%x \"%s\"",
|
||||
errorfc(fc, "option or name mismatch, new: 0x%x \"%s\", old: 0x%x \"%s\"",
|
||||
ctx->flags, ctx->name ?: "", root->flags, root->name);
|
||||
ret = -EINVAL;
|
||||
goto out_unlock;
|
||||
@@ -1180,7 +1174,7 @@ static int cgroup1_root_to_use(struct fs_context *fc)
|
||||
* can't create new one without subsys specification.
|
||||
*/
|
||||
if (!ctx->subsys_mask && !ctx->none)
|
||||
return cg_invalf(fc, "cgroup1: No subsys list or none specified");
|
||||
return invalfc(fc, "No subsys list or none specified");
|
||||
|
||||
/* Hierarchies may only be created in the initial cgroup namespace. */
|
||||
if (ctx->ns != &init_cgroup_ns)
|
||||
|
||||
+349
-109
@@ -1813,27 +1813,24 @@ int cgroup_show_path(struct seq_file *sf, struct kernfs_node *kf_node,
|
||||
enum cgroup2_param {
|
||||
Opt_nsdelegate,
|
||||
Opt_memory_localevents,
|
||||
Opt_memory_recursiveprot,
|
||||
nr__cgroup2_params
|
||||
};
|
||||
|
||||
static const struct fs_parameter_spec cgroup2_param_specs[] = {
|
||||
static const struct fs_parameter_spec cgroup2_fs_parameters[] = {
|
||||
fsparam_flag("nsdelegate", Opt_nsdelegate),
|
||||
fsparam_flag("memory_localevents", Opt_memory_localevents),
|
||||
fsparam_flag("memory_recursiveprot", Opt_memory_recursiveprot),
|
||||
{}
|
||||
};
|
||||
|
||||
static const struct fs_parameter_description cgroup2_fs_parameters = {
|
||||
.name = "cgroup2",
|
||||
.specs = cgroup2_param_specs,
|
||||
};
|
||||
|
||||
static int cgroup2_parse_param(struct fs_context *fc, struct fs_parameter *param)
|
||||
{
|
||||
struct cgroup_fs_context *ctx = cgroup_fc2context(fc);
|
||||
struct fs_parse_result result;
|
||||
int opt;
|
||||
|
||||
opt = fs_parse(fc, &cgroup2_fs_parameters, param, &result);
|
||||
opt = fs_parse(fc, cgroup2_fs_parameters, param, &result);
|
||||
if (opt < 0)
|
||||
return opt;
|
||||
|
||||
@@ -1844,6 +1841,9 @@ static int cgroup2_parse_param(struct fs_context *fc, struct fs_parameter *param
|
||||
case Opt_memory_localevents:
|
||||
ctx->flags |= CGRP_ROOT_MEMORY_LOCAL_EVENTS;
|
||||
return 0;
|
||||
case Opt_memory_recursiveprot:
|
||||
ctx->flags |= CGRP_ROOT_MEMORY_RECURSIVE_PROT;
|
||||
return 0;
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
@@ -1860,6 +1860,11 @@ static void apply_cgroup_root_flags(unsigned int root_flags)
|
||||
cgrp_dfl_root.flags |= CGRP_ROOT_MEMORY_LOCAL_EVENTS;
|
||||
else
|
||||
cgrp_dfl_root.flags &= ~CGRP_ROOT_MEMORY_LOCAL_EVENTS;
|
||||
|
||||
if (root_flags & CGRP_ROOT_MEMORY_RECURSIVE_PROT)
|
||||
cgrp_dfl_root.flags |= CGRP_ROOT_MEMORY_RECURSIVE_PROT;
|
||||
else
|
||||
cgrp_dfl_root.flags &= ~CGRP_ROOT_MEMORY_RECURSIVE_PROT;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1869,6 +1874,8 @@ static int cgroup_show_options(struct seq_file *seq, struct kernfs_root *kf_root
|
||||
seq_puts(seq, ",nsdelegate");
|
||||
if (cgrp_dfl_root.flags & CGRP_ROOT_MEMORY_LOCAL_EVENTS)
|
||||
seq_puts(seq, ",memory_localevents");
|
||||
if (cgrp_dfl_root.flags & CGRP_ROOT_MEMORY_RECURSIVE_PROT)
|
||||
seq_puts(seq, ",memory_recursiveprot");
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -1959,7 +1966,8 @@ int cgroup_setup_root(struct cgroup_root *root, u16 ss_mask)
|
||||
|
||||
root->kf_root = kernfs_create_root(kf_sops,
|
||||
KERNFS_ROOT_CREATE_DEACTIVATED |
|
||||
KERNFS_ROOT_SUPPORT_EXPORTOP,
|
||||
KERNFS_ROOT_SUPPORT_EXPORTOP |
|
||||
KERNFS_ROOT_SUPPORT_USER_XATTR,
|
||||
root_cgrp);
|
||||
if (IS_ERR(root->kf_root)) {
|
||||
ret = PTR_ERR(root->kf_root);
|
||||
@@ -2156,7 +2164,7 @@ static void cgroup_kill_sb(struct super_block *sb)
|
||||
struct file_system_type cgroup_fs_type = {
|
||||
.name = "cgroup",
|
||||
.init_fs_context = cgroup_init_fs_context,
|
||||
.parameters = &cgroup1_fs_parameters,
|
||||
.parameters = cgroup1_fs_parameters,
|
||||
.kill_sb = cgroup_kill_sb,
|
||||
.fs_flags = FS_USERNS_MOUNT,
|
||||
};
|
||||
@@ -2164,7 +2172,7 @@ struct file_system_type cgroup_fs_type = {
|
||||
static struct file_system_type cgroup2_fs_type = {
|
||||
.name = "cgroup2",
|
||||
.init_fs_context = cgroup_init_fs_context,
|
||||
.parameters = &cgroup2_fs_parameters,
|
||||
.parameters = cgroup2_fs_parameters,
|
||||
.kill_sb = cgroup_kill_sb,
|
||||
.fs_flags = FS_USERNS_MOUNT,
|
||||
};
|
||||
@@ -2719,11 +2727,7 @@ int cgroup_attach_task(struct cgroup *dst_cgrp, struct task_struct *leader,
|
||||
{
|
||||
DEFINE_CGROUP_MGCTX(mgctx);
|
||||
struct task_struct *task;
|
||||
int ret;
|
||||
|
||||
ret = cgroup_migrate_vet_dst(dst_cgrp);
|
||||
if (ret)
|
||||
return ret;
|
||||
int ret = 0;
|
||||
|
||||
/* look up all src csets */
|
||||
spin_lock_irq(&css_set_lock);
|
||||
@@ -3547,21 +3551,21 @@ static int cpu_stat_show(struct seq_file *seq, void *v)
|
||||
static int cgroup_io_pressure_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct cgroup *cgrp = seq_css(seq)->cgroup;
|
||||
struct psi_group *psi = cgroup_id(cgrp) == 1 ? &psi_system : &cgrp->psi;
|
||||
struct psi_group *psi = cgroup_ino(cgrp) == 1 ? &psi_system : &cgrp->psi;
|
||||
|
||||
return psi_show(seq, psi, PSI_IO);
|
||||
}
|
||||
static int cgroup_memory_pressure_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct cgroup *cgrp = seq_css(seq)->cgroup;
|
||||
struct psi_group *psi = cgroup_id(cgrp) == 1 ? &psi_system : &cgrp->psi;
|
||||
struct psi_group *psi = cgroup_ino(cgrp) == 1 ? &psi_system : &cgrp->psi;
|
||||
|
||||
return psi_show(seq, psi, PSI_MEM);
|
||||
}
|
||||
static int cgroup_cpu_pressure_show(struct seq_file *seq, void *v)
|
||||
{
|
||||
struct cgroup *cgrp = seq_css(seq)->cgroup;
|
||||
struct psi_group *psi = cgroup_id(cgrp) == 1 ? &psi_system : &cgrp->psi;
|
||||
struct psi_group *psi = cgroup_ino(cgrp) == 1 ? &psi_system : &cgrp->psi;
|
||||
|
||||
return psi_show(seq, psi, PSI_CPU);
|
||||
}
|
||||
@@ -4153,7 +4157,8 @@ struct cgroup_subsys_state *css_next_child(struct cgroup_subsys_state *pos,
|
||||
} else if (likely(!(pos->flags & CSS_RELEASED))) {
|
||||
next = list_entry_rcu(pos->sibling.next, struct cgroup_subsys_state, sibling);
|
||||
} else {
|
||||
list_for_each_entry_rcu(next, &parent->children, sibling)
|
||||
list_for_each_entry_rcu(next, &parent->children, sibling,
|
||||
lockdep_is_held(&cgroup_mutex))
|
||||
if (next->serial_nr > pos->serial_nr)
|
||||
break;
|
||||
}
|
||||
@@ -4396,25 +4401,24 @@ static void css_task_iter_advance_css_set(struct css_task_iter *it)
|
||||
|
||||
lockdep_assert_held(&css_set_lock);
|
||||
|
||||
/* Advance to the next non-empty css_set */
|
||||
do {
|
||||
cset = css_task_iter_next_css_set(it);
|
||||
if (!cset) {
|
||||
it->task_pos = NULL;
|
||||
return;
|
||||
/* Advance to the next non-empty css_set and find first non-empty tasks list*/
|
||||
while ((cset = css_task_iter_next_css_set(it))) {
|
||||
if (!list_empty(&cset->tasks)) {
|
||||
it->cur_tasks_head = &cset->tasks;
|
||||
break;
|
||||
} else if (!list_empty(&cset->mg_tasks)) {
|
||||
it->cur_tasks_head = &cset->mg_tasks;
|
||||
break;
|
||||
} else if (!list_empty(&cset->dying_tasks)) {
|
||||
it->cur_tasks_head = &cset->dying_tasks;
|
||||
break;
|
||||
}
|
||||
} while (!css_set_populated(cset) && list_empty(&cset->dying_tasks));
|
||||
|
||||
if (!list_empty(&cset->tasks))
|
||||
it->task_pos = cset->tasks.next;
|
||||
else if (!list_empty(&cset->mg_tasks))
|
||||
it->task_pos = cset->mg_tasks.next;
|
||||
else
|
||||
it->task_pos = cset->dying_tasks.next;
|
||||
|
||||
it->tasks_head = &cset->tasks;
|
||||
it->mg_tasks_head = &cset->mg_tasks;
|
||||
it->dying_tasks_head = &cset->dying_tasks;
|
||||
}
|
||||
if (!cset) {
|
||||
it->task_pos = NULL;
|
||||
return;
|
||||
}
|
||||
it->task_pos = it->cur_tasks_head->next;
|
||||
|
||||
/*
|
||||
* We don't keep css_sets locked across iteration steps and thus
|
||||
@@ -4459,20 +4463,24 @@ static void css_task_iter_advance(struct css_task_iter *it)
|
||||
repeat:
|
||||
if (it->task_pos) {
|
||||
/*
|
||||
* Advance iterator to find next entry. cset->tasks is
|
||||
* consumed first and then ->mg_tasks. After ->mg_tasks,
|
||||
* we move onto the next cset.
|
||||
* Advance iterator to find next entry. We go through cset
|
||||
* tasks, mg_tasks and dying_tasks, when consumed we move onto
|
||||
* the next cset.
|
||||
*/
|
||||
if (it->flags & CSS_TASK_ITER_SKIPPED)
|
||||
it->flags &= ~CSS_TASK_ITER_SKIPPED;
|
||||
else
|
||||
it->task_pos = it->task_pos->next;
|
||||
|
||||
if (it->task_pos == it->tasks_head)
|
||||
it->task_pos = it->mg_tasks_head->next;
|
||||
if (it->task_pos == it->mg_tasks_head)
|
||||
it->task_pos = it->dying_tasks_head->next;
|
||||
if (it->task_pos == it->dying_tasks_head)
|
||||
if (it->task_pos == &it->cur_cset->tasks) {
|
||||
it->cur_tasks_head = &it->cur_cset->mg_tasks;
|
||||
it->task_pos = it->cur_tasks_head->next;
|
||||
}
|
||||
if (it->task_pos == &it->cur_cset->mg_tasks) {
|
||||
it->cur_tasks_head = &it->cur_cset->dying_tasks;
|
||||
it->task_pos = it->cur_tasks_head->next;
|
||||
}
|
||||
if (it->task_pos == &it->cur_cset->dying_tasks)
|
||||
css_task_iter_advance_css_set(it);
|
||||
} else {
|
||||
/* called from start, proceed to the first cset */
|
||||
@@ -4490,11 +4498,12 @@ repeat:
|
||||
goto repeat;
|
||||
|
||||
/* and dying leaders w/o live member threads */
|
||||
if (!atomic_read(&task->signal->live))
|
||||
if (it->cur_tasks_head == &it->cur_cset->dying_tasks &&
|
||||
!atomic_read(&task->signal->live))
|
||||
goto repeat;
|
||||
} else {
|
||||
/* skip all dying ones */
|
||||
if (task->flags & PF_EXITING)
|
||||
if (it->cur_tasks_head == &it->cur_cset->dying_tasks)
|
||||
goto repeat;
|
||||
}
|
||||
}
|
||||
@@ -4600,6 +4609,9 @@ static void *cgroup_procs_next(struct seq_file *s, void *v, loff_t *pos)
|
||||
struct kernfs_open_file *of = s->private;
|
||||
struct css_task_iter *it = of->priv;
|
||||
|
||||
if (pos)
|
||||
(*pos)++;
|
||||
|
||||
return css_task_iter_next(it);
|
||||
}
|
||||
|
||||
@@ -4615,7 +4627,7 @@ static void *__cgroup_procs_start(struct seq_file *s, loff_t *pos,
|
||||
* from position 0, so we can simply keep iterating on !0 *pos.
|
||||
*/
|
||||
if (!it) {
|
||||
if (WARN_ON_ONCE((*pos)++))
|
||||
if (WARN_ON_ONCE((*pos)))
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
it = kzalloc(sizeof(*it), GFP_KERNEL);
|
||||
@@ -4623,10 +4635,11 @@ static void *__cgroup_procs_start(struct seq_file *s, loff_t *pos,
|
||||
return ERR_PTR(-ENOMEM);
|
||||
of->priv = it;
|
||||
css_task_iter_start(&cgrp->self, iter_flags, it);
|
||||
} else if (!(*pos)++) {
|
||||
} else if (!(*pos)) {
|
||||
css_task_iter_end(it);
|
||||
css_task_iter_start(&cgrp->self, iter_flags, it);
|
||||
}
|
||||
} else
|
||||
return it->cur_task;
|
||||
|
||||
return cgroup_procs_next(s, NULL, NULL);
|
||||
}
|
||||
@@ -4654,13 +4667,28 @@ static int cgroup_procs_show(struct seq_file *s, void *v)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cgroup_may_write(const struct cgroup *cgrp, struct super_block *sb)
|
||||
{
|
||||
int ret;
|
||||
struct inode *inode;
|
||||
|
||||
lockdep_assert_held(&cgroup_mutex);
|
||||
|
||||
inode = kernfs_get_inode(sb, cgrp->procs_file.kn);
|
||||
if (!inode)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = inode_permission(inode, MAY_WRITE);
|
||||
iput(inode);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int cgroup_procs_write_permission(struct cgroup *src_cgrp,
|
||||
struct cgroup *dst_cgrp,
|
||||
struct super_block *sb)
|
||||
{
|
||||
struct cgroup_namespace *ns = current->nsproxy->cgroup_ns;
|
||||
struct cgroup *com_cgrp = src_cgrp;
|
||||
struct inode *inode;
|
||||
int ret;
|
||||
|
||||
lockdep_assert_held(&cgroup_mutex);
|
||||
@@ -4670,12 +4698,7 @@ static int cgroup_procs_write_permission(struct cgroup *src_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);
|
||||
ret = cgroup_may_write(com_cgrp, sb);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -4691,6 +4714,26 @@ static int cgroup_procs_write_permission(struct cgroup *src_cgrp,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cgroup_attach_permissions(struct cgroup *src_cgrp,
|
||||
struct cgroup *dst_cgrp,
|
||||
struct super_block *sb, bool threadgroup)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = cgroup_procs_write_permission(src_cgrp, dst_cgrp, sb);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = cgroup_migrate_vet_dst(dst_cgrp);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (!threadgroup && (src_cgrp->dom_cgrp != dst_cgrp->dom_cgrp))
|
||||
ret = -EOPNOTSUPP;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t cgroup_procs_write(struct kernfs_open_file *of,
|
||||
char *buf, size_t nbytes, loff_t off)
|
||||
{
|
||||
@@ -4713,8 +4756,8 @@ static ssize_t cgroup_procs_write(struct kernfs_open_file *of,
|
||||
src_cgrp = task_cgroup_from_root(task, &cgrp_dfl_root);
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
|
||||
ret = cgroup_procs_write_permission(src_cgrp, dst_cgrp,
|
||||
of->file->f_path.dentry->d_sb);
|
||||
ret = cgroup_attach_permissions(src_cgrp, dst_cgrp,
|
||||
of->file->f_path.dentry->d_sb, true);
|
||||
if (ret)
|
||||
goto out_finish;
|
||||
|
||||
@@ -4758,16 +4801,11 @@ static ssize_t cgroup_threads_write(struct kernfs_open_file *of,
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
|
||||
/* thread migrations follow the cgroup.procs delegation rule */
|
||||
ret = cgroup_procs_write_permission(src_cgrp, dst_cgrp,
|
||||
of->file->f_path.dentry->d_sb);
|
||||
ret = cgroup_attach_permissions(src_cgrp, dst_cgrp,
|
||||
of->file->f_path.dentry->d_sb, false);
|
||||
if (ret)
|
||||
goto out_finish;
|
||||
|
||||
/* and must be contained in the same domain */
|
||||
ret = -EOPNOTSUPP;
|
||||
if (src_cgrp->dom_cgrp != dst_cgrp->dom_cgrp)
|
||||
goto out_finish;
|
||||
|
||||
ret = cgroup_attach_task(dst_cgrp, task, false);
|
||||
|
||||
out_finish:
|
||||
@@ -5856,8 +5894,7 @@ out:
|
||||
* @child: pointer to task_struct of forking parent process.
|
||||
*
|
||||
* A task is associated with the init_css_set until cgroup_post_fork()
|
||||
* attaches it to the parent's css_set. Empty cg_list indicates that
|
||||
* @child isn't holding reference to its css_set.
|
||||
* attaches it to the target css_set.
|
||||
*/
|
||||
void cgroup_fork(struct task_struct *child)
|
||||
{
|
||||
@@ -5865,21 +5902,172 @@ void cgroup_fork(struct task_struct *child)
|
||||
INIT_LIST_HEAD(&child->cg_list);
|
||||
}
|
||||
|
||||
static struct cgroup *cgroup_get_from_file(struct file *f)
|
||||
{
|
||||
struct cgroup_subsys_state *css;
|
||||
struct cgroup *cgrp;
|
||||
|
||||
css = css_tryget_online_from_dir(f->f_path.dentry, NULL);
|
||||
if (IS_ERR(css))
|
||||
return ERR_CAST(css);
|
||||
|
||||
cgrp = css->cgroup;
|
||||
if (!cgroup_on_dfl(cgrp)) {
|
||||
cgroup_put(cgrp);
|
||||
return ERR_PTR(-EBADF);
|
||||
}
|
||||
|
||||
return cgrp;
|
||||
}
|
||||
|
||||
/**
|
||||
* cgroup_css_set_fork - find or create a css_set for a child process
|
||||
* @kargs: the arguments passed to create the child process
|
||||
*
|
||||
* This functions finds or creates a new css_set which the child
|
||||
* process will be attached to in cgroup_post_fork(). By default,
|
||||
* the child process will be given the same css_set as its parent.
|
||||
*
|
||||
* If CLONE_INTO_CGROUP is specified this function will try to find an
|
||||
* existing css_set which includes the requested cgroup and if not create
|
||||
* a new css_set that the child will be attached to later. If this function
|
||||
* succeeds it will hold cgroup_threadgroup_rwsem on return. If
|
||||
* CLONE_INTO_CGROUP is requested this function will grab cgroup mutex
|
||||
* before grabbing cgroup_threadgroup_rwsem and will hold a reference
|
||||
* to the target cgroup.
|
||||
*/
|
||||
static int cgroup_css_set_fork(struct kernel_clone_args *kargs)
|
||||
__acquires(&cgroup_mutex) __acquires(&cgroup_threadgroup_rwsem)
|
||||
{
|
||||
int ret;
|
||||
struct cgroup *dst_cgrp = NULL;
|
||||
struct css_set *cset;
|
||||
struct super_block *sb;
|
||||
struct file *f;
|
||||
|
||||
if (kargs->flags & CLONE_INTO_CGROUP)
|
||||
mutex_lock(&cgroup_mutex);
|
||||
|
||||
cgroup_threadgroup_change_begin(current);
|
||||
|
||||
spin_lock_irq(&css_set_lock);
|
||||
cset = task_css_set(current);
|
||||
get_css_set(cset);
|
||||
spin_unlock_irq(&css_set_lock);
|
||||
|
||||
if (!(kargs->flags & CLONE_INTO_CGROUP)) {
|
||||
kargs->cset = cset;
|
||||
return 0;
|
||||
}
|
||||
|
||||
f = fget_raw(kargs->cgroup);
|
||||
if (!f) {
|
||||
ret = -EBADF;
|
||||
goto err;
|
||||
}
|
||||
sb = f->f_path.dentry->d_sb;
|
||||
|
||||
dst_cgrp = cgroup_get_from_file(f);
|
||||
if (IS_ERR(dst_cgrp)) {
|
||||
ret = PTR_ERR(dst_cgrp);
|
||||
dst_cgrp = NULL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (cgroup_is_dead(dst_cgrp)) {
|
||||
ret = -ENODEV;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify that we the target cgroup is writable for us. This is
|
||||
* usually done by the vfs layer but since we're not going through
|
||||
* the vfs layer here we need to do it "manually".
|
||||
*/
|
||||
ret = cgroup_may_write(dst_cgrp, sb);
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
ret = cgroup_attach_permissions(cset->dfl_cgrp, dst_cgrp, sb,
|
||||
!(kargs->flags & CLONE_THREAD));
|
||||
if (ret)
|
||||
goto err;
|
||||
|
||||
kargs->cset = find_css_set(cset, dst_cgrp);
|
||||
if (!kargs->cset) {
|
||||
ret = -ENOMEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
put_css_set(cset);
|
||||
fput(f);
|
||||
kargs->cgrp = dst_cgrp;
|
||||
return ret;
|
||||
|
||||
err:
|
||||
cgroup_threadgroup_change_end(current);
|
||||
mutex_unlock(&cgroup_mutex);
|
||||
if (f)
|
||||
fput(f);
|
||||
if (dst_cgrp)
|
||||
cgroup_put(dst_cgrp);
|
||||
put_css_set(cset);
|
||||
if (kargs->cset)
|
||||
put_css_set(kargs->cset);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* cgroup_css_set_put_fork - drop references we took during fork
|
||||
* @kargs: the arguments passed to create the child process
|
||||
*
|
||||
* Drop references to the prepared css_set and target cgroup if
|
||||
* CLONE_INTO_CGROUP was requested.
|
||||
*/
|
||||
static void cgroup_css_set_put_fork(struct kernel_clone_args *kargs)
|
||||
__releases(&cgroup_threadgroup_rwsem) __releases(&cgroup_mutex)
|
||||
{
|
||||
cgroup_threadgroup_change_end(current);
|
||||
|
||||
if (kargs->flags & CLONE_INTO_CGROUP) {
|
||||
struct cgroup *cgrp = kargs->cgrp;
|
||||
struct css_set *cset = kargs->cset;
|
||||
|
||||
mutex_unlock(&cgroup_mutex);
|
||||
|
||||
if (cset) {
|
||||
put_css_set(cset);
|
||||
kargs->cset = NULL;
|
||||
}
|
||||
|
||||
if (cgrp) {
|
||||
cgroup_put(cgrp);
|
||||
kargs->cgrp = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cgroup_can_fork - called on a new task before the process is exposed
|
||||
* @child: the task in question.
|
||||
* @child: the child process
|
||||
*
|
||||
* This calls the subsystem can_fork() callbacks. If the can_fork() callback
|
||||
* returns an error, the fork aborts with that error code. This allows for
|
||||
* a cgroup subsystem to conditionally allow or deny new forks.
|
||||
* This prepares a new css_set for the child process which the child will
|
||||
* be attached to in cgroup_post_fork().
|
||||
* This calls the subsystem can_fork() callbacks. If the cgroup_can_fork()
|
||||
* callback returns an error, the fork aborts with that error code. This
|
||||
* allows for a cgroup subsystem to conditionally allow or deny new forks.
|
||||
*/
|
||||
int cgroup_can_fork(struct task_struct *child)
|
||||
int cgroup_can_fork(struct task_struct *child, struct kernel_clone_args *kargs)
|
||||
{
|
||||
struct cgroup_subsys *ss;
|
||||
int i, j, ret;
|
||||
|
||||
ret = cgroup_css_set_fork(kargs);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
do_each_subsys_mask(ss, i, have_canfork_callback) {
|
||||
ret = ss->can_fork(child);
|
||||
ret = ss->can_fork(child, kargs->cset);
|
||||
if (ret)
|
||||
goto out_revert;
|
||||
} while_each_subsys_mask();
|
||||
@@ -5891,52 +6079,65 @@ out_revert:
|
||||
if (j >= i)
|
||||
break;
|
||||
if (ss->cancel_fork)
|
||||
ss->cancel_fork(child);
|
||||
ss->cancel_fork(child, kargs->cset);
|
||||
}
|
||||
|
||||
cgroup_css_set_put_fork(kargs);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* cgroup_cancel_fork - called if a fork failed after cgroup_can_fork()
|
||||
* @child: the task in question
|
||||
* @child: the child process
|
||||
* @kargs: the arguments passed to create the child process
|
||||
*
|
||||
* This calls the cancel_fork() callbacks if a fork failed *after*
|
||||
* cgroup_can_fork() succeded.
|
||||
* cgroup_can_fork() succeded and cleans up references we took to
|
||||
* prepare a new css_set for the child process in cgroup_can_fork().
|
||||
*/
|
||||
void cgroup_cancel_fork(struct task_struct *child)
|
||||
void cgroup_cancel_fork(struct task_struct *child,
|
||||
struct kernel_clone_args *kargs)
|
||||
{
|
||||
struct cgroup_subsys *ss;
|
||||
int i;
|
||||
|
||||
for_each_subsys(ss, i)
|
||||
if (ss->cancel_fork)
|
||||
ss->cancel_fork(child);
|
||||
ss->cancel_fork(child, kargs->cset);
|
||||
|
||||
cgroup_css_set_put_fork(kargs);
|
||||
}
|
||||
|
||||
/**
|
||||
* cgroup_post_fork - called on a new task after adding it to the task list
|
||||
* @child: the task in question
|
||||
* cgroup_post_fork - finalize cgroup setup for the child process
|
||||
* @child: the child process
|
||||
*
|
||||
* Adds the task to the list running through its css_set if necessary and
|
||||
* call the subsystem fork() callbacks. Has to be after the task is
|
||||
* visible on the task list in case we race with the first call to
|
||||
* cgroup_task_iter_start() - to guarantee that the new task ends up on its
|
||||
* list.
|
||||
* Attach the child process to its css_set calling the subsystem fork()
|
||||
* callbacks.
|
||||
*/
|
||||
void cgroup_post_fork(struct task_struct *child)
|
||||
void cgroup_post_fork(struct task_struct *child,
|
||||
struct kernel_clone_args *kargs)
|
||||
__releases(&cgroup_threadgroup_rwsem) __releases(&cgroup_mutex)
|
||||
{
|
||||
struct cgroup_subsys *ss;
|
||||
struct css_set *cset;
|
||||
int i;
|
||||
|
||||
cset = kargs->cset;
|
||||
kargs->cset = NULL;
|
||||
|
||||
spin_lock_irq(&css_set_lock);
|
||||
|
||||
WARN_ON_ONCE(!list_empty(&child->cg_list));
|
||||
cset = task_css_set(current); /* current is @child's parent */
|
||||
get_css_set(cset);
|
||||
cset->nr_tasks++;
|
||||
css_set_move_task(child, NULL, cset, false);
|
||||
/* init tasks are special, only link regular threads */
|
||||
if (likely(child->pid)) {
|
||||
WARN_ON_ONCE(!list_empty(&child->cg_list));
|
||||
cset->nr_tasks++;
|
||||
css_set_move_task(child, NULL, cset, false);
|
||||
} else {
|
||||
put_css_set(cset);
|
||||
cset = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the cgroup has to be frozen, the new task has too. Let's set
|
||||
@@ -5967,6 +6168,17 @@ void cgroup_post_fork(struct task_struct *child)
|
||||
do_each_subsys_mask(ss, i, have_fork_callback) {
|
||||
ss->fork(child);
|
||||
} while_each_subsys_mask();
|
||||
|
||||
/* Make the new cset the root_cset of the new cgroup namespace. */
|
||||
if (kargs->flags & CLONE_NEWCGROUP) {
|
||||
struct css_set *rcset = child->nsproxy->cgroup_ns->root_cset;
|
||||
|
||||
get_css_set(cset);
|
||||
child->nsproxy->cgroup_ns->root_cset = cset;
|
||||
put_css_set(rcset);
|
||||
}
|
||||
|
||||
cgroup_css_set_put_fork(kargs);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -6153,7 +6365,6 @@ EXPORT_SYMBOL_GPL(cgroup_get_from_path);
|
||||
*/
|
||||
struct cgroup *cgroup_get_from_fd(int fd)
|
||||
{
|
||||
struct cgroup_subsys_state *css;
|
||||
struct cgroup *cgrp;
|
||||
struct file *f;
|
||||
|
||||
@@ -6161,17 +6372,8 @@ struct cgroup *cgroup_get_from_fd(int fd)
|
||||
if (!f)
|
||||
return ERR_PTR(-EBADF);
|
||||
|
||||
css = css_tryget_online_from_dir(f->f_path.dentry, NULL);
|
||||
cgrp = cgroup_get_from_file(f);
|
||||
fput(f);
|
||||
if (IS_ERR(css))
|
||||
return ERR_CAST(css);
|
||||
|
||||
cgrp = css->cgroup;
|
||||
if (!cgroup_on_dfl(cgrp)) {
|
||||
cgroup_put(cgrp);
|
||||
return ERR_PTR(-EBADF);
|
||||
}
|
||||
|
||||
return cgrp;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cgroup_get_from_fd);
|
||||
@@ -6260,6 +6462,10 @@ void cgroup_sk_alloc(struct sock_cgroup_data *skcd)
|
||||
return;
|
||||
}
|
||||
|
||||
/* Don't associate the sock with unrelated interrupted task's cgroup. */
|
||||
if (in_interrupt())
|
||||
return;
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
while (true) {
|
||||
@@ -6288,27 +6494,58 @@ void cgroup_sk_free(struct sock_cgroup_data *skcd)
|
||||
#endif /* CONFIG_SOCK_CGROUP_DATA */
|
||||
|
||||
#ifdef CONFIG_CGROUP_BPF
|
||||
int cgroup_bpf_attach(struct cgroup *cgrp, struct bpf_prog *prog,
|
||||
struct bpf_prog *replace_prog, enum bpf_attach_type type,
|
||||
int cgroup_bpf_attach(struct cgroup *cgrp,
|
||||
struct bpf_prog *prog, struct bpf_prog *replace_prog,
|
||||
struct bpf_cgroup_link *link,
|
||||
enum bpf_attach_type type,
|
||||
u32 flags)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&cgroup_mutex);
|
||||
ret = __cgroup_bpf_attach(cgrp, prog, replace_prog, type, flags);
|
||||
ret = __cgroup_bpf_attach(cgrp, prog, replace_prog, link, type, flags);
|
||||
mutex_unlock(&cgroup_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int cgroup_bpf_replace(struct bpf_link *link, struct bpf_prog *old_prog,
|
||||
struct bpf_prog *new_prog)
|
||||
{
|
||||
struct bpf_cgroup_link *cg_link;
|
||||
int ret;
|
||||
|
||||
if (link->ops != &bpf_cgroup_link_lops)
|
||||
return -EINVAL;
|
||||
|
||||
cg_link = container_of(link, struct bpf_cgroup_link, link);
|
||||
|
||||
mutex_lock(&cgroup_mutex);
|
||||
/* link might have been auto-released by dying cgroup, so fail */
|
||||
if (!cg_link->cgroup) {
|
||||
ret = -EINVAL;
|
||||
goto out_unlock;
|
||||
}
|
||||
if (old_prog && link->prog != old_prog) {
|
||||
ret = -EPERM;
|
||||
goto out_unlock;
|
||||
}
|
||||
ret = __cgroup_bpf_replace(cg_link->cgroup, cg_link, new_prog);
|
||||
out_unlock:
|
||||
mutex_unlock(&cgroup_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int cgroup_bpf_detach(struct cgroup *cgrp, struct bpf_prog *prog,
|
||||
enum bpf_attach_type type, u32 flags)
|
||||
enum bpf_attach_type type)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&cgroup_mutex);
|
||||
ret = __cgroup_bpf_detach(cgrp, prog, type);
|
||||
ret = __cgroup_bpf_detach(cgrp, prog, NULL, type);
|
||||
mutex_unlock(&cgroup_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int cgroup_bpf_query(struct cgroup *cgrp, const union bpf_attr *attr,
|
||||
union bpf_attr __user *uattr)
|
||||
{
|
||||
@@ -6366,7 +6603,10 @@ static struct kobj_attribute cgroup_delegate_attr = __ATTR_RO(delegate);
|
||||
static ssize_t features_show(struct kobject *kobj, struct kobj_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
return snprintf(buf, PAGE_SIZE, "nsdelegate\nmemory_localevents\n");
|
||||
return snprintf(buf, PAGE_SIZE,
|
||||
"nsdelegate\n"
|
||||
"memory_localevents\n"
|
||||
"memory_recursiveprot\n");
|
||||
}
|
||||
static struct kobj_attribute cgroup_features_attr = __ATTR_RO(features);
|
||||
|
||||
|
||||
@@ -358,8 +358,12 @@ static DECLARE_WORK(cpuset_hotplug_work, cpuset_hotplug_workfn);
|
||||
static DECLARE_WAIT_QUEUE_HEAD(cpuset_attach_wq);
|
||||
|
||||
/*
|
||||
* Cgroup v2 behavior is used when on default hierarchy or the
|
||||
* cgroup_v2_mode flag is set.
|
||||
* Cgroup v2 behavior is used on the "cpus" and "mems" control files when
|
||||
* on default hierarchy or when the cpuset_v2_mode flag is set by mounting
|
||||
* the v1 cpuset cgroup filesystem with the "cpuset_v2_mode" mount option.
|
||||
* With v2 behavior, "cpus" and "mems" are always what the users have
|
||||
* requested and won't be changed by hotplug events. Only the effective
|
||||
* cpus or mems will be affected.
|
||||
*/
|
||||
static inline bool is_in_v2_mode(void)
|
||||
{
|
||||
|
||||
+11
-4
@@ -33,6 +33,7 @@
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/cgroup.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/sched/task.h>
|
||||
|
||||
#define PIDS_MAX (PID_MAX_LIMIT + 1ULL)
|
||||
#define PIDS_MAX_STR "max"
|
||||
@@ -214,13 +215,16 @@ static void pids_cancel_attach(struct cgroup_taskset *tset)
|
||||
* task_css_check(true) in pids_can_fork() and pids_cancel_fork() relies
|
||||
* on cgroup_threadgroup_change_begin() held by the copy_process().
|
||||
*/
|
||||
static int pids_can_fork(struct task_struct *task)
|
||||
static int pids_can_fork(struct task_struct *task, struct css_set *cset)
|
||||
{
|
||||
struct cgroup_subsys_state *css;
|
||||
struct pids_cgroup *pids;
|
||||
int err;
|
||||
|
||||
css = task_css_check(current, pids_cgrp_id, true);
|
||||
if (cset)
|
||||
css = cset->subsys[pids_cgrp_id];
|
||||
else
|
||||
css = task_css_check(current, pids_cgrp_id, true);
|
||||
pids = css_pids(css);
|
||||
err = pids_try_charge(pids, 1);
|
||||
if (err) {
|
||||
@@ -235,12 +239,15 @@ static int pids_can_fork(struct task_struct *task)
|
||||
return err;
|
||||
}
|
||||
|
||||
static void pids_cancel_fork(struct task_struct *task)
|
||||
static void pids_cancel_fork(struct task_struct *task, struct css_set *cset)
|
||||
{
|
||||
struct cgroup_subsys_state *css;
|
||||
struct pids_cgroup *pids;
|
||||
|
||||
css = task_css_check(current, pids_cgrp_id, true);
|
||||
if (cset)
|
||||
css = cset->subsys[pids_cgrp_id];
|
||||
else
|
||||
css = task_css_check(current, pids_cgrp_id, true);
|
||||
pids = css_pids(css);
|
||||
pids_uncharge(pids, 1);
|
||||
}
|
||||
|
||||
@@ -26,70 +26,6 @@
|
||||
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
static int __compat_get_timeval(struct timeval *tv, const struct old_timeval32 __user *ctv)
|
||||
{
|
||||
return (!access_ok(ctv, sizeof(*ctv)) ||
|
||||
__get_user(tv->tv_sec, &ctv->tv_sec) ||
|
||||
__get_user(tv->tv_usec, &ctv->tv_usec)) ? -EFAULT : 0;
|
||||
}
|
||||
|
||||
static int __compat_put_timeval(const struct timeval *tv, struct old_timeval32 __user *ctv)
|
||||
{
|
||||
return (!access_ok(ctv, sizeof(*ctv)) ||
|
||||
__put_user(tv->tv_sec, &ctv->tv_sec) ||
|
||||
__put_user(tv->tv_usec, &ctv->tv_usec)) ? -EFAULT : 0;
|
||||
}
|
||||
|
||||
static int __compat_get_timespec(struct timespec *ts, const struct old_timespec32 __user *cts)
|
||||
{
|
||||
return (!access_ok(cts, sizeof(*cts)) ||
|
||||
__get_user(ts->tv_sec, &cts->tv_sec) ||
|
||||
__get_user(ts->tv_nsec, &cts->tv_nsec)) ? -EFAULT : 0;
|
||||
}
|
||||
|
||||
static int __compat_put_timespec(const struct timespec *ts, struct old_timespec32 __user *cts)
|
||||
{
|
||||
return (!access_ok(cts, sizeof(*cts)) ||
|
||||
__put_user(ts->tv_sec, &cts->tv_sec) ||
|
||||
__put_user(ts->tv_nsec, &cts->tv_nsec)) ? -EFAULT : 0;
|
||||
}
|
||||
|
||||
int compat_get_timeval(struct timeval *tv, const void __user *utv)
|
||||
{
|
||||
if (COMPAT_USE_64BIT_TIME)
|
||||
return copy_from_user(tv, utv, sizeof(*tv)) ? -EFAULT : 0;
|
||||
else
|
||||
return __compat_get_timeval(tv, utv);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(compat_get_timeval);
|
||||
|
||||
int compat_put_timeval(const struct timeval *tv, void __user *utv)
|
||||
{
|
||||
if (COMPAT_USE_64BIT_TIME)
|
||||
return copy_to_user(utv, tv, sizeof(*tv)) ? -EFAULT : 0;
|
||||
else
|
||||
return __compat_put_timeval(tv, utv);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(compat_put_timeval);
|
||||
|
||||
int compat_get_timespec(struct timespec *ts, const void __user *uts)
|
||||
{
|
||||
if (COMPAT_USE_64BIT_TIME)
|
||||
return copy_from_user(ts, uts, sizeof(*ts)) ? -EFAULT : 0;
|
||||
else
|
||||
return __compat_get_timespec(ts, uts);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(compat_get_timespec);
|
||||
|
||||
int compat_put_timespec(const struct timespec *ts, void __user *uts)
|
||||
{
|
||||
if (COMPAT_USE_64BIT_TIME)
|
||||
return copy_to_user(uts, ts, sizeof(*ts)) ? -EFAULT : 0;
|
||||
else
|
||||
return __compat_put_timespec(ts, uts);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(compat_put_timespec);
|
||||
|
||||
#ifdef __ARCH_WANT_SYS_SIGPROCMASK
|
||||
|
||||
/*
|
||||
|
||||
+4
-5
@@ -47,10 +47,9 @@ ikconfig_read_current(struct file *file, char __user *buf,
|
||||
&kernel_config_data);
|
||||
}
|
||||
|
||||
static const struct file_operations ikconfig_file_ops = {
|
||||
.owner = THIS_MODULE,
|
||||
.read = ikconfig_read_current,
|
||||
.llseek = default_llseek,
|
||||
static const struct proc_ops config_gz_proc_ops = {
|
||||
.proc_read = ikconfig_read_current,
|
||||
.proc_lseek = default_llseek,
|
||||
};
|
||||
|
||||
static int __init ikconfig_init(void)
|
||||
@@ -59,7 +58,7 @@ static int __init ikconfig_init(void)
|
||||
|
||||
/* create the current config file */
|
||||
entry = proc_create("config.gz", S_IFREG | S_IRUGO, NULL,
|
||||
&ikconfig_file_ops);
|
||||
&config_gz_proc_ops);
|
||||
if (!entry)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ CONFIG_CC_OPTIMIZE_FOR_SIZE=y
|
||||
CONFIG_KERNEL_XZ=y
|
||||
# CONFIG_KERNEL_LZO is not set
|
||||
# CONFIG_KERNEL_LZ4 is not set
|
||||
CONFIG_OPTIMIZE_INLINING=y
|
||||
# CONFIG_SLAB is not set
|
||||
# CONFIG_SLUB is not set
|
||||
CONFIG_SLOB=y
|
||||
|
||||
@@ -198,11 +198,13 @@ void __init context_tracking_cpu_set(int cpu)
|
||||
if (initialized)
|
||||
return;
|
||||
|
||||
#ifdef CONFIG_HAVE_TIF_NOHZ
|
||||
/*
|
||||
* Set TIF_NOHZ to init/0 and let it propagate to all tasks through fork
|
||||
* This assumes that init is the only task at this early boot stage.
|
||||
*/
|
||||
set_tsk_thread_flag(&init_task, TIF_NOHZ);
|
||||
#endif
|
||||
WARN_ON_ONCE(!tasklist_empty());
|
||||
|
||||
initialized = true;
|
||||
|
||||
+131
-16
@@ -331,12 +331,12 @@ void lockdep_assert_cpus_held(void)
|
||||
|
||||
static void lockdep_acquire_cpus_lock(void)
|
||||
{
|
||||
rwsem_acquire(&cpu_hotplug_lock.rw_sem.dep_map, 0, 0, _THIS_IP_);
|
||||
rwsem_acquire(&cpu_hotplug_lock.dep_map, 0, 0, _THIS_IP_);
|
||||
}
|
||||
|
||||
static void lockdep_release_cpus_lock(void)
|
||||
{
|
||||
rwsem_release(&cpu_hotplug_lock.rw_sem.dep_map, _THIS_IP_);
|
||||
rwsem_release(&cpu_hotplug_lock.dep_map, _THIS_IP_);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1041,7 +1041,7 @@ static int cpu_down_maps_locked(unsigned int cpu, enum cpuhp_state target)
|
||||
return _cpu_down(cpu, 0, target);
|
||||
}
|
||||
|
||||
static int do_cpu_down(unsigned int cpu, enum cpuhp_state target)
|
||||
static int cpu_down(unsigned int cpu, enum cpuhp_state target)
|
||||
{
|
||||
int err;
|
||||
|
||||
@@ -1051,11 +1051,72 @@ static int do_cpu_down(unsigned int cpu, enum cpuhp_state target)
|
||||
return err;
|
||||
}
|
||||
|
||||
int cpu_down(unsigned int cpu)
|
||||
/**
|
||||
* cpu_device_down - Bring down a cpu device
|
||||
* @dev: Pointer to the cpu device to offline
|
||||
*
|
||||
* This function is meant to be used by device core cpu subsystem only.
|
||||
*
|
||||
* Other subsystems should use remove_cpu() instead.
|
||||
*/
|
||||
int cpu_device_down(struct device *dev)
|
||||
{
|
||||
return do_cpu_down(cpu, CPUHP_OFFLINE);
|
||||
return cpu_down(dev->id, CPUHP_OFFLINE);
|
||||
}
|
||||
|
||||
int remove_cpu(unsigned int cpu)
|
||||
{
|
||||
int ret;
|
||||
|
||||
lock_device_hotplug();
|
||||
ret = device_offline(get_cpu_device(cpu));
|
||||
unlock_device_hotplug();
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(remove_cpu);
|
||||
|
||||
void smp_shutdown_nonboot_cpus(unsigned int primary_cpu)
|
||||
{
|
||||
unsigned int cpu;
|
||||
int error;
|
||||
|
||||
cpu_maps_update_begin();
|
||||
|
||||
/*
|
||||
* Make certain the cpu I'm about to reboot on is online.
|
||||
*
|
||||
* This is inline to what migrate_to_reboot_cpu() already do.
|
||||
*/
|
||||
if (!cpu_online(primary_cpu))
|
||||
primary_cpu = cpumask_first(cpu_online_mask);
|
||||
|
||||
for_each_online_cpu(cpu) {
|
||||
if (cpu == primary_cpu)
|
||||
continue;
|
||||
|
||||
error = cpu_down_maps_locked(cpu, CPUHP_OFFLINE);
|
||||
if (error) {
|
||||
pr_err("Failed to offline CPU%d - error=%d",
|
||||
cpu, error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure all but the reboot CPU are offline.
|
||||
*/
|
||||
BUG_ON(num_online_cpus() > 1);
|
||||
|
||||
/*
|
||||
* Make sure the CPUs won't be enabled by someone else after this
|
||||
* point. Kexec will reboot to a new kernel shortly resetting
|
||||
* everything along the way.
|
||||
*/
|
||||
cpu_hotplug_disabled++;
|
||||
|
||||
cpu_maps_update_done();
|
||||
}
|
||||
EXPORT_SYMBOL(cpu_down);
|
||||
|
||||
#else
|
||||
#define takedown_cpu NULL
|
||||
@@ -1124,8 +1185,8 @@ static int _cpu_up(unsigned int cpu, int tasks_frozen, enum cpuhp_state target)
|
||||
}
|
||||
|
||||
/*
|
||||
* The caller of do_cpu_up might have raced with another
|
||||
* caller. Ignore it for now.
|
||||
* The caller of cpu_up() might have raced with another
|
||||
* caller. Nothing to do.
|
||||
*/
|
||||
if (st->state >= target)
|
||||
goto out;
|
||||
@@ -1169,7 +1230,7 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int do_cpu_up(unsigned int cpu, enum cpuhp_state target)
|
||||
static int cpu_up(unsigned int cpu, enum cpuhp_state target)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
@@ -1203,16 +1264,70 @@ out:
|
||||
return err;
|
||||
}
|
||||
|
||||
int cpu_up(unsigned int cpu)
|
||||
/**
|
||||
* cpu_device_up - Bring up a cpu device
|
||||
* @dev: Pointer to the cpu device to online
|
||||
*
|
||||
* This function is meant to be used by device core cpu subsystem only.
|
||||
*
|
||||
* Other subsystems should use add_cpu() instead.
|
||||
*/
|
||||
int cpu_device_up(struct device *dev)
|
||||
{
|
||||
return do_cpu_up(cpu, CPUHP_ONLINE);
|
||||
return cpu_up(dev->id, CPUHP_ONLINE);
|
||||
}
|
||||
|
||||
int add_cpu(unsigned int cpu)
|
||||
{
|
||||
int ret;
|
||||
|
||||
lock_device_hotplug();
|
||||
ret = device_online(get_cpu_device(cpu));
|
||||
unlock_device_hotplug();
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(add_cpu);
|
||||
|
||||
/**
|
||||
* bringup_hibernate_cpu - Bring up the CPU that we hibernated on
|
||||
* @sleep_cpu: The cpu we hibernated on and should be brought up.
|
||||
*
|
||||
* On some architectures like arm64, we can hibernate on any CPU, but on
|
||||
* wake up the CPU we hibernated on might be offline as a side effect of
|
||||
* using maxcpus= for example.
|
||||
*/
|
||||
int bringup_hibernate_cpu(unsigned int sleep_cpu)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!cpu_online(sleep_cpu)) {
|
||||
pr_info("Hibernated on a CPU that is offline! Bringing CPU up.\n");
|
||||
ret = cpu_up(sleep_cpu, CPUHP_ONLINE);
|
||||
if (ret) {
|
||||
pr_err("Failed to bring hibernate-CPU up!\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void bringup_nonboot_cpus(unsigned int setup_max_cpus)
|
||||
{
|
||||
unsigned int cpu;
|
||||
|
||||
for_each_present_cpu(cpu) {
|
||||
if (num_online_cpus() >= setup_max_cpus)
|
||||
break;
|
||||
if (!cpu_online(cpu))
|
||||
cpu_up(cpu, CPUHP_ONLINE);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cpu_up);
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP_SMP
|
||||
static cpumask_var_t frozen_cpus;
|
||||
|
||||
int freeze_secondary_cpus(int primary)
|
||||
int __freeze_secondary_cpus(int primary, bool suspend)
|
||||
{
|
||||
int cpu, error = 0;
|
||||
|
||||
@@ -1237,7 +1352,7 @@ int freeze_secondary_cpus(int primary)
|
||||
if (cpu == primary)
|
||||
continue;
|
||||
|
||||
if (pm_wakeup_pending()) {
|
||||
if (suspend && pm_wakeup_pending()) {
|
||||
pr_info("Wakeup pending. Abort CPU freeze\n");
|
||||
error = -EBUSY;
|
||||
break;
|
||||
@@ -2028,9 +2143,9 @@ static ssize_t write_cpuhp_target(struct device *dev,
|
||||
goto out;
|
||||
|
||||
if (st->state < target)
|
||||
ret = do_cpu_up(dev->id, target);
|
||||
ret = cpu_up(dev->id, target);
|
||||
else
|
||||
ret = do_cpu_down(dev->id, target);
|
||||
ret = cpu_down(dev->id, target);
|
||||
out:
|
||||
unlock_device_hotplug();
|
||||
return ret ? ret : count;
|
||||
|
||||
@@ -675,8 +675,6 @@ void __init cred_init(void)
|
||||
* The caller may change these controls afterwards if desired.
|
||||
*
|
||||
* Returns the new credentials or NULL if out of memory.
|
||||
*
|
||||
* Does not take, and does not return holding current->cred_replace_mutex.
|
||||
*/
|
||||
struct cred *prepare_kernel_cred(struct task_struct *daemon)
|
||||
{
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
gen-kdb_cmds.c
|
||||
|
||||
@@ -412,7 +412,6 @@ static int kdb_bc(int argc, const char **argv)
|
||||
* assume that the breakpoint number is desired.
|
||||
*/
|
||||
if (addr < KDB_MAXBPT) {
|
||||
bp = &kdb_breakpoints[addr];
|
||||
lowbp = highbp = addr;
|
||||
highbp++;
|
||||
} else {
|
||||
|
||||
@@ -119,7 +119,6 @@ kdb_bt_cpu(unsigned long cpu)
|
||||
return;
|
||||
}
|
||||
|
||||
kdb_set_current_task(kdb_tsk);
|
||||
kdb_bt1(kdb_tsk, ~0UL, false);
|
||||
}
|
||||
|
||||
@@ -166,10 +165,8 @@ kdb_bt(int argc, const char **argv)
|
||||
if (diag)
|
||||
return diag;
|
||||
p = find_task_by_pid_ns(pid, &init_pid_ns);
|
||||
if (p) {
|
||||
kdb_set_current_task(p);
|
||||
if (p)
|
||||
return kdb_bt1(p, ~0UL, false);
|
||||
}
|
||||
kdb_printf("No process with pid == %ld found\n", pid);
|
||||
return 0;
|
||||
} else if (strcmp(argv[0], "btt") == 0) {
|
||||
@@ -178,11 +175,9 @@ kdb_bt(int argc, const char **argv)
|
||||
diag = kdbgetularg((char *)argv[1], &addr);
|
||||
if (diag)
|
||||
return diag;
|
||||
kdb_set_current_task((struct task_struct *)addr);
|
||||
return kdb_bt1((struct task_struct *)addr, ~0UL, false);
|
||||
} else if (strcmp(argv[0], "btc") == 0) {
|
||||
unsigned long cpu = ~0;
|
||||
struct task_struct *save_current_task = kdb_current_task;
|
||||
if (argc > 1)
|
||||
return KDB_ARGCOUNT;
|
||||
if (argc == 1) {
|
||||
@@ -204,7 +199,6 @@ kdb_bt(int argc, const char **argv)
|
||||
kdb_bt_cpu(cpu);
|
||||
touch_nmi_watchdog();
|
||||
}
|
||||
kdb_set_current_task(save_current_task);
|
||||
}
|
||||
return 0;
|
||||
} else {
|
||||
|
||||
@@ -553,7 +553,7 @@ int vkdb_printf(enum kdb_msgsrc src, const char *fmt, va_list ap)
|
||||
int this_cpu, old_cpu;
|
||||
char *cp, *cp2, *cphold = NULL, replaced_byte = ' ';
|
||||
char *moreprompt = "more> ";
|
||||
struct console *c = console_drivers;
|
||||
struct console *c;
|
||||
unsigned long uninitialized_var(flags);
|
||||
|
||||
/* Serialize kdb_printf if multiple cpus try to write at once.
|
||||
@@ -698,10 +698,9 @@ kdb_printit:
|
||||
cp2++;
|
||||
}
|
||||
}
|
||||
while (c) {
|
||||
for_each_console(c) {
|
||||
c->write(c, cp, retlen - (cp - kdb_buffer));
|
||||
touch_nmi_watchdog();
|
||||
c = c->next;
|
||||
}
|
||||
}
|
||||
if (logging) {
|
||||
@@ -752,7 +751,6 @@ kdb_printit:
|
||||
moreprompt = "more> ";
|
||||
|
||||
kdb_input_flush();
|
||||
c = console_drivers;
|
||||
|
||||
if (dbg_io_ops && !dbg_io_ops->is_console) {
|
||||
len = strlen(moreprompt);
|
||||
@@ -762,10 +760,9 @@ kdb_printit:
|
||||
cp++;
|
||||
}
|
||||
}
|
||||
while (c) {
|
||||
for_each_console(c) {
|
||||
c->write(c, moreprompt, strlen(moreprompt));
|
||||
touch_nmi_watchdog();
|
||||
c = c->next;
|
||||
}
|
||||
|
||||
if (logging)
|
||||
|
||||
+13
-10
@@ -73,7 +73,6 @@ int kdb_nextline = 1;
|
||||
int kdb_state; /* General KDB state */
|
||||
|
||||
struct task_struct *kdb_current_task;
|
||||
EXPORT_SYMBOL(kdb_current_task);
|
||||
struct pt_regs *kdb_current_regs;
|
||||
|
||||
const char *kdb_diemsg;
|
||||
@@ -399,6 +398,13 @@ int kdb_set(int argc, const char **argv)
|
||||
if (argc != 2)
|
||||
return KDB_ARGCOUNT;
|
||||
|
||||
/*
|
||||
* Censor sensitive variables
|
||||
*/
|
||||
if (strcmp(argv[1], "PROMPT") == 0 &&
|
||||
!kdb_check_flags(KDB_ENABLE_MEM_READ, kdb_cmd_enabled, false))
|
||||
return KDB_NOPERM;
|
||||
|
||||
/*
|
||||
* Check for internal variables
|
||||
*/
|
||||
@@ -1103,12 +1109,12 @@ static int handle_ctrl_cmd(char *cmd)
|
||||
case CTRL_P:
|
||||
if (cmdptr != cmd_tail)
|
||||
cmdptr = (cmdptr-1) % KDB_CMD_HISTORY_COUNT;
|
||||
strncpy(cmd_cur, cmd_hist[cmdptr], CMD_BUFLEN);
|
||||
strscpy(cmd_cur, cmd_hist[cmdptr], CMD_BUFLEN);
|
||||
return 1;
|
||||
case CTRL_N:
|
||||
if (cmdptr != cmd_head)
|
||||
cmdptr = (cmdptr+1) % KDB_CMD_HISTORY_COUNT;
|
||||
strncpy(cmd_cur, cmd_hist[cmdptr], CMD_BUFLEN);
|
||||
strscpy(cmd_cur, cmd_hist[cmdptr], CMD_BUFLEN);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
@@ -1139,7 +1145,7 @@ static void kdb_dumpregs(struct pt_regs *regs)
|
||||
console_loglevel = old_lvl;
|
||||
}
|
||||
|
||||
void kdb_set_current_task(struct task_struct *p)
|
||||
static void kdb_set_current_task(struct task_struct *p)
|
||||
{
|
||||
kdb_current_task = p;
|
||||
|
||||
@@ -1299,12 +1305,9 @@ static int kdb_local(kdb_reason_t reason, int error, struct pt_regs *regs,
|
||||
*(cmd_hist[cmd_head]) = '\0';
|
||||
|
||||
do_full_getstr:
|
||||
#if defined(CONFIG_SMP)
|
||||
/* PROMPT can only be set if we have MEM_READ permission. */
|
||||
snprintf(kdb_prompt_str, CMD_BUFLEN, kdbgetenv("PROMPT"),
|
||||
raw_smp_processor_id());
|
||||
#else
|
||||
snprintf(kdb_prompt_str, CMD_BUFLEN, kdbgetenv("PROMPT"));
|
||||
#endif
|
||||
if (defcmd_in_progress)
|
||||
strncat(kdb_prompt_str, "[defcmd]", CMD_BUFLEN);
|
||||
|
||||
@@ -1315,7 +1318,7 @@ do_full_getstr:
|
||||
if (*cmdbuf != '\n') {
|
||||
if (*cmdbuf < 32) {
|
||||
if (cmdptr == cmd_head) {
|
||||
strncpy(cmd_hist[cmd_head], cmd_cur,
|
||||
strscpy(cmd_hist[cmd_head], cmd_cur,
|
||||
CMD_BUFLEN);
|
||||
*(cmd_hist[cmd_head] +
|
||||
strlen(cmd_hist[cmd_head])-1) = '\0';
|
||||
@@ -1325,7 +1328,7 @@ do_full_getstr:
|
||||
cmdbuf = cmd_cur;
|
||||
goto do_full_getstr;
|
||||
} else {
|
||||
strncpy(cmd_hist[cmd_head], cmd_cur,
|
||||
strscpy(cmd_hist[cmd_head], cmd_cur,
|
||||
CMD_BUFLEN);
|
||||
}
|
||||
|
||||
|
||||
@@ -240,8 +240,8 @@ extern void *debug_kmalloc(size_t size, gfp_t flags);
|
||||
extern void debug_kfree(void *);
|
||||
extern void debug_kusage(void);
|
||||
|
||||
extern void kdb_set_current_task(struct task_struct *);
|
||||
extern struct task_struct *kdb_current_task;
|
||||
extern struct pt_regs *kdb_current_regs;
|
||||
|
||||
#ifdef CONFIG_KDB_KEYBOARD
|
||||
extern void kdb_kbd_cleanup_state(void);
|
||||
|
||||
@@ -134,7 +134,7 @@ static void *__dma_alloc_from_coherent(struct device *dev,
|
||||
|
||||
spin_lock_irqsave(&mem->spinlock, flags);
|
||||
|
||||
if (unlikely(size > (mem->size << PAGE_SHIFT)))
|
||||
if (unlikely(size > ((dma_addr_t)mem->size << PAGE_SHIFT)))
|
||||
goto err;
|
||||
|
||||
pageno = bitmap_find_free_region(mem->bitmap, mem->size, order);
|
||||
@@ -144,8 +144,9 @@ static void *__dma_alloc_from_coherent(struct device *dev,
|
||||
/*
|
||||
* Memory was found in the coherent area.
|
||||
*/
|
||||
*dma_handle = dma_get_device_base(dev, mem) + (pageno << PAGE_SHIFT);
|
||||
ret = mem->virt_base + (pageno << PAGE_SHIFT);
|
||||
*dma_handle = dma_get_device_base(dev, mem) +
|
||||
((dma_addr_t)pageno << PAGE_SHIFT);
|
||||
ret = mem->virt_base + ((dma_addr_t)pageno << PAGE_SHIFT);
|
||||
spin_unlock_irqrestore(&mem->spinlock, flags);
|
||||
memset(ret, 0, size);
|
||||
return ret;
|
||||
@@ -194,7 +195,7 @@ static int __dma_release_from_coherent(struct dma_coherent_mem *mem,
|
||||
int order, void *vaddr)
|
||||
{
|
||||
if (mem && vaddr >= mem->virt_base && vaddr <
|
||||
(mem->virt_base + (mem->size << PAGE_SHIFT))) {
|
||||
(mem->virt_base + ((dma_addr_t)mem->size << PAGE_SHIFT))) {
|
||||
int page = (vaddr - mem->virt_base) >> PAGE_SHIFT;
|
||||
unsigned long flags;
|
||||
|
||||
@@ -238,10 +239,10 @@ static int __dma_mmap_from_coherent(struct dma_coherent_mem *mem,
|
||||
struct vm_area_struct *vma, void *vaddr, size_t size, int *ret)
|
||||
{
|
||||
if (mem && vaddr >= mem->virt_base && vaddr + size <=
|
||||
(mem->virt_base + (mem->size << PAGE_SHIFT))) {
|
||||
(mem->virt_base + ((dma_addr_t)mem->size << PAGE_SHIFT))) {
|
||||
unsigned long off = vma->vm_pgoff;
|
||||
int start = (vaddr - mem->virt_base) >> PAGE_SHIFT;
|
||||
int user_count = vma_pages(vma);
|
||||
unsigned long user_count = vma_pages(vma);
|
||||
int count = PAGE_ALIGN(size) >> PAGE_SHIFT;
|
||||
|
||||
*ret = -ENXIO;
|
||||
|
||||
@@ -302,9 +302,16 @@ static int __init rmem_cma_setup(struct reserved_mem *rmem)
|
||||
phys_addr_t align = PAGE_SIZE << max(MAX_ORDER - 1, pageblock_order);
|
||||
phys_addr_t mask = align - 1;
|
||||
unsigned long node = rmem->fdt_node;
|
||||
bool default_cma = of_get_flat_dt_prop(node, "linux,cma-default", NULL);
|
||||
struct cma *cma;
|
||||
int err;
|
||||
|
||||
if (size_cmdline != -1 && default_cma) {
|
||||
pr_info("Reserved memory: bypass %s node, using cmdline CMA params instead\n",
|
||||
rmem->name);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (!of_get_flat_dt_prop(node, "reusable", NULL) ||
|
||||
of_get_flat_dt_prop(node, "no-map", NULL))
|
||||
return -EINVAL;
|
||||
@@ -322,7 +329,7 @@ static int __init rmem_cma_setup(struct reserved_mem *rmem)
|
||||
/* Architecture specific contiguous memory fixup. */
|
||||
dma_contiguous_early_fixup(rmem->base, rmem->size);
|
||||
|
||||
if (of_get_flat_dt_prop(node, "linux,cma-default", NULL))
|
||||
if (default_cma)
|
||||
dma_contiguous_set_default(cma);
|
||||
|
||||
rmem->ops = &rmem_cma_ops;
|
||||
|
||||
+6
-3
@@ -137,9 +137,12 @@ static const char *const maperr2str[] = {
|
||||
[MAP_ERR_CHECKED] = "dma map error checked",
|
||||
};
|
||||
|
||||
static const char *type2name[5] = { "single", "page",
|
||||
"scather-gather", "coherent",
|
||||
"resource" };
|
||||
static const char *type2name[] = {
|
||||
[dma_debug_single] = "single",
|
||||
[dma_debug_sg] = "scather-gather",
|
||||
[dma_debug_coherent] = "coherent",
|
||||
[dma_debug_resource] = "resource",
|
||||
};
|
||||
|
||||
static const char *dir2name[4] = { "DMA_BIDIRECTIONAL", "DMA_TO_DEVICE",
|
||||
"DMA_FROM_DEVICE", "DMA_NONE" };
|
||||
|
||||
+41
-48
@@ -23,18 +23,6 @@
|
||||
*/
|
||||
unsigned int zone_dma_bits __ro_after_init = 24;
|
||||
|
||||
static void report_addr(struct device *dev, dma_addr_t dma_addr, size_t size)
|
||||
{
|
||||
if (!dev->dma_mask) {
|
||||
dev_err_once(dev, "DMA map on device without dma_mask\n");
|
||||
} else if (*dev->dma_mask >= DMA_BIT_MASK(32) || dev->bus_dma_limit) {
|
||||
dev_err_once(dev,
|
||||
"overflow %pad+%zu of DMA mask %llx bus limit %llx\n",
|
||||
&dma_addr, size, *dev->dma_mask, dev->bus_dma_limit);
|
||||
}
|
||||
WARN_ON_ONCE(1);
|
||||
}
|
||||
|
||||
static inline dma_addr_t phys_to_dma_direct(struct device *dev,
|
||||
phys_addr_t phys)
|
||||
{
|
||||
@@ -51,7 +39,8 @@ static inline struct page *dma_direct_to_page(struct device *dev,
|
||||
|
||||
u64 dma_direct_get_required_mask(struct device *dev)
|
||||
{
|
||||
u64 max_dma = phys_to_dma_direct(dev, (max_pfn - 1) << PAGE_SHIFT);
|
||||
phys_addr_t phys = (phys_addr_t)(max_pfn - 1) << PAGE_SHIFT;
|
||||
u64 max_dma = phys_to_dma_direct(dev, phys);
|
||||
|
||||
return (1ULL << (fls64(max_dma) - 1)) * 2 - 1;
|
||||
}
|
||||
@@ -169,11 +158,8 @@ void *dma_direct_alloc_pages(struct device *dev, size_t size,
|
||||
ret = dma_common_contiguous_remap(page, PAGE_ALIGN(size),
|
||||
dma_pgprot(dev, PAGE_KERNEL, attrs),
|
||||
__builtin_return_address(0));
|
||||
if (!ret) {
|
||||
dma_free_contiguous(dev, page, size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!ret)
|
||||
goto out_free_pages;
|
||||
memset(ret, 0, size);
|
||||
goto done;
|
||||
}
|
||||
@@ -186,8 +172,7 @@ void *dma_direct_alloc_pages(struct device *dev, size_t size,
|
||||
* so log an error and fail.
|
||||
*/
|
||||
dev_info(dev, "Rejecting highmem page from CMA.\n");
|
||||
dma_free_contiguous(dev, page, size);
|
||||
return NULL;
|
||||
goto out_free_pages;
|
||||
}
|
||||
|
||||
ret = page_address(page);
|
||||
@@ -196,10 +181,12 @@ void *dma_direct_alloc_pages(struct device *dev, size_t size,
|
||||
|
||||
memset(ret, 0, size);
|
||||
|
||||
if (IS_ENABLED(CONFIG_ARCH_HAS_UNCACHED_SEGMENT) &&
|
||||
if (IS_ENABLED(CONFIG_ARCH_HAS_DMA_SET_UNCACHED) &&
|
||||
dma_alloc_need_uncached(dev, attrs)) {
|
||||
arch_dma_prep_coherent(page, size);
|
||||
ret = uncached_kernel_address(ret);
|
||||
ret = arch_dma_set_uncached(ret, size);
|
||||
if (IS_ERR(ret))
|
||||
goto out_free_pages;
|
||||
}
|
||||
done:
|
||||
if (force_dma_unencrypted(dev))
|
||||
@@ -207,6 +194,9 @@ done:
|
||||
else
|
||||
*dma_handle = phys_to_dma(dev, page_to_phys(page));
|
||||
return ret;
|
||||
out_free_pages:
|
||||
dma_free_contiguous(dev, page, size);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void dma_direct_free_pages(struct device *dev, size_t size, void *cpu_addr,
|
||||
@@ -230,6 +220,8 @@ void dma_direct_free_pages(struct device *dev, size_t size, void *cpu_addr,
|
||||
|
||||
if (IS_ENABLED(CONFIG_DMA_REMAP) && is_vmalloc_addr(cpu_addr))
|
||||
vunmap(cpu_addr);
|
||||
else if (IS_ENABLED(CONFIG_ARCH_HAS_DMA_CLEAR_UNCACHED))
|
||||
arch_dma_clear_uncached(cpu_addr, size);
|
||||
|
||||
dma_free_contiguous(dev, dma_direct_to_page(dev, dma_addr), size);
|
||||
}
|
||||
@@ -237,7 +229,7 @@ void dma_direct_free_pages(struct device *dev, size_t size, void *cpu_addr,
|
||||
void *dma_direct_alloc(struct device *dev, size_t size,
|
||||
dma_addr_t *dma_handle, gfp_t gfp, unsigned long attrs)
|
||||
{
|
||||
if (!IS_ENABLED(CONFIG_ARCH_HAS_UNCACHED_SEGMENT) &&
|
||||
if (!IS_ENABLED(CONFIG_ARCH_HAS_DMA_SET_UNCACHED) &&
|
||||
!IS_ENABLED(CONFIG_DMA_DIRECT_REMAP) &&
|
||||
dma_alloc_need_uncached(dev, attrs))
|
||||
return arch_dma_alloc(dev, size, dma_handle, gfp, attrs);
|
||||
@@ -247,7 +239,7 @@ void *dma_direct_alloc(struct device *dev, size_t size,
|
||||
void dma_direct_free(struct device *dev, size_t size,
|
||||
void *cpu_addr, dma_addr_t dma_addr, unsigned long attrs)
|
||||
{
|
||||
if (!IS_ENABLED(CONFIG_ARCH_HAS_UNCACHED_SEGMENT) &&
|
||||
if (!IS_ENABLED(CONFIG_ARCH_HAS_DMA_SET_UNCACHED) &&
|
||||
!IS_ENABLED(CONFIG_DMA_DIRECT_REMAP) &&
|
||||
dma_alloc_need_uncached(dev, attrs))
|
||||
arch_dma_free(dev, size, cpu_addr, dma_addr, attrs);
|
||||
@@ -357,13 +349,6 @@ void dma_direct_unmap_sg(struct device *dev, struct scatterlist *sgl,
|
||||
EXPORT_SYMBOL(dma_direct_unmap_sg);
|
||||
#endif
|
||||
|
||||
static inline bool dma_direct_possible(struct device *dev, dma_addr_t dma_addr,
|
||||
size_t size)
|
||||
{
|
||||
return swiotlb_force != SWIOTLB_FORCE &&
|
||||
dma_capable(dev, dma_addr, size, true);
|
||||
}
|
||||
|
||||
dma_addr_t dma_direct_map_page(struct device *dev, struct page *page,
|
||||
unsigned long offset, size_t size, enum dma_data_direction dir,
|
||||
unsigned long attrs)
|
||||
@@ -371,9 +356,16 @@ dma_addr_t dma_direct_map_page(struct device *dev, struct page *page,
|
||||
phys_addr_t phys = page_to_phys(page) + offset;
|
||||
dma_addr_t dma_addr = phys_to_dma(dev, phys);
|
||||
|
||||
if (unlikely(!dma_direct_possible(dev, dma_addr, size)) &&
|
||||
!swiotlb_map(dev, &phys, &dma_addr, size, dir, attrs)) {
|
||||
report_addr(dev, dma_addr, size);
|
||||
if (unlikely(swiotlb_force == SWIOTLB_FORCE))
|
||||
return swiotlb_map(dev, phys, size, dir, attrs);
|
||||
|
||||
if (unlikely(!dma_capable(dev, dma_addr, size, true))) {
|
||||
if (swiotlb_force != SWIOTLB_NO_FORCE)
|
||||
return swiotlb_map(dev, phys, size, dir, attrs);
|
||||
|
||||
dev_WARN_ONCE(dev, 1,
|
||||
"DMA addr %pad+%zu overflow (mask %llx, bus limit %llx).\n",
|
||||
&dma_addr, size, *dev->dma_mask, dev->bus_dma_limit);
|
||||
return DMA_MAPPING_ERROR;
|
||||
}
|
||||
|
||||
@@ -411,7 +403,10 @@ dma_addr_t dma_direct_map_resource(struct device *dev, phys_addr_t paddr,
|
||||
dma_addr_t dma_addr = paddr;
|
||||
|
||||
if (unlikely(!dma_capable(dev, dma_addr, size, false))) {
|
||||
report_addr(dev, dma_addr, size);
|
||||
dev_err_once(dev,
|
||||
"DMA addr %pad+%zu overflow (mask %llx, bus limit %llx).\n",
|
||||
&dma_addr, size, *dev->dma_mask, dev->bus_dma_limit);
|
||||
WARN_ON_ONCE(1);
|
||||
return DMA_MAPPING_ERROR;
|
||||
}
|
||||
|
||||
@@ -472,28 +467,26 @@ int dma_direct_mmap(struct device *dev, struct vm_area_struct *vma,
|
||||
}
|
||||
#endif /* CONFIG_MMU */
|
||||
|
||||
/*
|
||||
* Because 32-bit DMA masks are so common we expect every architecture to be
|
||||
* able to satisfy them - either by not supporting more physical memory, or by
|
||||
* providing a ZONE_DMA32. If neither is the case, the architecture needs to
|
||||
* use an IOMMU instead of the direct mapping.
|
||||
*/
|
||||
int dma_direct_supported(struct device *dev, u64 mask)
|
||||
{
|
||||
u64 min_mask;
|
||||
u64 min_mask = (max_pfn - 1) << PAGE_SHIFT;
|
||||
|
||||
if (IS_ENABLED(CONFIG_ZONE_DMA))
|
||||
min_mask = DMA_BIT_MASK(zone_dma_bits);
|
||||
else
|
||||
min_mask = DMA_BIT_MASK(32);
|
||||
|
||||
min_mask = min_t(u64, min_mask, (max_pfn - 1) << PAGE_SHIFT);
|
||||
/*
|
||||
* Because 32-bit DMA masks are so common we expect every architecture
|
||||
* to be able to satisfy them - either by not supporting more physical
|
||||
* memory, or by providing a ZONE_DMA32. If neither is the case, the
|
||||
* architecture needs to use an IOMMU instead of the direct mapping.
|
||||
*/
|
||||
if (mask >= DMA_BIT_MASK(32))
|
||||
return 1;
|
||||
|
||||
/*
|
||||
* This check needs to be against the actual bit mask value, so
|
||||
* use __phys_to_dma() here so that the SME encryption mask isn't
|
||||
* part of the check.
|
||||
*/
|
||||
if (IS_ENABLED(CONFIG_ZONE_DMA))
|
||||
min_mask = min_t(u64, min_mask, DMA_BIT_MASK(zone_dma_bits));
|
||||
return mask >= __phys_to_dma(dev, min_mask);
|
||||
}
|
||||
|
||||
|
||||
@@ -154,6 +154,8 @@ EXPORT_SYMBOL(dma_get_sgtable_attrs);
|
||||
*/
|
||||
pgprot_t dma_pgprot(struct device *dev, pgprot_t prot, unsigned long attrs)
|
||||
{
|
||||
if (force_dma_unencrypted(dev))
|
||||
prot = pgprot_decrypted(prot);
|
||||
if (dev_is_dma_coherent(dev) ||
|
||||
(IS_ENABLED(CONFIG_DMA_NONCOHERENT_CACHE_SYNC) &&
|
||||
(attrs & DMA_ATTR_NON_CONSISTENT)))
|
||||
|
||||
+23
-19
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <linux/cache.h>
|
||||
#include <linux/dma-direct.h>
|
||||
#include <linux/dma-noncoherent.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/spinlock.h>
|
||||
@@ -656,35 +657,38 @@ void swiotlb_tbl_sync_single(struct device *hwdev, phys_addr_t tlb_addr,
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a swiotlb mapping for the buffer at @phys, and in case of DMAing
|
||||
* Create a swiotlb mapping for the buffer at @paddr, and in case of DMAing
|
||||
* to the device copy the data into it as well.
|
||||
*/
|
||||
bool swiotlb_map(struct device *dev, phys_addr_t *phys, dma_addr_t *dma_addr,
|
||||
size_t size, enum dma_data_direction dir, unsigned long attrs)
|
||||
dma_addr_t swiotlb_map(struct device *dev, phys_addr_t paddr, size_t size,
|
||||
enum dma_data_direction dir, unsigned long attrs)
|
||||
{
|
||||
trace_swiotlb_bounced(dev, *dma_addr, size, swiotlb_force);
|
||||
phys_addr_t swiotlb_addr;
|
||||
dma_addr_t dma_addr;
|
||||
|
||||
if (unlikely(swiotlb_force == SWIOTLB_NO_FORCE)) {
|
||||
dev_warn_ratelimited(dev,
|
||||
"Cannot do DMA to address %pa\n", phys);
|
||||
return false;
|
||||
}
|
||||
trace_swiotlb_bounced(dev, phys_to_dma(dev, paddr), size,
|
||||
swiotlb_force);
|
||||
|
||||
/* Oh well, have to allocate and map a bounce buffer. */
|
||||
*phys = swiotlb_tbl_map_single(dev, __phys_to_dma(dev, io_tlb_start),
|
||||
*phys, size, size, dir, attrs);
|
||||
if (*phys == (phys_addr_t)DMA_MAPPING_ERROR)
|
||||
return false;
|
||||
swiotlb_addr = swiotlb_tbl_map_single(dev,
|
||||
__phys_to_dma(dev, io_tlb_start),
|
||||
paddr, size, size, dir, attrs);
|
||||
if (swiotlb_addr == (phys_addr_t)DMA_MAPPING_ERROR)
|
||||
return DMA_MAPPING_ERROR;
|
||||
|
||||
/* Ensure that the address returned is DMA'ble */
|
||||
*dma_addr = __phys_to_dma(dev, *phys);
|
||||
if (unlikely(!dma_capable(dev, *dma_addr, size, true))) {
|
||||
swiotlb_tbl_unmap_single(dev, *phys, size, size, dir,
|
||||
dma_addr = __phys_to_dma(dev, swiotlb_addr);
|
||||
if (unlikely(!dma_capable(dev, dma_addr, size, true))) {
|
||||
swiotlb_tbl_unmap_single(dev, swiotlb_addr, size, size, dir,
|
||||
attrs | DMA_ATTR_SKIP_CPU_SYNC);
|
||||
return false;
|
||||
dev_WARN_ONCE(dev, 1,
|
||||
"swiotlb addr %pad+%zu overflow (mask %llx, bus limit %llx).\n",
|
||||
&dma_addr, size, *dev->dma_mask, dev->bus_dma_limit);
|
||||
return DMA_MAPPING_ERROR;
|
||||
}
|
||||
|
||||
return true;
|
||||
if (!dev_is_dma_coherent(dev) && !(attrs & DMA_ATTR_SKIP_CPU_SYNC))
|
||||
arch_sync_dma_for_device(swiotlb_addr, size, dir);
|
||||
return dma_addr;
|
||||
}
|
||||
|
||||
size_t swiotlb_max_mapping_size(struct device *dev)
|
||||
|
||||
+490
-165
File diff suppressed because it is too large
Load Diff
+17
-17
@@ -10,7 +10,7 @@
|
||||
|
||||
#define RING_BUFFER_WRITABLE 0x01
|
||||
|
||||
struct ring_buffer {
|
||||
struct perf_buffer {
|
||||
refcount_t refcount;
|
||||
struct rcu_head rcu_head;
|
||||
#ifdef CONFIG_PERF_USE_VMALLOC
|
||||
@@ -58,17 +58,17 @@ struct ring_buffer {
|
||||
void *data_pages[0];
|
||||
};
|
||||
|
||||
extern void rb_free(struct ring_buffer *rb);
|
||||
extern void rb_free(struct perf_buffer *rb);
|
||||
|
||||
static inline void rb_free_rcu(struct rcu_head *rcu_head)
|
||||
{
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
|
||||
rb = container_of(rcu_head, struct ring_buffer, rcu_head);
|
||||
rb = container_of(rcu_head, struct perf_buffer, rcu_head);
|
||||
rb_free(rb);
|
||||
}
|
||||
|
||||
static inline void rb_toggle_paused(struct ring_buffer *rb, bool pause)
|
||||
static inline void rb_toggle_paused(struct perf_buffer *rb, bool pause)
|
||||
{
|
||||
if (!pause && rb->nr_pages)
|
||||
rb->paused = 0;
|
||||
@@ -76,16 +76,16 @@ static inline void rb_toggle_paused(struct ring_buffer *rb, bool pause)
|
||||
rb->paused = 1;
|
||||
}
|
||||
|
||||
extern struct ring_buffer *
|
||||
extern struct perf_buffer *
|
||||
rb_alloc(int nr_pages, long watermark, int cpu, int flags);
|
||||
extern void perf_event_wakeup(struct perf_event *event);
|
||||
extern int rb_alloc_aux(struct ring_buffer *rb, struct perf_event *event,
|
||||
extern int rb_alloc_aux(struct perf_buffer *rb, struct perf_event *event,
|
||||
pgoff_t pgoff, int nr_pages, long watermark, int flags);
|
||||
extern void rb_free_aux(struct ring_buffer *rb);
|
||||
extern struct ring_buffer *ring_buffer_get(struct perf_event *event);
|
||||
extern void ring_buffer_put(struct ring_buffer *rb);
|
||||
extern void rb_free_aux(struct perf_buffer *rb);
|
||||
extern struct perf_buffer *ring_buffer_get(struct perf_event *event);
|
||||
extern void ring_buffer_put(struct perf_buffer *rb);
|
||||
|
||||
static inline bool rb_has_aux(struct ring_buffer *rb)
|
||||
static inline bool rb_has_aux(struct perf_buffer *rb)
|
||||
{
|
||||
return !!rb->aux_nr_pages;
|
||||
}
|
||||
@@ -94,7 +94,7 @@ void perf_event_aux_event(struct perf_event *event, unsigned long head,
|
||||
unsigned long size, u64 flags);
|
||||
|
||||
extern struct page *
|
||||
perf_mmap_to_page(struct ring_buffer *rb, unsigned long pgoff);
|
||||
perf_mmap_to_page(struct perf_buffer *rb, unsigned long pgoff);
|
||||
|
||||
#ifdef CONFIG_PERF_USE_VMALLOC
|
||||
/*
|
||||
@@ -103,25 +103,25 @@ perf_mmap_to_page(struct ring_buffer *rb, unsigned long pgoff);
|
||||
* Required for architectures that have d-cache aliasing issues.
|
||||
*/
|
||||
|
||||
static inline int page_order(struct ring_buffer *rb)
|
||||
static inline int page_order(struct perf_buffer *rb)
|
||||
{
|
||||
return rb->page_order;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline int page_order(struct ring_buffer *rb)
|
||||
static inline int page_order(struct perf_buffer *rb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline unsigned long perf_data_size(struct ring_buffer *rb)
|
||||
static inline unsigned long perf_data_size(struct perf_buffer *rb)
|
||||
{
|
||||
return rb->nr_pages << (PAGE_SHIFT + page_order(rb));
|
||||
}
|
||||
|
||||
static inline unsigned long perf_aux_size(struct ring_buffer *rb)
|
||||
static inline unsigned long perf_aux_size(struct perf_buffer *rb)
|
||||
{
|
||||
return rb->aux_nr_pages << PAGE_SHIFT;
|
||||
}
|
||||
@@ -141,7 +141,7 @@ static inline unsigned long perf_aux_size(struct ring_buffer *rb)
|
||||
buf += written; \
|
||||
handle->size -= written; \
|
||||
if (!handle->size) { \
|
||||
struct ring_buffer *rb = handle->rb; \
|
||||
struct perf_buffer *rb = handle->rb; \
|
||||
\
|
||||
handle->page++; \
|
||||
handle->page &= rb->nr_pages - 1; \
|
||||
|
||||
+27
-27
@@ -35,7 +35,7 @@ static void perf_output_wakeup(struct perf_output_handle *handle)
|
||||
*/
|
||||
static void perf_output_get_handle(struct perf_output_handle *handle)
|
||||
{
|
||||
struct ring_buffer *rb = handle->rb;
|
||||
struct perf_buffer *rb = handle->rb;
|
||||
|
||||
preempt_disable();
|
||||
|
||||
@@ -49,7 +49,7 @@ static void perf_output_get_handle(struct perf_output_handle *handle)
|
||||
|
||||
static void perf_output_put_handle(struct perf_output_handle *handle)
|
||||
{
|
||||
struct ring_buffer *rb = handle->rb;
|
||||
struct perf_buffer *rb = handle->rb;
|
||||
unsigned long head;
|
||||
unsigned int nest;
|
||||
|
||||
@@ -150,7 +150,7 @@ __perf_output_begin(struct perf_output_handle *handle,
|
||||
struct perf_event *event, unsigned int size,
|
||||
bool backward)
|
||||
{
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
unsigned long tail, offset, head;
|
||||
int have_lost, page_shift;
|
||||
struct {
|
||||
@@ -301,7 +301,7 @@ void perf_output_end(struct perf_output_handle *handle)
|
||||
}
|
||||
|
||||
static void
|
||||
ring_buffer_init(struct ring_buffer *rb, long watermark, int flags)
|
||||
ring_buffer_init(struct perf_buffer *rb, long watermark, int flags)
|
||||
{
|
||||
long max_size = perf_data_size(rb);
|
||||
|
||||
@@ -361,7 +361,7 @@ void *perf_aux_output_begin(struct perf_output_handle *handle,
|
||||
{
|
||||
struct perf_event *output_event = event;
|
||||
unsigned long aux_head, aux_tail;
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
unsigned int nest;
|
||||
|
||||
if (output_event->parent)
|
||||
@@ -449,7 +449,7 @@ err:
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(perf_aux_output_begin);
|
||||
|
||||
static __always_inline bool rb_need_aux_wakeup(struct ring_buffer *rb)
|
||||
static __always_inline bool rb_need_aux_wakeup(struct perf_buffer *rb)
|
||||
{
|
||||
if (rb->aux_overwrite)
|
||||
return false;
|
||||
@@ -475,7 +475,7 @@ static __always_inline bool rb_need_aux_wakeup(struct ring_buffer *rb)
|
||||
void perf_aux_output_end(struct perf_output_handle *handle, unsigned long size)
|
||||
{
|
||||
bool wakeup = !!(handle->aux_flags & PERF_AUX_FLAG_TRUNCATED);
|
||||
struct ring_buffer *rb = handle->rb;
|
||||
struct perf_buffer *rb = handle->rb;
|
||||
unsigned long aux_head;
|
||||
|
||||
/* in overwrite mode, driver provides aux_head via handle */
|
||||
@@ -532,7 +532,7 @@ EXPORT_SYMBOL_GPL(perf_aux_output_end);
|
||||
*/
|
||||
int perf_aux_output_skip(struct perf_output_handle *handle, unsigned long size)
|
||||
{
|
||||
struct ring_buffer *rb = handle->rb;
|
||||
struct perf_buffer *rb = handle->rb;
|
||||
|
||||
if (size > handle->size)
|
||||
return -ENOSPC;
|
||||
@@ -569,8 +569,8 @@ long perf_output_copy_aux(struct perf_output_handle *aux_handle,
|
||||
struct perf_output_handle *handle,
|
||||
unsigned long from, unsigned long to)
|
||||
{
|
||||
struct perf_buffer *rb = aux_handle->rb;
|
||||
unsigned long tocopy, remainder, len = 0;
|
||||
struct ring_buffer *rb = aux_handle->rb;
|
||||
void *addr;
|
||||
|
||||
from &= (rb->aux_nr_pages << PAGE_SHIFT) - 1;
|
||||
@@ -626,7 +626,7 @@ static struct page *rb_alloc_aux_page(int node, int order)
|
||||
return page;
|
||||
}
|
||||
|
||||
static void rb_free_aux_page(struct ring_buffer *rb, int idx)
|
||||
static void rb_free_aux_page(struct perf_buffer *rb, int idx)
|
||||
{
|
||||
struct page *page = virt_to_page(rb->aux_pages[idx]);
|
||||
|
||||
@@ -635,7 +635,7 @@ static void rb_free_aux_page(struct ring_buffer *rb, int idx)
|
||||
__free_page(page);
|
||||
}
|
||||
|
||||
static void __rb_free_aux(struct ring_buffer *rb)
|
||||
static void __rb_free_aux(struct perf_buffer *rb)
|
||||
{
|
||||
int pg;
|
||||
|
||||
@@ -662,7 +662,7 @@ static void __rb_free_aux(struct ring_buffer *rb)
|
||||
}
|
||||
}
|
||||
|
||||
int rb_alloc_aux(struct ring_buffer *rb, struct perf_event *event,
|
||||
int rb_alloc_aux(struct perf_buffer *rb, struct perf_event *event,
|
||||
pgoff_t pgoff, int nr_pages, long watermark, int flags)
|
||||
{
|
||||
bool overwrite = !(flags & RING_BUFFER_WRITABLE);
|
||||
@@ -753,7 +753,7 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void rb_free_aux(struct ring_buffer *rb)
|
||||
void rb_free_aux(struct perf_buffer *rb)
|
||||
{
|
||||
if (refcount_dec_and_test(&rb->aux_refcount))
|
||||
__rb_free_aux(rb);
|
||||
@@ -766,7 +766,7 @@ void rb_free_aux(struct ring_buffer *rb)
|
||||
*/
|
||||
|
||||
static struct page *
|
||||
__perf_mmap_to_page(struct ring_buffer *rb, unsigned long pgoff)
|
||||
__perf_mmap_to_page(struct perf_buffer *rb, unsigned long pgoff)
|
||||
{
|
||||
if (pgoff > rb->nr_pages)
|
||||
return NULL;
|
||||
@@ -798,13 +798,13 @@ static void perf_mmap_free_page(void *addr)
|
||||
__free_page(page);
|
||||
}
|
||||
|
||||
struct ring_buffer *rb_alloc(int nr_pages, long watermark, int cpu, int flags)
|
||||
struct perf_buffer *rb_alloc(int nr_pages, long watermark, int cpu, int flags)
|
||||
{
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
unsigned long size;
|
||||
int i;
|
||||
|
||||
size = sizeof(struct ring_buffer);
|
||||
size = sizeof(struct perf_buffer);
|
||||
size += nr_pages * sizeof(void *);
|
||||
|
||||
if (order_base_2(size) >= PAGE_SHIFT+MAX_ORDER)
|
||||
@@ -843,7 +843,7 @@ fail:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void rb_free(struct ring_buffer *rb)
|
||||
void rb_free(struct perf_buffer *rb)
|
||||
{
|
||||
int i;
|
||||
|
||||
@@ -854,13 +854,13 @@ void rb_free(struct ring_buffer *rb)
|
||||
}
|
||||
|
||||
#else
|
||||
static int data_page_nr(struct ring_buffer *rb)
|
||||
static int data_page_nr(struct perf_buffer *rb)
|
||||
{
|
||||
return rb->nr_pages << page_order(rb);
|
||||
}
|
||||
|
||||
static struct page *
|
||||
__perf_mmap_to_page(struct ring_buffer *rb, unsigned long pgoff)
|
||||
__perf_mmap_to_page(struct perf_buffer *rb, unsigned long pgoff)
|
||||
{
|
||||
/* The '>' counts in the user page. */
|
||||
if (pgoff > data_page_nr(rb))
|
||||
@@ -878,11 +878,11 @@ static void perf_mmap_unmark_page(void *addr)
|
||||
|
||||
static void rb_free_work(struct work_struct *work)
|
||||
{
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
void *base;
|
||||
int i, nr;
|
||||
|
||||
rb = container_of(work, struct ring_buffer, work);
|
||||
rb = container_of(work, struct perf_buffer, work);
|
||||
nr = data_page_nr(rb);
|
||||
|
||||
base = rb->user_page;
|
||||
@@ -894,18 +894,18 @@ static void rb_free_work(struct work_struct *work)
|
||||
kfree(rb);
|
||||
}
|
||||
|
||||
void rb_free(struct ring_buffer *rb)
|
||||
void rb_free(struct perf_buffer *rb)
|
||||
{
|
||||
schedule_work(&rb->work);
|
||||
}
|
||||
|
||||
struct ring_buffer *rb_alloc(int nr_pages, long watermark, int cpu, int flags)
|
||||
struct perf_buffer *rb_alloc(int nr_pages, long watermark, int cpu, int flags)
|
||||
{
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
unsigned long size;
|
||||
void *all_buf;
|
||||
|
||||
size = sizeof(struct ring_buffer);
|
||||
size = sizeof(struct perf_buffer);
|
||||
size += sizeof(void *);
|
||||
|
||||
rb = kzalloc(size, GFP_KERNEL);
|
||||
@@ -939,7 +939,7 @@ fail:
|
||||
#endif
|
||||
|
||||
struct page *
|
||||
perf_mmap_to_page(struct ring_buffer *rb, unsigned long pgoff)
|
||||
perf_mmap_to_page(struct perf_buffer *rb, unsigned long pgoff)
|
||||
{
|
||||
if (rb->aux_nr_pages) {
|
||||
/* above AUX space */
|
||||
|
||||
+7
-13
@@ -103,17 +103,8 @@ static void __exit_signal(struct task_struct *tsk)
|
||||
|
||||
#ifdef CONFIG_POSIX_TIMERS
|
||||
posix_cpu_timers_exit(tsk);
|
||||
if (group_dead) {
|
||||
if (group_dead)
|
||||
posix_cpu_timers_exit_group(tsk);
|
||||
} else {
|
||||
/*
|
||||
* This can only happen if the caller is de_thread().
|
||||
* FIXME: this is the temporary hack, we should teach
|
||||
* posix-cpu-timers to handle this case correctly.
|
||||
*/
|
||||
if (unlikely(has_group_leader_pid(tsk)))
|
||||
posix_cpu_timers_exit_group(tsk);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (group_dead) {
|
||||
@@ -191,6 +182,7 @@ void put_task_struct_rcu_user(struct task_struct *task)
|
||||
void release_task(struct task_struct *p)
|
||||
{
|
||||
struct task_struct *leader;
|
||||
struct pid *thread_pid;
|
||||
int zap_leader;
|
||||
repeat:
|
||||
/* don't need to get the RCU readlock here - the process is dead and
|
||||
@@ -199,11 +191,11 @@ repeat:
|
||||
atomic_dec(&__task_cred(p)->user->processes);
|
||||
rcu_read_unlock();
|
||||
|
||||
proc_flush_task(p);
|
||||
cgroup_release(p);
|
||||
|
||||
write_lock_irq(&tasklist_lock);
|
||||
ptrace_release_task(p);
|
||||
thread_pid = get_pid(p->thread_pid);
|
||||
__exit_signal(p);
|
||||
|
||||
/*
|
||||
@@ -226,6 +218,7 @@ repeat:
|
||||
}
|
||||
|
||||
write_unlock_irq(&tasklist_lock);
|
||||
proc_flush_pid(thread_pid);
|
||||
release_thread(p);
|
||||
put_task_struct_rcu_user(p);
|
||||
|
||||
@@ -258,6 +251,7 @@ void rcuwait_wake_up(struct rcuwait *w)
|
||||
wake_up_process(task);
|
||||
rcu_read_unlock();
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(rcuwait_wake_up);
|
||||
|
||||
/*
|
||||
* Determine if a process group is "orphaned", according to the POSIX
|
||||
@@ -619,8 +613,8 @@ static void forget_original_parent(struct task_struct *father,
|
||||
reaper = find_new_reaper(father, reaper);
|
||||
list_for_each_entry(p, &father->children, sibling) {
|
||||
for_each_thread(p, t) {
|
||||
t->real_parent = reaper;
|
||||
BUG_ON((!t->ptrace) != (t->parent == father));
|
||||
RCU_INIT_POINTER(t->real_parent, reaper);
|
||||
BUG_ON((!t->ptrace) != (rcu_access_pointer(t->parent) == father));
|
||||
if (likely(!t->ptrace))
|
||||
t->parent = t->real_parent;
|
||||
if (t->pdeath_signal)
|
||||
|
||||
+2
-3
@@ -34,7 +34,8 @@ u32 __initdata __visible main_extable_sort_needed = 1;
|
||||
/* Sort the kernel's built-in exception table */
|
||||
void __init sort_main_extable(void)
|
||||
{
|
||||
if (main_extable_sort_needed && __stop___ex_table > __start___ex_table) {
|
||||
if (main_extable_sort_needed &&
|
||||
&__stop___ex_table > &__start___ex_table) {
|
||||
pr_notice("Sorting __ex_table...\n");
|
||||
sort_extable(__start___ex_table, __stop___ex_table);
|
||||
}
|
||||
@@ -149,8 +150,6 @@ int kernel_text_address(unsigned long addr)
|
||||
goto out;
|
||||
if (is_bpf_text_address(addr))
|
||||
goto out;
|
||||
if (is_bpf_image_address(addr))
|
||||
goto out;
|
||||
ret = 0;
|
||||
out:
|
||||
if (no_rcu)
|
||||
|
||||
+29
-24
@@ -281,7 +281,7 @@ static inline void free_thread_stack(struct task_struct *tsk)
|
||||
MEMCG_KERNEL_STACK_KB,
|
||||
-(int)(PAGE_SIZE / 1024));
|
||||
|
||||
memcg_kmem_uncharge(vm->pages[i], 0);
|
||||
memcg_kmem_uncharge_page(vm->pages[i], 0);
|
||||
}
|
||||
|
||||
for (i = 0; i < NR_CACHED_STACKS; i++) {
|
||||
@@ -361,6 +361,7 @@ struct vm_area_struct *vm_area_dup(struct vm_area_struct *orig)
|
||||
if (new) {
|
||||
*new = *orig;
|
||||
INIT_LIST_HEAD(&new->anon_vma_chain);
|
||||
new->vm_next = new->vm_prev = NULL;
|
||||
}
|
||||
return new;
|
||||
}
|
||||
@@ -397,8 +398,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);
|
||||
|
||||
mod_memcg_page_state(first_page, MEMCG_KERNEL_STACK_KB,
|
||||
account * (THREAD_SIZE / 1024));
|
||||
mod_memcg_obj_state(stack, MEMCG_KERNEL_STACK_KB,
|
||||
account * (THREAD_SIZE / 1024));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,12 +414,13 @@ static int memcg_charge_kernel_stack(struct task_struct *tsk)
|
||||
|
||||
for (i = 0; i < THREAD_SIZE / PAGE_SIZE; i++) {
|
||||
/*
|
||||
* If memcg_kmem_charge() fails, page->mem_cgroup
|
||||
* pointer is NULL, and both memcg_kmem_uncharge()
|
||||
* If memcg_kmem_charge_page() fails, page->mem_cgroup
|
||||
* pointer is NULL, and both memcg_kmem_uncharge_page()
|
||||
* and mod_memcg_page_state() in free_thread_stack()
|
||||
* will ignore this page. So it's safe.
|
||||
*/
|
||||
ret = memcg_kmem_charge(vm->pages[i], GFP_KERNEL, 0);
|
||||
ret = memcg_kmem_charge_page(vm->pages[i], GFP_KERNEL,
|
||||
0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -552,14 +554,15 @@ static __latent_entropy int dup_mmap(struct mm_struct *mm,
|
||||
if (retval)
|
||||
goto fail_nomem_anon_vma_fork;
|
||||
if (tmp->vm_flags & VM_WIPEONFORK) {
|
||||
/* VM_WIPEONFORK gets a clean slate in the child. */
|
||||
/*
|
||||
* VM_WIPEONFORK gets a clean slate in the child.
|
||||
* Don't prepare anon_vma until fault since we don't
|
||||
* copy page for current vma.
|
||||
*/
|
||||
tmp->anon_vma = NULL;
|
||||
if (anon_vma_prepare(tmp))
|
||||
goto fail_nomem_anon_vma_fork;
|
||||
} else if (anon_vma_fork(tmp, mpnt))
|
||||
goto fail_nomem_anon_vma_fork;
|
||||
tmp->vm_flags &= ~(VM_LOCKED | VM_LOCKONFAULT);
|
||||
tmp->vm_next = tmp->vm_prev = NULL;
|
||||
file = tmp->vm_file;
|
||||
if (file) {
|
||||
struct inode *inode = file_inode(file);
|
||||
@@ -692,7 +695,7 @@ void __mmdrop(struct mm_struct *mm)
|
||||
WARN_ON_ONCE(mm == current->active_mm);
|
||||
mm_free_pgd(mm);
|
||||
destroy_context(mm);
|
||||
mmu_notifier_mm_destroy(mm);
|
||||
mmu_notifier_subscriptions_destroy(mm);
|
||||
check_mm(mm);
|
||||
put_user_ns(mm->user_ns);
|
||||
free_mm(mm);
|
||||
@@ -1025,7 +1028,7 @@ static struct mm_struct *mm_init(struct mm_struct *mm, struct task_struct *p,
|
||||
mm_init_aio(mm);
|
||||
mm_init_owner(mm, p);
|
||||
RCU_INIT_POINTER(mm->exe_file, NULL);
|
||||
mmu_notifier_mm_init(mm);
|
||||
mmu_notifier_subscriptions_init(mm);
|
||||
init_tlb_flush_pending(mm);
|
||||
#if defined(CONFIG_TRANSPARENT_HUGEPAGE) && !USE_SPLIT_PMD_PTLOCKS
|
||||
mm->pmd_huge_pte = NULL;
|
||||
@@ -1224,7 +1227,7 @@ struct mm_struct *mm_access(struct task_struct *task, unsigned int mode)
|
||||
struct mm_struct *mm;
|
||||
int err;
|
||||
|
||||
err = mutex_lock_killable(&task->signal->cred_guard_mutex);
|
||||
err = mutex_lock_killable(&task->signal->exec_update_mutex);
|
||||
if (err)
|
||||
return ERR_PTR(err);
|
||||
|
||||
@@ -1234,7 +1237,7 @@ struct mm_struct *mm_access(struct task_struct *task, unsigned int mode)
|
||||
mmput(mm);
|
||||
mm = ERR_PTR(-EACCES);
|
||||
}
|
||||
mutex_unlock(&task->signal->cred_guard_mutex);
|
||||
mutex_unlock(&task->signal->exec_update_mutex);
|
||||
|
||||
return mm;
|
||||
}
|
||||
@@ -1508,7 +1511,7 @@ static int copy_sighand(unsigned long clone_flags, struct task_struct *tsk)
|
||||
return 0;
|
||||
}
|
||||
sig = kmem_cache_alloc(sighand_cachep, GFP_KERNEL);
|
||||
rcu_assign_pointer(tsk->sighand, sig);
|
||||
RCU_INIT_POINTER(tsk->sighand, sig);
|
||||
if (!sig)
|
||||
return -ENOMEM;
|
||||
|
||||
@@ -1594,6 +1597,7 @@ static int copy_signal(unsigned long clone_flags, struct task_struct *tsk)
|
||||
sig->oom_score_adj_min = current->signal->oom_score_adj_min;
|
||||
|
||||
mutex_init(&sig->cred_guard_mutex);
|
||||
mutex_init(&sig->exec_update_mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -2174,16 +2178,15 @@ static __latent_entropy struct task_struct *copy_process(
|
||||
INIT_LIST_HEAD(&p->thread_group);
|
||||
p->task_works = NULL;
|
||||
|
||||
cgroup_threadgroup_change_begin(current);
|
||||
/*
|
||||
* Ensure that the cgroup subsystem policies allow the new process to be
|
||||
* forked. It should be noted the the new process's css_set can be changed
|
||||
* between here and cgroup_post_fork() if an organisation operation is in
|
||||
* progress.
|
||||
*/
|
||||
retval = cgroup_can_fork(p);
|
||||
retval = cgroup_can_fork(p, args);
|
||||
if (retval)
|
||||
goto bad_fork_cgroup_threadgroup_change_end;
|
||||
goto bad_fork_put_pidfd;
|
||||
|
||||
/*
|
||||
* From this point on we must avoid any synchronous user-space
|
||||
@@ -2288,8 +2291,7 @@ static __latent_entropy struct task_struct *copy_process(
|
||||
write_unlock_irq(&tasklist_lock);
|
||||
|
||||
proc_fork_connector(p);
|
||||
cgroup_post_fork(p);
|
||||
cgroup_threadgroup_change_end(current);
|
||||
cgroup_post_fork(p, args);
|
||||
perf_event_fork(p);
|
||||
|
||||
trace_task_newtask(p, clone_flags);
|
||||
@@ -2300,9 +2302,7 @@ static __latent_entropy struct task_struct *copy_process(
|
||||
bad_fork_cancel_cgroup:
|
||||
spin_unlock(¤t->sighand->siglock);
|
||||
write_unlock_irq(&tasklist_lock);
|
||||
cgroup_cancel_fork(p);
|
||||
bad_fork_cgroup_threadgroup_change_end:
|
||||
cgroup_threadgroup_change_end(current);
|
||||
cgroup_cancel_fork(p, args);
|
||||
bad_fork_put_pidfd:
|
||||
if (clone_flags & CLONE_PIDFD) {
|
||||
fput(pidfile);
|
||||
@@ -2631,6 +2631,9 @@ noinline static int copy_clone_args_from_user(struct kernel_clone_args *kargs,
|
||||
!valid_signal(args.exit_signal)))
|
||||
return -EINVAL;
|
||||
|
||||
if ((args.flags & CLONE_INTO_CGROUP) && args.cgroup < 0)
|
||||
return -EINVAL;
|
||||
|
||||
*kargs = (struct kernel_clone_args){
|
||||
.flags = args.flags,
|
||||
.pidfd = u64_to_user_ptr(args.pidfd),
|
||||
@@ -2641,6 +2644,7 @@ noinline static int copy_clone_args_from_user(struct kernel_clone_args *kargs,
|
||||
.stack_size = args.stack_size,
|
||||
.tls = args.tls,
|
||||
.set_tid_size = args.set_tid_size,
|
||||
.cgroup = args.cgroup,
|
||||
};
|
||||
|
||||
if (args.set_tid &&
|
||||
@@ -2684,7 +2688,8 @@ static inline bool clone3_stack_valid(struct kernel_clone_args *kargs)
|
||||
static bool clone3_args_valid(struct kernel_clone_args *kargs)
|
||||
{
|
||||
/* Verify that no unknown flags are passed along. */
|
||||
if (kargs->flags & ~(CLONE_LEGACY_FLAGS | CLONE_CLEAR_SIGHAND))
|
||||
if (kargs->flags &
|
||||
~(CLONE_LEGACY_FLAGS | CLONE_CLEAR_SIGHAND | CLONE_INTO_CGROUP))
|
||||
return false;
|
||||
|
||||
/*
|
||||
|
||||
+60
-134
@@ -135,8 +135,7 @@
|
||||
*
|
||||
* Where (A) orders the waiters increment and the futex value read through
|
||||
* atomic operations (see hb_waiters_inc) and where (B) orders the write
|
||||
* to futex and the waiters read -- this is done by the barriers for both
|
||||
* shared and private futexes in get_futex_key_refs().
|
||||
* to futex and the waiters read (see hb_waiters_pending()).
|
||||
*
|
||||
* This yields the following case (where X:=waiters, Y:=futex):
|
||||
*
|
||||
@@ -331,17 +330,6 @@ static void compat_exit_robust_list(struct task_struct *curr);
|
||||
static inline void compat_exit_robust_list(struct task_struct *curr) { }
|
||||
#endif
|
||||
|
||||
static inline void futex_get_mm(union futex_key *key)
|
||||
{
|
||||
mmgrab(key->private.mm);
|
||||
/*
|
||||
* Ensure futex_get_mm() implies a full barrier such that
|
||||
* get_futex_key() implies a full barrier. This is relied upon
|
||||
* as smp_mb(); (B), see the ordering comment above.
|
||||
*/
|
||||
smp_mb__after_atomic();
|
||||
}
|
||||
|
||||
/*
|
||||
* Reflects a new waiter being added to the waitqueue.
|
||||
*/
|
||||
@@ -370,6 +358,10 @@ static inline void hb_waiters_dec(struct futex_hash_bucket *hb)
|
||||
static inline int hb_waiters_pending(struct futex_hash_bucket *hb)
|
||||
{
|
||||
#ifdef CONFIG_SMP
|
||||
/*
|
||||
* Full barrier (B), see the ordering comment above.
|
||||
*/
|
||||
smp_mb();
|
||||
return atomic_read(&hb->waiters);
|
||||
#else
|
||||
return 1;
|
||||
@@ -385,9 +377,9 @@ static inline int hb_waiters_pending(struct futex_hash_bucket *hb)
|
||||
*/
|
||||
static struct futex_hash_bucket *hash_futex(union futex_key *key)
|
||||
{
|
||||
u32 hash = jhash2((u32*)&key->both.word,
|
||||
(sizeof(key->both.word)+sizeof(key->both.ptr))/4,
|
||||
u32 hash = jhash2((u32 *)key, offsetof(typeof(*key), both.offset) / 4,
|
||||
key->both.offset);
|
||||
|
||||
return &futex_queues[hash & (futex_hashsize - 1)];
|
||||
}
|
||||
|
||||
@@ -407,70 +399,6 @@ static inline int match_futex(union futex_key *key1, union futex_key *key2)
|
||||
&& key1->both.offset == key2->both.offset);
|
||||
}
|
||||
|
||||
/*
|
||||
* Take a reference to the resource addressed by a key.
|
||||
* Can be called while holding spinlocks.
|
||||
*
|
||||
*/
|
||||
static void get_futex_key_refs(union futex_key *key)
|
||||
{
|
||||
if (!key->both.ptr)
|
||||
return;
|
||||
|
||||
/*
|
||||
* On MMU less systems futexes are always "private" as there is no per
|
||||
* process address space. We need the smp wmb nevertheless - yes,
|
||||
* arch/blackfin has MMU less SMP ...
|
||||
*/
|
||||
if (!IS_ENABLED(CONFIG_MMU)) {
|
||||
smp_mb(); /* explicit smp_mb(); (B) */
|
||||
return;
|
||||
}
|
||||
|
||||
switch (key->both.offset & (FUT_OFF_INODE|FUT_OFF_MMSHARED)) {
|
||||
case FUT_OFF_INODE:
|
||||
ihold(key->shared.inode); /* implies smp_mb(); (B) */
|
||||
break;
|
||||
case FUT_OFF_MMSHARED:
|
||||
futex_get_mm(key); /* implies smp_mb(); (B) */
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* Private futexes do not hold reference on an inode or
|
||||
* mm, therefore the only purpose of calling get_futex_key_refs
|
||||
* is because we need the barrier for the lockless waiter check.
|
||||
*/
|
||||
smp_mb(); /* explicit smp_mb(); (B) */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Drop a reference to the resource addressed by a key.
|
||||
* The hash bucket spinlock must not be held. This is
|
||||
* a no-op for private futexes, see comment in the get
|
||||
* counterpart.
|
||||
*/
|
||||
static void drop_futex_key_refs(union futex_key *key)
|
||||
{
|
||||
if (!key->both.ptr) {
|
||||
/* If we're here then we tried to put a key we failed to get */
|
||||
WARN_ON_ONCE(1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IS_ENABLED(CONFIG_MMU))
|
||||
return;
|
||||
|
||||
switch (key->both.offset & (FUT_OFF_INODE|FUT_OFF_MMSHARED)) {
|
||||
case FUT_OFF_INODE:
|
||||
iput(key->shared.inode);
|
||||
break;
|
||||
case FUT_OFF_MMSHARED:
|
||||
mmdrop(key->private.mm);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
enum futex_access {
|
||||
FUTEX_READ,
|
||||
FUTEX_WRITE
|
||||
@@ -505,6 +433,46 @@ futex_setup_timer(ktime_t *time, struct hrtimer_sleeper *timeout,
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate a machine wide unique identifier for this inode.
|
||||
*
|
||||
* This relies on u64 not wrapping in the life-time of the machine; which with
|
||||
* 1ns resolution means almost 585 years.
|
||||
*
|
||||
* This further relies on the fact that a well formed program will not unmap
|
||||
* the file while it has a (shared) futex waiting on it. This mapping will have
|
||||
* a file reference which pins the mount and inode.
|
||||
*
|
||||
* If for some reason an inode gets evicted and read back in again, it will get
|
||||
* a new sequence number and will _NOT_ match, even though it is the exact same
|
||||
* file.
|
||||
*
|
||||
* It is important that match_futex() will never have a false-positive, esp.
|
||||
* for PI futexes that can mess up the state. The above argues that false-negatives
|
||||
* are only possible for malformed programs.
|
||||
*/
|
||||
static u64 get_inode_sequence_number(struct inode *inode)
|
||||
{
|
||||
static atomic64_t i_seq;
|
||||
u64 old;
|
||||
|
||||
/* Does the inode already have a sequence number? */
|
||||
old = atomic64_read(&inode->i_sequence);
|
||||
if (likely(old))
|
||||
return old;
|
||||
|
||||
for (;;) {
|
||||
u64 new = atomic64_add_return(1, &i_seq);
|
||||
if (WARN_ON_ONCE(!new))
|
||||
continue;
|
||||
|
||||
old = atomic64_cmpxchg_relaxed(&inode->i_sequence, 0, new);
|
||||
if (old)
|
||||
return old;
|
||||
return new;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get_futex_key() - Get parameters which are the keys for a futex
|
||||
* @uaddr: virtual address of the futex
|
||||
@@ -517,9 +485,15 @@ futex_setup_timer(ktime_t *time, struct hrtimer_sleeper *timeout,
|
||||
*
|
||||
* 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).
|
||||
* We can usually work out the index without swapping in the page.
|
||||
* For shared mappings (when @fshared), the key is:
|
||||
* ( inode->i_sequence, page->index, offset_within_page )
|
||||
* [ also see get_inode_sequence_number() ]
|
||||
*
|
||||
* For private mappings (or when !@fshared), the key is:
|
||||
* ( current->mm, address, 0 )
|
||||
*
|
||||
* This allows (cross process, where applicable) identification of the futex
|
||||
* without keeping the page pinned for the duration of the FUTEX_WAIT.
|
||||
*
|
||||
* lock_page() might sleep, the caller should not hold a spinlock.
|
||||
*/
|
||||
@@ -556,7 +530,6 @@ get_futex_key(u32 __user *uaddr, int fshared, union futex_key *key, enum futex_a
|
||||
if (!fshared) {
|
||||
key->private.mm = mm;
|
||||
key->private.address = address;
|
||||
get_futex_key_refs(key); /* implies smp_mb(); (B) */
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -659,8 +632,6 @@ again:
|
||||
key->private.mm = mm;
|
||||
key->private.address = address;
|
||||
|
||||
get_futex_key_refs(key); /* implies smp_mb(); (B) */
|
||||
|
||||
} else {
|
||||
struct inode *inode;
|
||||
|
||||
@@ -692,36 +663,8 @@ again:
|
||||
goto again;
|
||||
}
|
||||
|
||||
/*
|
||||
* Take a reference unless it is about to be freed. Previously
|
||||
* this reference was taken by ihold under the page lock
|
||||
* pinning the inode in place so i_lock was unnecessary. The
|
||||
* only way for this check to fail is if the inode was
|
||||
* truncated in parallel which is almost certainly an
|
||||
* application bug. In such a case, just retry.
|
||||
*
|
||||
* We are not calling into get_futex_key_refs() in file-backed
|
||||
* cases, therefore a successful atomic_inc return below will
|
||||
* guarantee that get_futex_key() will still imply smp_mb(); (B).
|
||||
*/
|
||||
if (!atomic_inc_not_zero(&inode->i_count)) {
|
||||
rcu_read_unlock();
|
||||
put_page(page);
|
||||
|
||||
goto again;
|
||||
}
|
||||
|
||||
/* Should be impossible but lets be paranoid for now */
|
||||
if (WARN_ON_ONCE(inode->i_mapping != mapping)) {
|
||||
err = -EFAULT;
|
||||
rcu_read_unlock();
|
||||
iput(inode);
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
key->both.offset |= FUT_OFF_INODE; /* inode-based key */
|
||||
key->shared.inode = inode;
|
||||
key->shared.i_seq = get_inode_sequence_number(inode);
|
||||
key->shared.pgoff = basepage_index(tail);
|
||||
rcu_read_unlock();
|
||||
}
|
||||
@@ -733,7 +676,6 @@ out:
|
||||
|
||||
static inline void put_futex_key(union futex_key *key)
|
||||
{
|
||||
drop_futex_key_refs(key);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1723,10 +1665,9 @@ static int futex_atomic_op_inuser(unsigned int encoded_op, u32 __user *uaddr)
|
||||
oparg = 1 << oparg;
|
||||
}
|
||||
|
||||
if (!access_ok(uaddr, sizeof(u32)))
|
||||
return -EFAULT;
|
||||
|
||||
pagefault_disable();
|
||||
ret = arch_futex_atomic_op_inuser(op, oparg, &oldval, uaddr);
|
||||
pagefault_enable();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
@@ -1868,7 +1809,6 @@ void requeue_futex(struct futex_q *q, struct futex_hash_bucket *hb1,
|
||||
plist_add(&q->list, &hb2->chain);
|
||||
q->lock_ptr = &hb2->lock;
|
||||
}
|
||||
get_futex_key_refs(key2);
|
||||
q->key = *key2;
|
||||
}
|
||||
|
||||
@@ -1890,7 +1830,6 @@ static inline
|
||||
void requeue_pi_wake_futex(struct futex_q *q, union futex_key *key,
|
||||
struct futex_hash_bucket *hb)
|
||||
{
|
||||
get_futex_key_refs(key);
|
||||
q->key = *key;
|
||||
|
||||
__unqueue_futex(q);
|
||||
@@ -2001,7 +1940,7 @@ static int futex_requeue(u32 __user *uaddr1, unsigned int flags,
|
||||
u32 *cmpval, int requeue_pi)
|
||||
{
|
||||
union futex_key key1 = FUTEX_KEY_INIT, key2 = FUTEX_KEY_INIT;
|
||||
int drop_count = 0, task_count = 0, ret;
|
||||
int task_count = 0, ret;
|
||||
struct futex_pi_state *pi_state = NULL;
|
||||
struct futex_hash_bucket *hb1, *hb2;
|
||||
struct futex_q *this, *next;
|
||||
@@ -2122,7 +2061,6 @@ retry_private:
|
||||
*/
|
||||
if (ret > 0) {
|
||||
WARN_ON(pi_state);
|
||||
drop_count++;
|
||||
task_count++;
|
||||
/*
|
||||
* If we acquired the lock, then the user space value
|
||||
@@ -2242,7 +2180,6 @@ retry_private:
|
||||
* doing so.
|
||||
*/
|
||||
requeue_pi_wake_futex(this, &key2, hb2);
|
||||
drop_count++;
|
||||
continue;
|
||||
} else if (ret) {
|
||||
/*
|
||||
@@ -2263,7 +2200,6 @@ retry_private:
|
||||
}
|
||||
}
|
||||
requeue_futex(this, hb1, hb2, &key2);
|
||||
drop_count++;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -2278,15 +2214,6 @@ out_unlock:
|
||||
wake_up_q(&wake_q);
|
||||
hb_waiters_dec(hb2);
|
||||
|
||||
/*
|
||||
* drop_futex_key_refs() must be called outside the spinlocks. During
|
||||
* the requeue we moved futex_q's from the hash bucket at key1 to the
|
||||
* one at key2 and updated their key pointer. We no longer need to
|
||||
* hold the references to key1.
|
||||
*/
|
||||
while (--drop_count >= 0)
|
||||
drop_futex_key_refs(&key1);
|
||||
|
||||
out_put_keys:
|
||||
put_futex_key(&key2);
|
||||
out_put_key1:
|
||||
@@ -2416,7 +2343,6 @@ retry:
|
||||
ret = 1;
|
||||
}
|
||||
|
||||
drop_futex_key_refs(&q->key);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -58,7 +58,7 @@ struct gcov_node {
|
||||
struct dentry *dentry;
|
||||
struct dentry **links;
|
||||
int num_loaded;
|
||||
char name[0];
|
||||
char name[];
|
||||
};
|
||||
|
||||
static const char objtree[] = OBJTREE;
|
||||
@@ -108,9 +108,9 @@ static void *gcov_seq_next(struct seq_file *seq, void *data, loff_t *pos)
|
||||
{
|
||||
struct gcov_iterator *iter = data;
|
||||
|
||||
(*pos)++;
|
||||
if (gcov_iter_next(iter))
|
||||
return NULL;
|
||||
(*pos)++;
|
||||
|
||||
return iter;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ static struct gcov_info *gcov_info_head;
|
||||
struct gcov_fn_info {
|
||||
unsigned int ident;
|
||||
unsigned int checksum;
|
||||
unsigned int n_ctrs[0];
|
||||
unsigned int n_ctrs[];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -78,7 +78,7 @@ struct gcov_info {
|
||||
unsigned int n_functions;
|
||||
const struct gcov_fn_info *functions;
|
||||
unsigned int ctr_mask;
|
||||
struct gcov_ctr_info counts[0];
|
||||
struct gcov_ctr_info counts[];
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -352,7 +352,7 @@ struct gcov_iterator {
|
||||
unsigned int count;
|
||||
|
||||
int num_types;
|
||||
struct type_info type_info[0];
|
||||
struct type_info type_info[];
|
||||
};
|
||||
|
||||
static struct gcov_fn_info *get_func(struct gcov_iterator *iter)
|
||||
|
||||
@@ -68,7 +68,7 @@ struct gcov_fn_info {
|
||||
unsigned int ident;
|
||||
unsigned int lineno_checksum;
|
||||
unsigned int cfg_checksum;
|
||||
struct gcov_ctr_info ctrs[0];
|
||||
struct gcov_ctr_info ctrs[];
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -43,6 +43,10 @@ config GENERIC_IRQ_MIGRATION
|
||||
config AUTO_IRQ_AFFINITY
|
||||
bool
|
||||
|
||||
# Interrupt injection mechanism
|
||||
config GENERIC_IRQ_INJECTION
|
||||
bool
|
||||
|
||||
# Tasklet based software resend for pending interrupts on enable_irq()
|
||||
config HARDIRQS_SW_RESEND
|
||||
bool
|
||||
@@ -127,6 +131,7 @@ config SPARSE_IRQ
|
||||
config GENERIC_IRQ_DEBUGFS
|
||||
bool "Expose irq internals in debugfs"
|
||||
depends on DEBUG_FS
|
||||
select GENERIC_IRQ_INJECTION
|
||||
default n
|
||||
---help---
|
||||
|
||||
|
||||
+1
-1
@@ -278,7 +278,7 @@ int irq_startup(struct irq_desc *desc, bool resend, bool force)
|
||||
}
|
||||
}
|
||||
if (resend)
|
||||
check_irq_resend(desc);
|
||||
check_irq_resend(desc, false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
+2
-27
@@ -114,6 +114,7 @@ static const struct irq_bit_descr irqdata_states[] = {
|
||||
BIT_MASK_DESCR(IRQD_AFFINITY_MANAGED),
|
||||
BIT_MASK_DESCR(IRQD_MANAGED_SHUTDOWN),
|
||||
BIT_MASK_DESCR(IRQD_CAN_RESERVE),
|
||||
BIT_MASK_DESCR(IRQD_MSI_NOMASK_QUIRK),
|
||||
|
||||
BIT_MASK_DESCR(IRQD_FORWARDED_TO_VCPU),
|
||||
|
||||
@@ -189,33 +190,7 @@ static ssize_t irq_debug_write(struct file *file, const char __user *user_buf,
|
||||
return -EFAULT;
|
||||
|
||||
if (!strncmp(buf, "trigger", size)) {
|
||||
unsigned long flags;
|
||||
int err;
|
||||
|
||||
/* Try the HW interface first */
|
||||
err = irq_set_irqchip_state(irq_desc_get_irq(desc),
|
||||
IRQCHIP_STATE_PENDING, true);
|
||||
if (!err)
|
||||
return count;
|
||||
|
||||
/*
|
||||
* Otherwise, try to inject via the resend interface,
|
||||
* which may or may not succeed.
|
||||
*/
|
||||
chip_bus_lock(desc);
|
||||
raw_spin_lock_irqsave(&desc->lock, flags);
|
||||
|
||||
if (irq_settings_is_level(desc) || desc->istate & IRQS_NMI) {
|
||||
/* Can't do level nor NMIs, sorry */
|
||||
err = -EINVAL;
|
||||
} else {
|
||||
desc->istate |= IRQS_PENDING;
|
||||
check_irq_resend(desc);
|
||||
err = 0;
|
||||
}
|
||||
|
||||
raw_spin_unlock_irqrestore(&desc->lock, flags);
|
||||
chip_bus_sync_unlock(desc);
|
||||
int err = irq_inject_interrupt(irq_desc_get_irq(desc));
|
||||
|
||||
return err ? err : count;
|
||||
}
|
||||
|
||||
@@ -145,6 +145,13 @@ irqreturn_t __handle_irq_event_percpu(struct irq_desc *desc, unsigned int *flags
|
||||
for_each_action_of_desc(desc, action) {
|
||||
irqreturn_t res;
|
||||
|
||||
/*
|
||||
* If this IRQ would be threaded under force_irqthreads, mark it so.
|
||||
*/
|
||||
if (irq_settings_can_thread(desc) &&
|
||||
!(action->flags & (IRQF_NO_THREAD | IRQF_PERCPU | IRQF_ONESHOT)))
|
||||
lockdep_hardirq_threaded();
|
||||
|
||||
trace_irq_handler_entry(irq, action);
|
||||
res = action->handler(irq, action->dev_id);
|
||||
trace_irq_handler_exit(irq, action, res);
|
||||
|
||||
@@ -108,7 +108,7 @@ irqreturn_t handle_irq_event_percpu(struct irq_desc *desc);
|
||||
irqreturn_t handle_irq_event(struct irq_desc *desc);
|
||||
|
||||
/* Resending of interrupts :*/
|
||||
void check_irq_resend(struct irq_desc *desc);
|
||||
int check_irq_resend(struct irq_desc *desc, bool inject);
|
||||
bool irq_wait_for_poll(struct irq_desc *desc);
|
||||
void __irq_wake_thread(struct irq_desc *desc, struct irqaction *action);
|
||||
|
||||
@@ -128,8 +128,6 @@ 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);
|
||||
|
||||
extern void irq_set_thread_affinity(struct irq_desc *desc);
|
||||
|
||||
extern int irq_do_set_affinity(struct irq_data *data,
|
||||
@@ -427,6 +425,10 @@ static inline struct cpumask *irq_desc_get_pending_mask(struct irq_desc *desc)
|
||||
{
|
||||
return desc->pending_mask;
|
||||
}
|
||||
static inline bool handle_enforce_irqctx(struct irq_data *data)
|
||||
{
|
||||
return irqd_is_handle_enforce_irqctx(data);
|
||||
}
|
||||
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)
|
||||
@@ -453,6 +455,10 @@ static inline bool irq_fixup_move_pending(struct irq_desc *desc, bool fclear)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
static inline bool handle_enforce_irqctx(struct irq_data *data)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif /* !CONFIG_GENERIC_PENDING_IRQ */
|
||||
|
||||
#if !defined(CONFIG_IRQ_DOMAIN) || !defined(CONFIG_IRQ_DOMAIN_HIERARCHY)
|
||||
|
||||
@@ -638,9 +638,15 @@ void irq_init_desc(unsigned int irq)
|
||||
int generic_handle_irq(unsigned int irq)
|
||||
{
|
||||
struct irq_desc *desc = irq_to_desc(irq);
|
||||
struct irq_data *data;
|
||||
|
||||
if (!desc)
|
||||
return -EINVAL;
|
||||
|
||||
data = irq_desc_get_irq_data(desc);
|
||||
if (WARN_ON_ONCE(!in_irq() && handle_enforce_irqctx(data)))
|
||||
return -EPERM;
|
||||
|
||||
generic_handle_irq_desc(desc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -46,11 +46,11 @@ const struct fwnode_operations irqchip_fwnode_ops;
|
||||
EXPORT_SYMBOL_GPL(irqchip_fwnode_ops);
|
||||
|
||||
/**
|
||||
* irq_domain_alloc_fwnode - Allocate a fwnode_handle suitable for
|
||||
* __irq_domain_alloc_fwnode - Allocate a fwnode_handle suitable for
|
||||
* identifying an irq domain
|
||||
* @type: Type of irqchip_fwnode. See linux/irqdomain.h
|
||||
* @name: Optional user provided domain name
|
||||
* @id: Optional user provided id if name != NULL
|
||||
* @name: Optional user provided domain name
|
||||
* @pa: Optional user-provided physical address
|
||||
*
|
||||
* Allocate a struct irqchip_fwid, and return a poiner to the embedded
|
||||
@@ -1310,6 +1310,11 @@ int irq_domain_alloc_irqs_hierarchy(struct irq_domain *domain,
|
||||
unsigned int irq_base,
|
||||
unsigned int nr_irqs, void *arg)
|
||||
{
|
||||
if (!domain->ops->alloc) {
|
||||
pr_debug("domain->ops->alloc() is NULL\n");
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
return domain->ops->alloc(domain, irq_base, nr_irqs, arg);
|
||||
}
|
||||
|
||||
@@ -1347,11 +1352,6 @@ int __irq_domain_alloc_irqs(struct irq_domain *domain, int irq_base,
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!domain->ops->alloc) {
|
||||
pr_debug("domain->ops->alloc() is NULL\n");
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
if (realloc && irq_base >= 0) {
|
||||
virq = irq_base;
|
||||
} else {
|
||||
|
||||
+18
-18
@@ -323,7 +323,11 @@ int irq_set_affinity_locked(struct irq_data *data, const struct cpumask *mask,
|
||||
|
||||
if (desc->affinity_notify) {
|
||||
kref_get(&desc->affinity_notify->kref);
|
||||
schedule_work(&desc->affinity_notify->work);
|
||||
if (!schedule_work(&desc->affinity_notify->work)) {
|
||||
/* Work was already scheduled, drop our extra ref */
|
||||
kref_put(&desc->affinity_notify->kref,
|
||||
desc->affinity_notify->release);
|
||||
}
|
||||
}
|
||||
irqd_set(data, IRQD_AFFINITY_SET);
|
||||
|
||||
@@ -423,7 +427,10 @@ irq_set_affinity_notifier(unsigned int irq, struct irq_affinity_notify *notify)
|
||||
raw_spin_unlock_irqrestore(&desc->lock, flags);
|
||||
|
||||
if (old_notify) {
|
||||
cancel_work_sync(&old_notify->work);
|
||||
if (cancel_work_sync(&old_notify->work)) {
|
||||
/* Pending work had a ref, put that one too */
|
||||
kref_put(&old_notify->kref, old_notify->release);
|
||||
}
|
||||
kref_put(&old_notify->kref, old_notify->release);
|
||||
}
|
||||
|
||||
@@ -481,23 +488,9 @@ int irq_setup_affinity(struct irq_desc *desc)
|
||||
{
|
||||
return irq_select_affinity(irq_desc_get_irq(desc));
|
||||
}
|
||||
#endif
|
||||
#endif /* CONFIG_AUTO_IRQ_AFFINITY */
|
||||
#endif /* CONFIG_SMP */
|
||||
|
||||
/*
|
||||
* Called when a bogus affinity is set via /proc/irq
|
||||
*/
|
||||
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 = irq_setup_affinity(desc);
|
||||
raw_spin_unlock_irqrestore(&desc->lock, flags);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* irq_set_vcpu_affinity - Set vcpu affinity for the interrupt
|
||||
@@ -731,6 +724,13 @@ static int set_irq_wake_real(unsigned int irq, unsigned int on)
|
||||
*
|
||||
* Wakeup mode lets this IRQ wake the system from sleep
|
||||
* states like "suspend to RAM".
|
||||
*
|
||||
* Note: irq enable/disable state is completely orthogonal
|
||||
* to the enable/disable state of irq wake. An irq can be
|
||||
* disabled with disable_irq() and still wake the system as
|
||||
* long as the irq has wake enabled. If this does not hold,
|
||||
* then the underlying irq chip and the related driver need
|
||||
* to be investigated.
|
||||
*/
|
||||
int irq_set_irq_wake(unsigned int irq, unsigned int on)
|
||||
{
|
||||
|
||||
+4
-1
@@ -453,8 +453,11 @@ int msi_domain_alloc_irqs(struct irq_domain *domain, struct device *dev,
|
||||
continue;
|
||||
|
||||
irq_data = irq_domain_get_irq_data(domain, desc->irq);
|
||||
if (!can_reserve)
|
||||
if (!can_reserve) {
|
||||
irqd_clr_can_reserve(irq_data);
|
||||
if (domain->flags & IRQ_DOMAIN_MSI_NOMASK_QUIRK)
|
||||
irqd_set_msi_nomask_quirk(irq_data);
|
||||
}
|
||||
ret = irq_domain_activate_irq(irq_data, can_reserve);
|
||||
if (ret)
|
||||
goto cleanup;
|
||||
|
||||
+43
-21
@@ -111,6 +111,28 @@ static int irq_affinity_list_proc_show(struct seq_file *m, void *v)
|
||||
return show_irq_affinity(AFFINITY_LIST, m);
|
||||
}
|
||||
|
||||
#ifndef CONFIG_AUTO_IRQ_AFFINITY
|
||||
static inline int irq_select_affinity_usr(unsigned int irq)
|
||||
{
|
||||
/*
|
||||
* If the interrupt is started up already then this fails. The
|
||||
* interrupt is assigned to an online CPU already. There is no
|
||||
* point to move it around randomly. Tell user space that the
|
||||
* selected mask is bogus.
|
||||
*
|
||||
* If not then any change to the affinity is pointless because the
|
||||
* startup code invokes irq_setup_affinity() which will select
|
||||
* a online CPU anyway.
|
||||
*/
|
||||
return -EINVAL;
|
||||
}
|
||||
#else
|
||||
/* ALPHA magic affinity auto selector. Keep it for historical reasons. */
|
||||
static inline int irq_select_affinity_usr(unsigned int irq)
|
||||
{
|
||||
return irq_select_affinity(irq);
|
||||
}
|
||||
#endif
|
||||
|
||||
static ssize_t write_irq_affinity(int type, struct file *file,
|
||||
const char __user *buffer, size_t count, loff_t *pos)
|
||||
@@ -176,20 +198,20 @@ static int irq_affinity_list_proc_open(struct inode *inode, struct file *file)
|
||||
return single_open(file, irq_affinity_list_proc_show, PDE_DATA(inode));
|
||||
}
|
||||
|
||||
static const struct file_operations irq_affinity_proc_fops = {
|
||||
.open = irq_affinity_proc_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
.write = irq_affinity_proc_write,
|
||||
static const struct proc_ops irq_affinity_proc_ops = {
|
||||
.proc_open = irq_affinity_proc_open,
|
||||
.proc_read = seq_read,
|
||||
.proc_lseek = seq_lseek,
|
||||
.proc_release = single_release,
|
||||
.proc_write = irq_affinity_proc_write,
|
||||
};
|
||||
|
||||
static const struct file_operations irq_affinity_list_proc_fops = {
|
||||
.open = irq_affinity_list_proc_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
.write = irq_affinity_list_proc_write,
|
||||
static const struct proc_ops irq_affinity_list_proc_ops = {
|
||||
.proc_open = irq_affinity_list_proc_open,
|
||||
.proc_read = seq_read,
|
||||
.proc_lseek = seq_lseek,
|
||||
.proc_release = single_release,
|
||||
.proc_write = irq_affinity_list_proc_write,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_GENERIC_IRQ_EFFECTIVE_AFF_MASK
|
||||
@@ -246,12 +268,12 @@ static int default_affinity_open(struct inode *inode, struct file *file)
|
||||
return single_open(file, default_affinity_show, PDE_DATA(inode));
|
||||
}
|
||||
|
||||
static const struct file_operations default_affinity_proc_fops = {
|
||||
.open = default_affinity_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
.write = default_affinity_write,
|
||||
static const struct proc_ops default_affinity_proc_ops = {
|
||||
.proc_open = default_affinity_open,
|
||||
.proc_read = seq_read,
|
||||
.proc_lseek = seq_lseek,
|
||||
.proc_release = single_release,
|
||||
.proc_write = default_affinity_write,
|
||||
};
|
||||
|
||||
static int irq_node_proc_show(struct seq_file *m, void *v)
|
||||
@@ -342,7 +364,7 @@ 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, irqp);
|
||||
&irq_affinity_proc_ops, irqp);
|
||||
|
||||
/* create /proc/irq/<irq>/affinity_hint */
|
||||
proc_create_single_data("affinity_hint", 0444, desc->dir,
|
||||
@@ -350,7 +372,7 @@ void register_irq_proc(unsigned int irq, struct irq_desc *desc)
|
||||
|
||||
/* create /proc/irq/<irq>/smp_affinity_list */
|
||||
proc_create_data("smp_affinity_list", 0644, desc->dir,
|
||||
&irq_affinity_list_proc_fops, irqp);
|
||||
&irq_affinity_list_proc_ops, irqp);
|
||||
|
||||
proc_create_single_data("node", 0444, desc->dir, irq_node_proc_show,
|
||||
irqp);
|
||||
@@ -401,7 +423,7 @@ static void register_default_affinity_proc(void)
|
||||
{
|
||||
#ifdef CONFIG_SMP
|
||||
proc_create("irq/default_smp_affinity", 0644, NULL,
|
||||
&default_affinity_proc_fops);
|
||||
&default_affinity_proc_ops);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
+108
-37
@@ -47,6 +47,43 @@ static void resend_irqs(unsigned long arg)
|
||||
/* Tasklet to handle resend: */
|
||||
static DECLARE_TASKLET(resend_tasklet, resend_irqs, 0);
|
||||
|
||||
static int irq_sw_resend(struct irq_desc *desc)
|
||||
{
|
||||
unsigned int irq = irq_desc_get_irq(desc);
|
||||
|
||||
/*
|
||||
* Validate whether this interrupt can be safely injected from
|
||||
* non interrupt context
|
||||
*/
|
||||
if (handle_enforce_irqctx(&desc->irq_data))
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* If the interrupt is running in the thread context of the parent
|
||||
* irq we need to be careful, because we cannot trigger it
|
||||
* directly.
|
||||
*/
|
||||
if (irq_settings_is_nested_thread(desc)) {
|
||||
/*
|
||||
* If the parent_irq is valid, we retrigger the parent,
|
||||
* otherwise we do nothing.
|
||||
*/
|
||||
if (!desc->parent_irq)
|
||||
return -EINVAL;
|
||||
irq = desc->parent_irq;
|
||||
}
|
||||
|
||||
/* Set it pending and activate the softirq: */
|
||||
set_bit(irq, irqs_resend);
|
||||
tasklet_schedule(&resend_tasklet);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
static int irq_sw_resend(struct irq_desc *desc)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -54,49 +91,83 @@ static DECLARE_TASKLET(resend_tasklet, resend_irqs, 0);
|
||||
*
|
||||
* Is called with interrupts disabled and desc->lock held.
|
||||
*/
|
||||
void check_irq_resend(struct irq_desc *desc)
|
||||
int check_irq_resend(struct irq_desc *desc, bool inject)
|
||||
{
|
||||
int err = 0;
|
||||
|
||||
/*
|
||||
* We do not resend level type interrupts. Level type
|
||||
* interrupts are resent by hardware when they are still
|
||||
* active. Clear the pending bit so suspend/resume does not
|
||||
* get confused.
|
||||
* We do not resend level type interrupts. Level type interrupts
|
||||
* are resent by hardware when they are still active. Clear the
|
||||
* pending bit so suspend/resume does not get confused.
|
||||
*/
|
||||
if (irq_settings_is_level(desc)) {
|
||||
desc->istate &= ~IRQS_PENDING;
|
||||
return;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (desc->istate & IRQS_REPLAY)
|
||||
return;
|
||||
if (desc->istate & IRQS_PENDING) {
|
||||
desc->istate &= ~IRQS_PENDING;
|
||||
return -EBUSY;
|
||||
|
||||
if (!(desc->istate & IRQS_PENDING) && !inject)
|
||||
return 0;
|
||||
|
||||
desc->istate &= ~IRQS_PENDING;
|
||||
|
||||
if (!desc->irq_data.chip->irq_retrigger ||
|
||||
!desc->irq_data.chip->irq_retrigger(&desc->irq_data))
|
||||
err = irq_sw_resend(desc);
|
||||
|
||||
/* If the retrigger was successfull, mark it with the REPLAY bit */
|
||||
if (!err)
|
||||
desc->istate |= IRQS_REPLAY;
|
||||
|
||||
if (!desc->irq_data.chip->irq_retrigger ||
|
||||
!desc->irq_data.chip->irq_retrigger(&desc->irq_data)) {
|
||||
#ifdef CONFIG_HARDIRQS_SW_RESEND
|
||||
unsigned int irq = irq_desc_get_irq(desc);
|
||||
|
||||
/*
|
||||
* If the interrupt is running in the thread
|
||||
* context of the parent irq we need to be
|
||||
* careful, because we cannot trigger it
|
||||
* directly.
|
||||
*/
|
||||
if (irq_settings_is_nested_thread(desc)) {
|
||||
/*
|
||||
* If the parent_irq is valid, we
|
||||
* retrigger the parent, otherwise we
|
||||
* do nothing.
|
||||
*/
|
||||
if (!desc->parent_irq)
|
||||
return;
|
||||
irq = desc->parent_irq;
|
||||
}
|
||||
/* Set it pending and activate the softirq: */
|
||||
set_bit(irq, irqs_resend);
|
||||
tasklet_schedule(&resend_tasklet);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_GENERIC_IRQ_INJECTION
|
||||
/**
|
||||
* irq_inject_interrupt - Inject an interrupt for testing/error injection
|
||||
* @irq: The interrupt number
|
||||
*
|
||||
* This function must only be used for debug and testing purposes!
|
||||
*
|
||||
* Especially on x86 this can cause a premature completion of an interrupt
|
||||
* affinity change causing the interrupt line to become stale. Very
|
||||
* unlikely, but possible.
|
||||
*
|
||||
* The injection can fail for various reasons:
|
||||
* - Interrupt is not activated
|
||||
* - Interrupt is NMI type or currently replaying
|
||||
* - Interrupt is level type
|
||||
* - Interrupt does not support hardware retrigger and software resend is
|
||||
* either not enabled or not possible for the interrupt.
|
||||
*/
|
||||
int irq_inject_interrupt(unsigned int irq)
|
||||
{
|
||||
struct irq_desc *desc;
|
||||
unsigned long flags;
|
||||
int err;
|
||||
|
||||
/* Try the state injection hardware interface first */
|
||||
if (!irq_set_irqchip_state(irq, IRQCHIP_STATE_PENDING, true))
|
||||
return 0;
|
||||
|
||||
/* That failed, try via the resend mechanism */
|
||||
desc = irq_get_desc_buslock(irq, &flags, 0);
|
||||
if (!desc)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Only try to inject when the interrupt is:
|
||||
* - not NMI type
|
||||
* - activated
|
||||
*/
|
||||
if ((desc->istate & IRQS_NMI) || !irqd_is_activated(&desc->irq_data))
|
||||
err = -EINVAL;
|
||||
else
|
||||
err = check_irq_resend(desc, true);
|
||||
|
||||
irq_put_desc_busunlock(desc, flags);
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(irq_inject_interrupt);
|
||||
#endif
|
||||
|
||||
@@ -153,7 +153,9 @@ static void irq_work_run_list(struct llist_head *list)
|
||||
*/
|
||||
flags = atomic_fetch_andnot(IRQ_WORK_PENDING, &work->flags);
|
||||
|
||||
lockdep_irq_work_enter(work);
|
||||
work->func(work);
|
||||
lockdep_irq_work_exit(work);
|
||||
/*
|
||||
* Clear the BUSY bit and return to the free state if
|
||||
* no-one else claimed it meanwhile.
|
||||
|
||||
+9
-10
@@ -44,7 +44,7 @@ __attribute__((weak, section(".rodata")));
|
||||
extern const unsigned long kallsyms_relative_base
|
||||
__attribute__((weak, section(".rodata")));
|
||||
|
||||
extern const u8 kallsyms_token_table[] __weak;
|
||||
extern const char kallsyms_token_table[] __weak;
|
||||
extern const u16 kallsyms_token_index[] __weak;
|
||||
|
||||
extern const unsigned int kallsyms_markers[] __weak;
|
||||
@@ -58,7 +58,8 @@ static unsigned int kallsyms_expand_symbol(unsigned int off,
|
||||
char *result, size_t maxlen)
|
||||
{
|
||||
int len, skipped_first = 0;
|
||||
const u8 *tptr, *data;
|
||||
const char *tptr;
|
||||
const u8 *data;
|
||||
|
||||
/* Get the compressed symbol length from the first symbol byte. */
|
||||
data = &kallsyms_names[off];
|
||||
@@ -174,7 +175,6 @@ unsigned long kallsyms_lookup_name(const char *name)
|
||||
}
|
||||
return module_kallsyms_lookup_name(name);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(kallsyms_lookup_name);
|
||||
|
||||
int kallsyms_on_each_symbol(int (*fn)(void *, const char *, struct module *,
|
||||
unsigned long),
|
||||
@@ -193,7 +193,6 @@ int kallsyms_on_each_symbol(int (*fn)(void *, const char *, struct module *,
|
||||
}
|
||||
return module_kallsyms_on_each_symbol(fn, data);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(kallsyms_on_each_symbol);
|
||||
|
||||
static unsigned long get_symbol_pos(unsigned long addr,
|
||||
unsigned long *symbolsize,
|
||||
@@ -698,16 +697,16 @@ const char *kdb_walk_kallsyms(loff_t *pos)
|
||||
}
|
||||
#endif /* CONFIG_KGDB_KDB */
|
||||
|
||||
static const struct file_operations kallsyms_operations = {
|
||||
.open = kallsyms_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release_private,
|
||||
static const struct proc_ops kallsyms_proc_ops = {
|
||||
.proc_open = kallsyms_open,
|
||||
.proc_read = seq_read,
|
||||
.proc_lseek = seq_lseek,
|
||||
.proc_release = seq_release_private,
|
||||
};
|
||||
|
||||
static int __init kallsyms_init(void)
|
||||
{
|
||||
proc_create("kallsyms", 0444, NULL, &kallsyms_operations);
|
||||
proc_create("kallsyms", 0444, NULL, &kallsyms_proc_ops);
|
||||
return 0;
|
||||
}
|
||||
device_initcall(kallsyms_init);
|
||||
|
||||
+4
-4
@@ -173,8 +173,8 @@ SYSCALL_DEFINE5(kcmp, pid_t, pid1, pid_t, pid2, int, type,
|
||||
/*
|
||||
* One should have enough rights to inspect task details.
|
||||
*/
|
||||
ret = kcmp_lock(&task1->signal->cred_guard_mutex,
|
||||
&task2->signal->cred_guard_mutex);
|
||||
ret = kcmp_lock(&task1->signal->exec_update_mutex,
|
||||
&task2->signal->exec_update_mutex);
|
||||
if (ret)
|
||||
goto err;
|
||||
if (!ptrace_may_access(task1, PTRACE_MODE_READ_REALCREDS) ||
|
||||
@@ -229,8 +229,8 @@ SYSCALL_DEFINE5(kcmp, pid_t, pid1, pid_t, pid2, int, type,
|
||||
}
|
||||
|
||||
err_unlock:
|
||||
kcmp_unlock(&task1->signal->cred_guard_mutex,
|
||||
&task2->signal->cred_guard_mutex);
|
||||
kcmp_unlock(&task1->signal->exec_update_mutex,
|
||||
&task2->signal->exec_update_mutex);
|
||||
err:
|
||||
put_task_struct(task1);
|
||||
put_task_struct(task2);
|
||||
|
||||
+3
-3
@@ -35,7 +35,7 @@
|
||||
* (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,
|
||||
* smaller than 3200 pages. This assumes 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.
|
||||
@@ -120,7 +120,7 @@ out:
|
||||
* invoke it.
|
||||
*
|
||||
* If module auto-loading support is disabled then this function
|
||||
* becomes a no-operation.
|
||||
* simply returns -ENOENT.
|
||||
*/
|
||||
int __request_module(bool wait, const char *fmt, ...)
|
||||
{
|
||||
@@ -137,7 +137,7 @@ int __request_module(bool wait, const char *fmt, ...)
|
||||
WARN_ON_ONCE(wait && current_is_async());
|
||||
|
||||
if (!modprobe_path[0])
|
||||
return 0;
|
||||
return -ENOENT;
|
||||
|
||||
va_start(args, fmt);
|
||||
ret = vsnprintf(module_name, MODULE_NAME_LEN, fmt, args);
|
||||
|
||||
+15
-2
@@ -199,8 +199,15 @@ static void __kthread_parkme(struct kthread *self)
|
||||
if (!test_bit(KTHREAD_SHOULD_PARK, &self->flags))
|
||||
break;
|
||||
|
||||
/*
|
||||
* Thread is going to call schedule(), do not preempt it,
|
||||
* or the caller of kthread_park() may spend more time in
|
||||
* wait_task_inactive().
|
||||
*/
|
||||
preempt_disable();
|
||||
complete(&self->parked);
|
||||
schedule();
|
||||
schedule_preempt_disabled();
|
||||
preempt_enable();
|
||||
}
|
||||
__set_current_state(TASK_RUNNING);
|
||||
}
|
||||
@@ -245,8 +252,14 @@ static int kthread(void *_create)
|
||||
/* OK, tell user we're spawned, wait for stop or wakeup */
|
||||
__set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
create->result = current;
|
||||
/*
|
||||
* Thread is going to call schedule(), do not preempt it,
|
||||
* or the creator may spend more time in wait_task_inactive().
|
||||
*/
|
||||
preempt_disable();
|
||||
complete(done);
|
||||
schedule();
|
||||
schedule_preempt_disabled();
|
||||
preempt_enable();
|
||||
|
||||
ret = -EINTR;
|
||||
if (!test_bit(KTHREAD_SHOULD_STOP, &self->flags)) {
|
||||
|
||||
+7
-7
@@ -255,17 +255,17 @@ static int lstats_open(struct inode *inode, struct file *filp)
|
||||
return single_open(filp, lstats_show, NULL);
|
||||
}
|
||||
|
||||
static const struct file_operations lstats_fops = {
|
||||
.open = lstats_open,
|
||||
.read = seq_read,
|
||||
.write = lstats_write,
|
||||
.llseek = seq_lseek,
|
||||
.release = single_release,
|
||||
static const struct proc_ops lstats_proc_ops = {
|
||||
.proc_open = lstats_open,
|
||||
.proc_read = seq_read,
|
||||
.proc_write = lstats_write,
|
||||
.proc_lseek = seq_lseek,
|
||||
.proc_release = single_release,
|
||||
};
|
||||
|
||||
static int __init init_lstats_procfs(void)
|
||||
{
|
||||
proc_create("latency_stats", 0644, NULL, &lstats_fops);
|
||||
proc_create("latency_stats", 0644, NULL, &lstats_proc_ops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
+560
-137
File diff suppressed because it is too large
Load Diff
@@ -106,6 +106,12 @@ static const unsigned long LOCKF_USED_IN_IRQ_READ =
|
||||
#define STACK_TRACE_HASH_SIZE 16384
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Bit definitions for lock_chain.irq_context
|
||||
*/
|
||||
#define LOCK_CHAIN_SOFTIRQ_CONTEXT (1 << 0)
|
||||
#define LOCK_CHAIN_HARDIRQ_CONTEXT (1 << 1)
|
||||
|
||||
#define MAX_LOCKDEP_CHAINS (1UL << MAX_LOCKDEP_CHAINS_BITS)
|
||||
|
||||
#define MAX_LOCKDEP_CHAIN_HLOCKS (MAX_LOCKDEP_CHAINS*5)
|
||||
@@ -124,17 +130,21 @@ extern const char *__get_key_name(const struct lockdep_subclass_key *key,
|
||||
struct lock_class *lock_chain_get_class(struct lock_chain *chain, int i);
|
||||
|
||||
extern unsigned long nr_lock_classes;
|
||||
extern unsigned long nr_zapped_classes;
|
||||
extern unsigned long nr_zapped_lock_chains;
|
||||
extern unsigned long nr_list_entries;
|
||||
long lockdep_next_lockchain(long i);
|
||||
unsigned long lock_chain_count(void);
|
||||
extern int nr_chain_hlocks;
|
||||
extern unsigned long nr_stack_trace_entries;
|
||||
|
||||
extern unsigned int nr_hardirq_chains;
|
||||
extern unsigned int nr_softirq_chains;
|
||||
extern unsigned int nr_process_chains;
|
||||
extern unsigned int max_lockdep_depth;
|
||||
extern unsigned int nr_free_chain_hlocks;
|
||||
extern unsigned int nr_lost_chain_hlocks;
|
||||
extern unsigned int nr_large_chain_blocks;
|
||||
|
||||
extern unsigned int max_lockdep_depth;
|
||||
extern unsigned int max_bfs_queue_depth;
|
||||
|
||||
#ifdef CONFIG_PROVE_LOCKING
|
||||
|
||||
@@ -128,15 +128,22 @@ static int lc_show(struct seq_file *m, void *v)
|
||||
struct lock_chain *chain = v;
|
||||
struct lock_class *class;
|
||||
int i;
|
||||
static const char * const irq_strs[] = {
|
||||
[0] = "0",
|
||||
[LOCK_CHAIN_HARDIRQ_CONTEXT] = "hardirq",
|
||||
[LOCK_CHAIN_SOFTIRQ_CONTEXT] = "softirq",
|
||||
[LOCK_CHAIN_SOFTIRQ_CONTEXT|
|
||||
LOCK_CHAIN_HARDIRQ_CONTEXT] = "hardirq|softirq",
|
||||
};
|
||||
|
||||
if (v == SEQ_START_TOKEN) {
|
||||
if (nr_chain_hlocks > MAX_LOCKDEP_CHAIN_HLOCKS)
|
||||
if (!nr_free_chain_hlocks)
|
||||
seq_printf(m, "(buggered) ");
|
||||
seq_printf(m, "all lock chains:\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
seq_printf(m, "irq_context: %d\n", chain->irq_context);
|
||||
seq_printf(m, "irq_context: %s\n", irq_strs[chain->irq_context]);
|
||||
|
||||
for (i = 0; i < chain->depth; i++) {
|
||||
class = lock_chain_get_class(chain, i);
|
||||
@@ -271,8 +278,12 @@ static int lockdep_stats_show(struct seq_file *m, void *v)
|
||||
#ifdef CONFIG_PROVE_LOCKING
|
||||
seq_printf(m, " dependency chains: %11lu [max: %lu]\n",
|
||||
lock_chain_count(), MAX_LOCKDEP_CHAINS);
|
||||
seq_printf(m, " dependency chain hlocks: %11d [max: %lu]\n",
|
||||
nr_chain_hlocks, MAX_LOCKDEP_CHAIN_HLOCKS);
|
||||
seq_printf(m, " dependency chain hlocks used: %11lu [max: %lu]\n",
|
||||
MAX_LOCKDEP_CHAIN_HLOCKS -
|
||||
(nr_free_chain_hlocks + nr_lost_chain_hlocks),
|
||||
MAX_LOCKDEP_CHAIN_HLOCKS);
|
||||
seq_printf(m, " dependency chain hlocks lost: %11u\n",
|
||||
nr_lost_chain_hlocks);
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_TRACE_IRQFLAGS
|
||||
@@ -336,6 +347,18 @@ static int lockdep_stats_show(struct seq_file *m, void *v)
|
||||
seq_printf(m, " debug_locks: %11u\n",
|
||||
debug_locks);
|
||||
|
||||
/*
|
||||
* Zappped classes and lockdep data buffers reuse statistics.
|
||||
*/
|
||||
seq_puts(m, "\n");
|
||||
seq_printf(m, " zapped classes: %11lu\n",
|
||||
nr_zapped_classes);
|
||||
#ifdef CONFIG_PROVE_LOCKING
|
||||
seq_printf(m, " zapped lock chains: %11lu\n",
|
||||
nr_zapped_lock_chains);
|
||||
seq_printf(m, " large chain blocks: %11u\n",
|
||||
nr_large_chain_blocks);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -643,12 +666,12 @@ static int lock_stat_release(struct inode *inode, struct file *file)
|
||||
return seq_release(inode, file);
|
||||
}
|
||||
|
||||
static const struct file_operations proc_lock_stat_operations = {
|
||||
.open = lock_stat_open,
|
||||
.write = lock_stat_write,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = lock_stat_release,
|
||||
static const struct proc_ops lock_stat_proc_ops = {
|
||||
.proc_open = lock_stat_open,
|
||||
.proc_write = lock_stat_write,
|
||||
.proc_read = seq_read,
|
||||
.proc_lseek = seq_lseek,
|
||||
.proc_release = lock_stat_release,
|
||||
};
|
||||
#endif /* CONFIG_LOCK_STAT */
|
||||
|
||||
@@ -660,8 +683,7 @@ static int __init lockdep_proc_init(void)
|
||||
#endif
|
||||
proc_create_single("lockdep_stats", S_IRUSR, NULL, lockdep_stats_show);
|
||||
#ifdef CONFIG_LOCK_STAT
|
||||
proc_create("lock_stat", S_IRUSR | S_IWUSR, NULL,
|
||||
&proc_lock_stat_operations);
|
||||
proc_create("lock_stat", S_IRUSR | S_IWUSR, NULL, &lock_stat_proc_ops);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -618,7 +618,7 @@ static struct lock_torture_ops percpu_rwsem_lock_ops = {
|
||||
static int lock_torture_writer(void *arg)
|
||||
{
|
||||
struct lock_stress_stats *lwsp = arg;
|
||||
static DEFINE_TORTURE_RANDOM(rand);
|
||||
DEFINE_TORTURE_RANDOM(rand);
|
||||
|
||||
VERBOSE_TOROUT_STRING("lock_torture_writer task started");
|
||||
set_user_nice(current, MAX_NICE);
|
||||
@@ -655,7 +655,7 @@ static int lock_torture_writer(void *arg)
|
||||
static int lock_torture_reader(void *arg)
|
||||
{
|
||||
struct lock_stress_stats *lrsp = arg;
|
||||
static DEFINE_TORTURE_RANDOM(rand);
|
||||
DEFINE_TORTURE_RANDOM(rand);
|
||||
|
||||
VERBOSE_TOROUT_STRING("lock_torture_reader task started");
|
||||
set_user_nice(current, MAX_NICE);
|
||||
@@ -696,15 +696,16 @@ static void __torture_print_stats(char *page,
|
||||
if (statp[i].n_lock_fail)
|
||||
fail = true;
|
||||
sum += statp[i].n_lock_acquired;
|
||||
if (max < statp[i].n_lock_fail)
|
||||
max = statp[i].n_lock_fail;
|
||||
if (min > statp[i].n_lock_fail)
|
||||
min = statp[i].n_lock_fail;
|
||||
if (max < statp[i].n_lock_acquired)
|
||||
max = statp[i].n_lock_acquired;
|
||||
if (min > statp[i].n_lock_acquired)
|
||||
min = statp[i].n_lock_acquired;
|
||||
}
|
||||
page += sprintf(page,
|
||||
"%s: Total: %lld Max/Min: %ld/%ld %s Fail: %d %s\n",
|
||||
write ? "Writes" : "Reads ",
|
||||
sum, max, min, max / 2 > min ? "???" : "",
|
||||
sum, max, min,
|
||||
!onoff_interval && max / 2 > min ? "???" : "",
|
||||
fail, fail ? "!!!" : "");
|
||||
if (fail)
|
||||
atomic_inc(&cxt.n_lock_torture_errors);
|
||||
|
||||
@@ -85,7 +85,7 @@ void debug_mutex_init(struct mutex *lock, const char *name,
|
||||
* Make sure we are not reinitializing a held lock:
|
||||
*/
|
||||
debug_check_no_locks_freed((void *)lock, sizeof(*lock));
|
||||
lockdep_init_map(&lock->dep_map, name, key, 0);
|
||||
lockdep_init_map_wait(&lock->dep_map, name, key, 0, LD_WAIT_SLEEP);
|
||||
#endif
|
||||
lock->magic = lock;
|
||||
}
|
||||
|
||||
+146
-71
@@ -1,27 +1,29 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/percpu.h>
|
||||
#include <linux/wait.h>
|
||||
#include <linux/lockdep.h>
|
||||
#include <linux/percpu-rwsem.h>
|
||||
#include <linux/rcupdate.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/sched/task.h>
|
||||
#include <linux/errno.h>
|
||||
|
||||
#include "rwsem.h"
|
||||
|
||||
int __percpu_init_rwsem(struct percpu_rw_semaphore *sem,
|
||||
const char *name, struct lock_class_key *rwsem_key)
|
||||
const char *name, struct lock_class_key *key)
|
||||
{
|
||||
sem->read_count = alloc_percpu(int);
|
||||
if (unlikely(!sem->read_count))
|
||||
return -ENOMEM;
|
||||
|
||||
/* ->rw_sem represents the whole percpu_rw_semaphore for lockdep */
|
||||
rcu_sync_init(&sem->rss);
|
||||
__init_rwsem(&sem->rw_sem, name, rwsem_key);
|
||||
rcuwait_init(&sem->writer);
|
||||
sem->readers_block = 0;
|
||||
init_waitqueue_head(&sem->waiters);
|
||||
atomic_set(&sem->block, 0);
|
||||
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
||||
debug_check_no_locks_freed((void *)sem, sizeof(*sem));
|
||||
lockdep_init_map(&sem->dep_map, name, key, 0);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__percpu_init_rwsem);
|
||||
@@ -41,73 +43,140 @@ void percpu_free_rwsem(struct percpu_rw_semaphore *sem)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(percpu_free_rwsem);
|
||||
|
||||
int __percpu_down_read(struct percpu_rw_semaphore *sem, int try)
|
||||
static bool __percpu_down_read_trylock(struct percpu_rw_semaphore *sem)
|
||||
{
|
||||
__this_cpu_inc(*sem->read_count);
|
||||
|
||||
/*
|
||||
* Due to having preemption disabled the decrement happens on
|
||||
* the same CPU as the increment, avoiding the
|
||||
* increment-on-one-CPU-and-decrement-on-another problem.
|
||||
*
|
||||
* If the reader misses the writer's assignment of readers_block, then
|
||||
* the writer is guaranteed to see the reader's increment.
|
||||
* If the reader misses the writer's assignment of sem->block, then the
|
||||
* writer is guaranteed to see the reader's increment.
|
||||
*
|
||||
* Conversely, any readers that increment their sem->read_count after
|
||||
* the writer looks are guaranteed to see the readers_block value,
|
||||
* which in turn means that they are guaranteed to immediately
|
||||
* decrement their sem->read_count, so that it doesn't matter that the
|
||||
* writer missed them.
|
||||
* the writer looks are guaranteed to see the sem->block value, which
|
||||
* in turn means that they are guaranteed to immediately decrement
|
||||
* their sem->read_count, so that it doesn't matter that the writer
|
||||
* missed them.
|
||||
*/
|
||||
|
||||
smp_mb(); /* A matches D */
|
||||
|
||||
/*
|
||||
* If !readers_block the critical section starts here, matched by the
|
||||
* If !sem->block the critical section starts here, matched by the
|
||||
* release in percpu_up_write().
|
||||
*/
|
||||
if (likely(!smp_load_acquire(&sem->readers_block)))
|
||||
return 1;
|
||||
if (likely(!atomic_read_acquire(&sem->block)))
|
||||
return true;
|
||||
|
||||
/*
|
||||
* Per the above comment; we still have preemption disabled and
|
||||
* will thus decrement on the same CPU as we incremented.
|
||||
*/
|
||||
__percpu_up_read(sem);
|
||||
|
||||
if (try)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* We either call schedule() in the wait, or we'll fall through
|
||||
* and reschedule on the preempt_enable() in percpu_down_read().
|
||||
*/
|
||||
preempt_enable_no_resched();
|
||||
|
||||
/*
|
||||
* Avoid lockdep for the down/up_read() we already have them.
|
||||
*/
|
||||
__down_read(&sem->rw_sem);
|
||||
this_cpu_inc(*sem->read_count);
|
||||
__up_read(&sem->rw_sem);
|
||||
|
||||
preempt_disable();
|
||||
return 1;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__percpu_down_read);
|
||||
|
||||
void __percpu_up_read(struct percpu_rw_semaphore *sem)
|
||||
{
|
||||
smp_mb(); /* B matches C */
|
||||
/*
|
||||
* In other words, if they see our decrement (presumably to aggregate
|
||||
* zero, as that is the only time it matters) they will also see our
|
||||
* critical section.
|
||||
*/
|
||||
__this_cpu_dec(*sem->read_count);
|
||||
|
||||
/* Prod writer to recheck readers_active */
|
||||
/* Prod writer to re-evaluate readers_active_check() */
|
||||
rcuwait_wake_up(&sem->writer);
|
||||
|
||||
return false;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__percpu_up_read);
|
||||
|
||||
static inline bool __percpu_down_write_trylock(struct percpu_rw_semaphore *sem)
|
||||
{
|
||||
if (atomic_read(&sem->block))
|
||||
return false;
|
||||
|
||||
return atomic_xchg(&sem->block, 1) == 0;
|
||||
}
|
||||
|
||||
static bool __percpu_rwsem_trylock(struct percpu_rw_semaphore *sem, bool reader)
|
||||
{
|
||||
if (reader) {
|
||||
bool ret;
|
||||
|
||||
preempt_disable();
|
||||
ret = __percpu_down_read_trylock(sem);
|
||||
preempt_enable();
|
||||
|
||||
return ret;
|
||||
}
|
||||
return __percpu_down_write_trylock(sem);
|
||||
}
|
||||
|
||||
/*
|
||||
* The return value of wait_queue_entry::func means:
|
||||
*
|
||||
* <0 - error, wakeup is terminated and the error is returned
|
||||
* 0 - no wakeup, a next waiter is tried
|
||||
* >0 - woken, if EXCLUSIVE, counted towards @nr_exclusive.
|
||||
*
|
||||
* We use EXCLUSIVE for both readers and writers to preserve FIFO order,
|
||||
* and play games with the return value to allow waking multiple readers.
|
||||
*
|
||||
* Specifically, we wake readers until we've woken a single writer, or until a
|
||||
* trylock fails.
|
||||
*/
|
||||
static int percpu_rwsem_wake_function(struct wait_queue_entry *wq_entry,
|
||||
unsigned int mode, int wake_flags,
|
||||
void *key)
|
||||
{
|
||||
bool reader = wq_entry->flags & WQ_FLAG_CUSTOM;
|
||||
struct percpu_rw_semaphore *sem = key;
|
||||
struct task_struct *p;
|
||||
|
||||
/* concurrent against percpu_down_write(), can get stolen */
|
||||
if (!__percpu_rwsem_trylock(sem, reader))
|
||||
return 1;
|
||||
|
||||
p = get_task_struct(wq_entry->private);
|
||||
list_del_init(&wq_entry->entry);
|
||||
smp_store_release(&wq_entry->private, NULL);
|
||||
|
||||
wake_up_process(p);
|
||||
put_task_struct(p);
|
||||
|
||||
return !reader; /* wake (readers until) 1 writer */
|
||||
}
|
||||
|
||||
static void percpu_rwsem_wait(struct percpu_rw_semaphore *sem, bool reader)
|
||||
{
|
||||
DEFINE_WAIT_FUNC(wq_entry, percpu_rwsem_wake_function);
|
||||
bool wait;
|
||||
|
||||
spin_lock_irq(&sem->waiters.lock);
|
||||
/*
|
||||
* Serialize against the wakeup in percpu_up_write(), if we fail
|
||||
* the trylock, the wakeup must see us on the list.
|
||||
*/
|
||||
wait = !__percpu_rwsem_trylock(sem, reader);
|
||||
if (wait) {
|
||||
wq_entry.flags |= WQ_FLAG_EXCLUSIVE | reader * WQ_FLAG_CUSTOM;
|
||||
__add_wait_queue_entry_tail(&sem->waiters, &wq_entry);
|
||||
}
|
||||
spin_unlock_irq(&sem->waiters.lock);
|
||||
|
||||
while (wait) {
|
||||
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||
if (!smp_load_acquire(&wq_entry.private))
|
||||
break;
|
||||
schedule();
|
||||
}
|
||||
__set_current_state(TASK_RUNNING);
|
||||
}
|
||||
|
||||
bool __percpu_down_read(struct percpu_rw_semaphore *sem, bool try)
|
||||
{
|
||||
if (__percpu_down_read_trylock(sem))
|
||||
return true;
|
||||
|
||||
if (try)
|
||||
return false;
|
||||
|
||||
preempt_enable();
|
||||
percpu_rwsem_wait(sem, /* .reader = */ true);
|
||||
preempt_disable();
|
||||
|
||||
return true;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__percpu_down_read);
|
||||
|
||||
#define per_cpu_sum(var) \
|
||||
({ \
|
||||
@@ -124,6 +193,8 @@ EXPORT_SYMBOL_GPL(__percpu_up_read);
|
||||
* zero. If this sum is zero, then it is stable due to the fact that if any
|
||||
* newly arriving readers increment a given counter, they will immediately
|
||||
* decrement that same counter.
|
||||
*
|
||||
* Assumes sem->block is set.
|
||||
*/
|
||||
static bool readers_active_check(struct percpu_rw_semaphore *sem)
|
||||
{
|
||||
@@ -142,32 +213,36 @@ static bool readers_active_check(struct percpu_rw_semaphore *sem)
|
||||
|
||||
void percpu_down_write(struct percpu_rw_semaphore *sem)
|
||||
{
|
||||
might_sleep();
|
||||
rwsem_acquire(&sem->dep_map, 0, 0, _RET_IP_);
|
||||
|
||||
/* Notify readers to take the slow path. */
|
||||
rcu_sync_enter(&sem->rss);
|
||||
|
||||
down_write(&sem->rw_sem);
|
||||
/*
|
||||
* Try set sem->block; this provides writer-writer exclusion.
|
||||
* Having sem->block set makes new readers block.
|
||||
*/
|
||||
if (!__percpu_down_write_trylock(sem))
|
||||
percpu_rwsem_wait(sem, /* .reader = */ false);
|
||||
|
||||
/* smp_mb() implied by __percpu_down_write_trylock() on success -- D matches A */
|
||||
|
||||
/*
|
||||
* Notify new readers to block; up until now, and thus throughout the
|
||||
* longish rcu_sync_enter() above, new readers could still come in.
|
||||
*/
|
||||
WRITE_ONCE(sem->readers_block, 1);
|
||||
|
||||
smp_mb(); /* D matches A */
|
||||
|
||||
/*
|
||||
* If they don't see our writer of readers_block, then we are
|
||||
* guaranteed to see their sem->read_count increment, and therefore
|
||||
* will wait for them.
|
||||
* If they don't see our store of sem->block, then we are guaranteed to
|
||||
* see their sem->read_count increment, and therefore will wait for
|
||||
* them.
|
||||
*/
|
||||
|
||||
/* Wait for all now active readers to complete. */
|
||||
rcuwait_wait_event(&sem->writer, readers_active_check(sem));
|
||||
/* Wait for all active readers to complete. */
|
||||
rcuwait_wait_event(&sem->writer, readers_active_check(sem), TASK_UNINTERRUPTIBLE);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(percpu_down_write);
|
||||
|
||||
void percpu_up_write(struct percpu_rw_semaphore *sem)
|
||||
{
|
||||
rwsem_release(&sem->dep_map, _RET_IP_);
|
||||
|
||||
/*
|
||||
* Signal the writer is done, no fast path yet.
|
||||
*
|
||||
@@ -178,12 +253,12 @@ void percpu_up_write(struct percpu_rw_semaphore *sem)
|
||||
* Therefore we force it through the slow path which guarantees an
|
||||
* acquire and thereby guarantees the critical section's consistency.
|
||||
*/
|
||||
smp_store_release(&sem->readers_block, 0);
|
||||
atomic_set_release(&sem->block, 0);
|
||||
|
||||
/*
|
||||
* Release the write lock, this will allow readers back in the game.
|
||||
* Prod any pending reader/writer to make progress.
|
||||
*/
|
||||
up_write(&sem->rw_sem);
|
||||
__wake_up(&sem->waiters, TASK_NORMAL, 1, sem);
|
||||
|
||||
/*
|
||||
* Once this completes (at least one RCU-sched grace period hence) the
|
||||
|
||||
@@ -57,7 +57,7 @@ rt_mutex_set_owner(struct rt_mutex *lock, struct task_struct *owner)
|
||||
if (rt_mutex_has_waiters(lock))
|
||||
val |= RT_MUTEX_HAS_WAITERS;
|
||||
|
||||
lock->owner = (struct task_struct *)val;
|
||||
WRITE_ONCE(lock->owner, (struct task_struct *)val);
|
||||
}
|
||||
|
||||
static inline void clear_rt_mutex_waiters(struct rt_mutex *lock)
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/atomic.h>
|
||||
|
||||
#include "rwsem.h"
|
||||
#include "lock_events.h"
|
||||
|
||||
/*
|
||||
@@ -329,7 +328,7 @@ void __init_rwsem(struct rw_semaphore *sem, const char *name,
|
||||
* Make sure we are not reinitializing a held semaphore:
|
||||
*/
|
||||
debug_check_no_locks_freed((void *)sem, sizeof(*sem));
|
||||
lockdep_init_map(&sem->dep_map, name, key, 0);
|
||||
lockdep_init_map_wait(&sem->dep_map, name, key, 0, LD_WAIT_SLEEP);
|
||||
#endif
|
||||
#ifdef CONFIG_DEBUG_RWSEMS
|
||||
sem->magic = sem;
|
||||
@@ -660,8 +659,6 @@ static inline bool rwsem_can_spin_on_owner(struct rw_semaphore *sem,
|
||||
unsigned long flags;
|
||||
bool ret = true;
|
||||
|
||||
BUILD_BUG_ON(!(RWSEM_OWNER_UNKNOWN & RWSEM_NONSPINNABLE));
|
||||
|
||||
if (need_resched()) {
|
||||
lockevent_inc(rwsem_opt_fail);
|
||||
return false;
|
||||
@@ -1338,7 +1335,7 @@ static struct rw_semaphore *rwsem_downgrade_wake(struct rw_semaphore *sem)
|
||||
/*
|
||||
* lock for reading
|
||||
*/
|
||||
inline void __down_read(struct rw_semaphore *sem)
|
||||
static inline void __down_read(struct rw_semaphore *sem)
|
||||
{
|
||||
if (!rwsem_read_trylock(sem)) {
|
||||
rwsem_down_read_slowpath(sem, TASK_UNINTERRUPTIBLE);
|
||||
@@ -1426,7 +1423,7 @@ static inline int __down_write_trylock(struct rw_semaphore *sem)
|
||||
/*
|
||||
* unlock after reading
|
||||
*/
|
||||
inline void __up_read(struct rw_semaphore *sem)
|
||||
static inline void __up_read(struct rw_semaphore *sem)
|
||||
{
|
||||
long tmp;
|
||||
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
|
||||
#ifndef __INTERNAL_RWSEM_H
|
||||
#define __INTERNAL_RWSEM_H
|
||||
#include <linux/rwsem.h>
|
||||
|
||||
extern void __down_read(struct rw_semaphore *sem);
|
||||
extern void __up_read(struct rw_semaphore *sem);
|
||||
|
||||
#endif /* __INTERNAL_RWSEM_H */
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
#include <linux/export.h>
|
||||
|
||||
void __raw_spin_lock_init(raw_spinlock_t *lock, const char *name,
|
||||
struct lock_class_key *key)
|
||||
struct lock_class_key *key, short inner)
|
||||
{
|
||||
#ifdef CONFIG_DEBUG_LOCK_ALLOC
|
||||
/*
|
||||
* Make sure we are not reinitializing a held lock:
|
||||
*/
|
||||
debug_check_no_locks_freed((void *)lock, sizeof(*lock));
|
||||
lockdep_init_map(&lock->dep_map, name, key, 0);
|
||||
lockdep_init_map_wait(&lock->dep_map, name, key, 0, inner);
|
||||
#endif
|
||||
lock->raw_lock = (arch_spinlock_t)__ARCH_SPIN_LOCK_UNLOCKED;
|
||||
lock->magic = SPINLOCK_MAGIC;
|
||||
@@ -39,7 +39,7 @@ void __rwlock_init(rwlock_t *lock, const char *name,
|
||||
* Make sure we are not reinitializing a held lock:
|
||||
*/
|
||||
debug_check_no_locks_freed((void *)lock, sizeof(*lock));
|
||||
lockdep_init_map(&lock->dep_map, name, key, 0);
|
||||
lockdep_init_map_wait(&lock->dep_map, name, key, 0, LD_WAIT_CONFIG);
|
||||
#endif
|
||||
lock->raw_lock = (arch_rwlock_t) __ARCH_RW_LOCK_UNLOCKED;
|
||||
lock->magic = RWLOCK_MAGIC;
|
||||
|
||||
+22
-17
@@ -214,7 +214,8 @@ static struct module *mod_find(unsigned long addr)
|
||||
{
|
||||
struct module *mod;
|
||||
|
||||
list_for_each_entry_rcu(mod, &modules, list) {
|
||||
list_for_each_entry_rcu(mod, &modules, list,
|
||||
lockdep_is_held(&module_mutex)) {
|
||||
if (within_module(addr, mod))
|
||||
return mod;
|
||||
}
|
||||
@@ -448,7 +449,8 @@ bool each_symbol_section(bool (*fn)(const struct symsearch *arr,
|
||||
if (each_symbol_in_section(arr, ARRAY_SIZE(arr), NULL, fn, data))
|
||||
return true;
|
||||
|
||||
list_for_each_entry_rcu(mod, &modules, list) {
|
||||
list_for_each_entry_rcu(mod, &modules, list,
|
||||
lockdep_is_held(&module_mutex)) {
|
||||
struct symsearch arr[] = {
|
||||
{ mod->syms, mod->syms + mod->num_syms, mod->crcs,
|
||||
NOT_GPL_ONLY, false },
|
||||
@@ -616,7 +618,8 @@ static struct module *find_module_all(const char *name, size_t len,
|
||||
|
||||
module_assert_mutex_or_preempt();
|
||||
|
||||
list_for_each_entry_rcu(mod, &modules, list) {
|
||||
list_for_each_entry_rcu(mod, &modules, list,
|
||||
lockdep_is_held(&module_mutex)) {
|
||||
if (!even_unformed && mod->state == MODULE_STATE_UNFORMED)
|
||||
continue;
|
||||
if (strlen(mod->name) == len && !memcmp(mod->name, name, len))
|
||||
@@ -1512,7 +1515,7 @@ struct module_sect_attr {
|
||||
struct module_sect_attrs {
|
||||
struct attribute_group grp;
|
||||
unsigned int nsections;
|
||||
struct module_sect_attr attrs[0];
|
||||
struct module_sect_attr attrs[];
|
||||
};
|
||||
|
||||
static ssize_t module_sect_show(struct module_attribute *mattr,
|
||||
@@ -1605,7 +1608,7 @@ static void remove_sect_attrs(struct module *mod)
|
||||
struct module_notes_attrs {
|
||||
struct kobject *dir;
|
||||
unsigned int notes;
|
||||
struct bin_attribute attrs[0];
|
||||
struct bin_attribute attrs[];
|
||||
};
|
||||
|
||||
static ssize_t module_notes_read(struct file *filp, struct kobject *kobj,
|
||||
@@ -1781,6 +1784,8 @@ static int module_add_modinfo_attrs(struct module *mod)
|
||||
error_out:
|
||||
if (i > 0)
|
||||
module_remove_modinfo_attrs(mod, --i);
|
||||
else
|
||||
kfree(mod->modinfo_attrs);
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -2834,7 +2839,7 @@ static int module_sig_check(struct load_info *info, int flags)
|
||||
reason = "Loading of module with unavailable key";
|
||||
decide:
|
||||
if (is_module_sig_enforced()) {
|
||||
pr_notice("%s is rejected\n", reason);
|
||||
pr_notice("%s: %s is rejected\n", info->name, reason);
|
||||
return -EKEYREJECTED;
|
||||
}
|
||||
|
||||
@@ -3011,9 +3016,7 @@ static int setup_load_info(struct load_info *info, int flags)
|
||||
|
||||
/* Try to find a name early so we can log errors with a module name */
|
||||
info->index.info = find_sec(info, ".modinfo");
|
||||
if (!info->index.info)
|
||||
info->name = "(missing .modinfo section)";
|
||||
else
|
||||
if (info->index.info)
|
||||
info->name = get_modinfo(info, "name");
|
||||
|
||||
/* Find internal symbols and strings. */
|
||||
@@ -3028,14 +3031,15 @@ static int setup_load_info(struct load_info *info, int flags)
|
||||
}
|
||||
|
||||
if (info->index.sym == 0) {
|
||||
pr_warn("%s: module has no symbols (stripped?)\n", info->name);
|
||||
pr_warn("%s: module has no symbols (stripped?)\n",
|
||||
info->name ?: "(missing .modinfo section or name field)");
|
||||
return -ENOEXEC;
|
||||
}
|
||||
|
||||
info->index.mod = find_sec(info, ".gnu.linkonce.this_module");
|
||||
if (!info->index.mod) {
|
||||
pr_warn("%s: No module found in object\n",
|
||||
info->name ?: "(missing .modinfo name field)");
|
||||
info->name ?: "(missing .modinfo section or name field)");
|
||||
return -ENOEXEC;
|
||||
}
|
||||
/* This is temporary: point mod into copy of data. */
|
||||
@@ -4350,16 +4354,17 @@ static int modules_open(struct inode *inode, struct file *file)
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct file_operations proc_modules_operations = {
|
||||
.open = modules_open,
|
||||
.read = seq_read,
|
||||
.llseek = seq_lseek,
|
||||
.release = seq_release,
|
||||
static const struct proc_ops modules_proc_ops = {
|
||||
.proc_flags = PROC_ENTRY_PERMANENT,
|
||||
.proc_open = modules_open,
|
||||
.proc_read = seq_read,
|
||||
.proc_lseek = seq_lseek,
|
||||
.proc_release = seq_release,
|
||||
};
|
||||
|
||||
static int __init proc_modules_init(void)
|
||||
{
|
||||
proc_create("modules", 0, NULL, &proc_modules_operations);
|
||||
proc_create("modules", 0, NULL, &modules_proc_ops);
|
||||
return 0;
|
||||
}
|
||||
module_init(proc_modules_init);
|
||||
|
||||
+1
-1
@@ -519,7 +519,7 @@ NOKPROBE_SYMBOL(notify_die);
|
||||
|
||||
int register_die_notifier(struct notifier_block *nb)
|
||||
{
|
||||
vmalloc_sync_all();
|
||||
vmalloc_sync_mappings();
|
||||
return atomic_notifier_chain_register(&die_chain, nb);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(register_die_notifier);
|
||||
|
||||
+5
-4
@@ -512,7 +512,7 @@ static int padata_replace_one(struct padata_shell *ps)
|
||||
static int padata_replace(struct padata_instance *pinst)
|
||||
{
|
||||
struct padata_shell *ps;
|
||||
int err;
|
||||
int err = 0;
|
||||
|
||||
pinst->flags |= PADATA_RESET;
|
||||
|
||||
@@ -1038,12 +1038,13 @@ EXPORT_SYMBOL(padata_alloc_shell);
|
||||
*/
|
||||
void padata_free_shell(struct padata_shell *ps)
|
||||
{
|
||||
struct padata_instance *pinst = ps->pinst;
|
||||
if (!ps)
|
||||
return;
|
||||
|
||||
mutex_lock(&pinst->lock);
|
||||
mutex_lock(&ps->pinst->lock);
|
||||
list_del(&ps->list);
|
||||
padata_free_pd(rcu_dereference_protected(ps->pd, 1));
|
||||
mutex_unlock(&pinst->lock);
|
||||
mutex_unlock(&ps->pinst->lock);
|
||||
|
||||
kfree(ps);
|
||||
}
|
||||
|
||||
+101
-7
@@ -144,9 +144,6 @@ void free_pid(struct pid *pid)
|
||||
/* Handle a fork failure of the first process */
|
||||
WARN_ON(ns->child_reaper);
|
||||
ns->pid_allocated = 0;
|
||||
/* fall through */
|
||||
case 0:
|
||||
schedule_work(&ns->proc_work);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -247,17 +244,24 @@ struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid,
|
||||
tmp = tmp->parent;
|
||||
}
|
||||
|
||||
if (unlikely(is_child_reaper(pid))) {
|
||||
if (pid_ns_prepare_proc(ns))
|
||||
goto out_free;
|
||||
}
|
||||
/*
|
||||
* ENOMEM is not the most obvious choice especially for the case
|
||||
* where the child subreaper has already exited and the pid
|
||||
* namespace denies the creation of any new processes. But ENOMEM
|
||||
* is what we have exposed to userspace for a long time and it is
|
||||
* documented behavior for pid namespaces. So we can't easily
|
||||
* change it even if there were an error code better suited.
|
||||
*/
|
||||
retval = -ENOMEM;
|
||||
|
||||
get_pid_ns(ns);
|
||||
refcount_set(&pid->count, 1);
|
||||
spin_lock_init(&pid->lock);
|
||||
for (type = 0; type < PIDTYPE_MAX; ++type)
|
||||
INIT_HLIST_HEAD(&pid->tasks[type]);
|
||||
|
||||
init_waitqueue_head(&pid->wait_pidfd);
|
||||
INIT_HLIST_HEAD(&pid->inodes);
|
||||
|
||||
upid = pid->numbers + ns->level;
|
||||
spin_lock_irq(&pidmap_lock);
|
||||
@@ -578,3 +582,93 @@ void __init pid_idr_init(void)
|
||||
init_pid_ns.pid_cachep = KMEM_CACHE(pid,
|
||||
SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT);
|
||||
}
|
||||
|
||||
static struct file *__pidfd_fget(struct task_struct *task, int fd)
|
||||
{
|
||||
struct file *file;
|
||||
int ret;
|
||||
|
||||
ret = mutex_lock_killable(&task->signal->exec_update_mutex);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
if (ptrace_may_access(task, PTRACE_MODE_ATTACH_REALCREDS))
|
||||
file = fget_task(task, fd);
|
||||
else
|
||||
file = ERR_PTR(-EPERM);
|
||||
|
||||
mutex_unlock(&task->signal->exec_update_mutex);
|
||||
|
||||
return file ?: ERR_PTR(-EBADF);
|
||||
}
|
||||
|
||||
static int pidfd_getfd(struct pid *pid, int fd)
|
||||
{
|
||||
struct task_struct *task;
|
||||
struct file *file;
|
||||
int ret;
|
||||
|
||||
task = get_pid_task(pid, PIDTYPE_PID);
|
||||
if (!task)
|
||||
return -ESRCH;
|
||||
|
||||
file = __pidfd_fget(task, fd);
|
||||
put_task_struct(task);
|
||||
if (IS_ERR(file))
|
||||
return PTR_ERR(file);
|
||||
|
||||
ret = security_file_receive(file);
|
||||
if (ret) {
|
||||
fput(file);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = get_unused_fd_flags(O_CLOEXEC);
|
||||
if (ret < 0)
|
||||
fput(file);
|
||||
else
|
||||
fd_install(ret, file);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* sys_pidfd_getfd() - Get a file descriptor from another process
|
||||
*
|
||||
* @pidfd: the pidfd file descriptor of the process
|
||||
* @fd: the file descriptor number to get
|
||||
* @flags: flags on how to get the fd (reserved)
|
||||
*
|
||||
* This syscall gets a copy of a file descriptor from another process
|
||||
* based on the pidfd, and file descriptor number. It requires that
|
||||
* the calling process has the ability to ptrace the process represented
|
||||
* by the pidfd. The process which is having its file descriptor copied
|
||||
* is otherwise unaffected.
|
||||
*
|
||||
* Return: On success, a cloexec file descriptor is returned.
|
||||
* On error, a negative errno number will be returned.
|
||||
*/
|
||||
SYSCALL_DEFINE3(pidfd_getfd, int, pidfd, int, fd,
|
||||
unsigned int, flags)
|
||||
{
|
||||
struct pid *pid;
|
||||
struct fd f;
|
||||
int ret;
|
||||
|
||||
/* flags is currently unused - make sure it's unset */
|
||||
if (flags)
|
||||
return -EINVAL;
|
||||
|
||||
f = fdget(pidfd);
|
||||
if (!f.file)
|
||||
return -EBADF;
|
||||
|
||||
pid = pidfd_pid(f.file);
|
||||
if (IS_ERR(pid))
|
||||
ret = PTR_ERR(pid);
|
||||
else
|
||||
ret = pidfd_getfd(pid, fd);
|
||||
|
||||
fdput(f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
+19
-19
@@ -57,12 +57,6 @@ static struct kmem_cache *create_pid_cachep(unsigned int level)
|
||||
return READ_ONCE(*pkc);
|
||||
}
|
||||
|
||||
static void proc_cleanup_work(struct work_struct *work)
|
||||
{
|
||||
struct pid_namespace *ns = container_of(work, struct pid_namespace, proc_work);
|
||||
pid_ns_release_proc(ns);
|
||||
}
|
||||
|
||||
static struct ucounts *inc_pid_namespaces(struct user_namespace *ns)
|
||||
{
|
||||
return inc_ucount(ns, current_euid(), UCOUNT_PID_NAMESPACES);
|
||||
@@ -114,7 +108,6 @@ static struct pid_namespace *create_pid_namespace(struct user_namespace *user_ns
|
||||
ns->user_ns = get_user_ns(user_ns);
|
||||
ns->ucounts = ucounts;
|
||||
ns->pid_allocated = PIDNS_ADDING;
|
||||
INIT_WORK(&ns->proc_work, proc_cleanup_work);
|
||||
|
||||
return ns;
|
||||
|
||||
@@ -231,20 +224,27 @@ void zap_pid_ns_processes(struct pid_namespace *pid_ns)
|
||||
} while (rc != -ECHILD);
|
||||
|
||||
/*
|
||||
* kernel_wait4() above can't reap the EXIT_DEAD children but we do not
|
||||
* really care, we could reparent them to the global init. We could
|
||||
* exit and reap ->child_reaper even if it is not the last thread in
|
||||
* this pid_ns, free_pid(pid_allocated == 0) calls proc_cleanup_work(),
|
||||
* pid_ns can not go away until proc_kill_sb() drops the reference.
|
||||
* kernel_wait4() misses EXIT_DEAD children, and EXIT_ZOMBIE
|
||||
* process whose parents processes are outside of the pid
|
||||
* namespace. Such processes are created with setns()+fork().
|
||||
*
|
||||
* But this ns can also have other tasks injected by setns()+fork().
|
||||
* Again, ignoring the user visible semantics we do not really need
|
||||
* to wait until they are all reaped, but they can be reparented to
|
||||
* us and thus we need to ensure that pid->child_reaper stays valid
|
||||
* until they all go away. See free_pid()->wake_up_process().
|
||||
* If those EXIT_ZOMBIE processes are not reaped by their
|
||||
* parents before their parents exit, they will be reparented
|
||||
* to pid_ns->child_reaper. Thus pidns->child_reaper needs to
|
||||
* stay valid until they all go away.
|
||||
*
|
||||
* We rely on ignored SIGCHLD, an injected zombie must be autoreaped
|
||||
* if reparented.
|
||||
* The code relies on the the pid_ns->child_reaper ignoring
|
||||
* SIGCHILD to cause those EXIT_ZOMBIE processes to be
|
||||
* autoreaped if reparented.
|
||||
*
|
||||
* Semantically it is also desirable to wait for EXIT_ZOMBIE
|
||||
* processes before allowing the child_reaper to be reaped, as
|
||||
* that gives the invariant that when the init process of a
|
||||
* pid namespace is reaped all of the processes in the pid
|
||||
* namespace are gone.
|
||||
*
|
||||
* Once all of the other tasks are gone from the pid_namespace
|
||||
* free_pid() will awaken this task.
|
||||
*/
|
||||
for (;;) {
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
|
||||
@@ -80,9 +80,6 @@ config HIBERNATION
|
||||
|
||||
For more information take a look at <file:Documentation/power/swsusp.rst>.
|
||||
|
||||
config ARCH_SAVE_PAGE_KEYS
|
||||
bool
|
||||
|
||||
config PM_STD_PARTITION
|
||||
string "Default resume partition"
|
||||
depends on HIBERNATION
|
||||
|
||||
@@ -678,7 +678,7 @@ static int load_image_and_restore(void)
|
||||
error = swsusp_read(&flags);
|
||||
swsusp_close(FMODE_READ);
|
||||
if (!error)
|
||||
hibernation_restore(flags & SF_PLATFORM_MODE);
|
||||
error = hibernation_restore(flags & SF_PLATFORM_MODE);
|
||||
|
||||
pr_err("Failed to load image, recovering.\n");
|
||||
swsusp_free();
|
||||
|
||||
@@ -535,6 +535,13 @@ static ssize_t pm_debug_messages_store(struct kobject *kobj,
|
||||
|
||||
power_attr(pm_debug_messages);
|
||||
|
||||
static int __init pm_debug_messages_setup(char *str)
|
||||
{
|
||||
pm_debug_messages_on = true;
|
||||
return 1;
|
||||
}
|
||||
__setup("pm_debug_messages", pm_debug_messages_setup);
|
||||
|
||||
/**
|
||||
* __pm_pr_dbg - Print a suspend debug message to the kernel log.
|
||||
* @defer: Whether or not to use printk_deferred() to print the message.
|
||||
|
||||
+197
-424
@@ -1,31 +1,21 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* This module exposes the interface to kernel space for specifying
|
||||
* QoS dependencies. It provides infrastructure for registration of:
|
||||
* Power Management Quality of Service (PM QoS) support base.
|
||||
*
|
||||
* Dependents on a QoS value : register requests
|
||||
* Watchers of QoS value : get notified when target QoS value changes
|
||||
* Copyright (C) 2020 Intel Corporation
|
||||
*
|
||||
* This QoS design is best effort based. Dependents register their QoS needs.
|
||||
* Watchers register to keep track of the current QoS needs of the system.
|
||||
* Authors:
|
||||
* Mark Gross <mgross@linux.intel.com>
|
||||
* Rafael J. Wysocki <rafael.j.wysocki@intel.com>
|
||||
*
|
||||
* There are 3 basic classes of QoS parameter: latency, timeout, throughput
|
||||
* each have defined units:
|
||||
* latency: usec
|
||||
* timeout: usec <-- currently not used.
|
||||
* throughput: kbs (kilo byte / sec)
|
||||
* Provided here is an interface for specifying PM QoS dependencies. It allows
|
||||
* entities depending on QoS constraints to register their requests which are
|
||||
* aggregated as appropriate to produce effective constraints (target values)
|
||||
* that can be monitored by entities needing to respect them, either by polling
|
||||
* or through a built-in notification mechanism.
|
||||
*
|
||||
* There are lists of pm_qos_objects each one wrapping requests, notifiers
|
||||
*
|
||||
* User mode requests on a QOS parameter register themselves to the
|
||||
* subsystem by opening the device node /dev/... and writing there request to
|
||||
* the node. As long as the process holds a file handle open to the node the
|
||||
* client continues to be accounted for. Upon file release the usermode
|
||||
* request is removed and a new qos target is computed. This way when the
|
||||
* request that the application has is cleaned up when closes the file
|
||||
* pointer or exits the pm_qos_object will get an opportunity to clean up.
|
||||
*
|
||||
* Mark Gross <mgross@linux.intel.com>
|
||||
* In addition to the basic functionality, more specific interfaces for managing
|
||||
* global CPU latency QoS requests and frequency QoS requests are provided.
|
||||
*/
|
||||
|
||||
/*#define DEBUG*/
|
||||
@@ -54,56 +44,19 @@
|
||||
* or pm_qos_object list and pm_qos_objects need to happen with pm_qos_lock
|
||||
* held, taken with _irqsave. One lock to rule them all
|
||||
*/
|
||||
struct pm_qos_object {
|
||||
struct pm_qos_constraints *constraints;
|
||||
struct miscdevice pm_qos_power_miscdev;
|
||||
char *name;
|
||||
};
|
||||
|
||||
static DEFINE_SPINLOCK(pm_qos_lock);
|
||||
|
||||
static struct pm_qos_object null_pm_qos;
|
||||
|
||||
static BLOCKING_NOTIFIER_HEAD(cpu_dma_lat_notifier);
|
||||
static struct pm_qos_constraints cpu_dma_constraints = {
|
||||
.list = PLIST_HEAD_INIT(cpu_dma_constraints.list),
|
||||
.target_value = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE,
|
||||
.default_value = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE,
|
||||
.no_constraint_value = PM_QOS_CPU_DMA_LAT_DEFAULT_VALUE,
|
||||
.type = PM_QOS_MIN,
|
||||
.notifiers = &cpu_dma_lat_notifier,
|
||||
};
|
||||
static struct pm_qos_object cpu_dma_pm_qos = {
|
||||
.constraints = &cpu_dma_constraints,
|
||||
.name = "cpu_dma_latency",
|
||||
};
|
||||
|
||||
static struct pm_qos_object *pm_qos_array[] = {
|
||||
&null_pm_qos,
|
||||
&cpu_dma_pm_qos,
|
||||
};
|
||||
|
||||
static ssize_t pm_qos_power_write(struct file *filp, const char __user *buf,
|
||||
size_t count, loff_t *f_pos);
|
||||
static ssize_t pm_qos_power_read(struct file *filp, char __user *buf,
|
||||
size_t count, loff_t *f_pos);
|
||||
static int pm_qos_power_open(struct inode *inode, struct file *filp);
|
||||
static int pm_qos_power_release(struct inode *inode, struct file *filp);
|
||||
|
||||
static const struct file_operations pm_qos_power_fops = {
|
||||
.write = pm_qos_power_write,
|
||||
.read = pm_qos_power_read,
|
||||
.open = pm_qos_power_open,
|
||||
.release = pm_qos_power_release,
|
||||
.llseek = noop_llseek,
|
||||
};
|
||||
|
||||
/* unlocked internal variant */
|
||||
static inline int pm_qos_get_value(struct pm_qos_constraints *c)
|
||||
/**
|
||||
* pm_qos_read_value - Return the current effective constraint value.
|
||||
* @c: List of PM QoS constraint requests.
|
||||
*/
|
||||
s32 pm_qos_read_value(struct pm_qos_constraints *c)
|
||||
{
|
||||
struct plist_node *node;
|
||||
int total_value = 0;
|
||||
return READ_ONCE(c->target_value);
|
||||
}
|
||||
|
||||
static int pm_qos_get_value(struct pm_qos_constraints *c)
|
||||
{
|
||||
if (plist_head_empty(&c->list))
|
||||
return c->no_constraint_value;
|
||||
|
||||
@@ -114,111 +67,42 @@ static inline int pm_qos_get_value(struct pm_qos_constraints *c)
|
||||
case PM_QOS_MAX:
|
||||
return plist_last(&c->list)->prio;
|
||||
|
||||
case PM_QOS_SUM:
|
||||
plist_for_each(node, &c->list)
|
||||
total_value += node->prio;
|
||||
|
||||
return total_value;
|
||||
|
||||
default:
|
||||
/* runtime check for not using enum */
|
||||
BUG();
|
||||
WARN(1, "Unknown PM QoS type in %s\n", __func__);
|
||||
return PM_QOS_DEFAULT_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
s32 pm_qos_read_value(struct pm_qos_constraints *c)
|
||||
static void pm_qos_set_value(struct pm_qos_constraints *c, s32 value)
|
||||
{
|
||||
return c->target_value;
|
||||
WRITE_ONCE(c->target_value, value);
|
||||
}
|
||||
|
||||
static inline void pm_qos_set_value(struct pm_qos_constraints *c, s32 value)
|
||||
{
|
||||
c->target_value = value;
|
||||
}
|
||||
|
||||
static int pm_qos_debug_show(struct seq_file *s, void *unused)
|
||||
{
|
||||
struct pm_qos_object *qos = (struct pm_qos_object *)s->private;
|
||||
struct pm_qos_constraints *c;
|
||||
struct pm_qos_request *req;
|
||||
char *type;
|
||||
unsigned long flags;
|
||||
int tot_reqs = 0;
|
||||
int active_reqs = 0;
|
||||
|
||||
if (IS_ERR_OR_NULL(qos)) {
|
||||
pr_err("%s: bad qos param!\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
c = qos->constraints;
|
||||
if (IS_ERR_OR_NULL(c)) {
|
||||
pr_err("%s: Bad constraints on qos?\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Lock to ensure we have a snapshot */
|
||||
spin_lock_irqsave(&pm_qos_lock, flags);
|
||||
if (plist_head_empty(&c->list)) {
|
||||
seq_puts(s, "Empty!\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
switch (c->type) {
|
||||
case PM_QOS_MIN:
|
||||
type = "Minimum";
|
||||
break;
|
||||
case PM_QOS_MAX:
|
||||
type = "Maximum";
|
||||
break;
|
||||
case PM_QOS_SUM:
|
||||
type = "Sum";
|
||||
break;
|
||||
default:
|
||||
type = "Unknown";
|
||||
}
|
||||
|
||||
plist_for_each_entry(req, &c->list, node) {
|
||||
char *state = "Default";
|
||||
|
||||
if ((req->node).prio != c->default_value) {
|
||||
active_reqs++;
|
||||
state = "Active";
|
||||
}
|
||||
tot_reqs++;
|
||||
seq_printf(s, "%d: %d: %s\n", tot_reqs,
|
||||
(req->node).prio, state);
|
||||
}
|
||||
|
||||
seq_printf(s, "Type=%s, Value=%d, Requests: active=%d / total=%d\n",
|
||||
type, pm_qos_get_value(c), active_reqs, tot_reqs);
|
||||
|
||||
out:
|
||||
spin_unlock_irqrestore(&pm_qos_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_SHOW_ATTRIBUTE(pm_qos_debug);
|
||||
|
||||
/**
|
||||
* pm_qos_update_target - manages the constraints list and calls the notifiers
|
||||
* if needed
|
||||
* @c: constraints data struct
|
||||
* @node: request to add to the list, to update or to remove
|
||||
* @action: action to take on the constraints list
|
||||
* @value: value of the request to add or update
|
||||
* pm_qos_update_target - Update a list of PM QoS constraint requests.
|
||||
* @c: List of PM QoS requests.
|
||||
* @node: Target list entry.
|
||||
* @action: Action to carry out (add, update or remove).
|
||||
* @value: New request value for the target list entry.
|
||||
*
|
||||
* This function returns 1 if the aggregated constraint value has changed, 0
|
||||
* otherwise.
|
||||
* Update the given list of PM QoS constraint requests, @c, by carrying an
|
||||
* @action involving the @node list entry and @value on it.
|
||||
*
|
||||
* The recognized values of @action are PM_QOS_ADD_REQ (store @value in @node
|
||||
* and add it to the list), PM_QOS_UPDATE_REQ (remove @node from the list, store
|
||||
* @value in it and add it to the list again), and PM_QOS_REMOVE_REQ (remove
|
||||
* @node from the list, ignore @value).
|
||||
*
|
||||
* Return: 1 if the aggregate constraint value has changed, 0 otherwise.
|
||||
*/
|
||||
int pm_qos_update_target(struct pm_qos_constraints *c, struct plist_node *node,
|
||||
enum pm_qos_req_action action, int value)
|
||||
{
|
||||
unsigned long flags;
|
||||
int prev_value, curr_value, new_value;
|
||||
int ret;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&pm_qos_lock, flags);
|
||||
|
||||
prev_value = pm_qos_get_value(c);
|
||||
if (value == PM_QOS_DEFAULT_VALUE)
|
||||
new_value = c->default_value;
|
||||
@@ -231,9 +115,8 @@ int pm_qos_update_target(struct pm_qos_constraints *c, struct plist_node *node,
|
||||
break;
|
||||
case PM_QOS_UPDATE_REQ:
|
||||
/*
|
||||
* to change the list, we atomically remove, reinit
|
||||
* with new value and add, then see if the extremal
|
||||
* changed
|
||||
* To change the list, atomically remove, reinit with new value
|
||||
* and add, then see if the aggregate has changed.
|
||||
*/
|
||||
plist_del(node, &c->list);
|
||||
/* fall through */
|
||||
@@ -252,16 +135,14 @@ int pm_qos_update_target(struct pm_qos_constraints *c, struct plist_node *node,
|
||||
spin_unlock_irqrestore(&pm_qos_lock, flags);
|
||||
|
||||
trace_pm_qos_update_target(action, prev_value, curr_value);
|
||||
if (prev_value != curr_value) {
|
||||
ret = 1;
|
||||
if (c->notifiers)
|
||||
blocking_notifier_call_chain(c->notifiers,
|
||||
(unsigned long)curr_value,
|
||||
NULL);
|
||||
} else {
|
||||
ret = 0;
|
||||
}
|
||||
return ret;
|
||||
|
||||
if (prev_value == curr_value)
|
||||
return 0;
|
||||
|
||||
if (c->notifiers)
|
||||
blocking_notifier_call_chain(c->notifiers, curr_value, NULL);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,14 +164,12 @@ static void pm_qos_flags_remove_req(struct pm_qos_flags *pqf,
|
||||
|
||||
/**
|
||||
* pm_qos_update_flags - Update a set of PM QoS flags.
|
||||
* @pqf: Set of flags to update.
|
||||
* @pqf: Set of PM QoS flags to update.
|
||||
* @req: Request to add to the set, to modify, or to remove from the set.
|
||||
* @action: Action to take on the set.
|
||||
* @val: Value of the request to add or modify.
|
||||
*
|
||||
* Update the given set of PM QoS flags and call notifiers if the aggregate
|
||||
* value has changed. Returns 1 if the aggregate constraint value has changed,
|
||||
* 0 otherwise.
|
||||
* Return: 1 if the aggregate constraint value has changed, 0 otherwise.
|
||||
*/
|
||||
bool pm_qos_update_flags(struct pm_qos_flags *pqf,
|
||||
struct pm_qos_flags_request *req,
|
||||
@@ -326,288 +205,180 @@ bool pm_qos_update_flags(struct pm_qos_flags *pqf,
|
||||
spin_unlock_irqrestore(&pm_qos_lock, irqflags);
|
||||
|
||||
trace_pm_qos_update_flags(action, prev_value, curr_value);
|
||||
|
||||
return prev_value != curr_value;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CPU_IDLE
|
||||
/* Definitions related to the CPU latency QoS. */
|
||||
|
||||
static struct pm_qos_constraints cpu_latency_constraints = {
|
||||
.list = PLIST_HEAD_INIT(cpu_latency_constraints.list),
|
||||
.target_value = PM_QOS_CPU_LATENCY_DEFAULT_VALUE,
|
||||
.default_value = PM_QOS_CPU_LATENCY_DEFAULT_VALUE,
|
||||
.no_constraint_value = PM_QOS_CPU_LATENCY_DEFAULT_VALUE,
|
||||
.type = PM_QOS_MIN,
|
||||
};
|
||||
|
||||
/**
|
||||
* pm_qos_request - returns current system wide qos expectation
|
||||
* @pm_qos_class: identification of which qos value is requested
|
||||
*
|
||||
* This function returns the current target value.
|
||||
* cpu_latency_qos_limit - Return current system-wide CPU latency QoS limit.
|
||||
*/
|
||||
int pm_qos_request(int pm_qos_class)
|
||||
s32 cpu_latency_qos_limit(void)
|
||||
{
|
||||
return pm_qos_read_value(pm_qos_array[pm_qos_class]->constraints);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_qos_request);
|
||||
|
||||
int pm_qos_request_active(struct pm_qos_request *req)
|
||||
{
|
||||
return req->pm_qos_class != 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_qos_request_active);
|
||||
|
||||
static void __pm_qos_update_request(struct pm_qos_request *req,
|
||||
s32 new_value)
|
||||
{
|
||||
trace_pm_qos_update_request(req->pm_qos_class, new_value);
|
||||
|
||||
if (new_value != req->node.prio)
|
||||
pm_qos_update_target(
|
||||
pm_qos_array[req->pm_qos_class]->constraints,
|
||||
&req->node, PM_QOS_UPDATE_REQ, new_value);
|
||||
return pm_qos_read_value(&cpu_latency_constraints);
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_qos_work_fn - the timeout handler of pm_qos_update_request_timeout
|
||||
* @work: work struct for the delayed work (timeout)
|
||||
* cpu_latency_qos_request_active - Check the given PM QoS request.
|
||||
* @req: PM QoS request to check.
|
||||
*
|
||||
* This cancels the timeout request by falling back to the default at timeout.
|
||||
* Return: 'true' if @req has been added to the CPU latency QoS list, 'false'
|
||||
* otherwise.
|
||||
*/
|
||||
static void pm_qos_work_fn(struct work_struct *work)
|
||||
bool cpu_latency_qos_request_active(struct pm_qos_request *req)
|
||||
{
|
||||
struct pm_qos_request *req = container_of(to_delayed_work(work),
|
||||
struct pm_qos_request,
|
||||
work);
|
||||
return req->qos == &cpu_latency_constraints;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cpu_latency_qos_request_active);
|
||||
|
||||
__pm_qos_update_request(req, PM_QOS_DEFAULT_VALUE);
|
||||
static void cpu_latency_qos_apply(struct pm_qos_request *req,
|
||||
enum pm_qos_req_action action, s32 value)
|
||||
{
|
||||
int ret = pm_qos_update_target(req->qos, &req->node, action, value);
|
||||
if (ret > 0)
|
||||
wake_up_all_idle_cpus();
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_qos_add_request - inserts new qos request into the list
|
||||
* @req: pointer to a preallocated handle
|
||||
* @pm_qos_class: identifies which list of qos request to use
|
||||
* @value: defines the qos request
|
||||
* cpu_latency_qos_add_request - Add new CPU latency QoS request.
|
||||
* @req: Pointer to a preallocated handle.
|
||||
* @value: Requested constraint value.
|
||||
*
|
||||
* This function inserts a new entry in the pm_qos_class list of requested qos
|
||||
* performance characteristics. It recomputes the aggregate QoS expectations
|
||||
* for the pm_qos_class of parameters and initializes the pm_qos_request
|
||||
* handle. Caller needs to save this handle for later use in updates and
|
||||
* removal.
|
||||
* Use @value to initialize the request handle pointed to by @req, insert it as
|
||||
* a new entry to the CPU latency QoS list and recompute the effective QoS
|
||||
* constraint for that list.
|
||||
*
|
||||
* Callers need to save the handle for later use in updates and removal of the
|
||||
* QoS request represented by it.
|
||||
*/
|
||||
|
||||
void pm_qos_add_request(struct pm_qos_request *req,
|
||||
int pm_qos_class, s32 value)
|
||||
{
|
||||
if (!req) /*guard against callers passing in null */
|
||||
return;
|
||||
|
||||
if (pm_qos_request_active(req)) {
|
||||
WARN(1, KERN_ERR "pm_qos_add_request() called for already added request\n");
|
||||
return;
|
||||
}
|
||||
req->pm_qos_class = pm_qos_class;
|
||||
INIT_DELAYED_WORK(&req->work, pm_qos_work_fn);
|
||||
trace_pm_qos_add_request(pm_qos_class, value);
|
||||
pm_qos_update_target(pm_qos_array[pm_qos_class]->constraints,
|
||||
&req->node, PM_QOS_ADD_REQ, value);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_qos_add_request);
|
||||
|
||||
/**
|
||||
* pm_qos_update_request - modifies an existing qos request
|
||||
* @req : handle to list element holding a pm_qos request to use
|
||||
* @value: defines the qos request
|
||||
*
|
||||
* Updates an existing qos request for the pm_qos_class of parameters along
|
||||
* with updating the target pm_qos_class value.
|
||||
*
|
||||
* Attempts are made to make this code callable on hot code paths.
|
||||
*/
|
||||
void pm_qos_update_request(struct pm_qos_request *req,
|
||||
s32 new_value)
|
||||
{
|
||||
if (!req) /*guard against callers passing in null */
|
||||
return;
|
||||
|
||||
if (!pm_qos_request_active(req)) {
|
||||
WARN(1, KERN_ERR "pm_qos_update_request() called for unknown object\n");
|
||||
return;
|
||||
}
|
||||
|
||||
cancel_delayed_work_sync(&req->work);
|
||||
__pm_qos_update_request(req, new_value);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_qos_update_request);
|
||||
|
||||
/**
|
||||
* pm_qos_update_request_timeout - modifies an existing qos request temporarily.
|
||||
* @req : handle to list element holding a pm_qos request to use
|
||||
* @new_value: defines the temporal qos request
|
||||
* @timeout_us: the effective duration of this qos request in usecs.
|
||||
*
|
||||
* After timeout_us, this qos request is cancelled automatically.
|
||||
*/
|
||||
void pm_qos_update_request_timeout(struct pm_qos_request *req, s32 new_value,
|
||||
unsigned long timeout_us)
|
||||
void cpu_latency_qos_add_request(struct pm_qos_request *req, s32 value)
|
||||
{
|
||||
if (!req)
|
||||
return;
|
||||
if (WARN(!pm_qos_request_active(req),
|
||||
"%s called for unknown object.", __func__))
|
||||
return;
|
||||
|
||||
cancel_delayed_work_sync(&req->work);
|
||||
|
||||
trace_pm_qos_update_request_timeout(req->pm_qos_class,
|
||||
new_value, timeout_us);
|
||||
if (new_value != req->node.prio)
|
||||
pm_qos_update_target(
|
||||
pm_qos_array[req->pm_qos_class]->constraints,
|
||||
&req->node, PM_QOS_UPDATE_REQ, new_value);
|
||||
|
||||
schedule_delayed_work(&req->work, usecs_to_jiffies(timeout_us));
|
||||
}
|
||||
|
||||
/**
|
||||
* pm_qos_remove_request - modifies an existing qos request
|
||||
* @req: handle to request list element
|
||||
*
|
||||
* Will remove pm qos request from the list of constraints and
|
||||
* recompute the current target value for the pm_qos_class. Call this
|
||||
* on slow code paths.
|
||||
*/
|
||||
void pm_qos_remove_request(struct pm_qos_request *req)
|
||||
{
|
||||
if (!req) /*guard against callers passing in null */
|
||||
return;
|
||||
/* silent return to keep pcm code cleaner */
|
||||
|
||||
if (!pm_qos_request_active(req)) {
|
||||
WARN(1, KERN_ERR "pm_qos_remove_request() called for unknown object\n");
|
||||
if (cpu_latency_qos_request_active(req)) {
|
||||
WARN(1, KERN_ERR "%s called for already added request\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
cancel_delayed_work_sync(&req->work);
|
||||
trace_pm_qos_add_request(value);
|
||||
|
||||
trace_pm_qos_remove_request(req->pm_qos_class, PM_QOS_DEFAULT_VALUE);
|
||||
pm_qos_update_target(pm_qos_array[req->pm_qos_class]->constraints,
|
||||
&req->node, PM_QOS_REMOVE_REQ,
|
||||
PM_QOS_DEFAULT_VALUE);
|
||||
req->qos = &cpu_latency_constraints;
|
||||
cpu_latency_qos_apply(req, PM_QOS_ADD_REQ, value);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cpu_latency_qos_add_request);
|
||||
|
||||
/**
|
||||
* cpu_latency_qos_update_request - Modify existing CPU latency QoS request.
|
||||
* @req : QoS request to update.
|
||||
* @new_value: New requested constraint value.
|
||||
*
|
||||
* Use @new_value to update the QoS request represented by @req in the CPU
|
||||
* latency QoS list along with updating the effective constraint value for that
|
||||
* list.
|
||||
*/
|
||||
void cpu_latency_qos_update_request(struct pm_qos_request *req, s32 new_value)
|
||||
{
|
||||
if (!req)
|
||||
return;
|
||||
|
||||
if (!cpu_latency_qos_request_active(req)) {
|
||||
WARN(1, KERN_ERR "%s called for unknown object\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
trace_pm_qos_update_request(new_value);
|
||||
|
||||
if (new_value == req->node.prio)
|
||||
return;
|
||||
|
||||
cpu_latency_qos_apply(req, PM_QOS_UPDATE_REQ, new_value);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(cpu_latency_qos_update_request);
|
||||
|
||||
/**
|
||||
* cpu_latency_qos_remove_request - Remove existing CPU latency QoS request.
|
||||
* @req: QoS request to remove.
|
||||
*
|
||||
* Remove the CPU latency QoS request represented by @req from the CPU latency
|
||||
* QoS list along with updating the effective constraint value for that list.
|
||||
*/
|
||||
void cpu_latency_qos_remove_request(struct pm_qos_request *req)
|
||||
{
|
||||
if (!req)
|
||||
return;
|
||||
|
||||
if (!cpu_latency_qos_request_active(req)) {
|
||||
WARN(1, KERN_ERR "%s called for unknown object\n", __func__);
|
||||
return;
|
||||
}
|
||||
|
||||
trace_pm_qos_remove_request(PM_QOS_DEFAULT_VALUE);
|
||||
|
||||
cpu_latency_qos_apply(req, PM_QOS_REMOVE_REQ, PM_QOS_DEFAULT_VALUE);
|
||||
memset(req, 0, sizeof(*req));
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_qos_remove_request);
|
||||
EXPORT_SYMBOL_GPL(cpu_latency_qos_remove_request);
|
||||
|
||||
/**
|
||||
* pm_qos_add_notifier - sets notification entry for changes to target value
|
||||
* @pm_qos_class: identifies which qos target changes should be notified.
|
||||
* @notifier: notifier block managed by caller.
|
||||
*
|
||||
* will register the notifier into a notification chain that gets called
|
||||
* upon changes to the pm_qos_class target value.
|
||||
*/
|
||||
int pm_qos_add_notifier(int pm_qos_class, struct notifier_block *notifier)
|
||||
{
|
||||
int retval;
|
||||
/* User space interface to the CPU latency QoS via misc device. */
|
||||
|
||||
retval = blocking_notifier_chain_register(
|
||||
pm_qos_array[pm_qos_class]->constraints->notifiers,
|
||||
notifier);
|
||||
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_qos_add_notifier);
|
||||
|
||||
/**
|
||||
* pm_qos_remove_notifier - deletes notification entry from chain.
|
||||
* @pm_qos_class: identifies which qos target changes are notified.
|
||||
* @notifier: notifier block to be removed.
|
||||
*
|
||||
* will remove the notifier from the notification chain that gets called
|
||||
* upon changes to the pm_qos_class target value.
|
||||
*/
|
||||
int pm_qos_remove_notifier(int pm_qos_class, struct notifier_block *notifier)
|
||||
{
|
||||
int retval;
|
||||
|
||||
retval = blocking_notifier_chain_unregister(
|
||||
pm_qos_array[pm_qos_class]->constraints->notifiers,
|
||||
notifier);
|
||||
|
||||
return retval;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm_qos_remove_notifier);
|
||||
|
||||
/* User space interface to PM QoS classes via misc devices */
|
||||
static int register_pm_qos_misc(struct pm_qos_object *qos, struct dentry *d)
|
||||
{
|
||||
qos->pm_qos_power_miscdev.minor = MISC_DYNAMIC_MINOR;
|
||||
qos->pm_qos_power_miscdev.name = qos->name;
|
||||
qos->pm_qos_power_miscdev.fops = &pm_qos_power_fops;
|
||||
|
||||
debugfs_create_file(qos->name, S_IRUGO, d, (void *)qos,
|
||||
&pm_qos_debug_fops);
|
||||
|
||||
return misc_register(&qos->pm_qos_power_miscdev);
|
||||
}
|
||||
|
||||
static int find_pm_qos_object_by_minor(int minor)
|
||||
{
|
||||
int pm_qos_class;
|
||||
|
||||
for (pm_qos_class = PM_QOS_CPU_DMA_LATENCY;
|
||||
pm_qos_class < PM_QOS_NUM_CLASSES; pm_qos_class++) {
|
||||
if (minor ==
|
||||
pm_qos_array[pm_qos_class]->pm_qos_power_miscdev.minor)
|
||||
return pm_qos_class;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int pm_qos_power_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
long pm_qos_class;
|
||||
|
||||
pm_qos_class = find_pm_qos_object_by_minor(iminor(inode));
|
||||
if (pm_qos_class >= PM_QOS_CPU_DMA_LATENCY) {
|
||||
struct pm_qos_request *req = kzalloc(sizeof(*req), GFP_KERNEL);
|
||||
if (!req)
|
||||
return -ENOMEM;
|
||||
|
||||
pm_qos_add_request(req, pm_qos_class, PM_QOS_DEFAULT_VALUE);
|
||||
filp->private_data = req;
|
||||
|
||||
return 0;
|
||||
}
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
static int pm_qos_power_release(struct inode *inode, struct file *filp)
|
||||
static int cpu_latency_qos_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct pm_qos_request *req;
|
||||
|
||||
req = filp->private_data;
|
||||
pm_qos_remove_request(req);
|
||||
req = kzalloc(sizeof(*req), GFP_KERNEL);
|
||||
if (!req)
|
||||
return -ENOMEM;
|
||||
|
||||
cpu_latency_qos_add_request(req, PM_QOS_DEFAULT_VALUE);
|
||||
filp->private_data = req;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cpu_latency_qos_release(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct pm_qos_request *req = filp->private_data;
|
||||
|
||||
filp->private_data = NULL;
|
||||
|
||||
cpu_latency_qos_remove_request(req);
|
||||
kfree(req);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static ssize_t pm_qos_power_read(struct file *filp, char __user *buf,
|
||||
size_t count, loff_t *f_pos)
|
||||
static ssize_t cpu_latency_qos_read(struct file *filp, char __user *buf,
|
||||
size_t count, loff_t *f_pos)
|
||||
{
|
||||
s32 value;
|
||||
unsigned long flags;
|
||||
struct pm_qos_request *req = filp->private_data;
|
||||
unsigned long flags;
|
||||
s32 value;
|
||||
|
||||
if (!req)
|
||||
return -EINVAL;
|
||||
if (!pm_qos_request_active(req))
|
||||
if (!req || !cpu_latency_qos_request_active(req))
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&pm_qos_lock, flags);
|
||||
value = pm_qos_get_value(pm_qos_array[req->pm_qos_class]->constraints);
|
||||
value = pm_qos_get_value(&cpu_latency_constraints);
|
||||
spin_unlock_irqrestore(&pm_qos_lock, flags);
|
||||
|
||||
return simple_read_from_buffer(buf, count, f_pos, &value, sizeof(s32));
|
||||
}
|
||||
|
||||
static ssize_t pm_qos_power_write(struct file *filp, const char __user *buf,
|
||||
size_t count, loff_t *f_pos)
|
||||
static ssize_t cpu_latency_qos_write(struct file *filp, const char __user *buf,
|
||||
size_t count, loff_t *f_pos)
|
||||
{
|
||||
s32 value;
|
||||
struct pm_qos_request *req;
|
||||
|
||||
if (count == sizeof(s32)) {
|
||||
if (copy_from_user(&value, buf, sizeof(s32)))
|
||||
@@ -620,36 +391,38 @@ static ssize_t pm_qos_power_write(struct file *filp, const char __user *buf,
|
||||
return ret;
|
||||
}
|
||||
|
||||
req = filp->private_data;
|
||||
pm_qos_update_request(req, value);
|
||||
cpu_latency_qos_update_request(filp->private_data, value);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations cpu_latency_qos_fops = {
|
||||
.write = cpu_latency_qos_write,
|
||||
.read = cpu_latency_qos_read,
|
||||
.open = cpu_latency_qos_open,
|
||||
.release = cpu_latency_qos_release,
|
||||
.llseek = noop_llseek,
|
||||
};
|
||||
|
||||
static int __init pm_qos_power_init(void)
|
||||
static struct miscdevice cpu_latency_qos_miscdev = {
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "cpu_dma_latency",
|
||||
.fops = &cpu_latency_qos_fops,
|
||||
};
|
||||
|
||||
static int __init cpu_latency_qos_init(void)
|
||||
{
|
||||
int ret = 0;
|
||||
int i;
|
||||
struct dentry *d;
|
||||
int ret;
|
||||
|
||||
BUILD_BUG_ON(ARRAY_SIZE(pm_qos_array) != PM_QOS_NUM_CLASSES);
|
||||
|
||||
d = debugfs_create_dir("pm_qos", NULL);
|
||||
|
||||
for (i = PM_QOS_CPU_DMA_LATENCY; i < PM_QOS_NUM_CLASSES; i++) {
|
||||
ret = register_pm_qos_misc(pm_qos_array[i], d);
|
||||
if (ret < 0) {
|
||||
pr_err("%s: %s setup failed\n",
|
||||
__func__, pm_qos_array[i]->name);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
ret = misc_register(&cpu_latency_qos_miscdev);
|
||||
if (ret < 0)
|
||||
pr_err("%s: %s setup failed\n", __func__,
|
||||
cpu_latency_qos_miscdev.name);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
late_initcall(pm_qos_power_init);
|
||||
late_initcall(cpu_latency_qos_init);
|
||||
#endif /* CONFIG_CPU_IDLE */
|
||||
|
||||
/* Definitions related to the frequency QoS below. */
|
||||
|
||||
|
||||
+1
-19
@@ -1681,7 +1681,7 @@ static unsigned long minimum_image_size(unsigned long saveable)
|
||||
* hibernation for allocations made while saving the image and for device
|
||||
* drivers, in case they need to allocate memory from their hibernation
|
||||
* callbacks (these two numbers are given by PAGES_FOR_IO (which is a rough
|
||||
* estimate) and reserverd_size divided by PAGE_SIZE (which is tunable through
|
||||
* estimate) and reserved_size divided by PAGE_SIZE (which is tunable through
|
||||
* /sys/power/reserved_size, respectively). To make this happen, we compute the
|
||||
* total number of available page frames and allocate at least
|
||||
*
|
||||
@@ -1744,9 +1744,6 @@ int hibernate_preallocate_memory(void)
|
||||
count += highmem;
|
||||
count -= totalreserve_pages;
|
||||
|
||||
/* Add number of pages required for page keys (s390 only). */
|
||||
size += page_key_additional_pages(saveable);
|
||||
|
||||
/* Compute the maximum number of saveable pages to leave in memory. */
|
||||
max_size = (count - (size + PAGES_FOR_IO)) / 2
|
||||
- 2 * DIV_ROUND_UP(reserved_size, PAGE_SIZE);
|
||||
@@ -2075,8 +2072,6 @@ static inline void pack_pfns(unsigned long *buf, struct memory_bitmap *bm)
|
||||
buf[j] = memory_bm_next_pfn(bm);
|
||||
if (unlikely(buf[j] == BM_END_OF_MAP))
|
||||
break;
|
||||
/* Save page key for data page (s390 only). */
|
||||
page_key_read(buf + j);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2226,9 +2221,6 @@ static int unpack_orig_pfns(unsigned long *buf, struct memory_bitmap *bm)
|
||||
if (unlikely(buf[j] == BM_END_OF_MAP))
|
||||
break;
|
||||
|
||||
/* Extract and buffer page key for data page (s390 only). */
|
||||
page_key_memorize(buf + j);
|
||||
|
||||
if (pfn_valid(buf[j]) && memory_bm_pfn_present(bm, buf[j]))
|
||||
memory_bm_set_bit(bm, buf[j]);
|
||||
else
|
||||
@@ -2623,11 +2615,6 @@ int snapshot_write_next(struct snapshot_handle *handle)
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
/* Allocate buffer for page keys. */
|
||||
error = page_key_alloc(nr_copy_pages);
|
||||
if (error)
|
||||
return error;
|
||||
|
||||
hibernate_restore_protection_begin();
|
||||
} else if (handle->cur <= nr_meta_pages + 1) {
|
||||
error = unpack_orig_pfns(buffer, ©_bm);
|
||||
@@ -2649,8 +2636,6 @@ int snapshot_write_next(struct snapshot_handle *handle)
|
||||
}
|
||||
} else {
|
||||
copy_last_highmem_page();
|
||||
/* Restore page key for data page (s390 only). */
|
||||
page_key_write(handle->buffer);
|
||||
hibernate_restore_protect_page(handle->buffer);
|
||||
handle->buffer = get_buffer(&orig_bm, &ca);
|
||||
if (IS_ERR(handle->buffer))
|
||||
@@ -2673,9 +2658,6 @@ int snapshot_write_next(struct snapshot_handle *handle)
|
||||
void snapshot_write_finalize(struct snapshot_handle *handle)
|
||||
{
|
||||
copy_last_highmem_page();
|
||||
/* Restore page key for data page (s390 only). */
|
||||
page_key_write(handle->buffer);
|
||||
page_key_free();
|
||||
hibernate_restore_protect_page(handle->buffer);
|
||||
/* Do that only if we have loaded the image entirely */
|
||||
if (handle->cur > 1 && handle->cur > nr_meta_pages + nr_copy_pages) {
|
||||
|
||||
@@ -131,11 +131,12 @@ static void s2idle_loop(void)
|
||||
* to avoid them upfront.
|
||||
*/
|
||||
for (;;) {
|
||||
if (s2idle_ops && s2idle_ops->wake)
|
||||
s2idle_ops->wake();
|
||||
|
||||
if (pm_wakeup_pending())
|
||||
if (s2idle_ops && s2idle_ops->wake) {
|
||||
if (s2idle_ops->wake())
|
||||
break;
|
||||
} else if (pm_wakeup_pending()) {
|
||||
break;
|
||||
}
|
||||
|
||||
pm_wakeup_clear(false);
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user