Path: blob/master/arch/powerpc/platforms/cell/spufs/inode.c
26498 views
// SPDX-License-Identifier: GPL-2.0-or-later12/*3* SPU file system4*5* (C) Copyright IBM Deutschland Entwicklung GmbH 20056*7* Author: Arnd Bergmann <[email protected]>8*/910#include <linux/file.h>11#include <linux/fs.h>12#include <linux/fs_context.h>13#include <linux/fs_parser.h>14#include <linux/fsnotify.h>15#include <linux/backing-dev.h>16#include <linux/init.h>17#include <linux/ioctl.h>18#include <linux/module.h>19#include <linux/mount.h>20#include <linux/namei.h>21#include <linux/pagemap.h>22#include <linux/poll.h>23#include <linux/of.h>24#include <linux/seq_file.h>25#include <linux/slab.h>2627#include <asm/spu.h>28#include <asm/spu_priv1.h>29#include <linux/uaccess.h>3031#include "spufs.h"3233struct spufs_sb_info {34bool debug;35};3637static struct kmem_cache *spufs_inode_cache;38char *isolated_loader;39static int isolated_loader_size;4041static struct spufs_sb_info *spufs_get_sb_info(struct super_block *sb)42{43return sb->s_fs_info;44}4546static struct inode *47spufs_alloc_inode(struct super_block *sb)48{49struct spufs_inode_info *ei;5051ei = kmem_cache_alloc(spufs_inode_cache, GFP_KERNEL);52if (!ei)53return NULL;5455ei->i_gang = NULL;56ei->i_ctx = NULL;57ei->i_openers = 0;5859return &ei->vfs_inode;60}6162static void spufs_free_inode(struct inode *inode)63{64kmem_cache_free(spufs_inode_cache, SPUFS_I(inode));65}6667static void68spufs_init_once(void *p)69{70struct spufs_inode_info *ei = p;7172inode_init_once(&ei->vfs_inode);73}7475static struct inode *76spufs_new_inode(struct super_block *sb, umode_t mode)77{78struct inode *inode;7980inode = new_inode(sb);81if (!inode)82goto out;8384inode->i_ino = get_next_ino();85inode->i_mode = mode;86inode->i_uid = current_fsuid();87inode->i_gid = current_fsgid();88simple_inode_init_ts(inode);89out:90return inode;91}9293static int94spufs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,95struct iattr *attr)96{97struct inode *inode = d_inode(dentry);9899if ((attr->ia_valid & ATTR_SIZE) &&100(attr->ia_size != inode->i_size))101return -EINVAL;102setattr_copy(&nop_mnt_idmap, inode, attr);103mark_inode_dirty(inode);104return 0;105}106107108static int109spufs_new_file(struct super_block *sb, struct dentry *dentry,110const struct file_operations *fops, umode_t mode,111size_t size, struct spu_context *ctx)112{113static const struct inode_operations spufs_file_iops = {114.setattr = spufs_setattr,115};116struct inode *inode;117int ret;118119ret = -ENOSPC;120inode = spufs_new_inode(sb, S_IFREG | mode);121if (!inode)122goto out;123124ret = 0;125inode->i_op = &spufs_file_iops;126inode->i_fop = fops;127inode->i_size = size;128inode->i_private = SPUFS_I(inode)->i_ctx = get_spu_context(ctx);129d_add(dentry, inode);130out:131return ret;132}133134static void135spufs_evict_inode(struct inode *inode)136{137struct spufs_inode_info *ei = SPUFS_I(inode);138clear_inode(inode);139if (ei->i_ctx)140put_spu_context(ei->i_ctx);141if (ei->i_gang)142put_spu_gang(ei->i_gang);143}144145/* Caller must hold parent->i_mutex */146static void spufs_rmdir(struct inode *parent, struct dentry *dir)147{148struct spu_context *ctx = SPUFS_I(d_inode(dir))->i_ctx;149150locked_recursive_removal(dir, NULL);151spu_forget(ctx);152}153154static int spufs_fill_dir(struct dentry *dir,155const struct spufs_tree_descr *files, umode_t mode,156struct spu_context *ctx)157{158while (files->name && files->name[0]) {159int ret;160struct dentry *dentry = d_alloc_name(dir, files->name);161if (!dentry)162return -ENOMEM;163ret = spufs_new_file(dir->d_sb, dentry, files->ops,164files->mode & mode, files->size, ctx);165if (ret) {166dput(dentry);167return ret;168}169files++;170}171return 0;172}173174static void unuse_gang(struct dentry *dir)175{176struct inode *inode = dir->d_inode;177struct spu_gang *gang = SPUFS_I(inode)->i_gang;178179if (gang) {180bool dead;181182inode_lock(inode); // exclusion with spufs_create_context()183dead = !--gang->alive;184inode_unlock(inode);185186if (dead)187simple_recursive_removal(dir, NULL);188}189}190191static int spufs_dir_close(struct inode *inode, struct file *file)192{193struct inode *parent;194struct dentry *dir;195196dir = file->f_path.dentry;197parent = d_inode(dir->d_parent);198199inode_lock_nested(parent, I_MUTEX_PARENT);200spufs_rmdir(parent, dir);201inode_unlock(parent);202203unuse_gang(dir->d_parent);204return dcache_dir_close(inode, file);205}206207const struct file_operations spufs_context_fops = {208.open = dcache_dir_open,209.release = spufs_dir_close,210.llseek = dcache_dir_lseek,211.read = generic_read_dir,212.iterate_shared = dcache_readdir,213.fsync = noop_fsync,214};215EXPORT_SYMBOL_GPL(spufs_context_fops);216217static int218spufs_mkdir(struct inode *dir, struct dentry *dentry, unsigned int flags,219umode_t mode)220{221int ret;222struct inode *inode;223struct spu_context *ctx;224225inode = spufs_new_inode(dir->i_sb, mode | S_IFDIR);226if (!inode)227return -ENOSPC;228229inode_init_owner(&nop_mnt_idmap, inode, dir, mode | S_IFDIR);230ctx = alloc_spu_context(SPUFS_I(dir)->i_gang); /* XXX gang */231SPUFS_I(inode)->i_ctx = ctx;232if (!ctx) {233iput(inode);234return -ENOSPC;235}236237ctx->flags = flags;238inode->i_op = &simple_dir_inode_operations;239inode->i_fop = &simple_dir_operations;240241inode_lock(inode);242243dget(dentry);244inc_nlink(dir);245inc_nlink(inode);246247d_instantiate(dentry, inode);248249if (flags & SPU_CREATE_NOSCHED)250ret = spufs_fill_dir(dentry, spufs_dir_nosched_contents,251mode, ctx);252else253ret = spufs_fill_dir(dentry, spufs_dir_contents, mode, ctx);254255if (!ret && spufs_get_sb_info(dir->i_sb)->debug)256ret = spufs_fill_dir(dentry, spufs_dir_debug_contents,257mode, ctx);258259inode_unlock(inode);260261if (ret)262spufs_rmdir(dir, dentry);263264return ret;265}266267static int spufs_context_open(const struct path *path)268{269int ret;270struct file *filp;271272ret = get_unused_fd_flags(0);273if (ret < 0)274return ret;275276filp = dentry_open(path, O_RDONLY, current_cred());277if (IS_ERR(filp)) {278put_unused_fd(ret);279return PTR_ERR(filp);280}281282filp->f_op = &spufs_context_fops;283fd_install(ret, filp);284return ret;285}286287static struct spu_context *288spufs_assert_affinity(unsigned int flags, struct spu_gang *gang,289struct file *filp)290{291struct spu_context *tmp, *neighbor, *err;292int count, node;293int aff_supp;294295aff_supp = !list_empty(&(list_entry(cbe_spu_info[0].spus.next,296struct spu, cbe_list))->aff_list);297298if (!aff_supp)299return ERR_PTR(-EINVAL);300301if (flags & SPU_CREATE_GANG)302return ERR_PTR(-EINVAL);303304if (flags & SPU_CREATE_AFFINITY_MEM &&305gang->aff_ref_ctx &&306gang->aff_ref_ctx->flags & SPU_CREATE_AFFINITY_MEM)307return ERR_PTR(-EEXIST);308309if (gang->aff_flags & AFF_MERGED)310return ERR_PTR(-EBUSY);311312neighbor = NULL;313if (flags & SPU_CREATE_AFFINITY_SPU) {314if (!filp || filp->f_op != &spufs_context_fops)315return ERR_PTR(-EINVAL);316317neighbor = get_spu_context(318SPUFS_I(file_inode(filp))->i_ctx);319320if (!list_empty(&neighbor->aff_list) && !(neighbor->aff_head) &&321!list_is_last(&neighbor->aff_list, &gang->aff_list_head) &&322!list_entry(neighbor->aff_list.next, struct spu_context,323aff_list)->aff_head) {324err = ERR_PTR(-EEXIST);325goto out_put_neighbor;326}327328if (gang != neighbor->gang) {329err = ERR_PTR(-EINVAL);330goto out_put_neighbor;331}332333count = 1;334list_for_each_entry(tmp, &gang->aff_list_head, aff_list)335count++;336if (list_empty(&neighbor->aff_list))337count++;338339for (node = 0; node < MAX_NUMNODES; node++) {340if ((cbe_spu_info[node].n_spus - atomic_read(341&cbe_spu_info[node].reserved_spus)) >= count)342break;343}344345if (node == MAX_NUMNODES) {346err = ERR_PTR(-EEXIST);347goto out_put_neighbor;348}349}350351return neighbor;352353out_put_neighbor:354put_spu_context(neighbor);355return err;356}357358static void359spufs_set_affinity(unsigned int flags, struct spu_context *ctx,360struct spu_context *neighbor)361{362if (flags & SPU_CREATE_AFFINITY_MEM)363ctx->gang->aff_ref_ctx = ctx;364365if (flags & SPU_CREATE_AFFINITY_SPU) {366if (list_empty(&neighbor->aff_list)) {367list_add_tail(&neighbor->aff_list,368&ctx->gang->aff_list_head);369neighbor->aff_head = 1;370}371372if (list_is_last(&neighbor->aff_list, &ctx->gang->aff_list_head)373|| list_entry(neighbor->aff_list.next, struct spu_context,374aff_list)->aff_head) {375list_add(&ctx->aff_list, &neighbor->aff_list);376} else {377list_add_tail(&ctx->aff_list, &neighbor->aff_list);378if (neighbor->aff_head) {379neighbor->aff_head = 0;380ctx->aff_head = 1;381}382}383384if (!ctx->gang->aff_ref_ctx)385ctx->gang->aff_ref_ctx = ctx;386}387}388389static int390spufs_create_context(struct inode *inode, struct dentry *dentry,391struct vfsmount *mnt, int flags, umode_t mode,392struct file *aff_filp)393{394int ret;395int affinity;396struct spu_gang *gang = SPUFS_I(inode)->i_gang;397struct spu_context *neighbor;398struct path path = {.mnt = mnt, .dentry = dentry};399400if ((flags & SPU_CREATE_NOSCHED) &&401!capable(CAP_SYS_NICE))402return -EPERM;403404if ((flags & (SPU_CREATE_NOSCHED | SPU_CREATE_ISOLATE))405== SPU_CREATE_ISOLATE)406return -EINVAL;407408if ((flags & SPU_CREATE_ISOLATE) && !isolated_loader)409return -ENODEV;410411if (gang) {412if (!gang->alive)413return -ENOENT;414gang->alive++;415}416417neighbor = NULL;418affinity = flags & (SPU_CREATE_AFFINITY_MEM | SPU_CREATE_AFFINITY_SPU);419if (affinity) {420if (!gang)421return -EINVAL;422mutex_lock(&gang->aff_mutex);423neighbor = spufs_assert_affinity(flags, gang, aff_filp);424if (IS_ERR(neighbor)) {425ret = PTR_ERR(neighbor);426goto out_aff_unlock;427}428}429430ret = spufs_mkdir(inode, dentry, flags, mode & 0777);431if (ret) {432if (neighbor)433put_spu_context(neighbor);434goto out_aff_unlock;435}436437if (affinity) {438spufs_set_affinity(flags, SPUFS_I(d_inode(dentry))->i_ctx,439neighbor);440if (neighbor)441put_spu_context(neighbor);442}443444ret = spufs_context_open(&path);445if (ret < 0)446spufs_rmdir(inode, dentry);447448out_aff_unlock:449if (affinity)450mutex_unlock(&gang->aff_mutex);451if (ret && gang)452gang->alive--; // can't reach 0453return ret;454}455456static int457spufs_mkgang(struct inode *dir, struct dentry *dentry, umode_t mode)458{459int ret;460struct inode *inode;461struct spu_gang *gang;462463ret = -ENOSPC;464inode = spufs_new_inode(dir->i_sb, mode | S_IFDIR);465if (!inode)466goto out;467468ret = 0;469inode_init_owner(&nop_mnt_idmap, inode, dir, mode | S_IFDIR);470gang = alloc_spu_gang();471SPUFS_I(inode)->i_ctx = NULL;472SPUFS_I(inode)->i_gang = gang;473if (!gang) {474ret = -ENOMEM;475goto out_iput;476}477478inode->i_op = &simple_dir_inode_operations;479inode->i_fop = &simple_dir_operations;480481d_instantiate(dentry, inode);482dget(dentry);483inc_nlink(dir);484inc_nlink(d_inode(dentry));485return ret;486487out_iput:488iput(inode);489out:490return ret;491}492493static int spufs_gang_close(struct inode *inode, struct file *file)494{495unuse_gang(file->f_path.dentry);496return dcache_dir_close(inode, file);497}498499static const struct file_operations spufs_gang_fops = {500.open = dcache_dir_open,501.release = spufs_gang_close,502.llseek = dcache_dir_lseek,503.read = generic_read_dir,504.iterate_shared = dcache_readdir,505.fsync = noop_fsync,506};507508static int spufs_gang_open(const struct path *path)509{510int ret;511struct file *filp;512513ret = get_unused_fd_flags(0);514if (ret < 0)515return ret;516517/*518* get references for dget and mntget, will be released519* in error path of *_open().520*/521filp = dentry_open(path, O_RDONLY, current_cred());522if (IS_ERR(filp)) {523put_unused_fd(ret);524return PTR_ERR(filp);525}526527filp->f_op = &spufs_gang_fops;528fd_install(ret, filp);529return ret;530}531532static int spufs_create_gang(struct inode *inode,533struct dentry *dentry,534struct vfsmount *mnt, umode_t mode)535{536struct path path = {.mnt = mnt, .dentry = dentry};537int ret;538539ret = spufs_mkgang(inode, dentry, mode & 0777);540if (!ret) {541ret = spufs_gang_open(&path);542if (ret < 0)543unuse_gang(dentry);544}545return ret;546}547548549static struct file_system_type spufs_type;550551long spufs_create(const struct path *path, struct dentry *dentry,552unsigned int flags, umode_t mode, struct file *filp)553{554struct inode *dir = d_inode(path->dentry);555int ret;556557/* check if we are on spufs */558if (path->dentry->d_sb->s_type != &spufs_type)559return -EINVAL;560561/* don't accept undefined flags */562if (flags & (~SPU_CREATE_FLAG_ALL))563return -EINVAL;564565/* only threads can be underneath a gang */566if (path->dentry != path->dentry->d_sb->s_root)567if ((flags & SPU_CREATE_GANG) || !SPUFS_I(dir)->i_gang)568return -EINVAL;569570mode &= ~current_umask();571572if (flags & SPU_CREATE_GANG)573ret = spufs_create_gang(dir, dentry, path->mnt, mode);574else575ret = spufs_create_context(dir, dentry, path->mnt, flags, mode,576filp);577if (ret >= 0)578fsnotify_mkdir(dir, dentry);579580return ret;581}582583/* File system initialization */584struct spufs_fs_context {585kuid_t uid;586kgid_t gid;587umode_t mode;588};589590enum {591Opt_uid, Opt_gid, Opt_mode, Opt_debug,592};593594static const struct fs_parameter_spec spufs_fs_parameters[] = {595fsparam_u32 ("gid", Opt_gid),596fsparam_u32oct ("mode", Opt_mode),597fsparam_u32 ("uid", Opt_uid),598fsparam_flag ("debug", Opt_debug),599{}600};601602static int spufs_show_options(struct seq_file *m, struct dentry *root)603{604struct spufs_sb_info *sbi = spufs_get_sb_info(root->d_sb);605struct inode *inode = root->d_inode;606607if (!uid_eq(inode->i_uid, GLOBAL_ROOT_UID))608seq_printf(m, ",uid=%u",609from_kuid_munged(&init_user_ns, inode->i_uid));610if (!gid_eq(inode->i_gid, GLOBAL_ROOT_GID))611seq_printf(m, ",gid=%u",612from_kgid_munged(&init_user_ns, inode->i_gid));613if ((inode->i_mode & S_IALLUGO) != 0775)614seq_printf(m, ",mode=%o", inode->i_mode);615if (sbi->debug)616seq_puts(m, ",debug");617return 0;618}619620static int spufs_parse_param(struct fs_context *fc, struct fs_parameter *param)621{622struct spufs_fs_context *ctx = fc->fs_private;623struct spufs_sb_info *sbi = fc->s_fs_info;624struct fs_parse_result result;625kuid_t uid;626kgid_t gid;627int opt;628629opt = fs_parse(fc, spufs_fs_parameters, param, &result);630if (opt < 0)631return opt;632633switch (opt) {634case Opt_uid:635uid = make_kuid(current_user_ns(), result.uint_32);636if (!uid_valid(uid))637return invalf(fc, "Unknown uid");638ctx->uid = uid;639break;640case Opt_gid:641gid = make_kgid(current_user_ns(), result.uint_32);642if (!gid_valid(gid))643return invalf(fc, "Unknown gid");644ctx->gid = gid;645break;646case Opt_mode:647ctx->mode = result.uint_32 & S_IALLUGO;648break;649case Opt_debug:650sbi->debug = true;651break;652}653654return 0;655}656657static void spufs_exit_isolated_loader(void)658{659free_pages((unsigned long) isolated_loader,660get_order(isolated_loader_size));661}662663static void __init664spufs_init_isolated_loader(void)665{666struct device_node *dn;667const char *loader;668int size;669670dn = of_find_node_by_path("/spu-isolation");671if (!dn)672return;673674loader = of_get_property(dn, "loader", &size);675of_node_put(dn);676if (!loader)677return;678679/* the loader must be align on a 16 byte boundary */680isolated_loader = (char *)__get_free_pages(GFP_KERNEL, get_order(size));681if (!isolated_loader)682return;683684isolated_loader_size = size;685memcpy(isolated_loader, loader, size);686printk(KERN_INFO "spufs: SPU isolation mode enabled\n");687}688689static int spufs_create_root(struct super_block *sb, struct fs_context *fc)690{691struct spufs_fs_context *ctx = fc->fs_private;692struct inode *inode;693694if (!spu_management_ops)695return -ENODEV;696697inode = spufs_new_inode(sb, S_IFDIR | ctx->mode);698if (!inode)699return -ENOMEM;700701inode->i_uid = ctx->uid;702inode->i_gid = ctx->gid;703inode->i_op = &simple_dir_inode_operations;704inode->i_fop = &simple_dir_operations;705SPUFS_I(inode)->i_ctx = NULL;706inc_nlink(inode);707708sb->s_root = d_make_root(inode);709if (!sb->s_root)710return -ENOMEM;711return 0;712}713714static const struct super_operations spufs_ops = {715.alloc_inode = spufs_alloc_inode,716.free_inode = spufs_free_inode,717.statfs = simple_statfs,718.evict_inode = spufs_evict_inode,719.show_options = spufs_show_options,720};721722static int spufs_fill_super(struct super_block *sb, struct fs_context *fc)723{724sb->s_maxbytes = MAX_LFS_FILESIZE;725sb->s_blocksize = PAGE_SIZE;726sb->s_blocksize_bits = PAGE_SHIFT;727sb->s_magic = SPUFS_MAGIC;728sb->s_op = &spufs_ops;729730return spufs_create_root(sb, fc);731}732733static int spufs_get_tree(struct fs_context *fc)734{735return get_tree_single(fc, spufs_fill_super);736}737738static void spufs_free_fc(struct fs_context *fc)739{740kfree(fc->s_fs_info);741}742743static const struct fs_context_operations spufs_context_ops = {744.free = spufs_free_fc,745.parse_param = spufs_parse_param,746.get_tree = spufs_get_tree,747};748749static int spufs_init_fs_context(struct fs_context *fc)750{751struct spufs_fs_context *ctx;752struct spufs_sb_info *sbi;753754ctx = kzalloc(sizeof(struct spufs_fs_context), GFP_KERNEL);755if (!ctx)756goto nomem;757758sbi = kzalloc(sizeof(struct spufs_sb_info), GFP_KERNEL);759if (!sbi)760goto nomem_ctx;761762ctx->uid = current_uid();763ctx->gid = current_gid();764ctx->mode = 0755;765766fc->fs_private = ctx;767fc->s_fs_info = sbi;768fc->ops = &spufs_context_ops;769return 0;770771nomem_ctx:772kfree(ctx);773nomem:774return -ENOMEM;775}776777static struct file_system_type spufs_type = {778.owner = THIS_MODULE,779.name = "spufs",780.init_fs_context = spufs_init_fs_context,781.parameters = spufs_fs_parameters,782.kill_sb = kill_litter_super,783};784MODULE_ALIAS_FS("spufs");785786static int __init spufs_init(void)787{788int ret;789790ret = -ENODEV;791if (!spu_management_ops)792goto out;793794ret = -ENOMEM;795spufs_inode_cache = kmem_cache_create("spufs_inode_cache",796sizeof(struct spufs_inode_info), 0,797SLAB_HWCACHE_ALIGN|SLAB_ACCOUNT, spufs_init_once);798799if (!spufs_inode_cache)800goto out;801ret = spu_sched_init();802if (ret)803goto out_cache;804ret = register_spu_syscalls(&spufs_calls);805if (ret)806goto out_sched;807ret = register_filesystem(&spufs_type);808if (ret)809goto out_syscalls;810811spufs_init_isolated_loader();812813return 0;814815out_syscalls:816unregister_spu_syscalls(&spufs_calls);817out_sched:818spu_sched_exit();819out_cache:820kmem_cache_destroy(spufs_inode_cache);821out:822return ret;823}824module_init(spufs_init);825826static void __exit spufs_exit(void)827{828spu_sched_exit();829spufs_exit_isolated_loader();830unregister_spu_syscalls(&spufs_calls);831unregister_filesystem(&spufs_type);832kmem_cache_destroy(spufs_inode_cache);833}834module_exit(spufs_exit);835836MODULE_DESCRIPTION("SPU file system");837MODULE_LICENSE("GPL");838MODULE_AUTHOR("Arnd Bergmann <[email protected]>");839840841842