/*
* linux/fs/proc/scsi.c
* (c) 1995 Michael Neuffer neuffer@goofy.zdv.uni-mainz.de
*
* The original version was derived from linux/fs/proc/net.c,
* which is Copyright (C) 1991, 1992 Linus Torvalds.
* Much has been rewritten, but some of the code still remains.
*
* /proc/scsi directory handling functions
*
* last change: 95/07/04
*
* Initial version: March '95
* 95/05/15 Added subdirectories for each driver and show every
* registered HBA as a single file.
* 95/05/30 Added rudimentary write support for parameter passing
* 95/07/04 Fixed bugs in directory handling
* 95/09/13 Update to support the new proc-dir tree
*
* TODO: Improve support to write to the driver files
* Add some more comments
*/
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/proc_fs.h>
#include <linux/stat.h>
#include <linux/mm.h>
#include <asm/segment.h>
/* forward references */
static int proc_readscsi(struct inode * inode, struct file * file,
char * buf, int count);
static int proc_writescsi(struct inode * inode, struct file * file,
const char * buf, int count);
static int proc_scsilseek(struct inode *, struct file *, off_t, int);
extern void build_proc_dir_hba_entries(uint);
/* the *_get_info() functions are in the respective scsi driver code */
int (* dispatch_scsi_info_ptr) (int ino, char *buffer, char **start,
off_t offset, int length, int inout) = 0;
static struct file_operations proc_scsi_operations = {
proc_scsilseek, /* lseek */
proc_readscsi, /* read */
proc_writescsi, /* write */
proc_readdir, /* readdir */
NULL, /* select */
NULL, /* ioctl */
NULL, /* mmap */
NULL, /* no special open code */
NULL, /* no special release code */
NULL /* can't fsync */
};
/*
* proc directories can do almost nothing..
*/
struct inode_operations proc_scsi_inode_operations = {
&proc_scsi_operations, /* default scsi directory file-ops */
NULL, /* create */
proc_lookup, /* lookup */
NULL, /* link */
NULL, /* unlink */
NULL, /* symlink */
NULL, /* mkdir */
NULL, /* rmdir */
NULL, /* mknod */
NULL, /* rename */
NULL, /* readlink */
NULL, /* follow_link */
NULL, /* readpage */
NULL, /* writepage */
NULL, /* bmap */
NULL, /* truncate */
NULL /* permission */
};
int get_not_present_info(char *buffer, char **start, off_t offset, int length)
{
int len, pos, begin;
begin = 0;
pos = len = sprintf(buffer,
"No low-level scsi modules are currently present\n");
if(pos < offset) {
len = 0;
begin = pos;
}
*start = buffer + (offset - begin); /* Start of wanted data */
len -= (offset - begin);
if(len > length)
len = length;
return(len);
}
#define PROC_BLOCK_SIZE (3*1024) /* 4K page size, but our output routines
* use some slack for overruns
*/
static int proc_readscsi(struct inode * inode, struct file * file,
char * buf, int count)
{
int length;
int bytes = count;
int copied = 0;
int thistime;
char * page;
char * start;
if (count < -1) /* Normally I wouldn't do this, */
return(-EINVAL); /* but it saves some redundant code.
* Now it is possible to seek to the
* end of the file */
if (!(page = (char *) __get_free_page(GFP_KERNEL)))
return(-ENOMEM);
while(bytes > 0 || count == -1) {
thistime = bytes;
if(bytes > PROC_BLOCK_SIZE || count == -1)
thistime = PROC_BLOCK_SIZE;
if(dispatch_scsi_info_ptr)
length = dispatch_scsi_info_ptr(inode->i_ino, page, &start,
file->f_pos, thistime, 0);
else
length = get_not_present_info(page, &start, file->f_pos, thistime);
if(length < 0) {
free_page((ulong) page);
return(length);
}
/*
* We have been given a non page aligned block of
* the data we asked for + a bit. We have been given
* the start pointer and we know the length..
*/
if (length <= 0)
break;
/*
* Copy the bytes, if we're not doing a seek to
* the end of the file
*/
if (count != -1)
memcpy_tofs(buf + copied, start, length);
file->f_pos += length; /* Move down the file */
bytes -= length;
copied += length;
if(length < thistime)
break; /* End of file */
}
free_page((ulong) page);
return(copied);
}
static int proc_writescsi(struct inode * inode, struct file * file,
const char * buf, int count)
{
int ret = 0;
char * page;
if(count > PROC_BLOCK_SIZE) {
return(-EOVERFLOW);
}
if(dispatch_scsi_info_ptr != NULL) {
if (!(page = (char *) __get_free_page(GFP_KERNEL)))
return(-ENOMEM);
memcpy_fromfs(page, buf, count);
ret = dispatch_scsi_info_ptr(inode->i_ino, page, 0, 0, count, 1);
} else
return(-ENOPKG); /* Nothing here */
free_page((ulong) page);
return(ret);
}
static int proc_scsilseek(struct inode * inode, struct file * file,
off_t offset, int orig)
{
switch (orig) {
case 0:
file->f_pos = offset;
return(file->f_pos);
case 1:
file->f_pos += offset;
return(file->f_pos);
case 2: /* This ugly hack allows us to */
if (offset) /* to determine the length of the */
return(-EINVAL); /* file and then later safely to */
proc_readscsi(inode, file, 0, -1); /* seek in it */
return(file->f_pos);
default:
return(-EINVAL);
}
}
/*
* Overrides for Emacs so that we almost follow Linus's tabbing style.
* Emacs will notice this stuff at the end of the file and automatically
* adjust the settings for this buffer only. This must remain at the end
* of the file.
* ---------------------------------------------------------------------------
* Local variables:
* c-indent-level: 4
* c-brace-imaginary-offset: 0
* c-brace-offset: -4
* c-argdecl-indent: 4
* c-label-offset: -4
* c-continued-statement-offset: 4
* c-continued-brace-offset: 0
* indent-tabs-mode: nil
* tab-width: 8
* End:
*/