162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2008  by Karsten Keil <kkeil@novell.com>
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/slab.h>
762306a36Sopenharmony_ci#include <linux/types.h>
862306a36Sopenharmony_ci#include <linux/stddef.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/spinlock.h>
1162306a36Sopenharmony_ci#include <linux/mISDNif.h>
1262306a36Sopenharmony_ci#include "core.h"
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_cistatic u_int debug;
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ciMODULE_AUTHOR("Karsten Keil");
1762306a36Sopenharmony_ciMODULE_LICENSE("GPL");
1862306a36Sopenharmony_cimodule_param(debug, uint, S_IRUGO | S_IWUSR);
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistatic u64		device_ids;
2162306a36Sopenharmony_ci#define MAX_DEVICE_ID	63
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistatic LIST_HEAD(Bprotocols);
2462306a36Sopenharmony_cistatic DEFINE_RWLOCK(bp_lock);
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistatic void mISDN_dev_release(struct device *dev)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	/* nothing to do: the device is part of its parent's data structure */
2962306a36Sopenharmony_ci}
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic ssize_t id_show(struct device *dev,
3262306a36Sopenharmony_ci		       struct device_attribute *attr, char *buf)
3362306a36Sopenharmony_ci{
3462306a36Sopenharmony_ci	struct mISDNdevice *mdev = dev_to_mISDN(dev);
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci	if (!mdev)
3762306a36Sopenharmony_ci		return -ENODEV;
3862306a36Sopenharmony_ci	return sprintf(buf, "%d\n", mdev->id);
3962306a36Sopenharmony_ci}
4062306a36Sopenharmony_cistatic DEVICE_ATTR_RO(id);
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic ssize_t nrbchan_show(struct device *dev,
4362306a36Sopenharmony_ci			    struct device_attribute *attr, char *buf)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	struct mISDNdevice *mdev = dev_to_mISDN(dev);
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	if (!mdev)
4862306a36Sopenharmony_ci		return -ENODEV;
4962306a36Sopenharmony_ci	return sprintf(buf, "%d\n", mdev->nrbchan);
5062306a36Sopenharmony_ci}
5162306a36Sopenharmony_cistatic DEVICE_ATTR_RO(nrbchan);
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_cistatic ssize_t d_protocols_show(struct device *dev,
5462306a36Sopenharmony_ci				struct device_attribute *attr, char *buf)
5562306a36Sopenharmony_ci{
5662306a36Sopenharmony_ci	struct mISDNdevice *mdev = dev_to_mISDN(dev);
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	if (!mdev)
5962306a36Sopenharmony_ci		return -ENODEV;
6062306a36Sopenharmony_ci	return sprintf(buf, "%d\n", mdev->Dprotocols);
6162306a36Sopenharmony_ci}
6262306a36Sopenharmony_cistatic DEVICE_ATTR_RO(d_protocols);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistatic ssize_t b_protocols_show(struct device *dev,
6562306a36Sopenharmony_ci				struct device_attribute *attr, char *buf)
6662306a36Sopenharmony_ci{
6762306a36Sopenharmony_ci	struct mISDNdevice *mdev = dev_to_mISDN(dev);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	if (!mdev)
7062306a36Sopenharmony_ci		return -ENODEV;
7162306a36Sopenharmony_ci	return sprintf(buf, "%d\n", mdev->Bprotocols | get_all_Bprotocols());
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_cistatic DEVICE_ATTR_RO(b_protocols);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic ssize_t protocol_show(struct device *dev,
7662306a36Sopenharmony_ci			     struct device_attribute *attr, char *buf)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	struct mISDNdevice *mdev = dev_to_mISDN(dev);
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	if (!mdev)
8162306a36Sopenharmony_ci		return -ENODEV;
8262306a36Sopenharmony_ci	return sprintf(buf, "%d\n", mdev->D.protocol);
8362306a36Sopenharmony_ci}
8462306a36Sopenharmony_cistatic DEVICE_ATTR_RO(protocol);
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_cistatic ssize_t name_show(struct device *dev,
8762306a36Sopenharmony_ci			 struct device_attribute *attr, char *buf)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	strcpy(buf, dev_name(dev));
9062306a36Sopenharmony_ci	return strlen(buf);
9162306a36Sopenharmony_ci}
9262306a36Sopenharmony_cistatic DEVICE_ATTR_RO(name);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci#if 0 /* hangs */
9562306a36Sopenharmony_cistatic ssize_t name_set(struct device *dev, struct device_attribute *attr,
9662306a36Sopenharmony_ci			const char *buf, size_t count)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	int err = 0;
9962306a36Sopenharmony_ci	char *out = kmalloc(count + 1, GFP_KERNEL);
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	if (!out)
10262306a36Sopenharmony_ci		return -ENOMEM;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	memcpy(out, buf, count);
10562306a36Sopenharmony_ci	if (count && out[count - 1] == '\n')
10662306a36Sopenharmony_ci		out[--count] = 0;
10762306a36Sopenharmony_ci	if (count)
10862306a36Sopenharmony_ci		err = device_rename(dev, out);
10962306a36Sopenharmony_ci	kfree(out);
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	return (err < 0) ? err : count;
11262306a36Sopenharmony_ci}
11362306a36Sopenharmony_cistatic DEVICE_ATTR_RW(name);
11462306a36Sopenharmony_ci#endif
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic ssize_t channelmap_show(struct device *dev,
11762306a36Sopenharmony_ci			       struct device_attribute *attr, char *buf)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	struct mISDNdevice *mdev = dev_to_mISDN(dev);
12062306a36Sopenharmony_ci	char *bp = buf;
12162306a36Sopenharmony_ci	int i;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	for (i = 0; i <= mdev->nrbchan; i++)
12462306a36Sopenharmony_ci		*bp++ = test_channelmap(i, mdev->channelmap) ? '1' : '0';
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci	return bp - buf;
12762306a36Sopenharmony_ci}
12862306a36Sopenharmony_cistatic DEVICE_ATTR_RO(channelmap);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_cistatic struct attribute *mISDN_attrs[] = {
13162306a36Sopenharmony_ci	&dev_attr_id.attr,
13262306a36Sopenharmony_ci	&dev_attr_d_protocols.attr,
13362306a36Sopenharmony_ci	&dev_attr_b_protocols.attr,
13462306a36Sopenharmony_ci	&dev_attr_protocol.attr,
13562306a36Sopenharmony_ci	&dev_attr_channelmap.attr,
13662306a36Sopenharmony_ci	&dev_attr_nrbchan.attr,
13762306a36Sopenharmony_ci	&dev_attr_name.attr,
13862306a36Sopenharmony_ci	NULL,
13962306a36Sopenharmony_ci};
14062306a36Sopenharmony_ciATTRIBUTE_GROUPS(mISDN);
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_cistatic int mISDN_uevent(const struct device *dev, struct kobj_uevent_env *env)
14362306a36Sopenharmony_ci{
14462306a36Sopenharmony_ci	const struct mISDNdevice *mdev = dev_to_mISDN(dev);
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	if (!mdev)
14762306a36Sopenharmony_ci		return 0;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	if (add_uevent_var(env, "nchans=%d", mdev->nrbchan))
15062306a36Sopenharmony_ci		return -ENOMEM;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	return 0;
15362306a36Sopenharmony_ci}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic struct class mISDN_class = {
15662306a36Sopenharmony_ci	.name = "mISDN",
15762306a36Sopenharmony_ci	.dev_uevent = mISDN_uevent,
15862306a36Sopenharmony_ci	.dev_groups = mISDN_groups,
15962306a36Sopenharmony_ci	.dev_release = mISDN_dev_release,
16062306a36Sopenharmony_ci};
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_cistatic int
16362306a36Sopenharmony_ci_get_mdevice(struct device *dev, const void *id)
16462306a36Sopenharmony_ci{
16562306a36Sopenharmony_ci	struct mISDNdevice *mdev = dev_to_mISDN(dev);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	if (!mdev)
16862306a36Sopenharmony_ci		return 0;
16962306a36Sopenharmony_ci	if (mdev->id != *(const u_int *)id)
17062306a36Sopenharmony_ci		return 0;
17162306a36Sopenharmony_ci	return 1;
17262306a36Sopenharmony_ci}
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_cistruct mISDNdevice
17562306a36Sopenharmony_ci*get_mdevice(u_int id)
17662306a36Sopenharmony_ci{
17762306a36Sopenharmony_ci	return dev_to_mISDN(class_find_device(&mISDN_class, NULL, &id,
17862306a36Sopenharmony_ci					      _get_mdevice));
17962306a36Sopenharmony_ci}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_cistatic int
18262306a36Sopenharmony_ci_get_mdevice_count(struct device *dev, void *cnt)
18362306a36Sopenharmony_ci{
18462306a36Sopenharmony_ci	*(int *)cnt += 1;
18562306a36Sopenharmony_ci	return 0;
18662306a36Sopenharmony_ci}
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ciint
18962306a36Sopenharmony_ciget_mdevice_count(void)
19062306a36Sopenharmony_ci{
19162306a36Sopenharmony_ci	int cnt = 0;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	class_for_each_device(&mISDN_class, NULL, &cnt, _get_mdevice_count);
19462306a36Sopenharmony_ci	return cnt;
19562306a36Sopenharmony_ci}
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_cistatic int
19862306a36Sopenharmony_ciget_free_devid(void)
19962306a36Sopenharmony_ci{
20062306a36Sopenharmony_ci	u_int	i;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	for (i = 0; i <= MAX_DEVICE_ID; i++)
20362306a36Sopenharmony_ci		if (!test_and_set_bit(i, (u_long *)&device_ids))
20462306a36Sopenharmony_ci			break;
20562306a36Sopenharmony_ci	if (i > MAX_DEVICE_ID)
20662306a36Sopenharmony_ci		return -EBUSY;
20762306a36Sopenharmony_ci	return i;
20862306a36Sopenharmony_ci}
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ciint
21162306a36Sopenharmony_cimISDN_register_device(struct mISDNdevice *dev,
21262306a36Sopenharmony_ci		      struct device *parent, char *name)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	int	err;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	err = get_free_devid();
21762306a36Sopenharmony_ci	if (err < 0)
21862306a36Sopenharmony_ci		return err;
21962306a36Sopenharmony_ci	dev->id = err;
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	device_initialize(&dev->dev);
22262306a36Sopenharmony_ci	if (name && name[0])
22362306a36Sopenharmony_ci		dev_set_name(&dev->dev, "%s", name);
22462306a36Sopenharmony_ci	else
22562306a36Sopenharmony_ci		dev_set_name(&dev->dev, "mISDN%d", dev->id);
22662306a36Sopenharmony_ci	if (debug & DEBUG_CORE)
22762306a36Sopenharmony_ci		printk(KERN_DEBUG "mISDN_register %s %d\n",
22862306a36Sopenharmony_ci		       dev_name(&dev->dev), dev->id);
22962306a36Sopenharmony_ci	dev->dev.class = &mISDN_class;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	err = create_stack(dev);
23262306a36Sopenharmony_ci	if (err)
23362306a36Sopenharmony_ci		goto error1;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	dev->dev.platform_data = dev;
23662306a36Sopenharmony_ci	dev->dev.parent = parent;
23762306a36Sopenharmony_ci	dev_set_drvdata(&dev->dev, dev);
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci	err = device_add(&dev->dev);
24062306a36Sopenharmony_ci	if (err)
24162306a36Sopenharmony_ci		goto error3;
24262306a36Sopenharmony_ci	return 0;
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_cierror3:
24562306a36Sopenharmony_ci	delete_stack(dev);
24662306a36Sopenharmony_cierror1:
24762306a36Sopenharmony_ci	put_device(&dev->dev);
24862306a36Sopenharmony_ci	return err;
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci}
25162306a36Sopenharmony_ciEXPORT_SYMBOL(mISDN_register_device);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_civoid
25462306a36Sopenharmony_cimISDN_unregister_device(struct mISDNdevice *dev) {
25562306a36Sopenharmony_ci	if (debug & DEBUG_CORE)
25662306a36Sopenharmony_ci		printk(KERN_DEBUG "mISDN_unregister %s %d\n",
25762306a36Sopenharmony_ci		       dev_name(&dev->dev), dev->id);
25862306a36Sopenharmony_ci	/* sysfs_remove_link(&dev->dev.kobj, "device"); */
25962306a36Sopenharmony_ci	device_del(&dev->dev);
26062306a36Sopenharmony_ci	dev_set_drvdata(&dev->dev, NULL);
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	test_and_clear_bit(dev->id, (u_long *)&device_ids);
26362306a36Sopenharmony_ci	delete_stack(dev);
26462306a36Sopenharmony_ci	put_device(&dev->dev);
26562306a36Sopenharmony_ci}
26662306a36Sopenharmony_ciEXPORT_SYMBOL(mISDN_unregister_device);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ciu_int
26962306a36Sopenharmony_ciget_all_Bprotocols(void)
27062306a36Sopenharmony_ci{
27162306a36Sopenharmony_ci	struct Bprotocol	*bp;
27262306a36Sopenharmony_ci	u_int	m = 0;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	read_lock(&bp_lock);
27562306a36Sopenharmony_ci	list_for_each_entry(bp, &Bprotocols, list)
27662306a36Sopenharmony_ci		m |= bp->Bprotocols;
27762306a36Sopenharmony_ci	read_unlock(&bp_lock);
27862306a36Sopenharmony_ci	return m;
27962306a36Sopenharmony_ci}
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_cistruct Bprotocol *
28262306a36Sopenharmony_ciget_Bprotocol4mask(u_int m)
28362306a36Sopenharmony_ci{
28462306a36Sopenharmony_ci	struct Bprotocol	*bp;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	read_lock(&bp_lock);
28762306a36Sopenharmony_ci	list_for_each_entry(bp, &Bprotocols, list)
28862306a36Sopenharmony_ci		if (bp->Bprotocols & m) {
28962306a36Sopenharmony_ci			read_unlock(&bp_lock);
29062306a36Sopenharmony_ci			return bp;
29162306a36Sopenharmony_ci		}
29262306a36Sopenharmony_ci	read_unlock(&bp_lock);
29362306a36Sopenharmony_ci	return NULL;
29462306a36Sopenharmony_ci}
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_cistruct Bprotocol *
29762306a36Sopenharmony_ciget_Bprotocol4id(u_int id)
29862306a36Sopenharmony_ci{
29962306a36Sopenharmony_ci	u_int	m;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	if (id < ISDN_P_B_START || id > 63) {
30262306a36Sopenharmony_ci		printk(KERN_WARNING "%s id not in range  %d\n",
30362306a36Sopenharmony_ci		       __func__, id);
30462306a36Sopenharmony_ci		return NULL;
30562306a36Sopenharmony_ci	}
30662306a36Sopenharmony_ci	m = 1 << (id & ISDN_P_B_MASK);
30762306a36Sopenharmony_ci	return get_Bprotocol4mask(m);
30862306a36Sopenharmony_ci}
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_ciint
31162306a36Sopenharmony_cimISDN_register_Bprotocol(struct Bprotocol *bp)
31262306a36Sopenharmony_ci{
31362306a36Sopenharmony_ci	u_long			flags;
31462306a36Sopenharmony_ci	struct Bprotocol	*old;
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	if (debug & DEBUG_CORE)
31762306a36Sopenharmony_ci		printk(KERN_DEBUG "%s: %s/%x\n", __func__,
31862306a36Sopenharmony_ci		       bp->name, bp->Bprotocols);
31962306a36Sopenharmony_ci	old = get_Bprotocol4mask(bp->Bprotocols);
32062306a36Sopenharmony_ci	if (old) {
32162306a36Sopenharmony_ci		printk(KERN_WARNING
32262306a36Sopenharmony_ci		       "register duplicate protocol old %s/%x new %s/%x\n",
32362306a36Sopenharmony_ci		       old->name, old->Bprotocols, bp->name, bp->Bprotocols);
32462306a36Sopenharmony_ci		return -EBUSY;
32562306a36Sopenharmony_ci	}
32662306a36Sopenharmony_ci	write_lock_irqsave(&bp_lock, flags);
32762306a36Sopenharmony_ci	list_add_tail(&bp->list, &Bprotocols);
32862306a36Sopenharmony_ci	write_unlock_irqrestore(&bp_lock, flags);
32962306a36Sopenharmony_ci	return 0;
33062306a36Sopenharmony_ci}
33162306a36Sopenharmony_ciEXPORT_SYMBOL(mISDN_register_Bprotocol);
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_civoid
33462306a36Sopenharmony_cimISDN_unregister_Bprotocol(struct Bprotocol *bp)
33562306a36Sopenharmony_ci{
33662306a36Sopenharmony_ci	u_long	flags;
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	if (debug & DEBUG_CORE)
33962306a36Sopenharmony_ci		printk(KERN_DEBUG "%s: %s/%x\n", __func__, bp->name,
34062306a36Sopenharmony_ci		       bp->Bprotocols);
34162306a36Sopenharmony_ci	write_lock_irqsave(&bp_lock, flags);
34262306a36Sopenharmony_ci	list_del(&bp->list);
34362306a36Sopenharmony_ci	write_unlock_irqrestore(&bp_lock, flags);
34462306a36Sopenharmony_ci}
34562306a36Sopenharmony_ciEXPORT_SYMBOL(mISDN_unregister_Bprotocol);
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_cistatic const char *msg_no_channel = "<no channel>";
34862306a36Sopenharmony_cistatic const char *msg_no_stack = "<no stack>";
34962306a36Sopenharmony_cistatic const char *msg_no_stackdev = "<no stack device>";
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ciconst char *mISDNDevName4ch(struct mISDNchannel *ch)
35262306a36Sopenharmony_ci{
35362306a36Sopenharmony_ci	if (!ch)
35462306a36Sopenharmony_ci		return msg_no_channel;
35562306a36Sopenharmony_ci	if (!ch->st)
35662306a36Sopenharmony_ci		return msg_no_stack;
35762306a36Sopenharmony_ci	if (!ch->st->dev)
35862306a36Sopenharmony_ci		return msg_no_stackdev;
35962306a36Sopenharmony_ci	return dev_name(&ch->st->dev->dev);
36062306a36Sopenharmony_ci};
36162306a36Sopenharmony_ciEXPORT_SYMBOL(mISDNDevName4ch);
36262306a36Sopenharmony_ci
36362306a36Sopenharmony_cistatic int
36462306a36Sopenharmony_cimISDNInit(void)
36562306a36Sopenharmony_ci{
36662306a36Sopenharmony_ci	int	err;
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	printk(KERN_INFO "Modular ISDN core version %d.%d.%d\n",
36962306a36Sopenharmony_ci	       MISDN_MAJOR_VERSION, MISDN_MINOR_VERSION, MISDN_RELEASE);
37062306a36Sopenharmony_ci	mISDN_init_clock(&debug);
37162306a36Sopenharmony_ci	mISDN_initstack(&debug);
37262306a36Sopenharmony_ci	err = class_register(&mISDN_class);
37362306a36Sopenharmony_ci	if (err)
37462306a36Sopenharmony_ci		goto error1;
37562306a36Sopenharmony_ci	err = mISDN_inittimer(&debug);
37662306a36Sopenharmony_ci	if (err)
37762306a36Sopenharmony_ci		goto error2;
37862306a36Sopenharmony_ci	err = Isdnl1_Init(&debug);
37962306a36Sopenharmony_ci	if (err)
38062306a36Sopenharmony_ci		goto error3;
38162306a36Sopenharmony_ci	err = Isdnl2_Init(&debug);
38262306a36Sopenharmony_ci	if (err)
38362306a36Sopenharmony_ci		goto error4;
38462306a36Sopenharmony_ci	err = misdn_sock_init(&debug);
38562306a36Sopenharmony_ci	if (err)
38662306a36Sopenharmony_ci		goto error5;
38762306a36Sopenharmony_ci	return 0;
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_cierror5:
39062306a36Sopenharmony_ci	Isdnl2_cleanup();
39162306a36Sopenharmony_cierror4:
39262306a36Sopenharmony_ci	Isdnl1_cleanup();
39362306a36Sopenharmony_cierror3:
39462306a36Sopenharmony_ci	mISDN_timer_cleanup();
39562306a36Sopenharmony_cierror2:
39662306a36Sopenharmony_ci	class_unregister(&mISDN_class);
39762306a36Sopenharmony_cierror1:
39862306a36Sopenharmony_ci	return err;
39962306a36Sopenharmony_ci}
40062306a36Sopenharmony_ci
40162306a36Sopenharmony_cistatic void mISDN_cleanup(void)
40262306a36Sopenharmony_ci{
40362306a36Sopenharmony_ci	misdn_sock_cleanup();
40462306a36Sopenharmony_ci	Isdnl2_cleanup();
40562306a36Sopenharmony_ci	Isdnl1_cleanup();
40662306a36Sopenharmony_ci	mISDN_timer_cleanup();
40762306a36Sopenharmony_ci	class_unregister(&mISDN_class);
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	printk(KERN_DEBUG "mISDNcore unloaded\n");
41062306a36Sopenharmony_ci}
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_cimodule_init(mISDNInit);
41362306a36Sopenharmony_cimodule_exit(mISDN_cleanup);
414