blob: 31f8e3e2eaa2f22bfc247ef57f31e65a070e6bbc [file] [log] [blame]
/*
* Copyright (C) 2008-2013 Imagination Technologies Ltd.
* Licensed under the GPL
*
* Based on hostfs for UML.
*
*/
#include <linux/fs.h>
#include <linux/magic.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/pagemap.h>
#include <linux/seq_file.h>
#include <linux/mount.h>
#include <linux/slab.h>
#include <asm/da.h>
#include <asm/hwthread.h>
#include "imgdafs.h"
static int fscall(int in_system_call, int in_arg1, int in_arg2, int in_arg3,
int in_arg4, int in_arg5, int *fserrno)
{
register int arg1 asm("D1Ar1") = in_arg1;
register int arg2 asm("D0Ar2") = in_arg2;
register int arg3 asm("D1Ar3") = in_arg3;
register int arg4 asm("D0Ar4") = in_arg4;
register int arg5 asm("D1Ar5") = in_arg5;
register int system_call asm("D0Ar6") = in_system_call;
register int result asm("D0Re0");
register int errno asm("D1Re0");
asm volatile (
"MSETL [A0StP++], %7,%5,%3\n\t"
"ADD A0StP, A0StP, #8\n\t"
"SWITCH #0x0C00208\n\t"
"GETL %0, %1, [A0StP+#-8]\n\t"
"SUB A0StP, A0StP, #(4*6)+8\n\t"
: "=r" (result),
"=r" (errno)
: "r" (arg1),
"r" (arg2),
"r" (arg3),
"r" (arg4),
"r" (arg5),
"r" (system_call)
: "memory");
if (fserrno)
*fserrno = errno;
return result;
}
struct dafs_inode_info {
int fd;
fmode_t mode;
struct inode vfs_inode;
};
static inline struct dafs_inode_info *DAFS_I(struct inode *inode)
{
return container_of(inode, struct dafs_inode_info, vfs_inode);
}
#define FILE_DAFS_I(file) DAFS_I(file_inode(file))
static int dafs_d_delete(const struct dentry *dentry)
{
return 1;
}
static const struct dentry_operations dafs_dentry_ops = {
.d_delete = dafs_d_delete,
};
static const struct inode_operations dafs_dir_iops;
static char *__dentry_name(struct dentry *dentry, char *name)
{
char *p = dentry_path_raw(dentry, name, PATH_MAX);
char *root;
size_t len;
root = dentry->d_sb->s_fs_info;
len = strlen(root);
if (IS_ERR(p)) {
__putname(name);
return NULL;
}
strlcpy(name, root, PATH_MAX);
if (len > p - name) {
__putname(name);
return NULL;
}
if (p > name + len) {
char *s = name + len;
while ((*s++ = *p++) != '\0')
;
}
return name;
}
static char *dentry_name(struct dentry *dentry)
{
char *name = __getname();
if (!name)
return NULL;
return __dentry_name(dentry, name);
}
static int stat_file(const char *path, struct da_stat *p, int fd)
{
int ret;
int fserrno;
memset(p, 0, sizeof(*p));
if (fd >= 0) {
ret = fscall(DA_OP_FSTAT, fd, (int)p, 0, 0, 0, &fserrno);
if (ret < 0) {
/* Some versions of Codescape do not fill out errno. */
if (ret < 0 && fserrno == 0)
fserrno = ENOENT;
return -fserrno;
}
} else {
ret = fscall(DA_OP_STAT, (int)path, (int)p, strlen(path), 0, 0,
&fserrno);
if (ret < 0) {
/* Some versions of Codescape do not fill out errno. */
if (ret < 0 && fserrno == 0)
fserrno = ENOENT;
return -fserrno;
}
}
return 0;
}
static struct inode *dafs_iget(struct super_block *sb)
{
struct inode *inode = new_inode(sb);
if (!inode)
return ERR_PTR(-ENOMEM);
return inode;
}
static struct inode *dafs_alloc_inode(struct super_block *sb)
{
struct dafs_inode_info *hi;
hi = kmalloc(sizeof(*hi), GFP_KERNEL);
if (hi == NULL)
return NULL;
hi->fd = -1;
hi->mode = 0;
inode_init_once(&hi->vfs_inode);
return &hi->vfs_inode;
}
static void close_file(void *stream)
{
int fd = *((int *) stream);
fscall(DA_OP_CLOSE, fd, 0, 0, 0, 0, NULL);
}
static void dafs_evict_inode(struct inode *inode)
{
truncate_inode_pages(&inode->i_data, 0);
clear_inode(inode);
if (DAFS_I(inode)->fd != -1) {
close_file(&DAFS_I(inode)->fd);
DAFS_I(inode)->fd = -1;
}
}
static void dafs_i_callback(struct rcu_head *head)
{
struct inode *inode = container_of(head, struct inode, i_rcu);
kfree(DAFS_I(inode));
}
static void dafs_destroy_inode(struct inode *inode)
{
call_rcu(&inode->i_rcu, dafs_i_callback);
}
static const struct super_operations dafs_sbops = {
.alloc_inode = dafs_alloc_inode,
.drop_inode = generic_delete_inode,
.evict_inode = dafs_evict_inode,
.destroy_inode = dafs_destroy_inode,
};
static int open_dir(char *path, struct da_finddata *finddata, int *fserrno)
{
int len = strlen(path);
char *buf;
int ret;
buf = kmalloc(len + 3, GFP_KERNEL);
if (!buf) {
*fserrno = ENOMEM;
return -1;
}
strcpy(buf, path);
if (buf[len - 1] != '/')
strcat(buf, "/*");
else
strcat(buf, "*");
ret = fscall(DA_OP_FINDFIRST, (int)buf, (int)finddata, 0, 0, 0,
fserrno);
kfree(buf);
return ret;
}
static void close_dir(int handle)
{
fscall(DA_OP_FINDCLOSE, handle, 0, 0, 0, 0, NULL);
}
static int read_dir(int handle, struct da_finddata *finddata)
{
return fscall(DA_OP_FINDNEXT, handle, (int)finddata, 0, 0, 0, NULL);
}
static int dafs_readdir(struct file *file, void *ent, filldir_t filldir)
{
struct inode *inode = file_inode(file);
struct super_block *sb = inode->i_sb;
char *name;
int handle;
int fserrno;
unsigned long long next, ino;
int error = 0;
struct da_finddata finddata;
name = dentry_name(file->f_path.dentry);
if (name == NULL)
return -ENOMEM;
handle = open_dir(name, &finddata, &fserrno);
__putname(name);
if (handle == -1)
return -fserrno;
next = 1;
if (file->f_pos == 0) {
error = (*filldir)(ent, ".", file->f_pos + 1,
file->f_pos, inode->i_ino,
DT_DIR);
if (error < 0)
goto out;
file->f_pos++;
}
while (1) {
error = read_dir(handle, &finddata);
if (error)
break;
if (next >= file->f_pos) {
size_t len = strlen(finddata.name);
ino = iunique(sb, 100);
error = (*filldir)(ent, finddata.name, len,
file->f_pos, ino,
(finddata.attrib & _A_SUBDIR) ?
DT_DIR : DT_REG);
if (error)
break;
file->f_pos++;
}
next++;
}
out:
close_dir(handle);
return 0;
}
static int dafs_file_open(struct inode *ino, struct file *file)
{
static DEFINE_MUTEX(open_mutex);
char *name;
fmode_t mode, fmode;
int flags = 0, r = 0, w = 0, fd;
int cpu;
fmode = file->f_mode & (FMODE_READ | FMODE_WRITE);
if ((fmode & DAFS_I(ino)->mode) == fmode)
return 0;
mode = ino->i_mode & (DA_S_IWUSR | DA_S_IRUSR);
mode |= DAFS_I(ino)->mode;
DAFS_I(ino)->mode |= fmode;
if (DAFS_I(ino)->mode & FMODE_READ)
r = 1;
if (DAFS_I(ino)->mode & FMODE_WRITE) {
w = 1;
r = 1;
}
retry:
if (r && !w)
flags |= DA_O_RDONLY;
else if (!r && w)
flags |= DA_O_WRONLY;
else if (r && w)
flags |= DA_O_RDWR;
if (file->f_flags & O_CREAT)
flags |= DA_O_CREAT;
if (file->f_flags & O_TRUNC)
flags |= DA_O_TRUNC;
/*
* Set the affinity for this file handle to all CPUs. If we
* don't do this then, if the process that opened the file
* migrates to a different cpu, the FileServer will not accept
* the file handle.
*/
for_each_possible_cpu(cpu) {
u8 hwthread = cpu_2_hwthread_id[cpu];
flags |= (1 << (DA_O_AFFINITY_SHIFT + hwthread));
}
name = dentry_name(file->f_path.dentry);
if (name == NULL)
return -ENOMEM;
fd = fscall(DA_OP_OPEN, (int)name, flags, mode, strlen(name), 0, NULL);
__putname(name);
if (fd < 0)
return fd;
mutex_lock(&open_mutex);
/* somebody else had handled it first? */
if ((mode & DAFS_I(ino)->mode) == mode) {
mutex_unlock(&open_mutex);
return 0;
}
if ((mode | DAFS_I(ino)->mode) != mode) {
mode |= DAFS_I(ino)->mode;
mutex_unlock(&open_mutex);
close_file(&fd);
goto retry;
}
DAFS_I(ino)->fd = fd;
DAFS_I(ino)->mode = mode;
mutex_unlock(&open_mutex);
return 0;
}
static const struct file_operations dafs_file_fops = {
.llseek = generic_file_llseek,
.read = do_sync_read,
.splice_read = generic_file_splice_read,
.aio_read = generic_file_aio_read,
.aio_write = generic_file_aio_write,
.write = do_sync_write,
.mmap = generic_file_mmap,
.open = dafs_file_open,
.release = NULL,
};
static const struct file_operations dafs_dir_fops = {
.llseek = generic_file_llseek,
.readdir = dafs_readdir,
.read = generic_read_dir,
};
static int read_file(int fd, unsigned long long *offset, const char *buf,
int len)
{
int n;
int fserrno;
n = fscall(DA_OP_PREAD, fd, (int)buf, len, (int)*offset, 0, &fserrno);
if (n < 0)
return -fserrno;
return n;
}
static int write_file(int fd, unsigned long long *offset, const char *buf,
int len)
{
int n;
int fserrno;
n = fscall(DA_OP_PWRITE, fd, (int)buf, len, (int)*offset, 0, &fserrno);
if (n < 0)
return -fserrno;
return n;
}
static int dafs_writepage(struct page *page, struct writeback_control *wbc)
{
struct address_space *mapping = page->mapping;
struct inode *inode = mapping->host;
char *buffer;
unsigned long long base;
int count = PAGE_CACHE_SIZE;
int end_index = inode->i_size >> PAGE_CACHE_SHIFT;
int err;
if (page->index >= end_index)
count = inode->i_size & (PAGE_CACHE_SIZE-1);
buffer = kmap(page);
base = ((unsigned long long) page->index) << PAGE_CACHE_SHIFT;
err = write_file(DAFS_I(inode)->fd, &base, buffer, count);
if (err != count) {
ClearPageUptodate(page);
goto out;
}
if (base > inode->i_size)
inode->i_size = base;
if (PageError(page))
ClearPageError(page);
err = 0;
out:
kunmap(page);
unlock_page(page);
return err;
}
static int dafs_readpage(struct file *file, struct page *page)
{
char *buffer;
long long start;
int err = 0;
start = (long long) page->index << PAGE_CACHE_SHIFT;
buffer = kmap(page);
err = read_file(FILE_DAFS_I(file)->fd, &start, buffer,
PAGE_CACHE_SIZE);
if (err < 0)
goto out;
memset(&buffer[err], 0, PAGE_CACHE_SIZE - err);
flush_dcache_page(page);
SetPageUptodate(page);
if (PageError(page))
ClearPageError(page);
err = 0;
out:
kunmap(page);
unlock_page(page);
return err;
}
static int dafs_write_begin(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned flags,
struct page **pagep, void **fsdata)
{
pgoff_t index = pos >> PAGE_CACHE_SHIFT;
*pagep = grab_cache_page_write_begin(mapping, index, flags);
if (!*pagep)
return -ENOMEM;
return 0;
}
static int dafs_write_end(struct file *file, struct address_space *mapping,
loff_t pos, unsigned len, unsigned copied,
struct page *page, void *fsdata)
{
struct inode *inode = mapping->host;
void *buffer;
unsigned from = pos & (PAGE_CACHE_SIZE - 1);
int err;
buffer = kmap(page);
err = write_file(FILE_DAFS_I(file)->fd, &pos, buffer + from, copied);
kunmap(page);
if (!PageUptodate(page) && err == PAGE_CACHE_SIZE)
SetPageUptodate(page);
/*
* If err > 0, write_file has added err to pos, so we are comparing
* i_size against the last byte written.
*/
if (err > 0 && (pos > inode->i_size))
inode->i_size = pos;
unlock_page(page);
page_cache_release(page);
return err;
}
static const struct address_space_operations dafs_aops = {
.writepage = dafs_writepage,
.readpage = dafs_readpage,
.set_page_dirty = __set_page_dirty_nobuffers,
.write_begin = dafs_write_begin,
.write_end = dafs_write_end,
};
static int read_name(struct inode *ino, char *name)
{
dev_t rdev;
struct da_stat st;
int err = stat_file(name, &st, -1);
if (err)
return err;
/* No valid maj and min from DA.*/
rdev = MKDEV(0, 0);
switch (st.st_mode & S_IFMT) {
case S_IFDIR:
ino->i_op = &dafs_dir_iops;
ino->i_fop = &dafs_dir_fops;
break;
case S_IFCHR:
case S_IFBLK:
case S_IFIFO:
case S_IFSOCK:
init_special_inode(ino, st.st_mode & S_IFMT, rdev);
break;
case S_IFLNK:
default:
ino->i_fop = &dafs_file_fops;
ino->i_mapping->a_ops = &dafs_aops;
}
ino->i_ino = st.st_ino;
ino->i_mode = st.st_mode;
set_nlink(ino, st.st_nlink);
i_uid_write(ino, st.st_uid);
i_gid_write(ino, st.st_gid);
ino->i_atime.tv_sec = st.st_atime;
ino->i_atime.tv_nsec = 0;
ino->i_mtime.tv_sec = st.st_mtime;
ino->i_mtime.tv_nsec = 0;
ino->i_ctime.tv_sec = st.st_ctime;
ino->i_ctime.tv_nsec = 0;
ino->i_size = st.st_size;
ino->i_blocks = st.st_blocks;
return 0;
}
static int dafs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
bool excl)
{
struct inode *inode;
char *name;
int error, fd;
int damode;
int creat_flags = DA_O_TRUNC | DA_O_CREAT | DA_O_WRONLY;
int cpu;
inode = dafs_iget(dir->i_sb);
if (IS_ERR(inode)) {
error = PTR_ERR(inode);
goto out;
}
damode = mode & (DA_S_IWUSR | DA_S_IRUSR);
error = -ENOMEM;
name = dentry_name(dentry);
if (name == NULL)
goto out_put;
/*
* creat() will only create text mode files on a Windows host
* at present. Replicate the creat() functionality with an
* open() call, which always creates binary files. Set the
* affinity to all hardware threads.
*/
for_each_possible_cpu(cpu) {
u8 hwthread = cpu_2_hwthread_id[cpu];
creat_flags |= (1 << (DA_O_AFFINITY_SHIFT + hwthread));
}
fd = fscall(DA_OP_OPEN, (int)name, creat_flags, damode, strlen(name),
0, NULL);
if (fd < 0)
error = fd;
else
error = read_name(inode, name);
kfree(name);
if (error)
goto out_put;
DAFS_I(inode)->fd = fd;
DAFS_I(inode)->mode = FMODE_READ | FMODE_WRITE;
d_instantiate(dentry, inode);
return 0;
out_put:
iput(inode);
out:
return error;
}
static struct dentry *dafs_lookup(struct inode *ino, struct dentry *dentry,
unsigned int flags)
{
struct inode *inode;
char *name;
int err;
inode = dafs_iget(ino->i_sb);
if (IS_ERR(inode)) {
err = PTR_ERR(inode);
goto out;
}
err = -ENOMEM;
name = dentry_name(dentry);
if (name == NULL)
goto out_put;
err = read_name(inode, name);
__putname(name);
if (err == -ENOENT) {
iput(inode);
inode = NULL;
} else if (err)
goto out_put;
d_add(dentry, inode);
return NULL;
out_put:
iput(inode);
out:
return ERR_PTR(err);
}
static int dafs_link(struct dentry *to, struct inode *ino, struct dentry *from)
{
char *from_name, *to_name;
int err;
from_name = dentry_name(from);
if (from_name == NULL)
return -ENOMEM;
to_name = dentry_name(to);
if (to_name == NULL) {
__putname(from_name);
return -ENOMEM;
}
err = -EINVAL;
__putname(from_name);
__putname(to_name);
return err;
}
static int dafs_unlink(struct inode *ino, struct dentry *dentry)
{
char *file;
int err;
int fserrno;
file = dentry_name(dentry);
if (file == NULL)
return -ENOMEM;
err = fscall(DA_OP_UNLINK, (int)file, 0, 0, 0, 0, &fserrno);
__putname(file);
if (err)
return -fserrno;
return 0;
}
static int do_mkdir(const char *file, int mode)
{
int err;
int fserrno;
err = fscall(DA_OP_MKDIR, (int)file, mode, strlen(file), 0, 0,
&fserrno);
if (err)
return -fserrno;
return 0;
}
static int dafs_mkdir(struct inode *ino, struct dentry *dentry, umode_t mode)
{
char *file;
int err;
file = dentry_name(dentry);
if (file == NULL)
return -ENOMEM;
err = do_mkdir(file, mode);
__putname(file);
return err;
}
static int do_rmdir(const char *file)
{
int err;
int fserrno;
err = fscall(DA_OP_RMDIR, (int)file, strlen(file), 0, 0, 0, &fserrno);
if (err)
return -fserrno;
return 0;
}
static int dafs_rmdir(struct inode *ino, struct dentry *dentry)
{
char *file;
int err;
file = dentry_name(dentry);
if (file == NULL)
return -ENOMEM;
err = do_rmdir(file);
__putname(file);
return err;
}
static int dafs_rename(struct inode *from_ino, struct dentry *from,
struct inode *to_ino, struct dentry *to)
{
char *from_name, *to_name;
int err;
from_name = dentry_name(from);
if (from_name == NULL)
return -ENOMEM;
to_name = dentry_name(to);
if (to_name == NULL) {
__putname(from_name);
return -ENOMEM;
}
err = -EINVAL;
__putname(from_name);
__putname(to_name);
return err;
}
static const struct inode_operations dafs_dir_iops = {
.create = dafs_create,
.lookup = dafs_lookup,
.link = dafs_link,
.unlink = dafs_unlink,
.mkdir = dafs_mkdir,
.rmdir = dafs_rmdir,
.rename = dafs_rename,
};
static char *host_root_path = ".";
static int dafs_fill_sb_common(struct super_block *sb, void *d, int silent)
{
struct inode *root_inode;
int err;
sb->s_blocksize = 1024;
sb->s_blocksize_bits = 10;
sb->s_magic = IMGDAFS_SUPER_MAGIC;
sb->s_op = &dafs_sbops;
sb->s_d_op = &dafs_dentry_ops;
sb->s_maxbytes = MAX_LFS_FILESIZE;
err = -ENOMEM;
root_inode = new_inode(sb);
if (!root_inode)
goto out;
err = read_name(root_inode, host_root_path);
if (err)
goto out_put;
err = -ENOMEM;
sb->s_fs_info = host_root_path;
sb->s_root = d_make_root(root_inode);
if (sb->s_root == NULL)
goto out;
return 0;
out_put:
iput(root_inode);
out:
return err;
}
static struct dentry *dafs_read_sb(struct file_system_type *type,
int flags, const char *dev_name,
void *data)
{
if (!metag_da_enabled())
return ERR_PTR(-ENODEV);
return mount_nodev(type, flags, data, dafs_fill_sb_common);
}
static struct file_system_type dafs_type = {
.owner = THIS_MODULE,
.name = "imgdafs",
.mount = dafs_read_sb,
.kill_sb = kill_anon_super,
.fs_flags = 0,
};
MODULE_ALIAS_FS("imgdafs");
static int __init init_dafs(void)
{
return register_filesystem(&dafs_type);
}
static void __exit exit_dafs(void)
{
unregister_filesystem(&dafs_type);
}
module_init(init_dafs)
module_exit(exit_dafs)
MODULE_LICENSE("GPL");