All of lore.kernel.org
 help / color / mirror / Atom feed
From: James Hogan <james.hogan@imgtec.com>
To: James Hogan <james.hogan@imgtec.com>
Cc: linux-kernel@vger.kernel.org, linux-arch@vger.kernel.org,
	Arnd Bergmann <arnd@arndb.de>,
	Alexander Viro <viro@zeniv.linux.org.uk>,
	Andrew Morton <akpm@linux-foundation.org>,
	Mauro Carvalho Chehab <mchehab@redhat.com>,
	Cesar Eduardo Barros <cesarb@cesarb.net>,
	Joe Perches <joe@perches.com>,
	"David S. Miller" <davem@davemloft.net>,
	linux-fsdevel@vger.kernel.org
Subject: Re: [PATCH v4 43/43] fs: imgdafs: Add IMG DAFS filesystem for metag
Date: Wed, 6 Feb 2013 15:02:03 +0000	[thread overview]
Message-ID: <5112706B.3040402@imgtec.com> (raw)
In-Reply-To: <1359468915-8760-6-git-send-email-james.hogan@imgtec.com>

Ping.

Any chance of an Ack from somebody on this patch? I'm currently holding
off including it in the arch/metag tree.

Thanks
James

On 29/01/13 14:15, James Hogan wrote:
> Add the IMG Debug Adapter File System (DAFS) for metag, which uses
> SWITCH operations to communicate with a file server on a host computer
> via a JTAG debug adapter.
> 
> Signed-off-by: James Hogan <james.hogan@imgtec.com>
> Cc: Alexander Viro <viro@zeniv.linux.org.uk>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Mauro Carvalho Chehab <mchehab@redhat.com>
> Cc: Cesar Eduardo Barros <cesarb@cesarb.net>
> Cc: Joe Perches <joe@perches.com>
> Cc: "David S. Miller" <davem@davemloft.net>
> Cc: linux-fsdevel@vger.kernel.org
> ---
> v4:
>   * clean up fscall similarly to chancall in tty/metag_da
> 
> v2:
>   * fixed fserrno problem (Al Viro)
>   * renamed to imgdafs
> 
>  MAINTAINERS          |    1 +
>  arch/metag/Kconfig   |    1 +
>  fs/Kconfig           |    1 +
>  fs/Makefile          |    1 +
>  fs/imgdafs/Kconfig   |    6 +
>  fs/imgdafs/Makefile  |    7 +
>  fs/imgdafs/imgdafs.h |   80 +++++
>  fs/imgdafs/inode.c   |  843 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  8 files changed, 940 insertions(+), 0 deletions(-)
>  create mode 100644 fs/imgdafs/Kconfig
>  create mode 100644 fs/imgdafs/Makefile
>  create mode 100644 fs/imgdafs/imgdafs.h
>  create mode 100644 fs/imgdafs/inode.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index cb89fe3..6b02aea 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5044,6 +5044,7 @@ F:	drivers/clocksource/metag_generic.c
>  F:	drivers/irqchip/irq-metag.c
>  F:	drivers/irqchip/irq-metag-ext.c
>  F:	drivers/tty/metag_da.c
> +F:	fs/imgdafs/
>  
>  MICROBLAZE ARCHITECTURE
>  M:	Michal Simek <monstr@monstr.eu>
> diff --git a/arch/metag/Kconfig b/arch/metag/Kconfig
> index 9cba2f9..30adc78 100644
> --- a/arch/metag/Kconfig
> +++ b/arch/metag/Kconfig
> @@ -224,6 +224,7 @@ config METAG_DA
>  	  This enables support for services provided by DA JTAG debug adapters,
>  	  such as:
>  	  - communication over DA channels (such as the console driver).
> +	  - use of the DA filesystem.
>  
>  menu "Boot options"
>  
> diff --git a/fs/Kconfig b/fs/Kconfig
> index 780725a..6deefc1 100644
> --- a/fs/Kconfig
> +++ b/fs/Kconfig
> @@ -211,6 +211,7 @@ source "fs/sysv/Kconfig"
>  source "fs/ufs/Kconfig"
>  source "fs/exofs/Kconfig"
>  source "fs/f2fs/Kconfig"
> +source "fs/imgdafs/Kconfig"
>  
>  endif # MISC_FILESYSTEMS
>  
> diff --git a/fs/Makefile b/fs/Makefile
> index 9d53192..b070fca 100644
> --- a/fs/Makefile
> +++ b/fs/Makefile
> @@ -127,3 +127,4 @@ obj-$(CONFIG_F2FS_FS)		+= f2fs/
>  obj-y				+= exofs/ # Multiple modules
>  obj-$(CONFIG_CEPH_FS)		+= ceph/
>  obj-$(CONFIG_PSTORE)		+= pstore/
> +obj-$(CONFIG_IMGDAFS_FS)	+= imgdafs/
> diff --git a/fs/imgdafs/Kconfig b/fs/imgdafs/Kconfig
> new file mode 100644
> index 0000000..5293adb
> --- /dev/null
> +++ b/fs/imgdafs/Kconfig
> @@ -0,0 +1,6 @@
> +config IMGDAFS_FS
> +	bool "Meta DA filesystem support"
> +	depends on METAG_DA
> +	help
> +	  This enables the DA filesystem, which allows Linux to
> +	  to access files on a system attached via a debug adapter.
> diff --git a/fs/imgdafs/Makefile b/fs/imgdafs/Makefile
> new file mode 100644
> index 0000000..169a3c6
> --- /dev/null
> +++ b/fs/imgdafs/Makefile
> @@ -0,0 +1,7 @@
> +#
> +# Makefile for DAfs the Debug Adapter filesystem.
> +#
> +
> +obj-$(CONFIG_IMGDAFS_FS) += imgdafs.o
> +
> +imgdafs-objs := inode.o
> diff --git a/fs/imgdafs/imgdafs.h b/fs/imgdafs/imgdafs.h
> new file mode 100644
> index 0000000..6c1da13
> --- /dev/null
> +++ b/fs/imgdafs/imgdafs.h
> @@ -0,0 +1,80 @@
> +#ifndef _IMGDAFS_H_
> +#define _IMGDAFS_H_
> +
> +#define DA_OP_OPEN 0
> +#define DA_OP_CREAT 1
> +#define DA_OP_READ 2
> +#define DA_OP_WRITE 3
> +#define DA_OP_CLOSE 4
> +#define DA_OP_LINK 5
> +#define DA_OP_LSEEK 6
> +#define DA_OP_UNLINK 7
> +#define DA_OP_ISATTY 8
> +#define DA_OP_FCNTL 9
> +#define DA_OP_STAT 10
> +#define DA_OP_FSTAT 11
> +#define DA_OP_GETCWD 12
> +#define DA_OP_CHDIR 13
> +#define DA_OP_MKDIR 14
> +#define DA_OP_RMDIR 15
> +#define DA_OP_FINDFIRST 16
> +#define DA_OP_FINDNEXT 17
> +#define DA_OP_FINDCLOSE 18
> +#define DA_OP_CHMOD 19
> +#define DA_OP_PREAD 20
> +#define DA_OP_PWRITE 21
> +
> +#define OS_TYPE_FILE 1
> +#define OS_TYPE_DIR 2
> +#define OS_TYPE_SYMLINK 3
> +#define OS_TYPE_CHARDEV 4
> +#define OS_TYPE_BLOCKDEV 5
> +#define OS_TYPE_FIFO 6
> +#define OS_TYPE_SOCK 7
> +
> +#define DA_O_RDONLY 0
> +#define DA_O_WRONLY 1
> +#define DA_O_RDWR 2
> +#define DA_O_APPEND 8
> +#define DA_O_CREAT 0x0200
> +#define DA_O_TRUNC 0x0400
> +#define DA_O_EXCL 0x0800
> +
> +#define DA_O_AFFINITY_THREAD_0 0x10000
> +#define DA_O_AFFINITY_THREAD_1 0x20000
> +#define DA_O_AFFINITY_THREAD_2 0x40000
> +#define DA_O_AFFINITY_THREAD_3 0x80000
> +#define DA_O_AFFINITY_SHIFT 16
> +
> +#define DA_S_IWUSR	0200	/* 0x80 */
> +#define DA_S_IRUSR	0400	/* 0x100 */
> +
> +struct da_stat {
> +	short st_dev;
> +	unsigned short st_ino;
> +	unsigned st_mode;
> +	unsigned short st_nlink;
> +	unsigned short st_uid;
> +	unsigned short st_gid;
> +	short st_rdev;
> +	int st_size;
> +	int st_atime;
> +	int st_spare1;
> +	int st_mtime;
> +	int st_spare2;
> +	int st_ctime;
> +	int st_spare3;
> +	int st_blksize;
> +	int st_blocks;
> +	int st_spare4[2];
> +};
> +
> +#define _A_SUBDIR 0x10
> +
> +struct da_finddata {
> +	unsigned long size;
> +	unsigned long attrib;
> +	char name[260];
> +};
> +
> +#endif
> diff --git a/fs/imgdafs/inode.c b/fs/imgdafs/inode.c
> new file mode 100644
> index 0000000..5808d90
> --- /dev/null
> +++ b/fs/imgdafs/inode.c
> @@ -0,0 +1,843 @@
> +/*
> + * Copyright (C) 2008,2009,2010 Imagination Technologies Ltd.
> + * Licensed under the GPL
> + *
> + * Based on hostfs for UML.
> + *
> + */
> +
> +#include <linux/fs.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;
> +	int 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)->f_path.dentry->d_inode)
> +
> +static int dafs_d_delete(const struct dentry *dentry)
> +{
> +	return 1;
> +}
> +
> +static const struct dentry_operations dafs_dentry_ops = {
> +	.d_delete		= dafs_d_delete,
> +};
> +
> +#define DAFS_SUPER_MAGIC 0xdadadaf5
> +
> +static const struct inode_operations dafs_iops;
> +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); /* will unlock */
> +}
> +
> +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 = kzalloc(sizeof(*hi), GFP_KERNEL);
> +	if (hi == NULL)
> +		return NULL;
> +
> +	hi->fd = -1;
> +	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[len + 3];
> +
> +	strcpy(buf, path);
> +	if (buf[len - 1] != '/')
> +		strcat(buf, "/*");
> +	else
> +		strcat(buf, "*");
> +
> +	return fscall(DA_OP_FINDFIRST, (int)buf, (int)finddata, 0, 0, 0,
> +		      fserrno);
> +}
> +
> +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->f_path.dentry->d_inode;
> +	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;
> +	int mode, fmode, 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);
> +		ino->i_op = &dafs_iops;
> +		break;
> +
> +	case S_IFLNK:
> +	default:
> +		ino->i_op = &dafs_iops;
> +		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_iops = {
> +	.create		= dafs_create,
> +	.link		= dafs_link,
> +	.unlink		= dafs_unlink,
> +	.mkdir		= dafs_mkdir,
> +	.rmdir		= dafs_rmdir,
> +	.rename		= dafs_rename,
> +};
> +
> +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 = DAFS_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,
> +};
> +
> +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");
> 

WARNING: multiple messages have this Message-ID (diff)
From: James Hogan <james.hogan@imgtec.com>
To: James Hogan <james.hogan@imgtec.com>
Cc: <linux-kernel@vger.kernel.org>, <linux-arch@vger.kernel.org>,
	"Arnd Bergmann" <arnd@arndb.de>,
	Alexander Viro <viro@zeniv.linux.org.uk>,
	"Andrew Morton" <akpm@linux-foundation.org>,
	Mauro Carvalho Chehab <mchehab@redhat.com>,
	Cesar Eduardo Barros <cesarb@cesarb.net>,
	Joe Perches <joe@perches.com>,
	"David S. Miller" <davem@davemloft.net>,
	<linux-fsdevel@vger.kernel.org>
Subject: Re: [PATCH v4 43/43] fs: imgdafs: Add IMG DAFS filesystem for metag
Date: Wed, 6 Feb 2013 15:02:03 +0000	[thread overview]
Message-ID: <5112706B.3040402@imgtec.com> (raw)
In-Reply-To: <1359468915-8760-6-git-send-email-james.hogan@imgtec.com>

Ping.

Any chance of an Ack from somebody on this patch? I'm currently holding
off including it in the arch/metag tree.

Thanks
James

On 29/01/13 14:15, James Hogan wrote:
> Add the IMG Debug Adapter File System (DAFS) for metag, which uses
> SWITCH operations to communicate with a file server on a host computer
> via a JTAG debug adapter.
> 
> Signed-off-by: James Hogan <james.hogan@imgtec.com>
> Cc: Alexander Viro <viro@zeniv.linux.org.uk>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Mauro Carvalho Chehab <mchehab@redhat.com>
> Cc: Cesar Eduardo Barros <cesarb@cesarb.net>
> Cc: Joe Perches <joe@perches.com>
> Cc: "David S. Miller" <davem@davemloft.net>
> Cc: linux-fsdevel@vger.kernel.org
> ---
> v4:
>   * clean up fscall similarly to chancall in tty/metag_da
> 
> v2:
>   * fixed fserrno problem (Al Viro)
>   * renamed to imgdafs
> 
>  MAINTAINERS          |    1 +
>  arch/metag/Kconfig   |    1 +
>  fs/Kconfig           |    1 +
>  fs/Makefile          |    1 +
>  fs/imgdafs/Kconfig   |    6 +
>  fs/imgdafs/Makefile  |    7 +
>  fs/imgdafs/imgdafs.h |   80 +++++
>  fs/imgdafs/inode.c   |  843 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  8 files changed, 940 insertions(+), 0 deletions(-)
>  create mode 100644 fs/imgdafs/Kconfig
>  create mode 100644 fs/imgdafs/Makefile
>  create mode 100644 fs/imgdafs/imgdafs.h
>  create mode 100644 fs/imgdafs/inode.c
> 
> diff --git a/MAINTAINERS b/MAINTAINERS
> index cb89fe3..6b02aea 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -5044,6 +5044,7 @@ F:	drivers/clocksource/metag_generic.c
>  F:	drivers/irqchip/irq-metag.c
>  F:	drivers/irqchip/irq-metag-ext.c
>  F:	drivers/tty/metag_da.c
> +F:	fs/imgdafs/
>  
>  MICROBLAZE ARCHITECTURE
>  M:	Michal Simek <monstr@monstr.eu>
> diff --git a/arch/metag/Kconfig b/arch/metag/Kconfig
> index 9cba2f9..30adc78 100644
> --- a/arch/metag/Kconfig
> +++ b/arch/metag/Kconfig
> @@ -224,6 +224,7 @@ config METAG_DA
>  	  This enables support for services provided by DA JTAG debug adapters,
>  	  such as:
>  	  - communication over DA channels (such as the console driver).
> +	  - use of the DA filesystem.
>  
>  menu "Boot options"
>  
> diff --git a/fs/Kconfig b/fs/Kconfig
> index 780725a..6deefc1 100644
> --- a/fs/Kconfig
> +++ b/fs/Kconfig
> @@ -211,6 +211,7 @@ source "fs/sysv/Kconfig"
>  source "fs/ufs/Kconfig"
>  source "fs/exofs/Kconfig"
>  source "fs/f2fs/Kconfig"
> +source "fs/imgdafs/Kconfig"
>  
>  endif # MISC_FILESYSTEMS
>  
> diff --git a/fs/Makefile b/fs/Makefile
> index 9d53192..b070fca 100644
> --- a/fs/Makefile
> +++ b/fs/Makefile
> @@ -127,3 +127,4 @@ obj-$(CONFIG_F2FS_FS)		+= f2fs/
>  obj-y				+= exofs/ # Multiple modules
>  obj-$(CONFIG_CEPH_FS)		+= ceph/
>  obj-$(CONFIG_PSTORE)		+= pstore/
> +obj-$(CONFIG_IMGDAFS_FS)	+= imgdafs/
> diff --git a/fs/imgdafs/Kconfig b/fs/imgdafs/Kconfig
> new file mode 100644
> index 0000000..5293adb
> --- /dev/null
> +++ b/fs/imgdafs/Kconfig
> @@ -0,0 +1,6 @@
> +config IMGDAFS_FS
> +	bool "Meta DA filesystem support"
> +	depends on METAG_DA
> +	help
> +	  This enables the DA filesystem, which allows Linux to
> +	  to access files on a system attached via a debug adapter.
> diff --git a/fs/imgdafs/Makefile b/fs/imgdafs/Makefile
> new file mode 100644
> index 0000000..169a3c6
> --- /dev/null
> +++ b/fs/imgdafs/Makefile
> @@ -0,0 +1,7 @@
> +#
> +# Makefile for DAfs the Debug Adapter filesystem.
> +#
> +
> +obj-$(CONFIG_IMGDAFS_FS) += imgdafs.o
> +
> +imgdafs-objs := inode.o
> diff --git a/fs/imgdafs/imgdafs.h b/fs/imgdafs/imgdafs.h
> new file mode 100644
> index 0000000..6c1da13
> --- /dev/null
> +++ b/fs/imgdafs/imgdafs.h
> @@ -0,0 +1,80 @@
> +#ifndef _IMGDAFS_H_
> +#define _IMGDAFS_H_
> +
> +#define DA_OP_OPEN 0
> +#define DA_OP_CREAT 1
> +#define DA_OP_READ 2
> +#define DA_OP_WRITE 3
> +#define DA_OP_CLOSE 4
> +#define DA_OP_LINK 5
> +#define DA_OP_LSEEK 6
> +#define DA_OP_UNLINK 7
> +#define DA_OP_ISATTY 8
> +#define DA_OP_FCNTL 9
> +#define DA_OP_STAT 10
> +#define DA_OP_FSTAT 11
> +#define DA_OP_GETCWD 12
> +#define DA_OP_CHDIR 13
> +#define DA_OP_MKDIR 14
> +#define DA_OP_RMDIR 15
> +#define DA_OP_FINDFIRST 16
> +#define DA_OP_FINDNEXT 17
> +#define DA_OP_FINDCLOSE 18
> +#define DA_OP_CHMOD 19
> +#define DA_OP_PREAD 20
> +#define DA_OP_PWRITE 21
> +
> +#define OS_TYPE_FILE 1
> +#define OS_TYPE_DIR 2
> +#define OS_TYPE_SYMLINK 3
> +#define OS_TYPE_CHARDEV 4
> +#define OS_TYPE_BLOCKDEV 5
> +#define OS_TYPE_FIFO 6
> +#define OS_TYPE_SOCK 7
> +
> +#define DA_O_RDONLY 0
> +#define DA_O_WRONLY 1
> +#define DA_O_RDWR 2
> +#define DA_O_APPEND 8
> +#define DA_O_CREAT 0x0200
> +#define DA_O_TRUNC 0x0400
> +#define DA_O_EXCL 0x0800
> +
> +#define DA_O_AFFINITY_THREAD_0 0x10000
> +#define DA_O_AFFINITY_THREAD_1 0x20000
> +#define DA_O_AFFINITY_THREAD_2 0x40000
> +#define DA_O_AFFINITY_THREAD_3 0x80000
> +#define DA_O_AFFINITY_SHIFT 16
> +
> +#define DA_S_IWUSR	0200	/* 0x80 */
> +#define DA_S_IRUSR	0400	/* 0x100 */
> +
> +struct da_stat {
> +	short st_dev;
> +	unsigned short st_ino;
> +	unsigned st_mode;
> +	unsigned short st_nlink;
> +	unsigned short st_uid;
> +	unsigned short st_gid;
> +	short st_rdev;
> +	int st_size;
> +	int st_atime;
> +	int st_spare1;
> +	int st_mtime;
> +	int st_spare2;
> +	int st_ctime;
> +	int st_spare3;
> +	int st_blksize;
> +	int st_blocks;
> +	int st_spare4[2];
> +};
> +
> +#define _A_SUBDIR 0x10
> +
> +struct da_finddata {
> +	unsigned long size;
> +	unsigned long attrib;
> +	char name[260];
> +};
> +
> +#endif
> diff --git a/fs/imgdafs/inode.c b/fs/imgdafs/inode.c
> new file mode 100644
> index 0000000..5808d90
> --- /dev/null
> +++ b/fs/imgdafs/inode.c
> @@ -0,0 +1,843 @@
> +/*
> + * Copyright (C) 2008,2009,2010 Imagination Technologies Ltd.
> + * Licensed under the GPL
> + *
> + * Based on hostfs for UML.
> + *
> + */
> +
> +#include <linux/fs.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;
> +	int 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)->f_path.dentry->d_inode)
> +
> +static int dafs_d_delete(const struct dentry *dentry)
> +{
> +	return 1;
> +}
> +
> +static const struct dentry_operations dafs_dentry_ops = {
> +	.d_delete		= dafs_d_delete,
> +};
> +
> +#define DAFS_SUPER_MAGIC 0xdadadaf5
> +
> +static const struct inode_operations dafs_iops;
> +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); /* will unlock */
> +}
> +
> +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 = kzalloc(sizeof(*hi), GFP_KERNEL);
> +	if (hi == NULL)
> +		return NULL;
> +
> +	hi->fd = -1;
> +	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[len + 3];
> +
> +	strcpy(buf, path);
> +	if (buf[len - 1] != '/')
> +		strcat(buf, "/*");
> +	else
> +		strcat(buf, "*");
> +
> +	return fscall(DA_OP_FINDFIRST, (int)buf, (int)finddata, 0, 0, 0,
> +		      fserrno);
> +}
> +
> +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->f_path.dentry->d_inode;
> +	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;
> +	int mode, fmode, 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);
> +		ino->i_op = &dafs_iops;
> +		break;
> +
> +	case S_IFLNK:
> +	default:
> +		ino->i_op = &dafs_iops;
> +		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_iops = {
> +	.create		= dafs_create,
> +	.link		= dafs_link,
> +	.unlink		= dafs_unlink,
> +	.mkdir		= dafs_mkdir,
> +	.rmdir		= dafs_rmdir,
> +	.rename		= dafs_rename,
> +};
> +
> +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 = DAFS_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,
> +};
> +
> +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");
> 

  reply	other threads:[~2013-02-06 15:02 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2013-01-29 14:15 [PATCH v4 00/43] Meta Linux Kernel Port James Hogan
2013-01-29 14:15 ` James Hogan
     [not found] ` <1359468915-8760-1-git-send-email-james.hogan-1AXoQHu6uovQT0dZR+AlfA@public.gmane.org>
2013-01-29 14:15   ` [PATCH v4 19/43] metag: Device tree James Hogan
2013-01-29 14:15     ` James Hogan
2013-01-29 14:15     ` James Hogan
2013-01-29 14:15 ` [PATCH v4 21/43] metag: Time keeping James Hogan
2013-01-29 14:15   ` James Hogan
2013-01-29 14:15 ` [PATCH v4 26/43] metag: Scheduling/Process management James Hogan
2013-01-29 14:15   ` James Hogan
2013-01-29 14:15 ` [PATCH v4 42/43] tty/metag_da: Add metag DA TTY driver James Hogan
2013-01-29 14:15   ` James Hogan
2013-01-29 14:15 ` [PATCH v4 43/43] fs: imgdafs: Add IMG DAFS filesystem for metag James Hogan
2013-01-29 14:15   ` James Hogan
2013-02-06 15:02   ` James Hogan [this message]
2013-02-06 15:02     ` James Hogan

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=5112706B.3040402@imgtec.com \
    --to=james.hogan@imgtec.com \
    --cc=akpm@linux-foundation.org \
    --cc=arnd@arndb.de \
    --cc=cesarb@cesarb.net \
    --cc=davem@davemloft.net \
    --cc=joe@perches.com \
    --cc=linux-arch@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mchehab@redhat.com \
    --cc=viro@zeniv.linux.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.