diff options
| author | Jeremy Fitzhardinge <jeremy.fitzhardinge@citrix.com> | 2009-02-12 16:03:24 -0500 |
|---|---|---|
| committer | Jeremy Fitzhardinge <jeremy.fitzhardinge@citrix.com> | 2009-03-30 12:26:49 -0400 |
| commit | 0a4666b539a0e896ec4e8396a034a479e3573125 (patch) | |
| tree | bfe63e7b6d3da3d8415c53919059bda72d4cf86a | |
| parent | c5cfef0f79cacc3aa438fc28f4747f0d10c54d0d (diff) | |
xen/dev-evtchn: clean up locking in evtchn
Define a new per_user_data mutex to serialize bind/unbind operations
to prevent them from racing with each other. Fix error returns
and don't do a bind while holding a spinlock.
Signed-off-by: Jeremy Fitzhardinge <jeremy.fitzhardinge@citrix.com>
| -rw-r--r-- | drivers/xen/evtchn.c | 37 |
1 files changed, 25 insertions, 12 deletions
diff --git a/drivers/xen/evtchn.c b/drivers/xen/evtchn.c index 517b9ee63e18..af031950f9b1 100644 --- a/drivers/xen/evtchn.c +++ b/drivers/xen/evtchn.c | |||
| @@ -54,6 +54,8 @@ | |||
| 54 | #include <asm/xen/hypervisor.h> | 54 | #include <asm/xen/hypervisor.h> |
| 55 | 55 | ||
| 56 | struct per_user_data { | 56 | struct per_user_data { |
| 57 | struct mutex bind_mutex; /* serialize bind/unbind operations */ | ||
| 58 | |||
| 57 | /* Notification ring, accessed via /dev/xen/evtchn. */ | 59 | /* Notification ring, accessed via /dev/xen/evtchn. */ |
| 58 | #define EVTCHN_RING_SIZE (PAGE_SIZE / sizeof(evtchn_port_t)) | 60 | #define EVTCHN_RING_SIZE (PAGE_SIZE / sizeof(evtchn_port_t)) |
| 59 | #define EVTCHN_RING_MASK(_i) ((_i)&(EVTCHN_RING_SIZE-1)) | 61 | #define EVTCHN_RING_MASK(_i) ((_i)&(EVTCHN_RING_SIZE-1)) |
| @@ -69,7 +71,7 @@ struct per_user_data { | |||
| 69 | 71 | ||
| 70 | /* Who's bound to each port? */ | 72 | /* Who's bound to each port? */ |
| 71 | static struct per_user_data *port_user[NR_EVENT_CHANNELS]; | 73 | static struct per_user_data *port_user[NR_EVENT_CHANNELS]; |
| 72 | static DEFINE_SPINLOCK(port_user_lock); | 74 | static DEFINE_SPINLOCK(port_user_lock); /* protects port_user[] and ring_prod */ |
| 73 | 75 | ||
| 74 | irqreturn_t evtchn_interrupt(int irq, void *data) | 76 | irqreturn_t evtchn_interrupt(int irq, void *data) |
| 75 | { | 77 | { |
| @@ -210,22 +212,24 @@ static ssize_t evtchn_write(struct file *file, const char __user *buf, | |||
| 210 | 212 | ||
| 211 | static int evtchn_bind_to_user(struct per_user_data *u, int port) | 213 | static int evtchn_bind_to_user(struct per_user_data *u, int port) |
| 212 | { | 214 | { |
| 213 | int irq; | ||
| 214 | int rc = 0; | 215 | int rc = 0; |
| 215 | 216 | ||
| 216 | spin_lock_irq(&port_user_lock); | 217 | /* |
| 217 | 218 | * Ports are never reused, so every caller should pass in a | |
| 219 | * unique port. | ||
| 220 | * | ||
| 221 | * (Locking not necessary because we haven't registered the | ||
| 222 | * interrupt handler yet, and our caller has already | ||
| 223 | * serialized bind operations.) | ||
| 224 | */ | ||
| 218 | BUG_ON(port_user[port] != NULL); | 225 | BUG_ON(port_user[port] != NULL); |
| 219 | |||
| 220 | irq = bind_evtchn_to_irqhandler(port, evtchn_interrupt, IRQF_DISABLED, | ||
| 221 | u->name, (void *)(unsigned long)port); | ||
| 222 | if (rc < 0) | ||
| 223 | goto fail; | ||
| 224 | |||
| 225 | port_user[port] = u; | 226 | port_user[port] = u; |
| 226 | 227 | ||
| 227 | fail: | 228 | rc = bind_evtchn_to_irqhandler(port, evtchn_interrupt, IRQF_DISABLED, |
| 228 | spin_unlock_irq(&port_user_lock); | 229 | u->name, (void *)(unsigned long)port); |
| 230 | if (rc >= 0) | ||
| 231 | rc = 0; | ||
| 232 | |||
| 229 | return rc; | 233 | return rc; |
| 230 | } | 234 | } |
| 231 | 235 | ||
| @@ -234,6 +238,10 @@ static void evtchn_unbind_from_user(struct per_user_data *u, int port) | |||
| 234 | int irq = irq_from_evtchn(port); | 238 | int irq = irq_from_evtchn(port); |
| 235 | 239 | ||
| 236 | unbind_from_irqhandler(irq, (void *)(unsigned long)port); | 240 | unbind_from_irqhandler(irq, (void *)(unsigned long)port); |
| 241 | |||
| 242 | /* make sure we unbind the irq handler before clearing the port */ | ||
| 243 | barrier(); | ||
| 244 | |||
| 237 | port_user[port] = NULL; | 245 | port_user[port] = NULL; |
| 238 | } | 246 | } |
| 239 | 247 | ||
| @@ -244,6 +252,9 @@ static long evtchn_ioctl(struct file *file, | |||
| 244 | struct per_user_data *u = file->private_data; | 252 | struct per_user_data *u = file->private_data; |
| 245 | void __user *uarg = (void __user *) arg; | 253 | void __user *uarg = (void __user *) arg; |
| 246 | 254 | ||
| 255 | /* Prevent bind from racing with unbind */ | ||
| 256 | mutex_lock(&u->bind_mutex); | ||
| 257 | |||
| 247 | switch (cmd) { | 258 | switch (cmd) { |
| 248 | case IOCTL_EVTCHN_BIND_VIRQ: { | 259 | case IOCTL_EVTCHN_BIND_VIRQ: { |
| 249 | struct ioctl_evtchn_bind_virq bind; | 260 | struct ioctl_evtchn_bind_virq bind; |
| @@ -368,6 +379,7 @@ static long evtchn_ioctl(struct file *file, | |||
| 368 | rc = -ENOSYS; | 379 | rc = -ENOSYS; |
| 369 | break; | 380 | break; |
| 370 | } | 381 | } |
| 382 | mutex_unlock(&u->bind_mutex); | ||
| 371 | 383 | ||
| 372 | return rc; | 384 | return rc; |
| 373 | } | 385 | } |
| @@ -414,6 +426,7 @@ static int evtchn_open(struct inode *inode, struct file *filp) | |||
| 414 | return -ENOMEM; | 426 | return -ENOMEM; |
| 415 | } | 427 | } |
| 416 | 428 | ||
| 429 | mutex_init(&u->bind_mutex); | ||
| 417 | mutex_init(&u->ring_cons_mutex); | 430 | mutex_init(&u->ring_cons_mutex); |
| 418 | 431 | ||
| 419 | filp->private_data = u; | 432 | filp->private_data = u; |
