diff options
author | kpfleming <kpfleming@5390a7c7-147a-4af0-8ec9-7488f05a26cb> | 2008-01-11 21:35:18 +0000 |
---|---|---|
committer | kpfleming <kpfleming@5390a7c7-147a-4af0-8ec9-7488f05a26cb> | 2008-01-11 21:35:18 +0000 |
commit | eecfbb328db7fca69f7a28f8bf1bf1e7b94e2194 (patch) | |
tree | bf2ef96d3d749a43040feade783fa1fc27d1fc22 | |
parent | 78628ea0f9ec7986bb459b0df00ca45c6e18c1d3 (diff) |
Implement atomic reference counting for tone zone structures, ensuring that they will never be freed while they are in use by a channel or as the default zone.
In passing, improve default zone handling so that there will never be a default zone value pointing to a zone that hasn't been loaded yet.
(closes issue #10593)
Reported by: jmhunter
Patches were provided by Matti, but a different solution was chosen
git-svn-id: http://svn.digium.com/svn/zaptel/branches/1.4@3667 5390a7c7-147a-4af0-8ec9-7488f05a26cb
-rw-r--r-- | zaptel-base.c | 83 | ||||
-rw-r--r-- | zconfig.h | 3 |
2 files changed, 52 insertions, 34 deletions
diff --git a/zaptel-base.c b/zaptel-base.c index a7feff6..f40e9a5 100644 --- a/zaptel-base.c +++ b/zaptel-base.c @@ -59,6 +59,7 @@ #include <linux/if.h> #include <linux/if_ppp.h> #endif +#include <asm/atomic.h> #ifndef CONFIG_OLD_HDLC_API #define NEW_HDLC_INTERFACE @@ -361,6 +362,7 @@ static spinlock_t bigzaplock = SPIN_LOCK_UNLOCKED; #endif struct zt_zone { + atomic_t refcount; char name[40]; /* Informational, only */ int ringcadence[ZT_MAX_CADENCE]; struct zt_tone *tones[ZT_TONE_MAX]; @@ -382,7 +384,7 @@ static int maxchans = 0; static int maxconfs = 0; static int maxlinks = 0; -static int default_zone = DEFAULT_TONE_ZONE; +static int default_zone = -1; short __zt_mulaw[256]; short __zt_alaw[256]; @@ -994,6 +996,8 @@ static void close_channel(struct zt_chan *chan) readchunkpreec = chan->readchunkpreec; chan->readchunkpreec = NULL; chan->curtone = NULL; + if (chan->curzone) + atomic_dec(&chan->curzone->refcount); chan->curzone = NULL; chan->cadencepos = 0; chan->pdialcount = 0; @@ -1072,18 +1076,35 @@ static int free_tone_zone(int num) { struct zt_zone *z; + if ((num >= ZT_TONE_ZONE_MAX) || (num < 0)) + return -EINVAL; + + write_lock(&zone_lock); z = tone_zones[num]; tone_zones[num] = NULL; - kfree(z); + write_unlock(&zone_lock); - return 0; + if (atomic_read(&z->refcount)) { + /* channels are still using this zone so put it back */ + write_lock(&zone_lock); + tone_zones[num] = z; + write_unlock(&zone_lock); + + return -EBUSY; + } else { + kfree(z); + + return 0; + } } static int zt_register_tone_zone(int num, struct zt_zone *zone) { - int res=0; + int res = 0; + if ((num >= ZT_TONE_ZONE_MAX) || (num < 0)) return -EINVAL; + write_lock(&zone_lock); if (tone_zones[num]) { res = -EINVAL; @@ -1092,8 +1113,10 @@ static int zt_register_tone_zone(int num, struct zt_zone *zone) tone_zones[num] = zone; } write_unlock(&zone_lock); + if (!res) printk(KERN_INFO "Registered tone zone %d (%s)\n", num, zone->name); + return res; } @@ -1147,20 +1170,27 @@ static int start_tone(struct zt_chan *chan, int tone) static int set_tone_zone(struct zt_chan *chan, int zone) { - int res=0; + int res = 0; + struct zt_zone *z; + /* Assumes channel is already locked */ - if ((zone >= ZT_TONE_ZONE_MAX) || (zone < -1)) + + if (zone == -1) + zone = default_zone; + + if ((zone >= ZT_TONE_ZONE_MAX) || (zone < 0)) return -EINVAL; read_lock(&zone_lock); - if (zone == -1) { - zone = default_zone; - } - if (tone_zones[zone]) { - chan->curzone = tone_zones[zone]; + if ((z = tone_zones[zone])) { + if (chan->curzone) + atomic_dec(&chan->curzone->refcount); + + atomic_inc(&z->refcount); + chan->curzone = z; chan->tonezone = zone; - memcpy(chan->ringcadence, chan->curzone->ringcadence, sizeof(chan->ringcadence)); + memcpy(chan->ringcadence, z->ringcadence, sizeof(chan->ringcadence)); } else { res = -ENODATA; } @@ -2602,6 +2632,8 @@ static int ioctl_load_zone(unsigned long data) for (x = 0; x < ZT_MAX_CADENCE; x++) z->ringcadence[x] = th.ringcadence[x]; + atomic_set(&z->refcount, 0); + for (x = 0; x < th.count; x++) { enum { REGULAR_TONE, @@ -3622,6 +3654,9 @@ static int zt_ctl_ioctl(struct inode *inode, struct file *file, unsigned int cmd write_unlock(&zone_lock); return -EINVAL; } + if ((default_zone != -1) && tone_zones[default_zone]) + atomic_dec(&tone_zones[default_zone]->refcount); + atomic_inc(&tone_zones[j]->refcount); default_zone = j; write_unlock(&zone_lock); break; @@ -3629,19 +3664,7 @@ static int zt_ctl_ioctl(struct inode *inode, struct file *file, unsigned int cmd return ioctl_load_zone(data); case ZT_FREEZONE: get_user(j, (int *) data); - if ((j < 0) || (j >= ZT_TONE_ZONE_MAX)) - return -EINVAL; - write_lock(&zone_lock); -#if 0 - if (j == default_zone) { - write_unlock(&zone_lock); - /* XXX: possibly a better return code here */ - return -EINVAL; - } -#endif - free_tone_zone(j); - write_unlock(&zone_lock); - break; + return free_tone_zone(j); case ZT_SET_DIALPARAMS: if (copy_from_user(&tdp, (struct zt_dialparams *) data, sizeof(tdp))) return -EFAULT; @@ -3982,19 +4005,17 @@ static int zt_chanandpseudo_ioctl(struct inode *inode, struct file *file, unsign rv = 0; break; case ZT_SETTONEZONE: - get_user(j,(int *)data); + get_user(j, (int *) data); spin_lock_irqsave(&chan->lock, flags); - rv = set_tone_zone(chan, j); + rv = set_tone_zone(chan, j); spin_unlock_irqrestore(&chan->lock, flags); return rv; case ZT_GETTONEZONE: spin_lock_irqsave(&chan->lock, flags); if (chan->curzone) - rv = chan->tonezone; - else - rv = default_zone; + j = chan->tonezone; spin_unlock_irqrestore(&chan->lock, flags); - put_user(rv,(int *)data); /* return value */ + put_user(j, (int *) data); break; case ZT_SENDTONE: get_user(j,(int *)data); @@ -136,9 +136,6 @@ */ /* #define CONFIG_ZAPTEL_WATCHDOG */ -/* Tone zone info */ -#define DEFAULT_TONE_ZONE 0 - /* * Uncomment for Non-standard FXS groundstart start state (A=Low, B=Low) * particularly for CAC channel bank groundstart FXO ports. |