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