capi4linux/linux/drivers/isdn/capi/core.c

488 lines
9.8 KiB
C

/*
* $Id$
*
* Copyright(C) 2004 Frank A. Uepping
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/skbuff.h>
#include <linux/isdn/capidevice.h>
#include <linux/isdn/capiutil.h>
#include <linux/isdn/capicmd.h>
static struct capi_device* capi_devices_table[CAPI_MAX_DEVS];
static spinlock_t capi_devices_table_lock;
atomic_t nr_capi_devices = ATOMIC_INIT(0);
int capi_device_register_sysfs (struct capi_device* dev);
static inline void
get_capi_device(struct capi_device* dev)
{
kref_get(&dev->refs);
}
static struct capi_device*
try_get_capi_device(struct capi_device* dev)
{
if (!dev)
return NULL;
spin_lock(&dev->state_lock);
if (unlikely(dev->state != CAPI_DEVICE_STATE_RUNNING))
dev = NULL;
else
get_capi_device(dev);
spin_unlock(&dev->state_lock);
return dev;
}
static inline void
put_capi_device(struct capi_device* dev)
{
kref_put(&dev->refs);
}
struct capi_device*
capi_device_alloc(void)
{
struct capi_device* dev = kmalloc(sizeof *dev, GFP_KERNEL);
if (unlikely(!dev))
return NULL;
memset(dev, 0, sizeof *dev);
dev->class_dev.class = &capi_class;
class_device_initialize(&dev->class_dev);
return dev;
}
static void
release_capi_device(struct kref* kref)
{
struct capi_device* dev = container_of(kref, struct capi_device, refs);
struct module* owner = dev->drv->owner;
int appls = atomic_read(&dev->appls);
while (appls--)
module_put(owner);
complete(&dev->done);
}
void
free_capi_device(struct class_device* cd)
{
struct capi_device* dev = to_capi_device(cd);
if (likely(dev->id)) {
spin_lock(&capi_devices_table_lock);
capi_devices_table[dev->id - 1] = NULL;
spin_unlock(&capi_devices_table_lock);
}
kfree(dev);
}
static inline int
register_capi_device(struct capi_device* dev)
{
int id;
spin_lock(&capi_devices_table_lock);
for (id = 0; id < CAPI_MAX_DEVS; id++)
if (!capi_devices_table[id]) {
capi_devices_table[id] = dev;
spin_unlock(&capi_devices_table_lock);
atomic_inc(&nr_capi_devices);
return dev->id = id + 1;
}
spin_unlock(&capi_devices_table_lock);
return 0;
}
int
capi_device_register(struct capi_device* dev)
{
int err;
if (unlikely(!dev))
return -EINVAL;
if (unlikely(!register_capi_device(dev)))
return -EMFILE;
err = capi_device_register_sysfs(dev);
if (unlikely(err))
return err;
kref_init(&dev->refs, release_capi_device);
init_completion(&dev->done);
atomic_set(&dev->appls, 0);
spin_lock_init(&dev->stats.lock);
spin_lock_init(&dev->state_lock);
dev->state = CAPI_DEVICE_STATE_RUNNING;
return 0;
}
void
capi_device_unregister(struct capi_device* dev)
{
class_device_del(&dev->class_dev);
spin_lock(&dev->state_lock);
dev->state = CAPI_DEVICE_STATE_ZOMBIE;
spin_unlock(&dev->state_lock);
atomic_dec(&nr_capi_devices);
BUG_ON(!module_is_live(dev->drv->owner) && atomic_read(&dev->refs.refcount) != 1);
put_capi_device(dev);
wait_for_completion(&dev->done);
}
static unsigned long __capi_appl_ids[BITS_TO_LONGS(CAPI_MAX_APPLS)];
static spinlock_t __capi_appl_ids_lock;
static inline int
alloc_capi_appl_id(struct capi_appl* appl)
{
int id;
appl->id = 0;
spin_lock(__capi_appl_ids_lock);
id = find_first_zero_bit(__capi_appl_ids, CAPI_MAX_APPLS);
if (likely(id < CAPI_MAX_APPLS)) {
__set_bit(id, __capi_appl_ids);
appl->id = id + 1;
}
spin_unlock(__capi_appl_ids_lock);
return appl->id;
}
static inline void
release_capi_appl_id(struct capi_appl* appl)
{
if (likely(appl->id))
__clear_bit(appl->id - 1, __capi_appl_ids);
}
capinfo_0x10
capi_register(struct capi_appl* appl)
{
struct capi_device* dev;
capinfo_0x10 info;
int id;
if (unlikely(!appl))
return CAPINFO_0X10_OSRESERR;
if (unlikely(appl->params.datablklen < 128))
return CAPINFO_0X10_LOGBLKSIZETOSMALL;
if (unlikely(!alloc_capi_appl_id(appl)))
return CAPINFO_0X10_TOOMANYAPPLS;
skb_queue_head_init(&appl->msg_queue);
appl->info = CAPINFO_0X11_NOERR;
spin_lock_init(&appl->stats.lock);
memset(&appl->stats, 0, sizeof appl->stats);
memset(&appl->devs, 0, sizeof appl->devs);
spin_lock(&capi_devices_table_lock);
for (id = 0; id < CAPI_MAX_DEVS; id++) {
dev = capi_devices_table[id];
if (!dev)
continue;
spin_lock(&dev->state_lock);
if (unlikely(!(dev->state == CAPI_DEVICE_STATE_RUNNING && try_module_get(dev->drv->owner)))) {
spin_unlock(&dev->state_lock);
continue;
}
get_capi_device(dev);
spin_unlock(&dev->state_lock);
spin_unlock(&capi_devices_table_lock);
info = dev->drv->capi_register(dev, appl);
if (unlikely(info)) {
struct module* owner = dev->drv->owner;
printk(KERN_NOTICE "capi: CAPI_REGISTER failed on device %d (info: %#x)\n", id + 1, info);
put_capi_device(dev);
module_put(owner);
} else {
__set_bit(id, appl->devs);
atomic_inc(&dev->appls);
smp_mb__after_atomic_inc();
capi_device_get(dev);
put_capi_device(dev);
}
spin_lock(&capi_devices_table_lock);
}
spin_unlock(&capi_devices_table_lock);
return CAPINFO_0X10_NOERR;
}
capinfo_0x11
capi_release(struct capi_appl* appl)
{
struct capi_device* dev;
int id;
for (id = 0; (id = find_next_bit(appl->devs, CAPI_MAX_DEVS, id)) < CAPI_MAX_DEVS; id++) {
dev = capi_devices_table[id];
BUG_ON(!dev);
if (likely(try_get_capi_device(dev))) {
dev->drv->capi_release(dev, appl);
atomic_dec(&dev->appls);
smp_mb__after_atomic_dec();
module_put(dev->drv->owner);
put_capi_device(dev);
}
capi_device_put(dev);
}
skb_queue_purge(&appl->msg_queue);
release_capi_appl_id(appl);
return appl->info;
}
capinfo_0x11
capi_put_message(struct capi_appl* appl, struct sk_buff* msg)
{
struct capi_device* dev;
int id;
capinfo_0x11 info = appl->info;
if (unlikely(info))
return info;
if (!msg || msg->len < CAPIMSG_BASELEN + 4)
return CAPINFO_0X11_ILLCMDORMSGTOSMALL;
id = CAPIMSG_CONTROLLER(msg->data);
if (unlikely(!(id && id <= CAPI_MAX_DEVS && test_bit(id - 1, appl->devs))))
return CAPINFO_0X11_OSRESERR;
dev = try_get_capi_device(capi_devices_table[id - 1]);
if (unlikely(!dev))
return CAPINFO_0X11_OSRESERR;
info = dev->drv->capi_put_message(dev, appl, msg);
put_capi_device(dev);
return info;
}
capinfo_0x11
capi_get_message(struct capi_appl* appl, struct sk_buff** msg)
{
if (unlikely(appl->info))
return appl->info;
*msg = skb_dequeue(&appl->msg_queue);
if (unlikely(!*msg))
return CAPINFO_0X11_QUEUEEMPTY;
return CAPINFO_0X11_NOERR;
}
capinfo_0x11
capi_peek_message(struct capi_appl* appl)
{
if (appl->info || !skb_queue_empty(&appl->msg_queue))
return CAPINFO_0X11_QUEUEFULL;
return CAPINFO_0X11_QUEUEEMPTY;
}
capinfo_0x11
capi_isinstalled(void)
{
if (atomic_read(&nr_capi_devices))
return CAPINFO_0X11_NOERR;
else
return CAPINFO_0X11_NOTINSTALLED;
}
static struct capi_device*
try_get_capi_device_by_id(int id)
{
struct capi_device* dev;
if (id < 1 || id > CAPI_MAX_DEVS)
return NULL;
spin_lock(&capi_devices_table_lock);
dev = try_get_capi_device(capi_devices_table[id - 1]);
spin_unlock(&capi_devices_table_lock);
return dev;
}
u8*
capi_get_manufacturer(int id, u8 manufacturer[CAPI_MANUFACTURER_LEN])
{
u8* res = manufacturer;
if (id) {
struct capi_device* dev = try_get_capi_device_by_id(id);
if (dev)
memcpy(manufacturer, dev->manufacturer, CAPI_MANUFACTURER_LEN);
else
res = NULL;
put_capi_device(dev);
} else
strlcpy(manufacturer, "NGC4Linux", CAPI_MANUFACTURER_LEN);
return res;
}
u8*
capi_get_serial(int id, u8 serial[CAPI_SERIAL_LEN])
{
u8* res = serial;
if (id) {
struct capi_device* dev = try_get_capi_device_by_id(id);
if (dev)
memcpy(serial, dev->serial, CAPI_SERIAL_LEN);
else
res = NULL;
put_capi_device(dev);
} else
strlcpy(serial, "0", CAPI_SERIAL_LEN);
return res;
}
struct capi_version*
capi_get_version(int id, struct capi_version* version)
{
static struct capi_version capicore_version = { 2, 0, 0, 1 };
if (id) {
struct capi_device* dev = try_get_capi_device_by_id(id);
if (dev)
*version = dev->version;
else
version = NULL;
put_capi_device(dev);
} else
*version = capicore_version;
return version;
}
capinfo_0x11
capi_get_profile(int id, struct capi_profile* profile)
{
capinfo_0x11 info = CAPINFO_0X11_NOERR;
if (id) {
struct capi_device* dev = try_get_capi_device_by_id(id);
if (dev) {
*profile = dev->profile;
profile->ncontroller = atomic_read(&nr_capi_devices);
} else
info = CAPINFO_0X11_OSRESERR;
put_capi_device(dev);
} else
profile->ncontroller = atomic_read(&nr_capi_devices);
return info;
}
static int __init
capicore_init(void)
{
return class_register(&capi_class);
}
static void __exit
capicore_exit(void)
{
class_unregister(&capi_class);
}
module_init(capicore_init);
module_exit(capicore_exit);
MODULE_AUTHOR("Frank A. Uepping <Frank.Uepping@web.de>");
MODULE_LICENSE("GPL");
EXPORT_SYMBOL(capi_device_alloc);
EXPORT_SYMBOL(capi_device_register);
EXPORT_SYMBOL(capi_device_unregister);
EXPORT_SYMBOL(capi_register);
EXPORT_SYMBOL(capi_release);
EXPORT_SYMBOL(capi_put_message);
EXPORT_SYMBOL(capi_get_message);
EXPORT_SYMBOL(capi_peek_message);
EXPORT_SYMBOL(capi_isinstalled);
EXPORT_SYMBOL(capi_get_manufacturer);
EXPORT_SYMBOL(capi_get_serial);
EXPORT_SYMBOL(capi_get_version);
EXPORT_SYMBOL(capi_get_profile);