xref: /openbmc/linux/fs/coda/psdev.c (revision 1ac731c529cd4d6adbce134754b51ff7d822b145)
12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
21da177e4SLinus Torvalds /*
31da177e4SLinus Torvalds  *      	An implementation of a loadable kernel mode driver providing
41da177e4SLinus Torvalds  *		multiple kernel/user space bidirectional communications links.
51da177e4SLinus Torvalds  *
6526719baSAlan Cox  * 		Author: 	Alan Cox <alan@lxorguk.ukuu.org.uk>
71da177e4SLinus Torvalds  *
81da177e4SLinus Torvalds  *              Adapted to become the Linux 2.0 Coda pseudo device
91da177e4SLinus Torvalds  *              Peter  Braam  <braam@maths.ox.ac.uk>
101da177e4SLinus Torvalds  *              Michael Callahan <mjc@emmy.smith.edu>
111da177e4SLinus Torvalds  *
121da177e4SLinus Torvalds  *              Changes for Linux 2.1
131da177e4SLinus Torvalds  *              Copyright (c) 1997 Carnegie-Mellon University
141da177e4SLinus Torvalds  */
151da177e4SLinus Torvalds 
161da177e4SLinus Torvalds #include <linux/module.h>
171da177e4SLinus Torvalds #include <linux/errno.h>
181da177e4SLinus Torvalds #include <linux/kernel.h>
191da177e4SLinus Torvalds #include <linux/major.h>
201da177e4SLinus Torvalds #include <linux/time.h>
21174cd4b1SIngo Molnar #include <linux/sched/signal.h>
221da177e4SLinus Torvalds #include <linux/slab.h>
231da177e4SLinus Torvalds #include <linux/ioport.h>
241da177e4SLinus Torvalds #include <linux/fcntl.h>
251da177e4SLinus Torvalds #include <linux/delay.h>
261da177e4SLinus Torvalds #include <linux/skbuff.h>
271da177e4SLinus Torvalds #include <linux/proc_fs.h>
281da177e4SLinus Torvalds #include <linux/vmalloc.h>
291da177e4SLinus Torvalds #include <linux/fs.h>
301da177e4SLinus Torvalds #include <linux/file.h>
311da177e4SLinus Torvalds #include <linux/poll.h>
321da177e4SLinus Torvalds #include <linux/init.h>
331da177e4SLinus Torvalds #include <linux/list.h>
34da47c19eSYoshihisa Abe #include <linux/mutex.h>
351da177e4SLinus Torvalds #include <linux/device.h>
369fd973e0SEric W. Biederman #include <linux/pid_namespace.h>
371da177e4SLinus Torvalds #include <asm/io.h>
38834b46c3SFabian Frederick #include <linux/uaccess.h>
391da177e4SLinus Torvalds 
401da177e4SLinus Torvalds #include <linux/coda.h>
418fc8b9dfSDavid Howells #include "coda_psdev.h"
4231a203dfSAl Viro #include "coda_linux.h"
4331a203dfSAl Viro 
44c98d8cfbSAdrian Bunk #include "coda_int.h"
451da177e4SLinus Torvalds 
461da177e4SLinus Torvalds /* statistics */
471da177e4SLinus Torvalds int           coda_hard;         /* allows signals during upcalls */
481da177e4SLinus Torvalds unsigned long coda_timeout = 30; /* .. secs, then signals will dequeue */
491da177e4SLinus Torvalds 
501da177e4SLinus Torvalds 
511da177e4SLinus Torvalds struct venus_comm coda_comms[MAX_CODADEVS];
521db560afSgregkh@suse.de static struct class *coda_psdev_class;
531da177e4SLinus Torvalds 
541da177e4SLinus Torvalds /*
551da177e4SLinus Torvalds  * Device operations
561da177e4SLinus Torvalds  */
571da177e4SLinus Torvalds 
coda_psdev_poll(struct file * file,poll_table * wait)58076ccb76SAl Viro static __poll_t coda_psdev_poll(struct file *file, poll_table * wait)
591da177e4SLinus Torvalds {
601da177e4SLinus Torvalds         struct venus_comm *vcp = (struct venus_comm *) file->private_data;
61a9a08845SLinus Torvalds 	__poll_t mask = EPOLLOUT | EPOLLWRNORM;
621da177e4SLinus Torvalds 
631da177e4SLinus Torvalds 	poll_wait(file, &vcp->vc_waitq, wait);
64da47c19eSYoshihisa Abe 	mutex_lock(&vcp->vc_mutex);
651da177e4SLinus Torvalds 	if (!list_empty(&vcp->vc_pending))
66a9a08845SLinus Torvalds                 mask |= EPOLLIN | EPOLLRDNORM;
67da47c19eSYoshihisa Abe 	mutex_unlock(&vcp->vc_mutex);
681da177e4SLinus Torvalds 
691da177e4SLinus Torvalds 	return mask;
701da177e4SLinus Torvalds }
711da177e4SLinus Torvalds 
coda_psdev_ioctl(struct file * filp,unsigned int cmd,unsigned long arg)7297718390SArnd Bergmann static long coda_psdev_ioctl(struct file * filp, unsigned int cmd, unsigned long arg)
731da177e4SLinus Torvalds {
741da177e4SLinus Torvalds 	unsigned int data;
751da177e4SLinus Torvalds 
761da177e4SLinus Torvalds 	switch(cmd) {
771da177e4SLinus Torvalds 	case CIOC_KERNEL_VERSION:
781da177e4SLinus Torvalds 		data = CODA_KERNEL_VERSION;
791da177e4SLinus Torvalds 		return put_user(data, (int __user *) arg);
801da177e4SLinus Torvalds 	default:
811da177e4SLinus Torvalds 		return -ENOTTY;
821da177e4SLinus Torvalds 	}
831da177e4SLinus Torvalds 
841da177e4SLinus Torvalds 	return 0;
851da177e4SLinus Torvalds }
861da177e4SLinus Torvalds 
871da177e4SLinus Torvalds /*
881da177e4SLinus Torvalds  *	Receive a message written by Venus to the psdev
891da177e4SLinus Torvalds  */
901da177e4SLinus Torvalds 
coda_psdev_write(struct file * file,const char __user * buf,size_t nbytes,loff_t * off)911da177e4SLinus Torvalds static ssize_t coda_psdev_write(struct file *file, const char __user *buf,
921da177e4SLinus Torvalds 				size_t nbytes, loff_t *off)
931da177e4SLinus Torvalds {
941da177e4SLinus Torvalds         struct venus_comm *vcp = (struct venus_comm *) file->private_data;
951da177e4SLinus Torvalds         struct upc_req *req = NULL;
961da177e4SLinus Torvalds         struct upc_req *tmp;
971da177e4SLinus Torvalds 	struct list_head *lh;
981da177e4SLinus Torvalds 	struct coda_in_hdr hdr;
991da177e4SLinus Torvalds 	ssize_t retval = 0, count = 0;
1001da177e4SLinus Torvalds 	int error;
1011da177e4SLinus Torvalds 
1026e51f8aaSJan Harkes 	/* make sure there is enough to copy out the (opcode, unique) values */
1036e51f8aaSJan Harkes 	if (nbytes < (2 * sizeof(u_int32_t)))
1046e51f8aaSJan Harkes 		return -EINVAL;
1056e51f8aaSJan Harkes 
1061da177e4SLinus Torvalds         /* Peek at the opcode, uniquefier */
1076e51f8aaSJan Harkes 	if (copy_from_user(&hdr, buf, 2 * sizeof(u_int32_t)))
1081da177e4SLinus Torvalds 	        return -EFAULT;
1091da177e4SLinus Torvalds 
1101da177e4SLinus Torvalds         if (DOWNCALL(hdr.opcode)) {
1111da177e4SLinus Torvalds 		union outputArgs *dcbuf;
1121da177e4SLinus Torvalds 		int size = sizeof(*dcbuf);
1131da177e4SLinus Torvalds 
1141da177e4SLinus Torvalds 		if  ( nbytes < sizeof(struct coda_out_hdr) ) {
115d9b4b319SFabian Frederick 			pr_warn("coda_downcall opc %d uniq %d, not enough!\n",
1161da177e4SLinus Torvalds 				hdr.opcode, hdr.unique);
1171da177e4SLinus Torvalds 			count = nbytes;
1181da177e4SLinus Torvalds 			goto out;
1191da177e4SLinus Torvalds 		}
1201da177e4SLinus Torvalds 		if ( nbytes > size ) {
121f38cfb25SFabian Frederick 			pr_warn("downcall opc %d, uniq %d, too much!",
1221da177e4SLinus Torvalds 				hdr.opcode, hdr.unique);
1231da177e4SLinus Torvalds 		        nbytes = size;
1241da177e4SLinus Torvalds 		}
125118b7ee1SJing Yangyang 
126118b7ee1SJing Yangyang 		dcbuf = vmemdup_user(buf, nbytes);
127118b7ee1SJing Yangyang 		if (IS_ERR(dcbuf)) {
128118b7ee1SJing Yangyang 			retval = PTR_ERR(dcbuf);
1291da177e4SLinus Torvalds 			goto out;
1301da177e4SLinus Torvalds 		}
1311da177e4SLinus Torvalds 
1321da177e4SLinus Torvalds 		/* what downcall errors does Venus handle ? */
1336e51f8aaSJan Harkes 		error = coda_downcall(vcp, hdr.opcode, dcbuf, nbytes);
1341da177e4SLinus Torvalds 
135936dae45SDan Carpenter 		kvfree(dcbuf);
1361da177e4SLinus Torvalds 		if (error) {
1376d6bd94fSFabian Frederick 			pr_warn("%s: coda_downcall error: %d\n",
1386d6bd94fSFabian Frederick 				__func__, error);
1391da177e4SLinus Torvalds 			retval = error;
1401da177e4SLinus Torvalds 			goto out;
1411da177e4SLinus Torvalds 		}
1421da177e4SLinus Torvalds 		count = nbytes;
1431da177e4SLinus Torvalds 		goto out;
1441da177e4SLinus Torvalds 	}
1451da177e4SLinus Torvalds 
1461da177e4SLinus Torvalds 	/* Look for the message on the processing queue. */
147da47c19eSYoshihisa Abe 	mutex_lock(&vcp->vc_mutex);
1481da177e4SLinus Torvalds 	list_for_each(lh, &vcp->vc_processing) {
1491da177e4SLinus Torvalds 		tmp = list_entry(lh, struct upc_req , uc_chain);
1501da177e4SLinus Torvalds 		if (tmp->uc_unique == hdr.unique) {
1511da177e4SLinus Torvalds 			req = tmp;
1521da177e4SLinus Torvalds 			list_del(&req->uc_chain);
1531da177e4SLinus Torvalds 			break;
1541da177e4SLinus Torvalds 		}
1551da177e4SLinus Torvalds 	}
156da47c19eSYoshihisa Abe 	mutex_unlock(&vcp->vc_mutex);
1571da177e4SLinus Torvalds 
1581da177e4SLinus Torvalds 	if (!req) {
1596d6bd94fSFabian Frederick 		pr_warn("%s: msg (%d, %d) not found\n",
1606d6bd94fSFabian Frederick 			__func__, hdr.opcode, hdr.unique);
1611da177e4SLinus Torvalds 		retval = -ESRCH;
1621da177e4SLinus Torvalds 		goto out;
1631da177e4SLinus Torvalds 	}
1641da177e4SLinus Torvalds 
1651da177e4SLinus Torvalds         /* move data into response buffer. */
1661da177e4SLinus Torvalds 	if (req->uc_outSize < nbytes) {
1676d6bd94fSFabian Frederick 		pr_warn("%s: too much cnt: %d, cnt: %ld, opc: %d, uniq: %d.\n",
1686d6bd94fSFabian Frederick 			__func__, req->uc_outSize, (long)nbytes,
1696d6bd94fSFabian Frederick 			hdr.opcode, hdr.unique);
1701da177e4SLinus Torvalds 		nbytes = req->uc_outSize; /* don't have more space! */
1711da177e4SLinus Torvalds 	}
1721da177e4SLinus Torvalds         if (copy_from_user(req->uc_data, buf, nbytes)) {
1734aeefdc6SJens Axboe 		req->uc_flags |= CODA_REQ_ABORT;
1741da177e4SLinus Torvalds 		wake_up(&req->uc_sleep);
1751da177e4SLinus Torvalds 		retval = -EFAULT;
1761da177e4SLinus Torvalds 		goto out;
1771da177e4SLinus Torvalds 	}
1781da177e4SLinus Torvalds 
1791da177e4SLinus Torvalds 	/* adjust outsize. is this useful ?? */
1801da177e4SLinus Torvalds 	req->uc_outSize = nbytes;
181112d421dSJan Harkes 	req->uc_flags |= CODA_REQ_WRITE;
1821da177e4SLinus Torvalds 	count = nbytes;
1831da177e4SLinus Torvalds 
1841da177e4SLinus Torvalds 	/* Convert filedescriptor into a file handle */
1851da177e4SLinus Torvalds 	if (req->uc_opcode == CODA_OPEN_BY_FD) {
1861da177e4SLinus Torvalds 		struct coda_open_by_fd_out *outp =
1871da177e4SLinus Torvalds 			(struct coda_open_by_fd_out *)req->uc_data;
18802551c23SZhouyang Jia 		if (!outp->oh.result) {
1891da177e4SLinus Torvalds 			outp->fh = fget(outp->fd);
19002551c23SZhouyang Jia 			if (!outp->fh)
19102551c23SZhouyang Jia 				return -EBADF;
19202551c23SZhouyang Jia 		}
1931da177e4SLinus Torvalds 	}
1941da177e4SLinus Torvalds 
1951da177e4SLinus Torvalds         wake_up(&req->uc_sleep);
1961da177e4SLinus Torvalds out:
1971da177e4SLinus Torvalds         return(count ? count : retval);
1981da177e4SLinus Torvalds }
1991da177e4SLinus Torvalds 
2001da177e4SLinus Torvalds /*
2011da177e4SLinus Torvalds  *	Read a message from the kernel to Venus
2021da177e4SLinus Torvalds  */
2031da177e4SLinus Torvalds 
coda_psdev_read(struct file * file,char __user * buf,size_t nbytes,loff_t * off)2041da177e4SLinus Torvalds static ssize_t coda_psdev_read(struct file * file, char __user * buf,
2051da177e4SLinus Torvalds 			       size_t nbytes, loff_t *off)
2061da177e4SLinus Torvalds {
2071da177e4SLinus Torvalds 	DECLARE_WAITQUEUE(wait, current);
2081da177e4SLinus Torvalds         struct venus_comm *vcp = (struct venus_comm *) file->private_data;
2091da177e4SLinus Torvalds         struct upc_req *req;
2101da177e4SLinus Torvalds 	ssize_t retval = 0, count = 0;
2111da177e4SLinus Torvalds 
2121da177e4SLinus Torvalds 	if (nbytes == 0)
2131da177e4SLinus Torvalds 		return 0;
2141da177e4SLinus Torvalds 
215da47c19eSYoshihisa Abe 	mutex_lock(&vcp->vc_mutex);
2161da177e4SLinus Torvalds 
2171da177e4SLinus Torvalds 	add_wait_queue(&vcp->vc_waitq, &wait);
2181da177e4SLinus Torvalds 	set_current_state(TASK_INTERRUPTIBLE);
2191da177e4SLinus Torvalds 
2201da177e4SLinus Torvalds 	while (list_empty(&vcp->vc_pending)) {
2211da177e4SLinus Torvalds 		if (file->f_flags & O_NONBLOCK) {
2221da177e4SLinus Torvalds 			retval = -EAGAIN;
2231da177e4SLinus Torvalds 			break;
2241da177e4SLinus Torvalds 		}
2251da177e4SLinus Torvalds 		if (signal_pending(current)) {
2261da177e4SLinus Torvalds 			retval = -ERESTARTSYS;
2271da177e4SLinus Torvalds 			break;
2281da177e4SLinus Torvalds 		}
229da47c19eSYoshihisa Abe 		mutex_unlock(&vcp->vc_mutex);
2301da177e4SLinus Torvalds 		schedule();
231da47c19eSYoshihisa Abe 		mutex_lock(&vcp->vc_mutex);
2321da177e4SLinus Torvalds 	}
2331da177e4SLinus Torvalds 
2341da177e4SLinus Torvalds 	set_current_state(TASK_RUNNING);
2351da177e4SLinus Torvalds 	remove_wait_queue(&vcp->vc_waitq, &wait);
2361da177e4SLinus Torvalds 
2371da177e4SLinus Torvalds 	if (retval)
2381da177e4SLinus Torvalds 		goto out;
2391da177e4SLinus Torvalds 
2401da177e4SLinus Torvalds 	req = list_entry(vcp->vc_pending.next, struct upc_req,uc_chain);
2411da177e4SLinus Torvalds 	list_del(&req->uc_chain);
2421da177e4SLinus Torvalds 
2431da177e4SLinus Torvalds 	/* Move the input args into userspace */
2441da177e4SLinus Torvalds 	count = req->uc_inSize;
2451da177e4SLinus Torvalds 	if (nbytes < req->uc_inSize) {
2466d6bd94fSFabian Frederick 		pr_warn("%s: Venus read %ld bytes of %d in message\n",
2476d6bd94fSFabian Frederick 			__func__, (long)nbytes, req->uc_inSize);
2481da177e4SLinus Torvalds 		count = nbytes;
2491da177e4SLinus Torvalds         }
2501da177e4SLinus Torvalds 
2511da177e4SLinus Torvalds 	if (copy_to_user(buf, req->uc_data, count))
2521da177e4SLinus Torvalds 	        retval = -EFAULT;
2531da177e4SLinus Torvalds 
2541da177e4SLinus Torvalds 	/* If request was not a signal, enqueue and don't free */
2554aeefdc6SJens Axboe 	if (!(req->uc_flags & CODA_REQ_ASYNC)) {
2564aeefdc6SJens Axboe 		req->uc_flags |= CODA_REQ_READ;
2578e13059aSAkinobu Mita 		list_add_tail(&(req->uc_chain), &vcp->vc_processing);
2581da177e4SLinus Torvalds 		goto out;
2591da177e4SLinus Torvalds 	}
2601da177e4SLinus Torvalds 
261936dae45SDan Carpenter 	kvfree(req->uc_data);
26237461e19SJan Harkes 	kfree(req);
2631da177e4SLinus Torvalds out:
264da47c19eSYoshihisa Abe 	mutex_unlock(&vcp->vc_mutex);
2651da177e4SLinus Torvalds 	return (count ? count : retval);
2661da177e4SLinus Torvalds }
2671da177e4SLinus Torvalds 
coda_psdev_open(struct inode * inode,struct file * file)2681da177e4SLinus Torvalds static int coda_psdev_open(struct inode * inode, struct file * file)
2691da177e4SLinus Torvalds {
2701da177e4SLinus Torvalds 	struct venus_comm *vcp;
27187065519SJan Harkes 	int idx, err;
27287065519SJan Harkes 
2739fd973e0SEric W. Biederman 	if (task_active_pid_ns(current) != &init_pid_ns)
2749fd973e0SEric W. Biederman 		return -EINVAL;
2759fd973e0SEric W. Biederman 
276d83f5901SEric W. Biederman 	if (current_user_ns() != &init_user_ns)
277d83f5901SEric W. Biederman 		return -EINVAL;
278d83f5901SEric W. Biederman 
27987065519SJan Harkes 	idx = iminor(inode);
28087065519SJan Harkes 	if (idx < 0 || idx >= MAX_CODADEVS)
28187065519SJan Harkes 		return -ENODEV;
2821da177e4SLinus Torvalds 
28387065519SJan Harkes 	err = -EBUSY;
2841da177e4SLinus Torvalds 	vcp = &coda_comms[idx];
285da47c19eSYoshihisa Abe 	mutex_lock(&vcp->vc_mutex);
286da47c19eSYoshihisa Abe 
28787065519SJan Harkes 	if (!vcp->vc_inuse) {
28887065519SJan Harkes 		vcp->vc_inuse++;
2891da177e4SLinus Torvalds 
2901da177e4SLinus Torvalds 		INIT_LIST_HEAD(&vcp->vc_pending);
2911da177e4SLinus Torvalds 		INIT_LIST_HEAD(&vcp->vc_processing);
2921da177e4SLinus Torvalds 		init_waitqueue_head(&vcp->vc_waitq);
2931da177e4SLinus Torvalds 		vcp->vc_sb = NULL;
2941da177e4SLinus Torvalds 		vcp->vc_seq = 0;
2951da177e4SLinus Torvalds 
2961da177e4SLinus Torvalds 		file->private_data = vcp;
29787065519SJan Harkes 		err = 0;
29887065519SJan Harkes 	}
2991da177e4SLinus Torvalds 
300da47c19eSYoshihisa Abe 	mutex_unlock(&vcp->vc_mutex);
30187065519SJan Harkes 	return err;
3021da177e4SLinus Torvalds }
3031da177e4SLinus Torvalds 
3041da177e4SLinus Torvalds 
coda_psdev_release(struct inode * inode,struct file * file)3051da177e4SLinus Torvalds static int coda_psdev_release(struct inode * inode, struct file * file)
3061da177e4SLinus Torvalds {
3071da177e4SLinus Torvalds 	struct venus_comm *vcp = (struct venus_comm *) file->private_data;
3081da177e4SLinus Torvalds 	struct upc_req *req, *tmp;
3091da177e4SLinus Torvalds 
31087065519SJan Harkes 	if (!vcp || !vcp->vc_inuse ) {
3116d6bd94fSFabian Frederick 		pr_warn("%s: Not open.\n", __func__);
3121da177e4SLinus Torvalds 		return -1;
3131da177e4SLinus Torvalds 	}
3141da177e4SLinus Torvalds 
315da47c19eSYoshihisa Abe 	mutex_lock(&vcp->vc_mutex);
3161da177e4SLinus Torvalds 
3171da177e4SLinus Torvalds 	/* Wakeup clients so they can return. */
3181da177e4SLinus Torvalds 	list_for_each_entry_safe(req, tmp, &vcp->vc_pending, uc_chain) {
31987065519SJan Harkes 		list_del(&req->uc_chain);
32087065519SJan Harkes 
3211da177e4SLinus Torvalds 		/* Async requests need to be freed here */
3224aeefdc6SJens Axboe 		if (req->uc_flags & CODA_REQ_ASYNC) {
323936dae45SDan Carpenter 			kvfree(req->uc_data);
32437461e19SJan Harkes 			kfree(req);
3251da177e4SLinus Torvalds 			continue;
3261da177e4SLinus Torvalds 		}
3274aeefdc6SJens Axboe 		req->uc_flags |= CODA_REQ_ABORT;
3281da177e4SLinus Torvalds 		wake_up(&req->uc_sleep);
3291da177e4SLinus Torvalds 	}
3301da177e4SLinus Torvalds 
33187065519SJan Harkes 	list_for_each_entry_safe(req, tmp, &vcp->vc_processing, uc_chain) {
33287065519SJan Harkes 		list_del(&req->uc_chain);
33387065519SJan Harkes 
3344aeefdc6SJens Axboe 		req->uc_flags |= CODA_REQ_ABORT;
3351da177e4SLinus Torvalds 		wake_up(&req->uc_sleep);
3361da177e4SLinus Torvalds 	}
3371da177e4SLinus Torvalds 
33887065519SJan Harkes 	file->private_data = NULL;
33987065519SJan Harkes 	vcp->vc_inuse--;
340da47c19eSYoshihisa Abe 	mutex_unlock(&vcp->vc_mutex);
3411da177e4SLinus Torvalds 	return 0;
3421da177e4SLinus Torvalds }
3431da177e4SLinus Torvalds 
3441da177e4SLinus Torvalds 
3454b6f5d20SArjan van de Ven static const struct file_operations coda_psdev_fops = {
3461da177e4SLinus Torvalds 	.owner		= THIS_MODULE,
3471da177e4SLinus Torvalds 	.read		= coda_psdev_read,
3481da177e4SLinus Torvalds 	.write		= coda_psdev_write,
3491da177e4SLinus Torvalds 	.poll		= coda_psdev_poll,
35097718390SArnd Bergmann 	.unlocked_ioctl	= coda_psdev_ioctl,
3511da177e4SLinus Torvalds 	.open		= coda_psdev_open,
3521da177e4SLinus Torvalds 	.release	= coda_psdev_release,
3536038f373SArnd Bergmann 	.llseek		= noop_llseek,
3541da177e4SLinus Torvalds };
3551da177e4SLinus Torvalds 
init_coda_psdev(void)356f9484528SFabian Frederick static int __init init_coda_psdev(void)
3571da177e4SLinus Torvalds {
3581da177e4SLinus Torvalds 	int i, err = 0;
3591da177e4SLinus Torvalds 	if (register_chrdev(CODA_PSDEV_MAJOR, "coda", &coda_psdev_fops)) {
3606d6bd94fSFabian Frederick 		pr_err("%s: unable to get major %d\n",
3616d6bd94fSFabian Frederick 		       __func__, CODA_PSDEV_MAJOR);
3621da177e4SLinus Torvalds 		return -EIO;
3631da177e4SLinus Torvalds 	}
364*1aaba11dSGreg Kroah-Hartman 	coda_psdev_class = class_create("coda");
3651da177e4SLinus Torvalds 	if (IS_ERR(coda_psdev_class)) {
3661da177e4SLinus Torvalds 		err = PTR_ERR(coda_psdev_class);
3671da177e4SLinus Torvalds 		goto out_chrdev;
3681da177e4SLinus Torvalds 	}
369da47c19eSYoshihisa Abe 	for (i = 0; i < MAX_CODADEVS; i++) {
370da47c19eSYoshihisa Abe 		mutex_init(&(&coda_comms[i])->vc_mutex);
371a9b12619SGreg Kroah-Hartman 		device_create(coda_psdev_class, NULL,
372a9b12619SGreg Kroah-Hartman 			      MKDEV(CODA_PSDEV_MAJOR, i), NULL, "cfs%d", i);
373da47c19eSYoshihisa Abe 	}
3741da177e4SLinus Torvalds 	coda_sysctl_init();
3751da177e4SLinus Torvalds 	goto out;
3761da177e4SLinus Torvalds 
3771da177e4SLinus Torvalds out_chrdev:
3781da177e4SLinus Torvalds 	unregister_chrdev(CODA_PSDEV_MAJOR, "coda");
3791da177e4SLinus Torvalds out:
3801da177e4SLinus Torvalds 	return err;
3811da177e4SLinus Torvalds }
3821da177e4SLinus Torvalds 
3835b7f13bdSJan Harkes MODULE_AUTHOR("Jan Harkes, Peter J. Braam");
3845b7f13bdSJan Harkes MODULE_DESCRIPTION("Coda Distributed File System VFS interface");
3855b7f13bdSJan Harkes MODULE_ALIAS_CHARDEV_MAJOR(CODA_PSDEV_MAJOR);
3861da177e4SLinus Torvalds MODULE_LICENSE("GPL");
38798d5b61eSJan Harkes MODULE_VERSION("7.2");
3881da177e4SLinus Torvalds 
init_coda(void)3891da177e4SLinus Torvalds static int __init init_coda(void)
3901da177e4SLinus Torvalds {
3911da177e4SLinus Torvalds 	int status;
3921da177e4SLinus Torvalds 	int i;
3931da177e4SLinus Torvalds 
3941da177e4SLinus Torvalds 	status = coda_init_inodecache();
3951da177e4SLinus Torvalds 	if (status)
3961da177e4SLinus Torvalds 		goto out2;
3971da177e4SLinus Torvalds 	status = init_coda_psdev();
3981da177e4SLinus Torvalds 	if ( status ) {
399d9b4b319SFabian Frederick 		pr_warn("Problem (%d) in init_coda_psdev\n", status);
4001da177e4SLinus Torvalds 		goto out1;
4011da177e4SLinus Torvalds 	}
4021da177e4SLinus Torvalds 
4031da177e4SLinus Torvalds 	status = register_filesystem(&coda_fs_type);
4041da177e4SLinus Torvalds 	if (status) {
405f38cfb25SFabian Frederick 		pr_warn("failed to register filesystem!\n");
4061da177e4SLinus Torvalds 		goto out;
4071da177e4SLinus Torvalds 	}
4081da177e4SLinus Torvalds 	return 0;
4091da177e4SLinus Torvalds out:
4108ab5e4c1SGreg Kroah-Hartman 	for (i = 0; i < MAX_CODADEVS; i++)
41162ca8792SKay Sievers 		device_destroy(coda_psdev_class, MKDEV(CODA_PSDEV_MAJOR, i));
4121db560afSgregkh@suse.de 	class_destroy(coda_psdev_class);
4131da177e4SLinus Torvalds 	unregister_chrdev(CODA_PSDEV_MAJOR, "coda");
4141da177e4SLinus Torvalds 	coda_sysctl_clean();
4151da177e4SLinus Torvalds out1:
4161da177e4SLinus Torvalds 	coda_destroy_inodecache();
4171da177e4SLinus Torvalds out2:
4181da177e4SLinus Torvalds 	return status;
4191da177e4SLinus Torvalds }
4201da177e4SLinus Torvalds 
exit_coda(void)4211da177e4SLinus Torvalds static void __exit exit_coda(void)
4221da177e4SLinus Torvalds {
4231da177e4SLinus Torvalds         int err, i;
4241da177e4SLinus Torvalds 
4251da177e4SLinus Torvalds 	err = unregister_filesystem(&coda_fs_type);
426d9b4b319SFabian Frederick 	if (err != 0)
427f38cfb25SFabian Frederick 		pr_warn("failed to unregister filesystem\n");
4288ab5e4c1SGreg Kroah-Hartman 	for (i = 0; i < MAX_CODADEVS; i++)
42962ca8792SKay Sievers 		device_destroy(coda_psdev_class, MKDEV(CODA_PSDEV_MAJOR, i));
4301db560afSgregkh@suse.de 	class_destroy(coda_psdev_class);
4311da177e4SLinus Torvalds 	unregister_chrdev(CODA_PSDEV_MAJOR, "coda");
4321da177e4SLinus Torvalds 	coda_sysctl_clean();
4331da177e4SLinus Torvalds 	coda_destroy_inodecache();
4341da177e4SLinus Torvalds }
4351da177e4SLinus Torvalds 
4361da177e4SLinus Torvalds module_init(init_coda);
4371da177e4SLinus Torvalds module_exit(exit_coda);
4381da177e4SLinus Torvalds 
439