unsigned long ss_mask);
 static void cgroup_destroy_css_killed(struct cgroup *cgrp);
 static int cgroup_destroy_locked(struct cgroup *cgrp);
+static int create_css(struct cgroup *cgrp, struct cgroup_subsys *ss);
+static void kill_css(struct cgroup_subsys_state *css);
 static int cgroup_addrm_files(struct cgroup *cgrp, struct cftype cfts[],
                              bool is_add);
 static void cgroup_pidlist_destroy_all(struct cgroup *cgrp);
 #define for_each_root(root)                                            \
        list_for_each_entry((root), &cgroup_roots, root_list)
 
+/* iterate over child cgrps, lock should be held throughout iteration */
+#define cgroup_for_each_live_child(child, cgrp)                                \
+       list_for_each_entry((child), &(cgrp)->children, sibling)        \
+               if (({ lockdep_assert_held(&cgroup_tree_mutex);         \
+                      cgroup_is_dead(child); }))                       \
+                       ;                                               \
+               else
+
 /**
  * cgroup_lock_live_group - take cgroup_mutex and check that cgrp is alive.
  * @cgrp: the cgroup to be checked for liveness
 
        for_each_subsys(ss, ssid)
                INIT_LIST_HEAD(&cgrp->e_csets[ssid]);
+
+       init_waitqueue_head(&cgrp->offline_waitq);
 }
 
 static void init_cgroup_root(struct cgroup_root *root,
 
        lockdep_assert_held(&cgroup_mutex);
 
+       /*
+        * Except for the root, child_subsys_mask must be zero for a cgroup
+        * with tasks so that child cgroups don't compete against tasks.
+        */
+       if (dst_cgrp && cgroup_on_dfl(dst_cgrp) && dst_cgrp->parent &&
+           dst_cgrp->child_subsys_mask)
+               return -EBUSY;
+
        /* look up the dst cset for each src cset and link it to src */
        list_for_each_entry_safe(src_cset, tmp_cset, preloaded_csets, mg_preload_node) {
                struct css_set *dst_cset;
        return 0;
 }
 
+static void cgroup_print_ss_mask(struct seq_file *seq, unsigned int ss_mask)
+{
+       struct cgroup_subsys *ss;
+       bool printed = false;
+       int ssid;
+
+       for_each_subsys(ss, ssid) {
+               if (ss_mask & (1 << ssid)) {
+                       if (printed)
+                               seq_putc(seq, ' ');
+                       seq_printf(seq, "%s", ss->name);
+                       printed = true;
+               }
+       }
+       if (printed)
+               seq_putc(seq, '\n');
+}
+
+/* show controllers which are currently attached to the default hierarchy */
+static int cgroup_root_controllers_show(struct seq_file *seq, void *v)
+{
+       struct cgroup *cgrp = seq_css(seq)->cgroup;
+
+       cgroup_print_ss_mask(seq, cgrp->root->subsys_mask);
+       return 0;
+}
+
+/* show controllers which are enabled from the parent */
+static int cgroup_controllers_show(struct seq_file *seq, void *v)
+{
+       struct cgroup *cgrp = seq_css(seq)->cgroup;
+
+       cgroup_print_ss_mask(seq, cgrp->parent->child_subsys_mask);
+       return 0;
+}
+
+/* show controllers which are enabled for a given cgroup's children */
+static int cgroup_subtree_control_show(struct seq_file *seq, void *v)
+{
+       struct cgroup *cgrp = seq_css(seq)->cgroup;
+
+       cgroup_print_ss_mask(seq, cgrp->child_subsys_mask);
+       return 0;
+}
+
+/**
+ * cgroup_update_dfl_csses - update css assoc of a subtree in default hierarchy
+ * @cgrp: root of the subtree to update csses for
+ *
+ * @cgrp's child_subsys_mask has changed and its subtree's (self excluded)
+ * css associations need to be updated accordingly.  This function looks up
+ * all css_sets which are attached to the subtree, creates the matching
+ * updated css_sets and migrates the tasks to the new ones.
+ */
+static int cgroup_update_dfl_csses(struct cgroup *cgrp)
+{
+       LIST_HEAD(preloaded_csets);
+       struct cgroup_subsys_state *css;
+       struct css_set *src_cset;
+       int ret;
+
+       lockdep_assert_held(&cgroup_tree_mutex);
+       lockdep_assert_held(&cgroup_mutex);
+
+       /* look up all csses currently attached to @cgrp's subtree */
+       down_read(&css_set_rwsem);
+       css_for_each_descendant_pre(css, cgroup_css(cgrp, NULL)) {
+               struct cgrp_cset_link *link;
+
+               /* self is not affected by child_subsys_mask change */
+               if (css->cgroup == cgrp)
+                       continue;
+
+               list_for_each_entry(link, &css->cgroup->cset_links, cset_link)
+                       cgroup_migrate_add_src(link->cset, cgrp,
+                                              &preloaded_csets);
+       }
+       up_read(&css_set_rwsem);
+
+       /* NULL dst indicates self on default hierarchy */
+       ret = cgroup_migrate_prepare_dst(NULL, &preloaded_csets);
+       if (ret)
+               goto out_finish;
+
+       list_for_each_entry(src_cset, &preloaded_csets, mg_preload_node) {
+               struct task_struct *last_task = NULL, *task;
+
+               /* src_csets precede dst_csets, break on the first dst_cset */
+               if (!src_cset->mg_src_cgrp)
+                       break;
+
+               /*
+                * All tasks in src_cset need to be migrated to the
+                * matching dst_cset.  Empty it process by process.  We
+                * walk tasks but migrate processes.  The leader might even
+                * belong to a different cset but such src_cset would also
+                * be among the target src_csets because the default
+                * hierarchy enforces per-process membership.
+                */
+               while (true) {
+                       down_read(&css_set_rwsem);
+                       task = list_first_entry_or_null(&src_cset->tasks,
+                                               struct task_struct, cg_list);
+                       if (task) {
+                               task = task->group_leader;
+                               WARN_ON_ONCE(!task_css_set(task)->mg_src_cgrp);
+                               get_task_struct(task);
+                       }
+                       up_read(&css_set_rwsem);
+
+                       if (!task)
+                               break;
+
+                       /* guard against possible infinite loop */
+                       if (WARN(last_task == task,
+                                "cgroup: update_dfl_csses failed to make progress, aborting in inconsistent state\n"))
+                               goto out_finish;
+                       last_task = task;
+
+                       threadgroup_lock(task);
+                       /* raced against de_thread() from another thread? */
+                       if (!thread_group_leader(task)) {
+                               threadgroup_unlock(task);
+                               put_task_struct(task);
+                               continue;
+                       }
+
+                       ret = cgroup_migrate(src_cset->dfl_cgrp, task, true);
+
+                       threadgroup_unlock(task);
+                       put_task_struct(task);
+
+                       if (WARN(ret, "cgroup: failed to update controllers for the default hierarchy (%d), further operations may crash or hang\n", ret))
+                               goto out_finish;
+               }
+       }
+
+out_finish:
+       cgroup_migrate_finish(&preloaded_csets);
+       return ret;
+}
+
+/* change the enabled child controllers for a cgroup in the default hierarchy */
+static int cgroup_subtree_control_write(struct cgroup_subsys_state *dummy_css,
+                                       struct cftype *cft, char *buffer)
+{
+       unsigned long enable_req = 0, disable_req = 0, enable, disable;
+       struct cgroup *cgrp = dummy_css->cgroup, *child;
+       struct cgroup_subsys *ss;
+       char *tok, *p;
+       int ssid, ret;
+
+       /*
+        * Parse input - white space separated list of subsystem names
+        * prefixed with either + or -.
+        */
+       p = buffer;
+       while ((tok = strsep(&p, " \t\n"))) {
+               for_each_subsys(ss, ssid) {
+                       if (ss->disabled || strcmp(tok + 1, ss->name))
+                               continue;
+
+                       if (*tok == '+') {
+                               enable_req |= 1 << ssid;
+                               disable_req &= ~(1 << ssid);
+                       } else if (*tok == '-') {
+                               disable_req |= 1 << ssid;
+                               enable_req &= ~(1 << ssid);
+                       } else {
+                               return -EINVAL;
+                       }
+                       break;
+               }
+               if (ssid == CGROUP_SUBSYS_COUNT)
+                       return -EINVAL;
+       }
+
+       /*
+        * We're gonna grab cgroup_tree_mutex which nests outside kernfs
+        * active_ref.  cgroup_lock_live_group() already provides enough
+        * protection.  Ensure @cgrp stays accessible and break the
+        * active_ref protection.
+        */
+       cgroup_get(cgrp);
+       kernfs_break_active_protection(cgrp->control_kn);
+retry:
+       enable = enable_req;
+       disable = disable_req;
+
+       mutex_lock(&cgroup_tree_mutex);
+
+       for_each_subsys(ss, ssid) {
+               if (enable & (1 << ssid)) {
+                       if (cgrp->child_subsys_mask & (1 << ssid)) {
+                               enable &= ~(1 << ssid);
+                               continue;
+                       }
+
+                       /*
+                        * Because css offlining is asynchronous, userland
+                        * might try to re-enable the same controller while
+                        * the previous instance is still around.  In such
+                        * cases, wait till it's gone using offline_waitq.
+                        */
+                       cgroup_for_each_live_child(child, cgrp) {
+                               wait_queue_t wait;
+
+                               if (!cgroup_css(child, ss))
+                                       continue;
+
+                               prepare_to_wait(&child->offline_waitq, &wait,
+                                               TASK_UNINTERRUPTIBLE);
+                               mutex_unlock(&cgroup_tree_mutex);
+                               schedule();
+                               finish_wait(&child->offline_waitq, &wait);
+                               goto retry;
+                       }
+
+                       /* unavailable or not enabled on the parent? */
+                       if (!(cgrp_dfl_root.subsys_mask & (1 << ssid)) ||
+                           (cgrp->parent &&
+                            !(cgrp->parent->child_subsys_mask & (1 << ssid)))) {
+                               ret = -ENOENT;
+                               goto out_unlock_tree;
+                       }
+               } else if (disable & (1 << ssid)) {
+                       if (!(cgrp->child_subsys_mask & (1 << ssid))) {
+                               disable &= ~(1 << ssid);
+                               continue;
+                       }
+
+                       /* a child has it enabled? */
+                       cgroup_for_each_live_child(child, cgrp) {
+                               if (child->child_subsys_mask & (1 << ssid)) {
+                                       ret = -EBUSY;
+                                       goto out_unlock_tree;
+                               }
+                       }
+               }
+       }
+
+       if (!enable && !disable) {
+               ret = 0;
+               goto out_unlock_tree;
+       }
+
+       if (!cgroup_lock_live_group(cgrp)) {
+               ret = -ENODEV;
+               goto out_unlock_tree;
+       }
+
+       /*
+        * Except for the root, child_subsys_mask must be zero for a cgroup
+        * with tasks so that child cgroups don't compete against tasks.
+        */
+       if (enable && cgrp->parent && !list_empty(&cgrp->cset_links)) {
+               ret = -EBUSY;
+               goto out_unlock;
+       }
+
+       /*
+        * Create csses for enables and update child_subsys_mask.  This
+        * changes cgroup_e_css() results which in turn makes the
+        * subsequent cgroup_update_dfl_csses() associate all tasks in the
+        * subtree to the updated csses.
+        */
+       for_each_subsys(ss, ssid) {
+               if (!(enable & (1 << ssid)))
+                       continue;
+
+               cgroup_for_each_live_child(child, cgrp) {
+                       ret = create_css(child, ss);
+                       if (ret)
+                               goto err_undo_css;
+               }
+       }
+
+       cgrp->child_subsys_mask |= enable;
+       cgrp->child_subsys_mask &= ~disable;
+
+       ret = cgroup_update_dfl_csses(cgrp);
+       if (ret)
+               goto err_undo_css;
+
+       /* all tasks are now migrated away from the old csses, kill them */
+       for_each_subsys(ss, ssid) {
+               if (!(disable & (1 << ssid)))
+                       continue;
+
+               cgroup_for_each_live_child(child, cgrp)
+                       kill_css(cgroup_css(child, ss));
+       }
+
+       kernfs_activate(cgrp->kn);
+       ret = 0;
+out_unlock:
+       mutex_unlock(&cgroup_mutex);
+out_unlock_tree:
+       mutex_unlock(&cgroup_tree_mutex);
+       kernfs_unbreak_active_protection(cgrp->control_kn);
+       cgroup_put(cgrp);
+       return ret;
+
+err_undo_css:
+       cgrp->child_subsys_mask &= ~enable;
+       cgrp->child_subsys_mask |= disable;
+
+       for_each_subsys(ss, ssid) {
+               if (!(enable & (1 << ssid)))
+                       continue;
+
+               cgroup_for_each_live_child(child, cgrp) {
+                       struct cgroup_subsys_state *css = cgroup_css(child, ss);
+                       if (css)
+                               kill_css(css);
+               }
+       }
+       goto out_unlock;
+}
+
 static ssize_t cgroup_file_write(struct kernfs_open_file *of, char *buf,
                                 size_t nbytes, loff_t off)
 {
                return PTR_ERR(kn);
 
        ret = cgroup_kn_set_ugid(kn);
-       if (ret)
+       if (ret) {
                kernfs_remove(kn);
-       return ret;
+               return ret;
+       }
+
+       if (cft->seq_show == cgroup_subtree_control_show)
+               cgrp->control_kn = kn;
+       return 0;
 }
 
 /**
                .flags = CFTYPE_ONLY_ON_ROOT,
                .seq_show = cgroup_sane_behavior_show,
        },
+       {
+               .name = "cgroup.controllers",
+               .flags = CFTYPE_ONLY_ON_DFL | CFTYPE_ONLY_ON_ROOT,
+               .seq_show = cgroup_root_controllers_show,
+       },
+       {
+               .name = "cgroup.controllers",
+               .flags = CFTYPE_ONLY_ON_DFL | CFTYPE_NOT_ON_ROOT,
+               .seq_show = cgroup_controllers_show,
+       },
+       {
+               .name = "cgroup.subtree_control",
+               .flags = CFTYPE_ONLY_ON_DFL,
+               .seq_show = cgroup_subtree_control_show,
+               .write_string = cgroup_subtree_control_write,
+       },
 
        /*
         * Historical crazy stuff.  These don't have "cgroup."  prefix and
        css->flags &= ~CSS_ONLINE;
        css->cgroup->nr_css--;
        RCU_INIT_POINTER(css->cgroup->subsys[ss->id], NULL);
+
+       wake_up_all(&css->cgroup->offline_waitq);
 }
 
 /**