Commit d2a03c2f authored by Martin Schwidefsky's avatar Martin Schwidefsky Committed by Linus Torvalds

[PATCH] s390: chandev.

Remove channel device layer.
parent c7d8d8ce
...@@ -524,47 +524,8 @@ config FDDI ...@@ -524,47 +524,8 @@ config FDDI
comment "S/390 network device drivers" comment "S/390 network device drivers"
depends on NETDEVICES depends on NETDEVICES
config CHANDEV
bool "Channel Device Configuration"
depends on NETDEVICES
---help---
The channel device layer is a layer to provide a consistent
interface for configuration & default machine check (devices
appearing & disappearing) handling on Linux for s/390 & z/Series
channel devices.
s/390 & z/Series channel devices include among others
lcs (the most common ethernet/token ring/fddi standard on
zSeries)
ctc/escon hi speed like serial link standard on zSeries
claw used to talk to cisco routers.
qeth gigabit ethernet.
These devices use two channels one read & one write for
configuration & communication (& a third channel, the data
channel the case of gigabit ethernet). The motivation
behind developing this layer was that there was a lot of
duplicate code among the channel device drivers for
configuration.
Also the lcs & ctc drivers tended to fight over
3088/08's & 3088/1F's which could be either 2216/3172
channel attached lcs compatible devices or escon/ctc pipes
had to be configured separately as they couldn't autodetect,
this is now simplified by doing the configuration in a single
place (the channel device layer).
This layer isn't invasive & it is quite okay to use channel
drivers which don't use the channel device layer in
conjunction with drivers which do.
For more info see the chandev manpage usually distributed in
<file:Documentation/s390/chandev.8> in the Linux source tree.
config HOTPLUG config HOTPLUG
bool bool
depends on CHANDEV
default y default y
---help--- ---help---
Say Y here if you want to plug devices into your computer while Say Y here if you want to plug devices into your computer while
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
# S/390 miscellaneous devices # S/390 miscellaneous devices
# #
obj-$(CONFIG_CHANDEV) += chandev.o # placeholder for stuff to come...
export-objs += chandev.o
include $(TOPDIR)/Rules.make include $(TOPDIR)/Rules.make
/*
* drivers/s390/misc/chandev.c
*
* Copyright (C) 2000 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Denis Joseph Barrow (djbarrow@de.ibm.com,barrow_dj@yahoo.com)
*
* Generic channel device initialisation support.
*/
#define TRUE 1
#define FALSE 0
#define __KERNEL_SYSCALLS__
#include <linux/module.h>
#include <linux/config.h>
#include <linux/types.h>
#include <linux/ctype.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <asm/irq.h>
#include <linux/init.h>
#include <linux/unistd.h>
#include <asm/chandev.h>
#include <linux/proc_fs.h>
#include <linux/vmalloc.h>
#include <asm/s390dyn.h>
#include <asm/queue.h>
#include <linux/kmod.h>
#include <linux/workqueue.h>
#ifndef MIN
#define MIN(a,b) ((a<b)?a:b)
#endif
#ifndef MAX
#define MAX(a,b) ((a>b)?a:b)
#endif
typedef struct chandev_model_info chandev_model_info;
struct chandev_model_info
{
struct chandev_model_info *next;
chandev_type chan_type;
s32 cu_type; /* control unit type -1 = don't care */
s16 cu_model; /* control unit model -1 = don't care */
s32 dev_type; /* device type -1 = don't care */
s16 dev_model; /* device model -1 = don't care */
u8 max_port_no;
int auto_msck_recovery;
u8 default_checksum_received_ip_pkts;
u8 default_use_hw_stats; /* where available e.g. lcs */
devreg_t drinfo;
};
typedef struct chandev chandev;
struct chandev
{
struct chandev *next;
chandev_model_info *model_info;
chandev_subchannel_info sch;
int owned;
};
typedef struct chandev_noauto_range chandev_noauto_range;
struct chandev_noauto_range
{
struct chandev_noauto_range *next;
u16 lo_devno;
u16 hi_devno;
};
typedef struct chandev_force chandev_force;
struct chandev_force
{
struct chandev_force *next;
chandev_type chan_type;
s32 devif_num; /* -1 don't care, -2 we are forcing a range e.g. tr0 implies 0 */
u16 read_lo_devno;
u16 write_hi_devno;
u16 data_devno; /* only used by gigabit ethernet */
s32 memory_usage_in_k;
s16 port_protocol_no; /* where available e.g. lcs,-1 don't care */
u8 checksum_received_ip_pkts;
u8 use_hw_stats; /* where available e.g. lcs */
/* claw specific stuff */
chandev_claw_info claw;
};
typedef struct chandev_probelist chandev_probelist;
struct chandev_probelist
{
struct chandev_probelist *next;
chandev_probefunc probefunc;
chandev_shutdownfunc shutdownfunc;
chandev_msck_notification_func msck_notfunc;
chandev_type chan_type;
int devices_found;
};
#define default_msck_bits ((1<<(chandev_status_not_oper-1))|(1<<(chandev_status_no_path-1))|(1<<(chandev_status_revalidate-1))|(1<<(chandev_status_gone-1)))
static char *msck_status_strs[]=
{
"good",
"not_operational",
"no_path",
"revalidate",
"device_gone"
};
typedef struct chandev_msck_range chandev_msck_range;
struct chandev_msck_range
{
struct chandev_msck_range *next;
u16 lo_devno;
u16 hi_devno;
int auto_msck_recovery;
};
static chandev_msck_range *chandev_msck_range_head=NULL;
typedef struct chandev_irqinfo chandev_irqinfo;
struct chandev_irqinfo
{
chandev_irqinfo *next;
chandev_subchannel_info sch;
chandev_msck_status msck_status;
void (*handler)(int, void *, struct pt_regs *);
unsigned long irqflags;
void *dev_id;
char devname[0];
};
static chandev_irqinfo *chandev_irqinfo_head=NULL;
typedef struct chandev_parms chandev_parms;
struct chandev_parms
{
chandev_parms *next;
chandev_type chan_type;
u16 lo_devno;
u16 hi_devno;
char parmstr[0];
};
static chandev_type chandev_persistent=0;
static chandev_parms *chandev_parms_head=NULL;
typedef struct chandev_activelist chandev_activelist;
struct chandev_activelist
{
struct chandev_activelist *next;
chandev_irqinfo *read_irqinfo;
chandev_irqinfo *write_irqinfo;
chandev_irqinfo *data_irqinfo;
chandev_probefunc probefunc;
chandev_shutdownfunc shutdownfunc;
chandev_msck_notification_func msck_notfunc;
chandev_unregfunc unreg_dev;
chandev_type chan_type;
u8 port_no;
chandev_category category;
s32 memory_usage_in_k;
void *dev_ptr;
char devname[0];
};
static chandev_model_info *chandev_models_head=NULL;
/* The only reason chandev_head is a queue is so that net devices */
/* will be by default named in the order of their irqs */
static qheader chandev_head={NULL,NULL};
static chandev_noauto_range *chandev_noauto_head=NULL;
static chandev_force *chandev_force_head=NULL;
static chandev_probelist *chandev_probelist_head=NULL;
static chandev_activelist *chandev_activelist_head=NULL;
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
/* not static, see comment in chandev.h */
int chandev_use_devno_names=FALSE;
#endif
static int chandev_cautious_auto_detect=TRUE;
static atomic_t chandev_conf_read=ATOMIC_INIT(FALSE);
static atomic_t chandev_initialised=ATOMIC_INIT(FALSE);
static unsigned long chandev_last_machine_check;
static void chandev_msck_task(void *unused);
static DECLARE_WORK(chandev_msck_work, chandev_msck_task, 0);
static atomic_t chandev_msck_thread_lock;
static atomic_t chandev_new_msck;
static unsigned long chandev_last_startmsck_list_update;
typedef enum
{
chandev_start,
chandev_first_tag=chandev_start,
chandev_msck,
chandev_num_notify_tags
} chandev_userland_notify_tag;
static char *userland_notify_strs[]=
{
"start",
"machine_check"
};
typedef struct chandev_userland_notify_list chandev_userland_notify_list;
struct chandev_userland_notify_list
{
chandev_userland_notify_list *next;
chandev_userland_notify_tag tag;
chandev_msck_status prev_status;
chandev_msck_status curr_status;
char devname[0];
};
static chandev_userland_notify_list *chandev_userland_notify_head=NULL;
static void chandev_read_conf_if_necessary(void);
static void chandev_read_conf(void);
#if LINUX_VERSION_CODE >=KERNEL_VERSION(2,3,0)
typedef struct net_device net_device;
#else
typedef struct device net_device;
static inline void init_waitqueue_head(wait_queue_head_t *q)
{
*q=NULL;
}
#endif
#if LINUX_VERSION_CODE<KERNEL_VERSION(2,3,45)
static __inline__ void netif_stop_queue(net_device *dev)
{
dev->tbusy=1;
}
static __inline__ void netif_start_queue(net_device *dev)
{
dev->tbusy=0;
}
#endif
#define CHANDEV_INVALID_LOCK_OWNER -1
static long chandev_lock_owner;
static int chandev_lock_cnt;
static spinlock_t chandev_spinlock;
#define CHANDEV_LOCK_DEBUG 0
#if CHANDEV_LOCK_DEBUG && !defined(CONFIG_ARCH_S390X)
#define CHANDEV_BACKTRACE_LOOPCNT 10
void *chandev_first_lock_addr[CHANDEV_BACKTRACE_LOOPCNT],
*chandev_last_lock_addr[CHANDEV_BACKTRACE_LOOPCNT],
*chandev_last_unlock_addr[CHANDEV_BACKTRACE_LOOPCNT];
#define CHANDEV_BACKTRACE(variable) \
memset((variable),0,sizeof(void *)*CHANDEV_BACKTRACE_LOOPCNT); \
(variable)[0]=__builtin_return_address(0); \
if(((long)variable[0])&0x80000000) \
{ \
(variable)[1]=__builtin_return_address(1); \
if(((long)variable[1])&0x80000000) \
{ \
(variable)[2]=__builtin_return_address(2); \
if(((long)variable[2])&0x80000000) \
{ \
(variable)[3]=__builtin_return_address(3); \
if(((long)variable[3])&0x80000000) \
{ \
(variable)[4]=__builtin_return_address(4); \
if(((long)variable[4])&0x80000000) \
{ \
(variable)[5]=__builtin_return_address(5); \
if(((long)variable[5])&0x80000000) \
{ \
(variable)[6]=__builtin_return_address(6); \
if(((long)variable[6])&0x80000000) \
{ \
(variable)[7]=__builtin_return_address(7); \
if(((long)variable[7])&0x80000000) \
{ \
(variable)[8]=__builtin_return_address(8); \
if(((long)variable[8])&0x80000000) \
{ \
(variable)[9]=__builtin_return_address(9); \
} \
} \
} \
} \
} \
} \
} \
} \
}
#else
#define CHANDEV_BACKTRACE(variable)
#endif
typedef struct chandev_not_oper_struct chandev_not_oper_struct;
struct chandev_not_oper_struct
{
chandev_not_oper_struct *next;
int irq;
int status;
};
/* May as well try to keep machine checks in the order they happen so
* we use qheader for chandev_not_oper_head instead of list.
*/
static qheader chandev_not_oper_head={NULL,NULL};
static spinlock_t chandev_not_oper_spinlock;
#define chandev_interrupt_check(name) \
if(in_interrupt()) \
printk(KERN_WARNING name " called under interrupt this shouldn't happen\n")
#define for_each(variable,head) \
for((variable)=(head);(variable)!=NULL;(variable)=(variable)->next)
#define for_each_allow_delete(variable,nextmember,head) \
for((variable)=(head),(nextmember)=((head) ? (head)->next:NULL); \
(variable)!=NULL; (variable)=(nextmember),(nextmember)=((nextmember) ? (nextmember->next) : NULL))
#define for_each_allow_delete2(variable,nextmember,head) \
for((variable)=(head);(variable)!=NULL;(variable)=(nextmember))
static void chandev_lock(void)
{
eieio();
chandev_interrupt_check("chandev_lock");
if(chandev_lock_owner!=(long)current)
{
while(!spin_trylock(&chandev_spinlock))
schedule();
chandev_lock_cnt=1;
chandev_lock_owner=(long)current;
CHANDEV_BACKTRACE(chandev_first_lock_addr)
}
else
{
chandev_lock_cnt++;
CHANDEV_BACKTRACE(chandev_last_lock_addr)
}
if(chandev_lock_cnt<0||chandev_lock_cnt>100)
{
printk("odd lock_cnt %d lcs_chan_lock",chandev_lock_cnt);
chandev_lock_cnt=1;
}
}
static int chandev_full_unlock(void)
{
int ret_lock_cnt=chandev_lock_cnt;
chandev_lock_cnt=0;
chandev_lock_owner=CHANDEV_INVALID_LOCK_OWNER;
spin_unlock(&chandev_spinlock);
return(ret_lock_cnt);
}
static void chandev_unlock(void)
{
if(chandev_lock_owner!=(long)current)
printk("chandev_unlock: current=%lx"
" chandev_lock_owner=%lx chandev_lock_cnt=%d\n",
(long)current,
chandev_lock_owner,
chandev_lock_cnt);
CHANDEV_BACKTRACE(chandev_last_unlock_addr)
if(--chandev_lock_cnt==0)
{
chandev_lock_owner=CHANDEV_INVALID_LOCK_OWNER;
spin_unlock(&chandev_spinlock);
}
if(chandev_lock_cnt<0)
{
printk("odd lock_cnt=%d in chan_unlock",chandev_lock_cnt);
chandev_full_unlock();
}
}
static void *chandev_alloc(size_t size)
{
void *mem=kmalloc(size,GFP_ATOMIC);
if(mem)
memset(mem,0,size);
return(mem);
}
static void chandev_add_to_list(list **listhead,void *member)
{
chandev_lock();
add_to_list(listhead,member);
chandev_unlock();
}
static void chandev_queuemember(qheader *qhead,void *member)
{
chandev_lock();
enqueue_tail(qhead,(queue *)member);
chandev_unlock();
}
static int chandev_remove_from_list(list **listhead,list *member)
{
int retval;
chandev_lock();
retval=remove_from_list(listhead,member);
chandev_unlock();
return(retval);
}
static int chandev_remove_from_queue(qheader *qhead,queue *member)
{
int retval;
chandev_lock();
retval=remove_from_queue(qhead,member);
chandev_unlock();
return(retval);
}
static void chandev_free_listmember(list **listhead,list *member)
{
chandev_lock();
if(member)
{
if(chandev_remove_from_list(listhead,member))
kfree(member);
else
printk(KERN_CRIT"chandev_free_listmember detected nonexistant"
"listmember listhead=%p member %p\n",listhead,member);
}
chandev_unlock();
}
static void chandev_free_queuemember(qheader *qhead,queue *member)
{
chandev_lock();
if(member)
{
if(chandev_remove_from_queue(qhead,member))
kfree(member);
else
printk(KERN_CRIT"chandev_free_queuemember detected nonexistant"
"queuemember qhead=%p member %p\n",qhead,member);
}
chandev_unlock();
}
static void chandev_free_all_list(list **listhead)
{
list *head;
chandev_lock();
while((head=remove_listhead(listhead)))
kfree(head);
chandev_unlock();
}
static void chandev_free_all_queue(qheader *qhead)
{
chandev_lock();
while(qhead->head)
chandev_free_queuemember(qhead,qhead->head);
chandev_unlock();
}
static void chandev_wait_for_root_fs(void)
{
wait_queue_head_t wait;
init_waitqueue_head(&wait);
/* We need to wait till there is a root filesystem */
while(init_task.fs->root==NULL)
{
sleep_on_timeout(&wait,HZ);
}
}
/* We are now hotplug compliant i.e. */
/* we typically get called in /sbin/hotplug chandev our parameters */
static int chandev_exec_start_script(void *unused)
{
char **argv,*tempname;
int retval=-ENOMEM;
int argc,loopcnt;
size_t allocsize;
chandev_userland_notify_list *member;
wait_queue_head_t wait;
int have_tag[chandev_num_notify_tags]={FALSE,};
chandev_userland_notify_tag tagidx;
static char * envp[] = { "HOME=/", "TERM=linux", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL };
init_waitqueue_head(&wait);
strcpy(current->comm,"chandev_script");
for(loopcnt=0;loopcnt<10&&(jiffies-chandev_last_startmsck_list_update)<HZ;loopcnt++)
{
sleep_on_timeout(&wait,HZ);
}
if(!chandev_userland_notify_head)
return(0);
chandev_lock();
argc=2;
for(tagidx=chandev_first_tag;tagidx<chandev_num_notify_tags;tagidx++)
{
for_each(member,chandev_userland_notify_head)
{
if(member->tag==tagidx)
{
switch(tagidx)
{
case chandev_start:
argc++;
break;
case chandev_msck:
argc+=3;
break;
default:
break;
}
if(have_tag[tagidx]==FALSE)
argc++;
have_tag[tagidx]=TRUE;
}
}
}
allocsize=(argc+1)*sizeof(char *);
/* Warning possible stack overflow */
/* We can't kmalloc the parameters here as execve will */
/* not return if successful */
argv=alloca(allocsize);
if(argv)
{
memset(argv,0,allocsize);
argv[0]=hotplug_path;
argv[1]="chandev";
argc=2;
for(tagidx=chandev_first_tag;tagidx<chandev_num_notify_tags;tagidx++)
{
if(have_tag[tagidx])
{
argv[argc++]=userland_notify_strs[tagidx];
for_each(member,chandev_userland_notify_head)
{
if(member->tag==tagidx)
{
tempname=alloca(strlen(member->devname)+1);
if(tempname)
{
strcpy(tempname,member->devname);
argv[argc++]=tempname;
}
else
goto Fail;
if(member->tag==chandev_msck)
{
argv[argc++]=msck_status_strs[member->prev_status];
argv[argc++]=msck_status_strs[member->curr_status];
}
}
}
}
}
chandev_free_all_list((list **)&chandev_userland_notify_head);
chandev_unlock();
chandev_wait_for_root_fs();
/* We are basically execve'ing here there normally is no */
/* return */
retval=exec_usermodehelper(hotplug_path, argv, envp);
goto Fail2;
}
Fail:
chandev_unlock();
Fail2:
return(retval);
}
static void *chandev_allocstr(const char *str,size_t offset)
{
char *member;
if((member=chandev_alloc(offset+strlen(str)+1)))
{
strcpy(&member[offset],str);
}
return((void *)member);
}
static int chandev_add_to_userland_notify_list(chandev_userland_notify_tag tag,
char *devname, chandev_msck_status prev_status,chandev_msck_status curr_status)
{
chandev_userland_notify_list *member,*nextmember;
int pid;
chandev_lock();
/* remove operations still outstanding for this device */
for_each_allow_delete(member,nextmember,chandev_userland_notify_head)
if(strcmp(member->devname,devname)==0)
chandev_free_listmember((list **)&chandev_userland_notify_head,(list *)member);
if((member=chandev_allocstr(devname,offsetof(chandev_userland_notify_list,devname))))
{
member->tag=tag;
member->prev_status=prev_status;
member->curr_status=curr_status;
add_to_list((list **)&chandev_userland_notify_head,(list *)member);
chandev_last_startmsck_list_update=jiffies;
chandev_unlock();
pid = kernel_thread(chandev_exec_start_script,NULL,SIGCHLD);
if(pid<0)
{
printk("error making kernel thread for chandev_exec_start_script\n");
return(pid);
}
else
return(0);
}
else
{
chandev_unlock();
printk("chandev_add_to_startmscklist memory allocation failed devname=%s\n",devname);
return(-ENOMEM);
}
}
static int chandev_oper_func(int irq,devreg_t *dreg)
{
chandev_last_machine_check=jiffies;
if(atomic_dec_and_test(&chandev_msck_thread_lock))
{
schedule_work(&chandev_msck_work);
}
atomic_set(&chandev_new_msck,TRUE);
return(0);
}
static void chandev_not_oper_handler(int irq,int status )
{
chandev_not_oper_struct *new_not_oper;
chandev_last_machine_check=jiffies;
if((new_not_oper=kmalloc(sizeof(chandev_not_oper_struct),GFP_ATOMIC)))
{
new_not_oper->irq=irq;
new_not_oper->status=status;
spin_lock(&chandev_not_oper_spinlock);
enqueue_tail(&chandev_not_oper_head,(queue *)new_not_oper);
spin_unlock(&chandev_not_oper_spinlock);
if(atomic_dec_and_test(&chandev_msck_thread_lock))
{
schedule_work(&chandev_msck_work);
}
}
else
printk("chandev_not_oper_handler failed to allocate memory & "
"lost a not operational interrupt %d %x",
irq,status);
}
static chandev_irqinfo *chandev_get_irqinfo_by_irq(int irq)
{
chandev_irqinfo *curr_irqinfo;
for_each(curr_irqinfo,chandev_irqinfo_head)
if(irq==curr_irqinfo->sch.irq)
return(curr_irqinfo);
return(NULL);
}
static chandev *chandev_get_by_irq(int irq)
{
chandev *curr_chandev;
for_each(curr_chandev,(chandev *)chandev_head.head)
if(curr_chandev->sch.irq==irq)
{
return(curr_chandev);
}
return(NULL);
}
static chandev_activelist *chandev_get_activelist_by_irq(int irq)
{
chandev_activelist *curr_device;
for_each(curr_device,chandev_activelist_head)
{
if(curr_device->read_irqinfo->sch.irq==irq||
curr_device->write_irqinfo->sch.irq==irq||
(curr_device->data_irqinfo&&curr_device->data_irqinfo->sch.irq==irq))
return(curr_device);
}
return(NULL);
}
static void chandev_remove_irqinfo_by_irq(unsigned int irq)
{
chandev_irqinfo *remove_irqinfo;
chandev_activelist *curr_device;
chandev_lock();
/* remove any orphan irqinfo left lying around. */
if((remove_irqinfo=chandev_get_irqinfo_by_irq(irq)))
{
for_each(curr_device,chandev_activelist_head)
{
if(curr_device->read_irqinfo==remove_irqinfo)
{
curr_device->read_irqinfo=NULL;
break;
}
if(curr_device->write_irqinfo==remove_irqinfo)
{
curr_device->write_irqinfo=NULL;
break;
}
if(curr_device->data_irqinfo&&curr_device->data_irqinfo==remove_irqinfo)
{
curr_device->data_irqinfo=NULL;
break;
}
}
chandev_free_listmember((list **)&chandev_irqinfo_head,
(list *)remove_irqinfo);
}
chandev_unlock();
}
static int chandev_add_schib_info(int irq,chandev_subchannel_info *sch)
{
schib_t *new_schib;
if((new_schib=s390_get_schib(irq)))
{
sch->pim=new_schib->pmcw.pim;
memcpy(&sch->chpid,&new_schib->pmcw.chpid,sizeof(sch->chpid));
return(0);
}
return(-ENODEV);
}
int chandev_request_irq(unsigned int irq,
void (*handler)(int, void *, struct pt_regs *),
unsigned long irqflags,
const char *devname,
void *dev_id)
{
chandev_irqinfo *new_irqinfo;
chandev_activelist *curr_device;
s390_dev_info_t devinfo;
int retval;
chandev_lock();
if((curr_device=chandev_get_activelist_by_irq(irq)))
{
printk("chandev_request_irq failed devname=%s irq=%d "
"it already belongs to %s shutdown this device first.\n",
devname,irq,curr_device->devname);
chandev_unlock();
return(-EPERM);
}
/* remove any orphan irqinfo left lying around. */
chandev_remove_irqinfo_by_irq(irq);
chandev_unlock();
if((new_irqinfo=chandev_allocstr(devname,offsetof(chandev_irqinfo,devname))))
{
if((retval=get_dev_info_by_irq(irq,&devinfo))||
(retval=s390_request_irq_special(irq,handler,
chandev_not_oper_handler,
irqflags,devname,dev_id)))
kfree(new_irqinfo);
else
{
new_irqinfo->msck_status=chandev_status_good;
new_irqinfo->sch.devno=devinfo.devno;
new_irqinfo->sch.irq=irq;
new_irqinfo->sch.cu_type=devinfo.sid_data.cu_type; /* control unit type */
new_irqinfo->sch.cu_model=devinfo.sid_data.cu_model; /* control unit model */
new_irqinfo->sch.dev_type=devinfo.sid_data.dev_type; /* device type */
new_irqinfo->sch.dev_model=devinfo.sid_data.dev_model; /* device model */
chandev_add_schib_info(irq,&new_irqinfo->sch);
new_irqinfo->handler=handler;
new_irqinfo->dev_id=dev_id;
chandev_add_to_list((list **)&chandev_irqinfo_head,new_irqinfo);
}
}
else
{
printk("chandev_request_irq memory allocation failed devname=%s irq=%d\n",devname,irq);
retval=-ENOMEM;
}
return(retval);
}
/* This should be safe to call even multiple times. */
void chandev_free_irq(unsigned int irq, void *dev_id)
{
s390_dev_info_t devinfo;
int err;
/* remove any orphan irqinfo left lying around. */
chandev_remove_irqinfo_by_irq(irq);
if((err=get_dev_info_by_irq(irq,&devinfo)))
{
printk("chandev_free_irq get_dev_info_by_irq reported err=%X on irq %d\n"
"should not happen\n",err,irq);
return;
}
if(devinfo.status&DEVSTAT_DEVICE_OWNED)
free_irq(irq,dev_id);
}
/* This should be safe even if chandev_free_irq is already called by the device */
static void chandev_free_irq_by_irqinfo(chandev_irqinfo *irqinfo)
{
if(irqinfo)
chandev_free_irq(irqinfo->sch.irq,irqinfo->dev_id);
}
static void chandev_sprint_type_model(char *buff,s32 type,s16 model)
{
if(type==-1)
strcpy(buff," * ");
else
sprintf(buff," 0x%04x ",(int)type);
buff+=strlen(buff);
if(model==-1)
strcpy(buff," * ");
else
sprintf(buff," 0x%02x ",(int)model);
}
static void chandev_sprint_devinfo(char *buff,s32 cu_type,s16 cu_model,s32 dev_type,s16 dev_model)
{
chandev_sprint_type_model(buff,cu_type,cu_model);
chandev_sprint_type_model(&buff[strlen(buff)],dev_type,dev_model);
}
static void chandev_remove_parms(chandev_type chan_type,int exact_match,int lo_devno)
{
chandev_parms *curr_parms,*next_parms;
chandev_lock();
for_each_allow_delete(curr_parms,next_parms,chandev_parms_head)
{
if(((chan_type&(curr_parms->chan_type)&&!exact_match)||
(chan_type==(curr_parms->chan_type)&&exact_match))&&
(lo_devno==-1||lo_devno==curr_parms->lo_devno))
chandev_free_listmember((list **)&chandev_parms_head,(list *)curr_parms);
}
chandev_unlock();
}
static void chandev_add_parms(chandev_type chan_type,u16 lo_devno,u16 hi_devno,char *parmstr)
{
chandev_parms *parms;
if(lo_devno>hi_devno)
{
printk("chandev_add_parms detected bad device range lo_devno=0x%04x hi_devno=0x%04x\n,",
(int)lo_devno,(int)hi_devno);
return;
}
chandev_lock();
for_each(parms,chandev_parms_head)
{
if(chan_type&(parms->chan_type))
{
u16 lomax=MAX(parms->lo_devno,lo_devno),
himin=MIN(parms->hi_devno,lo_devno);
if(lomax<=himin)
{
chandev_unlock();
printk("chandev_add_parms detected overlapping "
"parameter definitions for chan_type=0x%02x"
" lo_devno=0x%04x hi_devno=0x%04x\n,"
" do a del_parms.",chan_type,(int)lo_devno,(int)hi_devno);
return;
}
}
}
chandev_unlock();
if((parms=chandev_allocstr(parmstr,offsetof(chandev_parms,parmstr))))
{
parms->chan_type=chan_type;
parms->lo_devno=lo_devno;
parms->hi_devno=hi_devno;
chandev_add_to_list((list **)&chandev_parms_head,(void *)parms);
}
else
printk("chandev_add_parms memory request failed\n");
}
void chandev_add_model(chandev_type chan_type,s32 cu_type,s16 cu_model,
s32 dev_type,s16 dev_model,u8 max_port_no,int auto_msck_recovery,
u8 default_checksum_received_ip_pkts,u8 default_use_hw_stats)
{
chandev_model_info *newmodel;
int err;
char buff[40];
if((newmodel=chandev_alloc(sizeof(chandev_model_info))))
{
devreg_t *drinfo=&newmodel->drinfo;
newmodel->chan_type=chan_type;
newmodel->cu_type=cu_type;
newmodel->cu_model=cu_model;
newmodel->dev_type=dev_type;
newmodel->dev_model=dev_model;
newmodel->max_port_no=max_port_no;
newmodel->auto_msck_recovery=auto_msck_recovery;
newmodel->default_checksum_received_ip_pkts=default_checksum_received_ip_pkts;
newmodel->default_use_hw_stats=default_use_hw_stats; /* where available e.g. lcs */
if(cu_type==-1&&dev_type==-1)
{
chandev_sprint_devinfo(buff,newmodel->cu_type,newmodel->cu_model,
newmodel->dev_type,newmodel->dev_model);
printk(KERN_INFO"can't call s390_device_register for this device chan_type/chan_model/dev_type/dev_model %s\n",buff);
kfree(newmodel);
return;
}
drinfo->flag=DEVREG_TYPE_DEVCHARS;
if(cu_type!=-1)
drinfo->flag|=DEVREG_MATCH_CU_TYPE;
if(cu_model!=-1)
drinfo->flag|=DEVREG_MATCH_CU_MODEL;
if(dev_type!=-1)
drinfo->flag|=DEVREG_MATCH_DEV_TYPE;
if(dev_model!=-1)
drinfo->flag|=DEVREG_MATCH_DEV_MODEL;
drinfo->ci.hc.ctype=cu_type;
drinfo->ci.hc.cmode=cu_model;
drinfo->ci.hc.dtype=dev_type;
drinfo->ci.hc.dmode=dev_model;
drinfo->oper_func=chandev_oper_func;
if((err=s390_device_register(&newmodel->drinfo)))
{
chandev_sprint_devinfo(buff,newmodel->cu_type,newmodel->cu_model,
newmodel->dev_type,newmodel->dev_model);
printk("s390_device_register failed in chandev_add_model"
" this is nothing to worry about chan_type/chan_model/dev_type/dev_model %s\n",buff);
drinfo->oper_func=NULL;
}
chandev_add_to_list((list **)&chandev_models_head,newmodel);
}
}
static void chandev_remove(chandev *member)
{
chandev_free_queuemember(&chandev_head,(queue *)member);
}
static void chandev_remove_all(void)
{
chandev_free_all_queue(&chandev_head);
}
static void chandev_remove_model(chandev_model_info *model)
{
chandev *curr_chandev,*next_chandev;
chandev_lock();
for_each_allow_delete(curr_chandev,next_chandev,(chandev *)chandev_head.head)
if(curr_chandev->model_info==model)
chandev_remove(curr_chandev);
if(model->drinfo.oper_func)
s390_device_unregister(&model->drinfo);
chandev_free_listmember((list **)&chandev_models_head,(list *)model);
chandev_unlock();
}
static void chandev_remove_all_models(void)
{
chandev_lock();
while(chandev_models_head)
chandev_remove_model(chandev_models_head);
chandev_unlock();
}
void chandev_del_model(s32 cu_type,s16 cu_model,s32 dev_type,s16 dev_model)
{
chandev_model_info *curr_model,*next_model;
chandev_lock();
for_each_allow_delete(curr_model,next_model,chandev_models_head)
if((curr_model->cu_type==cu_type||cu_type==-1)&&
(curr_model->cu_model==cu_model||cu_model==-1)&&
(curr_model->dev_type==dev_type||dev_type==-1)&&
(curr_model->dev_model==dev_model||dev_model==-1))
chandev_remove_model(curr_model);
chandev_unlock();
}
static void chandev_init_default_models(void)
{
/* Usually P390/Planter 3172 emulation assume maximum 16 to be safe. */
chandev_add_model(chandev_type_lcs,0x3088,0x1,-1,-1,15,default_msck_bits,FALSE,FALSE);
/* 3172/2216 Paralell the 2216 allows 16 ports per card the */
/* the original 3172 only allows 4 we will assume the max of 16 */
chandev_add_model(chandev_type_lcs|chandev_type_ctc,0x3088,0x8,-1,-1,15,default_msck_bits,FALSE,FALSE);
/* 3172/2216 Escon serial the 2216 allows 16 ports per card the */
/* the original 3172 only allows 4 we will assume the max of 16 */
chandev_add_model(chandev_type_lcs|chandev_type_escon,0x3088,0x1F,-1,-1,15,default_msck_bits,FALSE,FALSE);
/* Only 2 ports allowed on OSA2 cards model 0x60 */
chandev_add_model(chandev_type_lcs,0x3088,0x60,-1,-1,1,default_msck_bits,FALSE,FALSE);
/* qeth gigabit ethernet */
chandev_add_model(chandev_type_qeth,0x1731,0x1,0x1732,0x1,0,default_msck_bits,FALSE,FALSE);
chandev_add_model(chandev_type_qeth,0x1731,0x5,0x1732,0x5,0,default_msck_bits,FALSE,FALSE);
/* Osa-D we currently aren't too emotionally involved with this */
chandev_add_model(chandev_type_osad,0x3088,0x62,-1,-1,0,default_msck_bits,FALSE,FALSE);
/* claw */
chandev_add_model(chandev_type_claw,0x3088,0x61,-1,-1,0,default_msck_bits,FALSE,FALSE);
/* ficon attached ctc */
chandev_add_model(chandev_type_escon,0x3088,0x1E,-1,-1,0,default_msck_bits,FALSE,FALSE);
}
static void chandev_del_noauto(u16 devno)
{
chandev_noauto_range *curr_noauto,*next_noauto;
chandev_lock();
for_each_allow_delete(curr_noauto,next_noauto,chandev_noauto_head)
if(curr_noauto->lo_devno<=devno&&curr_noauto->hi_devno>=devno)
chandev_free_listmember((list **)&chandev_noauto_head,(list *)curr_noauto);
chandev_unlock();
}
static void chandev_del_msck(u16 devno)
{
chandev_msck_range *curr_msck_range,*next_msck_range;
chandev_lock();
for_each_allow_delete(curr_msck_range,next_msck_range,chandev_msck_range_head)
if(curr_msck_range->lo_devno<=devno&&curr_msck_range->hi_devno>=devno)
chandev_free_listmember((list **)&chandev_msck_range_head,(list *)curr_msck_range);
chandev_unlock();
}
static void chandev_add(s390_dev_info_t *newdevinfo,
chandev_model_info *newmodelinfo)
{
chandev *new_chandev=NULL;
if((new_chandev=chandev_alloc(sizeof(chandev))))
{
new_chandev->model_info=newmodelinfo;
new_chandev->sch.devno=newdevinfo->devno;
new_chandev->sch.irq=newdevinfo->irq;
new_chandev->sch.cu_type=newdevinfo->sid_data.cu_type; /* control unit type */
new_chandev->sch.cu_model=newdevinfo->sid_data.cu_model; /* control unit model */
new_chandev->sch.dev_type=newdevinfo->sid_data.dev_type; /* device type */
new_chandev->sch.dev_model=newdevinfo->sid_data.dev_model; /* device model */
chandev_add_schib_info(newdevinfo->irq,&new_chandev->sch);
new_chandev->owned=(newdevinfo->status&DEVSTAT_DEVICE_OWNED ? TRUE:FALSE);
chandev_queuemember(&chandev_head,new_chandev);
}
}
static void chandev_unregister_probe(chandev_probefunc probefunc)
{
chandev_probelist *curr_probe,*next_probe;
chandev_lock();
for_each_allow_delete(curr_probe,next_probe,chandev_probelist_head)
if(curr_probe->probefunc==probefunc)
chandev_free_listmember((list **)&chandev_probelist_head,
(list *)curr_probe);
chandev_unlock();
}
static void chandev_unregister_probe_by_chan_type(chandev_type chan_type)
{
chandev_probelist *curr_probe,*next_probe;
chandev_lock();
for_each_allow_delete(curr_probe,next_probe,chandev_probelist_head)
if(curr_probe->chan_type==chan_type)
chandev_free_listmember((list **)&chandev_probelist_head,
(list *)curr_probe);
chandev_unlock();
}
static void chandev_reset(void)
{
chandev_lock();
chandev_remove_all_models();
chandev_free_all_list((list **)&chandev_noauto_head);
chandev_free_all_list((list **)&chandev_msck_range_head);
chandev_free_all_list((list **)&chandev_force_head);
chandev_remove_parms(-1,FALSE,-1);
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
chandev_use_devno_names=FALSE;
#endif
chandev_persistent=0;
chandev_unlock();
}
static int chandev_is_chandev(int irq,s390_dev_info_t *devinfo,
chandev_force **forceinfo,chandev_model_info **ret_model)
{
chandev_force *curr_force;
chandev_model_info *curr_model=NULL;
int err;
int retval=FALSE;
if(forceinfo)
*forceinfo=NULL;
if(ret_model)
*ret_model=NULL;
if((err=get_dev_info_by_irq(irq,devinfo)))
{
printk("chandev_is_chandev get_dev_info_by_irq reported err=%X on irq %d\n"
"should not happen\n",err,irq);
return FALSE;
}
chandev_lock();
for_each(curr_model,chandev_models_head)
{
if(((curr_model->cu_type==devinfo->sid_data.cu_type)||(curr_model->cu_type==-1))&&
((curr_model->cu_model==devinfo->sid_data.cu_model)||(curr_model->cu_model==-1))&&
((curr_model->dev_type==devinfo->sid_data.dev_type)||(curr_model->dev_type==-1))&&
((curr_model->dev_model==devinfo->sid_data.dev_model)||(curr_model->dev_model==-1)))
{
retval=TRUE;
if(ret_model)
*ret_model=curr_model;
break;
}
}
for_each(curr_force,chandev_force_head)
{
if(((curr_force->read_lo_devno==devinfo->devno)&&
(curr_force->write_hi_devno==devinfo->devno)&&
(curr_force->devif_num!=-2))||
((curr_force->read_lo_devno>=devinfo->devno)&&
(curr_force->write_hi_devno<=devinfo->devno)&&
(curr_force->devif_num==-2)))
{
if(forceinfo)
*forceinfo=curr_force;
break;
}
}
chandev_unlock();
return(retval);
}
static void chandev_collect_devices(void)
{
int curr_irq,loopcnt=0;
s390_dev_info_t curr_devinfo;
chandev_model_info *curr_model;
for(curr_irq=get_irq_first();curr_irq>=0; curr_irq=get_irq_next(curr_irq))
{
/* check read chandev
* we had to do the cu_model check also because ctc devices
* have the same cutype & after asking some people
* the model numbers are given out pseudo randomly so
* we can't just take a range of them also the dev_type & models are 0
*/
loopcnt++;
if(loopcnt>0x10000)
{
printk(KERN_ERR"chandev_collect_devices detected infinite loop bug in get_irq_next\n");
break;
}
chandev_lock();
if(chandev_is_chandev(curr_irq,&curr_devinfo,NULL,&curr_model))
chandev_add(&curr_devinfo,curr_model);
chandev_unlock();
}
}
static int chandev_add_force(chandev_type chan_type, s32 devif_num,
u16 read_lo_devno, u16 write_hi_devno,
u16 data_devno,s32 memory_usage_in_k,
s16 port_protocol_no,u8 checksum_received_ip_pkts,
u8 use_hw_stats,char *host_name,
char *adapter_name,char *api_type)
{
chandev_force *new_chandev_force;
if(devif_num==-2&&read_lo_devno>write_hi_devno)
{
printk("chandev_add_force detected bad device range lo_devno=0x%04x hi_devno=0x%04x\n,",
(int)read_lo_devno,(int)write_hi_devno);
return(-1);
}
if(memory_usage_in_k<0)
{
printk("chandev_add_force memory_usage_in_k is bad\n");
return(-1);
}
if(chan_type==chandev_type_claw)
{
int host_name_len=strlen(host_name),
adapter_name_len=strlen(adapter_name),
api_type_len=strlen(api_type);
if(host_name_len>=CLAW_NAMELEN||host_name_len==0||
adapter_name_len>=CLAW_NAMELEN||adapter_name_len==0||
api_type_len>=CLAW_NAMELEN||api_type_len==0)
return(-1);
}
if((new_chandev_force=chandev_alloc(sizeof(chandev_force))))
{
new_chandev_force->chan_type=chan_type;
new_chandev_force->devif_num=devif_num;
new_chandev_force->read_lo_devno=read_lo_devno;
new_chandev_force->write_hi_devno=write_hi_devno;
new_chandev_force->data_devno=data_devno;
new_chandev_force->memory_usage_in_k=memory_usage_in_k;
new_chandev_force->port_protocol_no=port_protocol_no;
new_chandev_force->checksum_received_ip_pkts=checksum_received_ip_pkts;
new_chandev_force->use_hw_stats=use_hw_stats;
if(chan_type==chandev_type_claw)
{
strcpy(new_chandev_force->claw.host_name,host_name);
strcpy(new_chandev_force->claw.adapter_name,adapter_name);
strcpy(new_chandev_force->claw.api_type,api_type);
}
chandev_add_to_list((list **)&chandev_force_head,new_chandev_force);
}
return(0);
}
static void chandev_del_force(int read_lo_devno)
{
chandev_force *curr_force,*next_force;
chandev_lock();
for_each_allow_delete(curr_force,next_force,chandev_force_head)
{
if(curr_force->read_lo_devno==read_lo_devno||read_lo_devno==-1)
chandev_free_listmember((list **)&chandev_force_head,
(list *)curr_force);
}
chandev_unlock();
}
static void chandev_shutdown(chandev_activelist *curr_device)
{
int err=0;
chandev_lock();
/* unregister_netdev calls the dev->close so we shouldn't do this */
/* this otherwise we crash */
if(curr_device->unreg_dev)
{
curr_device->unreg_dev(curr_device->dev_ptr);
curr_device->unreg_dev=NULL;
}
if(curr_device->shutdownfunc)
{
err=curr_device->shutdownfunc(curr_device->dev_ptr);
}
if(err)
printk("chandev_shutdown unable to fully shutdown & unload %s err=%d\n"
"probably some upper layer still requires the device to exist\n",
curr_device->devname,err);
else
{
chandev_free_irq_by_irqinfo(curr_device->read_irqinfo);
chandev_free_irq_by_irqinfo(curr_device->write_irqinfo);
if(curr_device->data_irqinfo)
chandev_free_irq_by_irqinfo(curr_device->data_irqinfo);
chandev_free_listmember((list **)&chandev_activelist_head,
(list *)curr_device);
}
chandev_unlock();
}
static void chandev_shutdown_all(void)
{
while(chandev_activelist_head)
chandev_shutdown(chandev_activelist_head);
}
static void chandev_shutdown_by_name(char *devname)
{
chandev_activelist *curr_device;
chandev_lock();
for_each(curr_device,chandev_activelist_head)
if(strcmp(devname,curr_device->devname)==0)
{
chandev_shutdown(curr_device);
break;
}
chandev_unlock();
}
static chandev_activelist *chandev_active(u16 devno)
{
chandev_activelist *curr_device;
for_each(curr_device,chandev_activelist_head)
if(curr_device->read_irqinfo->sch.devno==devno||
curr_device->write_irqinfo->sch.devno==devno||
(curr_device->data_irqinfo&&curr_device->data_irqinfo->sch.devno==devno))
{
return(curr_device);
}
return(NULL);
}
static void chandev_shutdown_by_devno(u16 devno)
{
chandev_activelist *curr_device;
chandev_lock();
curr_device=chandev_active(devno);
if(curr_device)
chandev_shutdown(curr_device);
chandev_unlock();
}
static int chandev_pack_args(char *str)
{
char *newstr=str,*next;
int strcnt=1;
while(*str)
{
next=str+1;
/*remove dead spaces */
if(isspace(*str)&&isspace(*next))
{
str++;
continue;
}
if(isspace(*str))
{
*str=',';
goto pack_dn;
}
if(((*str)==';')&&(*next))
{
strcnt++;
*str=0;
}
pack_dn:
*newstr++=*str++;
}
*newstr=0;
return(strcnt);
}
typedef enum
{
isnull=0,
isstr=1,
isnum=2,
iscomma=4,
} chandev_strval;
static chandev_strval chandev_strcmp(char *teststr,char **str,long *endlong)
{
char *cur;
chandev_strval retval=isnull;
int len=strlen(teststr);
if(strncmp(teststr,*str,len)==0)
{
*str+=len;
retval=isstr;
cur=*str;
*endlong=simple_strtol(cur,str,0);
if(cur!=*str)
retval|=isnum;
if(**str==',')
{
retval|=iscomma;
*str+=1;
}
else if(**str!=0)
retval=isnull;
}
return(retval);
}
int chandev_initdevice(chandev_probeinfo *probeinfo,void *dev_ptr,u8 port_no,char *devname,chandev_category category,chandev_unregfunc unreg_dev)
{
chandev_activelist *newdevice,*curr_device;
chandev_interrupt_check("chandev_initdevice");
if(probeinfo->newdevice!=NULL)
{
printk("probeinfo->newdevice!=NULL in chandev_initdevice for %s",devname);
return(-EPERM);
}
chandev_lock();
for_each(curr_device,chandev_activelist_head)
{
if(strcmp(curr_device->devname,devname)==0)
{
printk("chandev_initdevice detected duplicate devicename %s\n",devname);
chandev_unlock();
return(-EPERM);
}
}
if((newdevice=chandev_allocstr(devname,offsetof(chandev_activelist,devname))))
{
newdevice->read_irqinfo=chandev_get_irqinfo_by_irq(probeinfo->read.irq);
newdevice->write_irqinfo=chandev_get_irqinfo_by_irq(probeinfo->write.irq);
if(probeinfo->data_exists)
newdevice->data_irqinfo=chandev_get_irqinfo_by_irq(probeinfo->data.irq);
chandev_unlock();
if(newdevice->read_irqinfo==NULL||newdevice->write_irqinfo==NULL||
(probeinfo->data_exists&&newdevice->data_irqinfo==NULL))
{
printk("chandev_initdevice, it appears that chandev_request_irq was not "
"called for devname=%s read_irq=%d write_irq=%d data_irq=%d\n",
devname,probeinfo->read.irq,probeinfo->write.irq,probeinfo->data.irq);
kfree(newdevice);
return(-EPERM);
}
newdevice->chan_type=probeinfo->chan_type;
newdevice->dev_ptr=dev_ptr;
newdevice->port_no=port_no;
newdevice->memory_usage_in_k=probeinfo->memory_usage_in_k;
newdevice->category=category;
newdevice->unreg_dev=unreg_dev;
probeinfo->newdevice=newdevice;
return(0);
}
chandev_unlock();
return(-ENOMEM);
}
char *chandev_build_device_name(chandev_probeinfo *probeinfo,char *destnamebuff,char *basename,int buildfullname)
{
if (chandev_use_devno_names&&(!probeinfo->device_forced||probeinfo->devif_num==-1))
sprintf(destnamebuff,"%s%04x",basename,(int)probeinfo->read.devno);
else
{
if(probeinfo->devif_num==-1)
{
if(buildfullname)
{
int idx,len=strlen(basename);
chandev_activelist *curr_device;
for(idx=0;idx<0xffff;idx++)
{
for_each(curr_device,chandev_activelist_head)
{
if(strncmp(curr_device->devname,basename,len)==0)
{
char numbuff[10];
sprintf(numbuff,"%d",idx);
if(strcmp(&curr_device->devname[len],numbuff)==0)
continue;
}
}
sprintf(destnamebuff,"%s%d",basename,idx);
return(destnamebuff);
}
printk("chandev_build_device_name was usable to build a unique name for %s\n",basename);
return(NULL);
}
else
sprintf(destnamebuff,"%s%%d",basename);
}
else
{
sprintf(destnamebuff,"%s%d",basename,(int)probeinfo->devif_num);
}
}
return(destnamebuff);
}
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
struct net_device *chandev_init_netdev(chandev_probeinfo *probeinfo,
char *basename, struct net_device *dev, int sizeof_priv,
struct net_device *(*init_netdevfunc)
(struct net_device *dev, int sizeof_priv))
#else
struct device *chandev_init_netdev(chandev_probeinfo *probeinfo,
char *basename, struct device *dev, int sizeof_priv,
struct device *(*init_netdevfunc)
(struct device *dev, int sizeof_priv))
#endif
{
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
struct net_device *retdevice=NULL;
int new_device = FALSE;
#else
struct device *retdevice=NULL;
#endif
chandev_interrupt_check("chandev_init_netdev");
if (!init_netdevfunc)
{
printk("init_netdevfunc=NULL in chandev_init_netdev, it should not be valid.\n");
return NULL;
}
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
/* Allocate a device if one is not provided. */
if (dev == NULL)
{
/* ensure 32-byte alignment of the private area */
int alloc_size = sizeof (*dev) + sizeof_priv + 31;
dev = (struct net_device *) kmalloc (alloc_size, GFP_KERNEL);
if (dev == NULL)
{
printk(KERN_ERR "chandev_initnetdevice: Unable to allocate device memory.\n");
return NULL;
}
memset(dev, 0, alloc_size);
if (sizeof_priv)
dev->priv = (void *) (((long)(dev + 1) + 31) & ~31);
new_device=TRUE;
}
chandev_build_device_name(probeinfo,dev->name,basename,FALSE);
#endif
retdevice=init_netdevfunc(dev,sizeof_priv);
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
/* Register device if necessary */
/* we need to do this as init_netdev doesn't call register_netdevice */
/* for already allocated devices */
if (retdevice && new_device)
register_netdev(retdevice);
#endif
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
/* We allocated it, so we should free it on error */
if (!retdevice && new_device)
kfree(dev);
#endif
return retdevice;
}
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
struct net_device *chandev_initnetdevice(chandev_probeinfo *probeinfo,u8 port_no,
struct net_device *dev, int sizeof_priv, char *basename,
struct net_device *(*init_netdevfunc)(struct net_device *dev, int sizeof_priv),
void (*unreg_netdevfunc)(struct net_device *dev))
#else
struct device *chandev_initnetdevice(chandev_probeinfo *probeinfo,u8 port_no,
struct device *dev, int sizeof_priv, char *basename,
struct device *(*init_netdevfunc)(struct device *dev, int sizeof_priv),
void (*unreg_netdevfunc)(struct device *dev))
#endif
{
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
struct net_device *retdevice=NULL;
int new_device=(dev==NULL);
#else
struct device *retdevice=NULL;
#endif
if (!unreg_netdevfunc)
{
printk("unreg_netdevfunc=NULL in chandev_initnetdevice, it should not be valid.\n");
return NULL;
}
chandev_interrupt_check("chandev_initnetdevice");
retdevice=chandev_init_netdev(probeinfo,basename,dev,sizeof_priv,init_netdevfunc);
if (retdevice)
{
if (chandev_initdevice(probeinfo,retdevice,port_no,retdevice->name,
chandev_category_network_device,(chandev_unregfunc)unreg_netdevfunc))
{
unreg_netdevfunc(retdevice);
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
/* We allocated it, so we should free it on error */
if(new_device)
kfree(dev);
#endif
retdevice = NULL;
}
}
return retdevice;
}
static int chandev_compare_chpid_info(chandev_subchannel_info *chan1,
chandev_subchannel_info *chan2)
{
return (chan1->pim!=chan2->pim || *chan1->chpid!=*chan2->chpid);
}
static int chandev_compare_cu_dev_info(chandev_subchannel_info *chan1,
chandev_subchannel_info *chan2)
{
return ((chan1->cu_type != chan2->cu_type)||
(chan1->cu_model != chan2->cu_model)||
(chan1->dev_type != chan2->dev_type)||
(chan1->dev_model != chan2->dev_model));
}
static int chandev_compare_subchannel_info(chandev_subchannel_info *chan1,
chandev_subchannel_info *chan2)
{
return((chan1->devno == chan2->devno) &&
(chan1->cu_type == chan2->cu_type) &&
(chan1->cu_model == chan2->cu_model) &&
(chan1->dev_type == chan2->dev_type) &&
(chan1->dev_model == chan2->dev_model) &&
(chan1->pim == chan2->pim) &&
(*chan1->chpid == *chan2->chpid));
}
static int chandev_doprobe(chandev_force *force,chandev *read,
chandev *write,chandev *data)
{
chandev_probelist *probe;
chandev_model_info *model_info;
chandev_probeinfo probeinfo;
int rc=-1,hint=-1;
chandev_activelist *newdevice;
chandev_probefunc probefunc;
chandev_parms *curr_parms;
chandev_model_info dummy_model_info;
memset(&probeinfo,0,sizeof(probeinfo));
memset(&dummy_model_info,0,sizeof(dummy_model_info));
probeinfo.device_forced=(force!=NULL);
probeinfo.chpid_info_inconsistent=chandev_compare_chpid_info(&read->sch,&write->sch)||
(data&&chandev_compare_chpid_info(&read->sch,&data->sch));
probeinfo.cu_dev_info_inconsistent=chandev_compare_cu_dev_info(&read->sch,&write->sch)||
(data&&chandev_compare_cu_dev_info(&read->sch,&data->sch));
if(read->model_info)
model_info=read->model_info;
else
{
dummy_model_info.chan_type=chandev_type_none;
dummy_model_info.max_port_no=16;
model_info=&dummy_model_info;
}
for_each(probe,chandev_probelist_head)
{
if(force)
probeinfo.chan_type = ( probe->chan_type & force->chan_type );
else
{
if(chandev_cautious_auto_detect)
probeinfo.chan_type = ( probe->chan_type == model_info->chan_type ?
probe->chan_type : chandev_type_none );
else
probeinfo.chan_type = ( probe->chan_type & model_info->chan_type );
}
if(probeinfo.chan_type && (force || ( !probeinfo.cu_dev_info_inconsistent &&
((probe->chan_type&(chandev_type_ctc|chandev_type_escon)) ||
!probeinfo.chpid_info_inconsistent))))
{
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
if(chandev_use_devno_names)
probeinfo.devif_num=read->sch.devno;
else
#endif
probeinfo.devif_num=-1;
probeinfo.read=read->sch;
probeinfo.write=write->sch;
if(data)
{
probeinfo.data=data->sch;
probeinfo.data_exists=TRUE;
}
probeinfo.max_port_no=(force&&(force->port_protocol_no!=-1) ?
force->port_protocol_no : model_info->max_port_no);
for_each(curr_parms,chandev_parms_head)
{
if(probe->chan_type==curr_parms->chan_type&&
read->sch.devno>=curr_parms->lo_devno&&
read->sch.devno<=curr_parms->hi_devno)
{
probeinfo.parmstr=curr_parms->parmstr;
break;
}
}
if(force)
{
if(force->chan_type==chandev_type_claw)
memcpy(&probeinfo.claw,&force->claw,sizeof(chandev_claw_info));
probeinfo.port_protocol_no=force->port_protocol_no;
if(force->devif_num==-1&&force->devif_num==-2)
probeinfo.devif_num=-1;
else
probeinfo.devif_num=force->devif_num;
probeinfo.memory_usage_in_k=force->memory_usage_in_k;
probeinfo.checksum_received_ip_pkts=force->checksum_received_ip_pkts;
probeinfo.use_hw_stats=force->use_hw_stats;
}
else
{
probeinfo.port_protocol_no=0;
probeinfo.checksum_received_ip_pkts=model_info->default_checksum_received_ip_pkts;
probeinfo.use_hw_stats=model_info->default_use_hw_stats;
probeinfo.memory_usage_in_k=0;
if(probe->chan_type&chandev_type_lcs)
{
hint=(read->sch.devno&0xFF)>>1;
if(hint>model_info->max_port_no)
{
/* The card is possibly emulated e.g P/390 */
/* or possibly configured to use a shared */
/* port configured by osa-sf. */
hint=0;
}
}
}
probeinfo.hint_port_no=hint;
probefunc=probe->probefunc;
rc=probefunc(&probeinfo);
if(rc==0)
{
newdevice=probeinfo.newdevice;
if(newdevice)
{
newdevice->probefunc=probe->probefunc;
newdevice->shutdownfunc=probe->shutdownfunc;
newdevice->msck_notfunc=probe->msck_notfunc;
probe->devices_found++;
chandev_add_to_list((list **)&chandev_activelist_head,
newdevice);
chandev_add_to_userland_notify_list(chandev_start,
newdevice->devname,chandev_status_good,chandev_status_good);
}
else
{
printk("chandev_initdevice either failed or wasn't called for device read_irq=0x%04x\n",probeinfo.read.irq);
}
break;
}
}
}
chandev_remove(read);
chandev_remove(write);
if(data)
chandev_remove(data);
return(rc);
}
static int chandev_request_irq_from_irqinfo(chandev_irqinfo *irqinfo,chandev *this_chandev)
{
int retval=s390_request_irq_special(irqinfo->sch.irq,
irqinfo->handler,
chandev_not_oper_handler,
irqinfo->irqflags,
irqinfo->devname,
irqinfo->dev_id);
if(retval==0)
{
irqinfo->msck_status=chandev_status_good;
this_chandev->owned=TRUE;
}
return(retval);
}
static void chandev_irqallocerr(chandev_irqinfo *irqinfo,int err)
{
printk("chandev_probe failed to realloc irq=%d for %s err=%d\n",irqinfo->sch.irq,irqinfo->devname,err);
}
static void chandev_call_notification_func(chandev_activelist *curr_device,
chandev_irqinfo *curr_irqinfo,
chandev_msck_status prevstatus)
{
if(curr_irqinfo->msck_status!=prevstatus)
{
chandev_msck_status new_msck_status=curr_irqinfo->msck_status;
if(curr_irqinfo->msck_status==chandev_status_good)
{
if(curr_device->read_irqinfo->msck_status==chandev_status_good&&
curr_device->write_irqinfo->msck_status==chandev_status_good)
{
if(curr_device->data_irqinfo)
{
if(curr_device->data_irqinfo->msck_status==chandev_status_good)
new_msck_status=chandev_status_all_chans_good;
}
else
new_msck_status=chandev_status_all_chans_good;
}
}
if(curr_device->msck_notfunc)
{
curr_device->msck_notfunc(curr_device->dev_ptr,
curr_irqinfo->sch.irq,
prevstatus,new_msck_status);
}
if(new_msck_status!=chandev_status_good)
{
/* No point in sending a machine check if only one channel is good */
chandev_add_to_userland_notify_list(chandev_msck,curr_device->devname,
prevstatus,curr_irqinfo->msck_status);
}
}
}
static int chandev_find_eligible_channels(chandev *first_chandev_to_check,
chandev **read,chandev **write,
chandev **data,chandev **next,
chandev_type chan_type)
{
chandev *curr_chandev;
int eligible_found=FALSE,changed;
*next=first_chandev_to_check->next;
*read=*write=*data=NULL;
for_each(curr_chandev,first_chandev_to_check)
if((curr_chandev->sch.devno&1)==0&&curr_chandev->model_info->chan_type!=chandev_type_claw)
{
*read=curr_chandev;
if(chan_type==chandev_type_none)
chan_type=(*read)->model_info->chan_type;
break;
}
if(*read)
{
for_each(curr_chandev,(chandev *)chandev_head.head)
if((((*read)->sch.devno|1)==curr_chandev->sch.devno)&&
(chandev_compare_cu_dev_info(&(*read)->sch,&curr_chandev->sch)==0)&&
((chan_type&(chandev_type_ctc|chandev_type_escon))||
chandev_compare_chpid_info(&(*read)->sch,&curr_chandev->sch)==0))
{
*write=curr_chandev;
break;
}
}
if((chan_type&chandev_type_qeth))
{
if(*write)
{
for_each(curr_chandev,(chandev *)chandev_head.head)
if((curr_chandev!=*read&&curr_chandev!=*write)&&
(chandev_compare_cu_dev_info(&(*read)->sch,&curr_chandev->sch)==0)&&
(chandev_compare_chpid_info(&(*read)->sch,&curr_chandev->sch)==0))
{
*data=curr_chandev;
break;
}
if(*data)
eligible_found=TRUE;
}
}
else
if(*write)
eligible_found=TRUE;
if(eligible_found)
{
do
{
changed=FALSE;
if(*next&&
((*read&&(*read==*next))||
(*write&&(*write==*next))||
(*data&&(*data==*next))))
{
*next=(*next)->next;
changed=TRUE;
}
}while(changed==TRUE);
}
return(eligible_found);
}
static chandev *chandev_get_free_chandev_by_devno(int devno)
{
chandev *curr_chandev;
if(devno==-1)
return(NULL);
for_each(curr_chandev,(chandev *)chandev_head.head)
if(curr_chandev->sch.devno==devno)
{
if(chandev_active(devno))
return(NULL);
else
return(curr_chandev);
}
return(NULL);
}
static void chandev_probe(void)
{
chandev *read_chandev,*write_chandev,*data_chandev,*curr_chandev,*next_chandev;
chandev_force *curr_force;
chandev_noauto_range *curr_noauto;
chandev_activelist *curr_device;
chandev_irqinfo *curr_irqinfo;
s390_dev_info_t curr_devinfo;
int err;
int auto_msck_recovery;
chandev_msck_status prevstatus;
chandev_msck_range *curr_msck_range;
chandev_interrupt_check("chandev_probe");
chandev_read_conf_if_necessary();
chandev_collect_devices();
chandev_lock();
for_each(curr_irqinfo,chandev_irqinfo_head)
{
if((curr_device=chandev_get_activelist_by_irq(curr_irqinfo->sch.irq)))
{
prevstatus=curr_irqinfo->msck_status;
if(curr_irqinfo->msck_status!=chandev_status_good)
{
curr_chandev=chandev_get_by_irq(curr_irqinfo->sch.irq);
if(curr_chandev)
{
auto_msck_recovery=curr_chandev->model_info->
auto_msck_recovery;
}
else
goto remove;
for_each(curr_msck_range,chandev_msck_range_head)
{
if(curr_msck_range->lo_devno<=
curr_irqinfo->sch.devno&&
curr_msck_range->hi_devno>=
curr_irqinfo->sch.devno)
{
auto_msck_recovery=
curr_msck_range->
auto_msck_recovery;
break;
}
}
if((1<<(curr_irqinfo->msck_status-1))&auto_msck_recovery)
{
if(curr_irqinfo->msck_status==chandev_status_revalidate)
{
if((get_dev_info_by_irq(curr_irqinfo->sch.irq,&curr_devinfo)==0))
{
curr_irqinfo->sch.devno=curr_devinfo.devno;
curr_irqinfo->msck_status=chandev_status_good;
}
}
else
{
if(curr_chandev)
{
/* Has the device reappeared */
if(chandev_compare_subchannel_info(
&curr_chandev->sch,
&curr_device->read_irqinfo->sch)||
chandev_compare_subchannel_info(
&curr_chandev->sch,
&curr_device->write_irqinfo->sch)||
(curr_device->data_irqinfo&&
chandev_compare_subchannel_info(
&curr_chandev->sch,
&curr_device->data_irqinfo->sch)))
{
if((err=chandev_request_irq_from_irqinfo(curr_irqinfo,curr_chandev))==0)
curr_irqinfo->msck_status=chandev_status_good;
else
chandev_irqallocerr(curr_irqinfo,err);
}
}
}
}
}
chandev_call_notification_func(curr_device,curr_irqinfo,prevstatus);
}
/* This is required because the device can go & come back */
/* even before we realize it is gone owing to the waits in our kernel threads */
/* & the device will be marked as not owned but its status will be good */
/* & an attempt to accidentally reprobe it may be done. */
remove:
chandev_remove(chandev_get_by_irq(curr_irqinfo->sch.irq));
}
/* extra sanity */
for_each_allow_delete(curr_chandev,next_chandev,(chandev *)chandev_head.head)
if(curr_chandev->owned)
chandev_remove(curr_chandev);
for_each(curr_force,chandev_force_head)
{
if(curr_force->devif_num==-2)
{
for_each_allow_delete2(curr_chandev,next_chandev,(chandev *)chandev_head.head)
{
if(chandev_find_eligible_channels(curr_chandev,&read_chandev,
&write_chandev,&data_chandev,
&next_chandev,
curr_force->chan_type));
{
if((curr_force->read_lo_devno>=read_chandev->sch.devno)&&
(curr_force->write_hi_devno<=read_chandev->sch.devno)&&
(curr_force->read_lo_devno>=write_chandev->sch.devno)&&
(curr_force->write_hi_devno<=write_chandev->sch.devno)&&
(!data_chandev||(data_chandev&&
(curr_force->read_lo_devno>=data_chandev->sch.devno)&&
(curr_force->write_hi_devno<=data_chandev->sch.devno))))
chandev_doprobe(curr_force,read_chandev,write_chandev,
data_chandev);
}
}
}
else
{
read_chandev=chandev_get_free_chandev_by_devno(curr_force->read_lo_devno);
if(read_chandev)
{
write_chandev=chandev_get_free_chandev_by_devno(curr_force->write_hi_devno);
if(write_chandev)
{
if(curr_force->chan_type==chandev_type_qeth)
{
data_chandev=chandev_get_free_chandev_by_devno(curr_force->data_devno);
if(data_chandev==NULL)
printk("chandev_probe unable to force gigabit_ethernet driver invalid device no 0x%04x given\n",curr_force->data_devno);
}
else
data_chandev=NULL;
chandev_doprobe(curr_force,read_chandev,write_chandev,
data_chandev);
}
}
}
}
for_each_allow_delete(curr_chandev,next_chandev,(chandev *)chandev_head.head)
{
for_each(curr_noauto,chandev_noauto_head)
{
if(curr_chandev->sch.devno>=curr_noauto->lo_devno&&
curr_chandev->sch.devno<=curr_noauto->hi_devno)
{
chandev_remove(curr_chandev);
break;
}
}
}
for_each_allow_delete2(curr_chandev,next_chandev,(chandev *)chandev_head.head)
{
if(chandev_find_eligible_channels(curr_chandev,&read_chandev,
&write_chandev,&data_chandev,
&next_chandev,
chandev_type_none))
chandev_doprobe(NULL,read_chandev,write_chandev,
data_chandev);
}
chandev_remove_all();
chandev_unlock();
}
static void chandev_not_oper_func(int irq,int status)
{
chandev_irqinfo *curr_irqinfo;
chandev_activelist *curr_device;
chandev_lock();
for_each(curr_irqinfo,chandev_irqinfo_head)
if(curr_irqinfo->sch.irq==irq)
{
chandev_msck_status prevstatus=curr_irqinfo->msck_status;
switch(status)
{
/* Currently defined but not used in kernel */
/* Despite being in specs */
case DEVSTAT_NOT_OPER:
curr_irqinfo->msck_status=chandev_status_not_oper;
break;
#ifdef DEVSTAT_NO_PATH
/* Kernel hasn't this defined currently. */
/* Despite being in specs */
case DEVSTAT_NO_PATH:
curr_irqinfo->msck_status=chandev_status_no_path;
break;
#endif
case DEVSTAT_REVALIDATE:
curr_irqinfo->msck_status=chandev_status_revalidate;
break;
case DEVSTAT_DEVICE_GONE:
curr_irqinfo->msck_status=chandev_status_gone;
break;
}
if((curr_device=chandev_get_activelist_by_irq(irq)))
chandev_call_notification_func(curr_device,curr_irqinfo,prevstatus);
else
printk("chandev_not_oper_func received channel check for unowned irq %d",irq);
}
chandev_unlock();
}
static int chandev_msck_thread(void *unused)
{
int loopcnt,not_oper_probe_required=FALSE;
wait_queue_head_t wait;
chandev_not_oper_struct *new_not_oper;
/* This loop exists because machine checks tend to come in groups & we have
to wait for the other devnos to appear also */
init_waitqueue_head(&wait);
for(loopcnt=0;loopcnt<10||(jiffies-chandev_last_machine_check)<HZ;loopcnt++)
{
sleep_on_timeout(&wait,HZ);
}
atomic_set(&chandev_msck_thread_lock,1);
while(!atomic_compare_and_swap(TRUE,FALSE,&chandev_new_msck));
{
chandev_probe();
}
while(TRUE)
{
unsigned long flags;
spin_lock_irqsave(&chandev_not_oper_spinlock,flags);
new_not_oper=(chandev_not_oper_struct *)dequeue_head(&chandev_not_oper_head);
spin_unlock_irqrestore(&chandev_not_oper_spinlock,flags);
if(new_not_oper)
{
chandev_not_oper_func(new_not_oper->irq,new_not_oper->status);
not_oper_probe_required=TRUE;
kfree(new_not_oper);
}
else
break;
}
if(not_oper_probe_required)
chandev_probe();
return(0);
}
static void chandev_msck_task(void *unused)
{
if(kernel_thread(chandev_msck_thread,NULL,SIGCHLD)<0)
{
atomic_set(&chandev_msck_thread_lock,1);
printk("error making chandev_msck_thread kernel thread\n");
}
}
static char *argstrs[]=
{
"noauto",
"del_noauto",
"ctc",
"escon",
"lcs",
"osad",
"qeth",
"claw",
"add_parms",
"del_parms",
"del_force",
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
"use_devno_names",
"dont_use_devno_names",
#endif
"cautious_auto_detect",
"non_cautious_auto_detect",
"add_model",
"del_model",
"auto_msck",
"del_auto_msck",
"del_all_models",
"reset_conf_clean",
"reset_conf",
"shutdown",
"reprobe",
"unregister_probe",
"unregister_probe_by_chan_type",
"read_conf",
"dont_read_conf",
"persist"
};
typedef enum
{
stridx_mult=256,
first_stridx=0,
noauto_stridx=first_stridx,
del_noauto_stridx,
ctc_stridx,
escon_stridx,
lcs_stridx,
osad_stridx,
qeth_stridx,
claw_stridx,
add_parms_stridx,
del_parms_stridx,
del_force_stridx,
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
use_devno_names_stridx,
dont_use_devno_names_stridx,
#endif
cautious_auto_detect_stridx,
non_cautious_auto_detect_stridx,
add_model_stridx,
del_model_stridx,
auto_msck_stridx,
del_auto_msck_stridx,
del_all_models_stridx,
reset_conf_clean_stridx,
reset_conf_stridx,
shutdown_stridx,
reprobe_stridx,
unregister_probe_stridx,
unregister_probe_by_chan_type_stridx,
read_conf_stridx,
dont_read_conf_stridx,
persist_stridx,
last_stridx,
} chandev_str_enum;
static void chandev_add_noauto(u16 lo_devno,u16 hi_devno)
{
chandev_noauto_range *new_range;
if((new_range=chandev_alloc(sizeof(chandev_noauto_range))))
{
new_range->lo_devno=lo_devno;
new_range->hi_devno=hi_devno;
chandev_add_to_list((list **)&chandev_noauto_head,new_range);
}
}
static void chandev_add_msck_range(u16 lo_devno,u16 hi_devno,
int auto_msck_recovery)
{
chandev_msck_range *new_range;
if((new_range=chandev_alloc(sizeof(chandev_msck_range))))
{
new_range->lo_devno=lo_devno;
new_range->hi_devno=hi_devno;
new_range->auto_msck_recovery=auto_msck_recovery;
chandev_add_to_list((list **)&chandev_msck_range_head,new_range);
}
}
static char chandev_keydescript[]=
"\nchan_type key bitfield ctc=0x1,escon=0x2,lcs=0x4,osad=0x8,qeth=0x10,claw=0x20\n";
#if CONFIG_ARCH_S390X
/* We need this as we sometimes use this to evaluate pointers */
typedef long chandev_int;
#else
typedef int chandev_int;
#endif
#if (LINUX_VERSION_CODE<KERNEL_VERSION(2,3,0)) || (CONFIG_ARCH_S390X)
/*
* Read an int from an option string; if available accept a subsequent
* comma as well.
*
* Return values:
* 0 : no int in string
* 1 : int found, no subsequent comma
* 2 : int found including a subsequent comma
*/
static chandev_int chandev_get_option(char **str,chandev_int *pint)
{
char *cur = *str;
if (!cur || !(*cur)) return 0;
*pint = simple_strtol(cur,str,0);
if (cur==*str) return 0;
if (**str==',') {
(*str)++;
return 2;
}
return 1;
}
static char *chandev_get_options(char *str, int nints, chandev_int *ints)
{
int res,i=1;
while (i<nints)
{
res = chandev_get_option(&str, ints+i);
if (res==0) break;
i++;
if (res==1) break;
}
ints[0] = i-1;
return(str);
}
#else
#define chandev_get_option get_option
#define chandev_get_options get_options
#endif
/*
* Read an string from an option string; if available accept a subsequent
* comma as well & set this comma to a null character when returning the string.
*
* Return values:
* 0 : no string found
* 1 : string found, no subsequent comma
* 2 : string found including a subsequent comma
*/
static int chandev_get_string(char **instr,char **outstr)
{
char *cur = *instr;
if (!cur ||*cur==0)
{
*outstr=NULL;
return 0;
}
*outstr=*instr;
for(;;)
{
if(*(++cur)==',')
{
*cur=0;
*instr=cur+1;
return 2;
}
else if(*cur==0)
{
*instr=cur+1;
return 1;
}
}
}
static int chandev_setup(int in_read_conf,char *instr,char *errstr,int lineno)
{
chandev_strval val=isnull;
chandev_str_enum stridx;
long endlong;
chandev_type chan_type;
char *str,*currstr,*interpretstr=NULL;
int cnt,strcnt;
int retval=0;
#define CHANDEV_MAX_EXTRA_INTS 12
chandev_int ints[CHANDEV_MAX_EXTRA_INTS+1];
currstr=alloca(strlen(instr)+1);
strcpy(currstr,instr);
strcnt=chandev_pack_args(currstr);
for(cnt=1;cnt<=strcnt;cnt++)
{
interpretstr=currstr;
memset(ints,0,sizeof(ints));
for(stridx=first_stridx;stridx<last_stridx;stridx++)
{
str=currstr;
if((val=chandev_strcmp(argstrs[stridx],&str,&endlong)))
break;
}
currstr=str;
if(val)
{
val=(((chandev_strval)stridx)*stridx_mult)+(val&~isstr);
switch(val)
{
case (add_parms_stridx*stridx_mult)|iscomma:
currstr=chandev_get_options(currstr,4,ints);
if(*currstr&&ints[0]>=1)
{
if(ints[0]==1)
{
ints[2]=0;
ints[3]=0xffff;
}
else if(ints[0]==2)
ints[3]=ints[2];
chandev_add_parms(ints[1],ints[2],ints[3],currstr);
goto NextOption;
}
else
goto BadArgs;
break;
case (claw_stridx*stridx_mult)|isnum|iscomma:
case (claw_stridx*stridx_mult)|iscomma:
currstr=chandev_get_options(str,6,ints);
break;
default:
if(val&iscomma)
currstr=chandev_get_options(str,CHANDEV_MAX_EXTRA_INTS,ints);
break;
}
switch(val)
{
case noauto_stridx*stridx_mult:
case (noauto_stridx*stridx_mult)|iscomma:
switch(ints[0])
{
case 0:
chandev_free_all_list((list **)&chandev_noauto_head);
chandev_add_noauto(0,0xffff);
break;
case 1:
ints[2]=ints[1];
case 2:
chandev_add_noauto(ints[1],ints[2]);
break;
default:
goto BadArgs;
}
break;
case (auto_msck_stridx*stridx_mult)|iscomma:
switch(ints[0])
{
case 1:
chandev_free_all_list((list **)&chandev_msck_range_head);
chandev_add_msck_range(0,0xffff,ints[1]);
break;
case 2:
chandev_add_msck_range(ints[1],ints[1],ints[2]);
break;
case 3:
chandev_add_msck_range(ints[1],ints[2],ints[3]);
break;
default:
goto BadArgs;
}
case del_auto_msck_stridx*stridx_mult:
case (del_auto_msck_stridx*stridx_mult)|iscomma:
switch(ints[0])
{
case 0:
chandev_free_all_list((list **)&chandev_msck_range_head);
break;
case 1:
chandev_del_msck(ints[1]);
default:
goto BadArgs;
}
case del_noauto_stridx*stridx_mult:
chandev_free_all_list((list **)&chandev_noauto_head);
break;
case (del_noauto_stridx*stridx_mult)|iscomma:
if(ints[0]==1)
chandev_del_noauto(ints[1]);
else
goto BadArgs;
break;
case (qeth_stridx*stridx_mult)|isnum|iscomma:
if(ints[0]<3||ints[0]>7)
goto BadArgs;
chandev_add_force(chandev_type_qeth,endlong,ints[1],ints[2],
ints[3],ints[4],ints[5],ints[6],ints[7],
NULL,NULL,NULL);
break;
case (ctc_stridx*stridx_mult)|isnum|iscomma:
case (escon_stridx*stridx_mult)|isnum|iscomma:
case (lcs_stridx*stridx_mult)|isnum|iscomma:
case (osad_stridx*stridx_mult)|isnum|iscomma:
case (ctc_stridx*stridx_mult)|iscomma:
case (escon_stridx*stridx_mult)|iscomma:
case (lcs_stridx*stridx_mult)|iscomma:
case (osad_stridx*stridx_mult)|iscomma:
switch(val&~(isnum|iscomma))
{
case (ctc_stridx*stridx_mult):
chan_type=chandev_type_ctc;
break;
case (escon_stridx*stridx_mult):
chan_type=chandev_type_escon;
break;
case (lcs_stridx*stridx_mult):
chan_type=chandev_type_lcs;
break;
case (osad_stridx*stridx_mult):
chan_type=chandev_type_osad;
break;
case (qeth_stridx*stridx_mult):
chan_type=chandev_type_qeth;
break;
default:
goto BadArgs;
}
if((val&isnum)==0)
endlong=-2;
if(ints[0]<2||ints[0]>6)
goto BadArgs;
chandev_add_force(chan_type,endlong,ints[1],ints[2],
0,ints[3],ints[4],ints[5],ints[6],
NULL,NULL,NULL);
break;
case (claw_stridx*stridx_mult)|isnum|iscomma:
case (claw_stridx*stridx_mult)|iscomma:
if(ints[0]>=2&&ints[0]<=5)
{
char *host_name,*adapter_name,*api_type;
char *clawstr=alloca(strlen(currstr)+1);
strcpy(clawstr,currstr);
if(!(chandev_get_string(&clawstr,&host_name)==2&&
chandev_get_string(&clawstr,&adapter_name)==2&&
chandev_get_string(&clawstr,&api_type)==1&&
chandev_add_force(chandev_type_claw,
endlong,ints[1],ints[2],0,
ints[3],0,ints[4],ints[5],
host_name,adapter_name,api_type)==0))
goto BadArgs;
}
else
goto BadArgs;
break;
case (del_parms_stridx*stridx_mult):
ints[1]=-1;
case (del_parms_stridx*stridx_mult)|iscomma:
if(ints[0]==0)
ints[1]=-1;
if(ints[0]<=1)
ints[2]=FALSE;
if(ints[0]<=2)
ints[3]=-1;
if(ints[0]>3)
goto BadArgs;
chandev_remove_parms(ints[1],ints[2],ints[3]);
break;
case (del_force_stridx*stridx_mult)|iscomma:
if(ints[0]!=1)
goto BadArgs;
chandev_del_force(ints[1]);
break;
case (del_force_stridx*stridx_mult):
chandev_del_force(-1);
break;
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
case (use_devno_names_stridx*stridx_mult):
chandev_use_devno_names=TRUE;
break;
case (dont_use_devno_names_stridx*stridx_mult):
chandev_use_devno_names=FALSE;
break;
#endif
case (cautious_auto_detect_stridx*stridx_mult):
chandev_cautious_auto_detect=TRUE;
break;
case (non_cautious_auto_detect_stridx*stridx_mult):
chandev_cautious_auto_detect=FALSE;
break;
case (add_model_stridx*stridx_mult)|iscomma:
if(ints[0]<3)
goto BadArgs;
if(ints[0]==3)
ints[4]=-1;
if(ints[0]<=4)
ints[5]=-1;
if(ints[0]<=5)
ints[6]=-1;
if(ints[0]<=6)
ints[7]=default_msck_bits;
if(ints[0]<=7)
ints[8]=FALSE;
if(ints[0]<=8)
ints[9]=FALSE;
ints[0]=7;
chandev_add_model(ints[1],ints[2],ints[3],
ints[4],ints[5],ints[6],ints[7],ints[8],ints[9]);
break;
case (del_model_stridx*stridx_mult)|iscomma:
if(ints[0]<2||ints[0]>4)
goto BadArgs;
if(ints[0]<3)
ints[3]=-2;
if(ints[0]<4)
ints[4]=-2;
ints[0]=4;
chandev_del_model(ints[1],ints[2],ints[3],ints[4]);
break;
case del_all_models_stridx*stridx_mult:
chandev_remove_all_models();
break;
case reset_conf_stridx*stridx_mult:
chandev_reset();
chandev_init_default_models();
break;
case reset_conf_clean_stridx*stridx_mult:
chandev_reset();
break;
case shutdown_stridx*stridx_mult:
chandev_shutdown_all();
break;
case (shutdown_stridx*stridx_mult)|iscomma:
switch(ints[0])
{
case 0:
if(strlen(str))
chandev_shutdown_by_name(str);
else
goto BadArgs;
break;
case 1:
chandev_shutdown_by_devno(ints[1]);
break;
default:
goto BadArgs;
}
break;
case reprobe_stridx*stridx_mult:
chandev_probe();
break;
case unregister_probe_stridx*stridx_mult:
chandev_free_all_list((list **)&chandev_probelist_head);
break;
case (unregister_probe_stridx*stridx_mult)|iscomma:
if(ints[0]!=1)
goto BadArgs;
chandev_unregister_probe((chandev_probefunc)ints[1]);
break;
case (unregister_probe_by_chan_type_stridx*stridx_mult)|iscomma:
if(ints[0]!=1)
goto BadArgs;
chandev_unregister_probe_by_chan_type((chandev_type)ints[1]);
break;
case read_conf_stridx*stridx_mult:
if(in_read_conf)
{
printk("attempt to recursively call read_conf\n");
goto BadArgs;
}
chandev_read_conf();
break;
case dont_read_conf_stridx*stridx_mult:
atomic_set(&chandev_conf_read,TRUE);
break;
case (persist_stridx*stridx_mult)|iscomma:
if(ints[0]==1)
chandev_persistent=ints[1];
else
goto BadArgs;
break;
default:
goto BadArgs;
}
}
else
goto BadArgs;
NextOption:
if(cnt<strcnt)
{
/* eat up stuff till next string */
while(*(currstr++));
}
}
retval=1;
BadArgs:
if(!retval)
{
printk("chandev_setup %s %s",(val==0 ? "unknown verb":"bad argument"),instr);
if(errstr)
{
printk("%s %d interpreted as %s",errstr,lineno,interpretstr);
if(strcnt>1)
{
if(cnt==strcnt)
printk(" after the last semicolon\n");
else
printk(" before semicolon no %d",cnt);
}
}
printk(".\n Type man chandev for more info.\n\n");
}
return(retval);
}
#define CHANDEV_KEYWORD "chandev="
static int chandev_setup_bootargs(char *str,int paramno)
{
int len;
char *copystr;
for(len=0;str[len]!=0&&!isspace(str[len]);len++);
copystr=alloca(len+1);
strncpy(copystr,str,len);
copystr[len]=0;
if(chandev_setup(FALSE,copystr,"at "CHANDEV_KEYWORD" bootparam no",paramno)==0)
return(0);
return(len);
}
/*
We can't parse using a __setup function as kmalloc isn't available
at this time.
*/
static void __init chandev_parse_args(void)
{
#define CHANDEV_KEYWORD "chandev="
extern char saved_command_line[];
int cnt,len,paramno=1;
len=strlen(saved_command_line)-sizeof(CHANDEV_KEYWORD);
for(cnt=0;cnt<len;cnt++)
{
if(strncmp(&saved_command_line[cnt],CHANDEV_KEYWORD,
sizeof(CHANDEV_KEYWORD)-1)==0)
{
cnt+=(sizeof(CHANDEV_KEYWORD)-1);
cnt+=chandev_setup_bootargs(&saved_command_line[cnt],paramno);
paramno++;
}
}
}
static int chandev_do_setup(int in_read_conf,char *buff,int size)
{
int curr,comment=FALSE,newline=FALSE,oldnewline=TRUE;
char *startline=NULL,*endbuff=&buff[size];
int lineno=0;
*endbuff=0;
for(;buff<=endbuff;curr++,buff++)
{
if(*buff==0xa||*buff==0xc||*buff==0)
{
if(*buff==0xa||*buff==0)
lineno++;
*buff=0;
newline=TRUE;
}
else
{
newline=FALSE;
if(*buff=='#')
comment=TRUE;
}
if(comment==TRUE)
*buff=0;
if(startline==NULL&&isalpha(*buff))
startline=buff;
if(startline&&(buff>startline)&&(oldnewline==FALSE)&&(newline==TRUE))
{
if((chandev_setup(in_read_conf,startline," on line no",lineno))==0)
return(-EINVAL);
startline=NULL;
}
if(newline)
comment=FALSE;
oldnewline=newline;
}
return(0);
}
static void chandev_read_conf(void)
{
#define CHANDEV_FILE "/etc/chandev.conf"
struct stat statbuf;
char *buff;
int curr,left,len,fd;
mm_segment_t oldfs;
/* if called from chandev_register_and_probe &
the driver is compiled into the kernel the
parameters will need to be passed in from
the kernel boot parameter line as the root
fs is not mounted yet, we can't wait here.
*/
if(in_interrupt()||current->fs->root==NULL)
return;
atomic_set(&chandev_conf_read,TRUE);
oldfs = get_fs();
set_fs(KERNEL_DS);
if(stat(CHANDEV_FILE,&statbuf)==0)
{
set_fs(USER_DS);
buff=vmalloc(statbuf.st_size+1);
if(buff)
{
set_fs(KERNEL_DS);
if((fd=open(CHANDEV_FILE,O_RDONLY,0))!=-1)
{
curr=0;
left=statbuf.st_size;
while((len=read(fd,&buff[curr],left))>0)
{
curr+=len;
left-=len;
}
close(fd);
}
set_fs(USER_DS);
chandev_do_setup(TRUE,buff,statbuf.st_size);
vfree(buff);
}
}
set_fs(oldfs);
}
static void chandev_read_conf_if_necessary(void)
{
if(in_interrupt()||current->fs->root==NULL)
return;
if(!atomic_compare_and_swap(FALSE,TRUE,&chandev_conf_read))
chandev_read_conf();
}
#ifdef CONFIG_PROC_FS
#define chandev_printf(exitchan,args...) \
splen=sprintf(spbuff,##args); \
spoffset+=splen; \
if(spoffset>offset) { \
spbuff+=splen; \
currlen+=splen; \
} \
if(currlen>=length) \
goto exitchan;
static void sprintf_msck(char *buff,int auto_msck_recovery)
{
chandev_msck_status idx;
int first_time=TRUE;
buff[0]=0;
for(idx=chandev_status_first_msck;idx<chandev_status_last_msck;idx++)
{
if((1<<(idx-1))&auto_msck_recovery)
{
buff+=sprintf(buff,"%s%s",(first_time ? "":","),
msck_status_strs[idx]);
first_time=FALSE;
}
}
}
static int chandev_read_proc(char *page, char **start, off_t offset,
int length, int *eof, void *data)
{
char *spbuff=*start=page;
int currlen=0,splen=0;
off_t spoffset=0;
chandev_model_info *curr_model;
chandev_noauto_range *curr_noauto;
chandev_force *curr_force;
chandev_activelist *curr_device;
chandev_probelist *curr_probe;
chandev_msck_range *curr_msck_range;
s390_dev_info_t curr_devinfo;
int pass,chandevs_detected,curr_irq,loopcnt;
chandev_irqinfo *read_irqinfo,*write_irqinfo,*data_irqinfo;
char buff[3][80];
chandev_lock();
chandev_printf(chan_exit,"\n%s\n"
"*'s for cu/dev type/models indicate don't cares\n",chandev_keydescript);
chandev_printf(chan_exit,"\ncautious_auto_detect: %s\n",chandev_cautious_auto_detect ? "on":"off");
chandev_printf(chan_exit,"\npersist = 0x%02x\n",chandev_persistent);
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
chandev_printf(chan_exit,"\nuse_devno_names: %s\n\n",chandev_use_devno_names ? "on":"off");
#endif
if(chandev_models_head)
{
chandev_printf(chan_exit,"Channels enabled for detection\n");
chandev_printf(chan_exit," chan cu cu dev dev max checksum use hw auto recovery\n");
chandev_printf(chan_exit," type type model type model port_no. received stats type\n");
chandev_printf(chan_exit,"==============================================================================\n");
for_each(curr_model,chandev_models_head)
{
chandev_sprint_devinfo(buff[0],curr_model->cu_type,
curr_model->cu_model,
curr_model->dev_type,
curr_model->dev_model);
sprintf_msck(buff[1],curr_model->auto_msck_recovery);
chandev_printf(chan_exit," 0x%02x %s%3d %s %s %s\n",
curr_model->chan_type,buff[0],
(int)curr_model->max_port_no,
curr_model->default_checksum_received_ip_pkts ? "yes":"no ",
curr_model->default_use_hw_stats ? "yes":"no ",
buff[1]);
}
}
if(chandev_noauto_head)
{
chandev_printf(chan_exit,"\nNo auto devno ranges\n");
chandev_printf(chan_exit," From To \n");
chandev_printf(chan_exit,"====================\n");
for_each(curr_noauto,chandev_noauto_head)
{
chandev_printf(chan_exit," 0x%04x 0x%04x\n",
curr_noauto->lo_devno,
curr_noauto->hi_devno);
}
}
if(chandev_msck_range_head)
{
chandev_printf(chan_exit,"\nAutomatic machine check recovery devno ranges\n");
chandev_printf(chan_exit," From To automatic recovery type\n");
chandev_printf(chan_exit,"===========================================\n");
for_each(curr_msck_range,chandev_msck_range_head)
{
sprintf_msck(buff[0],curr_msck_range->auto_msck_recovery);
chandev_printf(chan_exit," 0x%04x 0x%04x %s\n",
curr_msck_range->lo_devno,
curr_msck_range->hi_devno,buff[0])
}
}
if(chandev_force_head)
{
chandev_printf(chan_exit,"\nForced devices\n");
chandev_printf(chan_exit," chan defif read write data memory port ip hw host adapter api\n");
chandev_printf(chan_exit," type num devno devno devno usage(k) protocol no. chksum stats name name name\n");
chandev_printf(chan_exit,"===============================================================================================\n");
for_each(curr_force,chandev_force_head)
{
if(curr_force->memory_usage_in_k==0)
strcpy(buff[0],"default");
else
sprintf(buff[0],"%6d",curr_force->memory_usage_in_k);
chandev_printf(chan_exit," 0x%02x %3d 0x%04x 0x%04x 0x%04x %7s %3d %1d %1d%s",
(int)curr_force->chan_type,(int)curr_force->devif_num,
(int)curr_force->read_lo_devno,(int)curr_force->write_hi_devno,
(int)curr_force->data_devno,buff[0],
(int)curr_force->port_protocol_no,(int)curr_force->checksum_received_ip_pkts,
(int)curr_force->use_hw_stats,curr_force->chan_type==chandev_type_claw ? "":"\n");
if(curr_force->chan_type==chandev_type_claw)
{
chandev_printf(chan_exit," %9s %9s %9s\n",
curr_force->claw.host_name,
curr_force->claw.adapter_name,
curr_force->claw.api_type);
}
}
}
if(chandev_probelist_head)
{
#if CONFIG_ARCH_S390X
chandev_printf(chan_exit,"\nRegistered probe functions\n"
"probefunc shutdownfunc msck_notfunc chan devices devices\n"
" type found active\n"
"==================================================================================\n");
#else
chandev_printf(chan_exit,"\nRegistered probe functions\n"
"probefunc shutdownfunc msck_notfunc chan devices devices\n"
" type found active\n"
"===============================================================\n");
#endif
for_each(curr_probe,chandev_probelist_head)
{
int devices_active=0;
for_each(curr_device,chandev_activelist_head)
{
if(curr_device->probefunc==curr_probe->probefunc)
devices_active++;
}
chandev_printf(chan_exit,"0x%p 0x%p 0x%p 0x%02x %d %d\n",
curr_probe->probefunc,
curr_probe->shutdownfunc,
curr_probe->msck_notfunc,
curr_probe->chan_type,
curr_probe->devices_found,
devices_active);
}
}
if(chandev_activelist_head)
{
unsigned long long total_memory_usage_in_k=0;
chandev_printf(chan_exit,
"\nInitialised Devices\n"
" read write data read write data chan port dev dev memory read msck write msck data msck\n"
" irq irq irq devno devno devno type no. ptr name usage(k) status status status\n"
"=====================================================================================================================\n");
/* We print this list backwards for cosmetic reasons */
for(curr_device=chandev_activelist_head;
curr_device->next!=NULL;curr_device=curr_device->next);
while(curr_device)
{
read_irqinfo=curr_device->read_irqinfo;
write_irqinfo=curr_device->write_irqinfo;
data_irqinfo=curr_device->data_irqinfo;
if(data_irqinfo)
{
sprintf(buff[0],"0x%04x",data_irqinfo->sch.irq);
sprintf(buff[1],"0x%04x",(int)data_irqinfo->sch.devno);
}
else
{
strcpy(buff[0]," n/a ");
strcpy(buff[1]," n/a ");
}
if(curr_device->memory_usage_in_k<0)
{
sprintf(buff[2],"%d",(int)-curr_device->memory_usage_in_k);
total_memory_usage_in_k-=curr_device->memory_usage_in_k;
}
else
strcpy(buff[2]," n/a ");
chandev_printf(chan_exit,
"0x%04x 0x%04x %s 0x%04x 0x%04x %s 0x%02x %2d 0x%p %-10s %6s %-12s %-12s %-12s\n",
read_irqinfo->sch.irq,
write_irqinfo->sch.irq,
buff[0],
(int)read_irqinfo->sch.devno,
(int)write_irqinfo->sch.devno,
buff[1],
curr_device->chan_type,(int)curr_device->port_no,
curr_device->dev_ptr,curr_device->devname,
buff[2],
msck_status_strs[read_irqinfo->msck_status],
msck_status_strs[write_irqinfo->msck_status],
data_irqinfo ? msck_status_strs[data_irqinfo->msck_status] :
"not applicable");
get_prev((list *)chandev_activelist_head,
(list *)curr_device,
(list **)&curr_device);
}
chandev_printf(chan_exit,"\nTotal device memory usage %Luk.\n",total_memory_usage_in_k);
}
chandevs_detected=FALSE;
for(pass=FALSE;pass<=TRUE;pass++)
{
if(pass&&chandevs_detected)
{
chandev_printf(chan_exit,"\nchannels detected\n");
chandev_printf(chan_exit," chan cu cu dev dev in chandev\n");
chandev_printf(chan_exit," irq devno type type model type model pim chpids use reg.\n");
chandev_printf(chan_exit,"===============================================================================\n");
}
for(curr_irq=get_irq_first(),loopcnt=0;curr_irq>=0; curr_irq=get_irq_next(curr_irq),loopcnt++)
{
if(loopcnt>0x10000)
{
printk(KERN_ERR"chandev_read_proc detected infinite loop bug in get_irq_next\n");
goto chan_error;
}
if(chandev_is_chandev(curr_irq,&curr_devinfo,&curr_force,&curr_model))
{
schib_t *curr_schib;
curr_schib=s390_get_schib(curr_irq);
chandevs_detected=TRUE;
if(pass)
{
chandev_printf(chan_exit,"0x%04x 0x%04x 0x%02x 0x%04x 0x%02x 0x%04x 0x%02x 0x%02x 0x%016Lx %-5s%-5s\n",
curr_irq,curr_devinfo.devno,
( curr_force ? curr_force->chan_type :
( curr_model ? curr_model->chan_type :
chandev_type_none )),
(int)curr_devinfo.sid_data.cu_type,
(int)curr_devinfo.sid_data.cu_model,
(int)curr_devinfo.sid_data.dev_type,
(int)curr_devinfo.sid_data.dev_model,
(int)(curr_schib ? curr_schib->pmcw.pim : 0),
*(long long *)(curr_schib ? &curr_schib->pmcw.chpid[0] : 0),
(curr_devinfo.status&DEVSTAT_DEVICE_OWNED) ? "yes":"no ",
(chandev_get_irqinfo_by_irq(curr_irq) ? "yes":"no "));
}
}
}
}
if(chandev_parms_head)
{
chandev_parms *curr_parms;
chandev_printf(chan_exit,"\n driver specific parameters\n");
chandev_printf(chan_exit,"chan lo hi driver\n");
chandev_printf(chan_exit,"type devno devno parameters\n");
chandev_printf(chan_exit,"=============================================================================\n");
for_each(curr_parms,chandev_parms_head)
{
chandev_printf(chan_exit,"0x%02x 0x%04x 0x%04x %s\n",
curr_parms->chan_type,(int)curr_parms->lo_devno,
(int)curr_parms->hi_devno,curr_parms->parmstr);
}
}
chan_error:
*eof=TRUE;
chan_exit:
if(currlen>length) {
/* rewind to previous printf so that we are correctly
* aligned if we get called to print another page.
*/
currlen-=splen;
}
chandev_unlock();
return(currlen);
}
static int chandev_write_proc(struct file *file, const char *buffer,
unsigned long count, void *data)
{
int rc;
char *buff;
if ((buff=vmalloc(count+1)) == 0)
return -ENOMEM;
if (copy_from_user(buff,buffer,count) == 0) {
chandev_do_setup(FALSE,buff,count);
rc=count;
} else {
rc=-EFAULT;
}
vfree(buff);
return rc;
}
static void __init chandev_create_proc(void)
{
struct proc_dir_entry *dir_entry=
create_proc_entry("chandev",0644,
&proc_root);
if(dir_entry)
{
dir_entry->read_proc=&chandev_read_proc;
dir_entry->write_proc=&chandev_write_proc;
}
}
#endif
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
static
#endif
int __init chandev_init(void)
{
atomic_set(&chandev_initialised,TRUE);
chandev_parse_args();
chandev_init_default_models();
#if CONFIG_PROC_FS
chandev_create_proc();
#endif
chandev_last_startmsck_list_update=chandev_last_machine_check=jiffies-HZ;
atomic_set(&chandev_msck_thread_lock,1);
chandev_lock_owner=CHANDEV_INVALID_LOCK_OWNER;
chandev_lock_cnt=0;
spin_lock_init(&chandev_spinlock);
spin_lock_init(&chandev_not_oper_spinlock);
atomic_set(&chandev_new_msck,FALSE);
return(0);
}
#if LINUX_VERSION_CODE>=KERNEL_VERSION(2,3,0)
__initcall(chandev_init);
#endif
int chandev_register_and_probe(chandev_probefunc probefunc,
chandev_shutdownfunc shutdownfunc,
chandev_msck_notification_func msck_notfunc,
chandev_type chan_type)
{
chandev_probelist *new_probe,*curr_probe;
/* Avoid chicked & egg situations where we may be called before we */
/* are initialised. */
chandev_interrupt_check("chandev_register_and_probe");
if(!atomic_compare_and_swap(FALSE,TRUE,&chandev_initialised))
chandev_init();
chandev_lock();
for_each(curr_probe,chandev_probelist_head)
{
if(curr_probe->probefunc==probefunc)
{
chandev_unlock();
printk("chandev_register_and_probe detected duplicate probefunc %p"
" for chan_type 0x%02x \n",probefunc,chan_type);
return (-EPERM);
}
}
chandev_unlock();
if((new_probe=chandev_alloc(sizeof(chandev_probelist))))
{
new_probe->probefunc=probefunc;
new_probe->shutdownfunc=shutdownfunc;
new_probe->msck_notfunc=msck_notfunc;
new_probe->chan_type=chan_type;
new_probe->devices_found=0;
chandev_add_to_list((list **)&chandev_probelist_head,new_probe);
chandev_probe();
}
return(new_probe ? new_probe->devices_found:-ENOMEM);
}
void chandev_unregister(chandev_probefunc probefunc,int call_shutdown)
{
chandev_probelist *curr_probe;
chandev_activelist *curr_device,*next_device;
chandev_interrupt_check("chandev_unregister");
chandev_lock();
for_each(curr_probe,chandev_probelist_head)
{
if(curr_probe->probefunc==probefunc)
{
for_each_allow_delete(curr_device,next_device,chandev_activelist_head)
if(curr_device->probefunc==probefunc&&call_shutdown)
chandev_shutdown(curr_device);
chandev_free_listmember((list **)&chandev_probelist_head,
(list *)curr_probe);
break;
}
}
chandev_unlock();
}
int chandev_persist(chandev_type chan_type)
{
return((chandev_persistent&chan_type) ? TRUE:FALSE);
}
EXPORT_SYMBOL(chandev_register_and_probe);
EXPORT_SYMBOL(chandev_request_irq);
EXPORT_SYMBOL(chandev_unregister);
EXPORT_SYMBOL(chandev_initdevice);
EXPORT_SYMBOL(chandev_build_device_name);
EXPORT_SYMBOL(chandev_initnetdevice);
/* see chandev.h as to why this is exported */
EXPORT_SYMBOL(chandev_init_netdev);
EXPORT_SYMBOL(chandev_use_devno_names);
EXPORT_SYMBOL(chandev_free_irq);
/* see chandev.h as to why chandev_add_model and chandev_del_model
* are exported */
EXPORT_SYMBOL(chandev_add_model);
EXPORT_SYMBOL(chandev_del_model);
EXPORT_SYMBOL(chandev_persist);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment