/*
       * linux/kernel/ldt.c
       *
       * Copyright (C) 1992 Krishna Balasubramanian and Linus Torvalds
       * Copyright (C) 1999 Ingo Molnar <mingo@redhat.com>
       */
      
      #include <linux/errno.h>
      #include <linux/sched.h>
      #include <linux/string.h>
      #include <linux/mm.h>
      #include <linux/smp.h>
      #include <linux/smp_lock.h>
      #include <linux/vmalloc.h>
      
      #include <asm/uaccess.h>
      #include <asm/system.h>
      #include <asm/ldt.h>
      #include <asm/desc.h>
      
      /*
       * read_ldt() is not really atomic - this is not a problem since
       * synchronization of reads and writes done to the LDT has to be
       * assured by user-space anyway. Writes are atomic, to protect
       * the security checks done on new descriptors.
       */
  27  static int read_ldt(void * ptr, unsigned long bytecount)
      {
      	int err;
      	unsigned long size;
      	struct mm_struct * mm = current->mm;
      
      	err = 0;
  34  	if (!mm->context.segments)
  35  		goto out;
      
      	size = LDT_ENTRIES*LDT_ENTRY_SIZE;
  38  	if (size > bytecount)
      		size = bytecount;
      
      	err = size;
  42  	if (copy_to_user(ptr, mm->context.segments, size))
      		err = -EFAULT;
      out:
  45  	return err;
      }
      
  48  static int write_ldt(void * ptr, unsigned long bytecount, int oldmode)
      {
      	struct mm_struct * mm = current->mm;
      	__u32 entry_1, entry_2, *lp;
      	int error;
      	struct modify_ldt_ldt_s ldt_info;
      
      	error = -EINVAL;
  56  	if (bytecount != sizeof(ldt_info))
  57  		goto out;
      	error = -EFAULT; 	
  59  	if (copy_from_user(&ldt_info, ptr, sizeof(ldt_info)))
  60  		goto out;
      
      	error = -EINVAL;
  63  	if (ldt_info.entry_number >= LDT_ENTRIES)
  64  		goto out;
  65  	if (ldt_info.contents == 3) {
  66  		if (oldmode)
  67  			goto out;
  68  		if (ldt_info.seg_not_present == 0)
  69  			goto out;
      	}
      
      	/*
      	 * Horrible dependencies! Try to get rid of this. This is wrong,
      	 * as it only reloads the ldt for the first process with this
      	 * mm. The implications are that you should really make sure that
      	 * you have a ldt before you do the first clone(), otherwise
      	 * you get strange behaviour (the kernel is safe, it's just user
      	 * space strangeness).
      	 *
      	 * we have two choices: either we preallocate the LDT descriptor
      	 * and can do a shared modify_ldt(), or we postallocate it and do
      	 * an smp message pass to update it. Currently we are a bit
      	 * un-nice to user-space and reload the LDT only on the next
      	 * schedule. (only an issue on SMP)
      	 *
      	 * the GDT index of the LDT is allocated dynamically, and is
      	 * limited by MAX_LDT_DESCRIPTORS.
      	 */
      	down(&mm->mmap_sem);
  90  	if (!mm->context.segments) {
      		error = -ENOMEM;
      		mm->context.segments = vmalloc(LDT_ENTRIES*LDT_ENTRY_SIZE);
  93  		if (!mm->context.segments)
  94  			goto out_unlock;
      		memset(mm->context.segments, 0, LDT_ENTRIES*LDT_ENTRY_SIZE);
      		
  97  		if (atomic_read(&mm->mm_users) > 1)
      			printk(KERN_WARNING "LDT allocated for cloned task!\n");
      		/*
      		 * Possibly do an SMP cross-call to other CPUs to reload
      		 * their LDTs?
      		 */
      		load_LDT(mm);
      	}
      
      	lp = (__u32 *) ((ldt_info.entry_number << 3) + (char *) mm->context.segments);
      
         	/* Allow LDTs to be cleared by the user. */
 109     	if (ldt_info.base_addr == 0 && ldt_info.limit == 0) {
      		if (oldmode ||
      		    (ldt_info.contents == 0		&&
      		     ldt_info.read_exec_only == 1	&&
      		     ldt_info.seg_32bit == 0		&&
      		     ldt_info.limit_in_pages == 0	&&
      		     ldt_info.seg_not_present == 1	&&
 116  		     ldt_info.useable == 0 )) {
      			entry_1 = 0;
      			entry_2 = 0;
 119  			goto install;
      		}
      	}
      
      	entry_1 = ((ldt_info.base_addr & 0x0000ffff) << 16) |
      		  (ldt_info.limit & 0x0ffff);
      	entry_2 = (ldt_info.base_addr & 0xff000000) |
      		  ((ldt_info.base_addr & 0x00ff0000) >> 16) |
      		  (ldt_info.limit & 0xf0000) |
      		  ((ldt_info.read_exec_only ^ 1) << 9) |
      		  (ldt_info.contents << 10) |
      		  ((ldt_info.seg_not_present ^ 1) << 15) |
      		  (ldt_info.seg_32bit << 22) |
      		  (ldt_info.limit_in_pages << 23) |
      		  0x7000;
 134  	if (!oldmode)
      		entry_2 |= (ldt_info.useable << 20);
      
      	/* Install the new entry ...  */
      install:
      	*lp	= entry_1;
      	*(lp+1)	= entry_2;
      	error = 0;
      
      out_unlock:
      	up(&mm->mmap_sem);
      out:
 146  	return error;
      }
      
 149  asmlinkage int sys_modify_ldt(int func, void *ptr, unsigned long bytecount)
      {
      	int ret = -ENOSYS;
      
 153  	switch (func) {
 154  	case 0:
      		ret = read_ldt(ptr, bytecount);
 156  		break;
 157  	case 1:
      		ret = write_ldt(ptr, bytecount, 1);
 159  		break;
 160  	case 0x11:
      		ret = write_ldt(ptr, bytecount, 0);
 162  		break;
      	}
 164  	return ret;
      }