aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--arch/x86/include/asm/io_bitmap.h5
-rw-r--r--arch/x86/kernel/ioport.c48
-rw-r--r--arch/x86/kernel/process.c39
3 files changed, 50 insertions, 42 deletions
diff --git a/arch/x86/include/asm/io_bitmap.h b/arch/x86/include/asm/io_bitmap.h
index 784a88edb25d..b664baadf736 100644
--- a/arch/x86/include/asm/io_bitmap.h
+++ b/arch/x86/include/asm/io_bitmap.h
@@ -2,15 +2,20 @@
#ifndef _ASM_X86_IOBITMAP_H
#define _ASM_X86_IOBITMAP_H
+#include <linux/refcount.h>
#include <asm/processor.h>
struct io_bitmap {
u64 sequence;
+ refcount_t refcnt;
/* The maximum number of bytes to copy so all zero bits are covered */
unsigned int max;
unsigned long bitmap[IO_BITMAP_LONGS];
};
+struct task_struct;
+
+void io_bitmap_share(struct task_struct *tsk);
void io_bitmap_exit(void);
void tss_update_io_bitmap(void);
diff --git a/arch/x86/kernel/ioport.c b/arch/x86/kernel/ioport.c
index f9fc69aeb033..f82ca1c62e3e 100644
--- a/arch/x86/kernel/ioport.c
+++ b/arch/x86/kernel/ioport.c
@@ -16,6 +16,17 @@
static atomic64_t io_bitmap_sequence;
+void io_bitmap_share(struct task_struct *tsk)
+ {
+ /*
+ * Take a refcount on current's bitmap. It can be used by
+ * both tasks as long as none of them changes the bitmap.
+ */
+ refcount_inc(&current->thread.io_bitmap->refcnt);
+ tsk->thread.io_bitmap = current->thread.io_bitmap;
+ set_tsk_thread_flag(tsk, TIF_IO_BITMAP);
+}
+
void io_bitmap_exit(void)
{
struct io_bitmap *iobm = current->thread.io_bitmap;
@@ -25,7 +36,8 @@ void io_bitmap_exit(void)
preempt_disable();
tss_update_io_bitmap();
preempt_enable();
- kfree(iobm);
+ if (iobm && refcount_dec_and_test(&iobm->refcnt))
+ kfree(iobm);
}
/*
@@ -58,9 +70,32 @@ long ksys_ioperm(unsigned long from, unsigned long num, int turn_on)
return -ENOMEM;
memset(iobm->bitmap, 0xff, sizeof(iobm->bitmap));
+ refcount_set(&iobm->refcnt, 1);
+ }
+
+ /*
+ * If the bitmap is not shared, then nothing can take a refcount as
+ * current can obviously not fork at the same time. If it's shared
+ * duplicate it and drop the refcount on the original one.
+ */
+ if (refcount_read(&iobm->refcnt) > 1) {
+ iobm = kmemdup(iobm, sizeof(*iobm), GFP_KERNEL);
+ if (!iobm)
+ return -ENOMEM;
+ refcount_set(&iobm->refcnt, 1);
+ io_bitmap_exit();
}
/*
+ * Store the bitmap pointer (might be the same if the task already
+ * head one). Must be done here so freeing the bitmap when all
+ * permissions are dropped has the pointer set up.
+ */
+ t->io_bitmap = iobm;
+ /* Mark it active for context switching and exit to user mode */
+ set_thread_flag(TIF_IO_BITMAP);
+
+ /*
* Update the tasks bitmap. The update of the TSS bitmap happens on
* exit to user mode. So this needs no protection.
*/
@@ -86,16 +121,11 @@ long ksys_ioperm(unsigned long from, unsigned long num, int turn_on)
iobm->max = (max_long + 1) * sizeof(unsigned long);
- /* Update the sequence number to force an update in switch_to() */
- iobm->sequence = atomic64_add_return(1, &io_bitmap_sequence);
-
/*
- * Store the bitmap pointer (might be the same if the task already
- * head one). Set the TIF flag, just in case this is the first
- * invocation.
+ * Update the sequence number to force a TSS update on return to
+ * user mode.
*/
- t->io_bitmap = iobm;
- set_thread_flag(TIF_IO_BITMAP);
+ iobm->sequence = atomic64_add_return(1, &io_bitmap_sequence);
return 0;
}
diff --git a/arch/x86/kernel/process.c b/arch/x86/kernel/process.c
index 7ba4d54aec17..0b19c137c442 100644
--- a/arch/x86/kernel/process.c
+++ b/arch/x86/kernel/process.c
@@ -122,37 +122,13 @@ static int set_new_tls(struct task_struct *p, unsigned long tls)
return do_set_thread_area_64(p, ARCH_SET_FS, tls);
}
-static inline int copy_io_bitmap(struct task_struct *tsk)
-{
- struct io_bitmap *iobm = current->thread.io_bitmap;
-
- if (likely(!test_tsk_thread_flag(current, TIF_IO_BITMAP)))
- return 0;
-
- tsk->thread.io_bitmap = kmemdup(iobm, sizeof(*iobm), GFP_KERNEL);
-
- if (!tsk->thread.io_bitmap)
- return -ENOMEM;
-
- set_tsk_thread_flag(tsk, TIF_IO_BITMAP);
- return 0;
-}
-
-static inline void free_io_bitmap(struct task_struct *tsk)
-{
- if (tsk->thread.io_bitmap) {
- kfree(tsk->thread.io_bitmap);
- tsk->thread.io_bitmap = NULL;
- }
-}
-
int copy_thread_tls(unsigned long clone_flags, unsigned long sp,
unsigned long arg, struct task_struct *p, unsigned long tls)
{
struct inactive_task_frame *frame;
struct fork_frame *fork_frame;
struct pt_regs *childregs;
- int ret;
+ int ret = 0;
childregs = task_pt_regs(p);
fork_frame = container_of(childregs, struct fork_frame, regs);
@@ -199,16 +175,13 @@ int copy_thread_tls(unsigned long clone_flags, unsigned long sp,
task_user_gs(p) = get_user_gs(current_pt_regs());
#endif
- ret = copy_io_bitmap(p);
- if (ret)
- return ret;
-
/* Set a new TLS for the child thread? */
- if (clone_flags & CLONE_SETTLS) {
+ if (clone_flags & CLONE_SETTLS)
ret = set_new_tls(p, tls);
- if (ret)
- free_io_bitmap(p);
- }
+
+ if (!ret && unlikely(test_tsk_thread_flag(current, TIF_IO_BITMAP)))
+ io_bitmap_share(p);
+
return ret;
}