/*
 * General data structures and routines for segmentation
 * Copyright (c) 2001,2003,2004 David H. Hovemeyer <daveho@cs.umd.edu>
 * Copyright (c) 2003,2013,2014 Jeffrey K. Hollingsworth <hollings@cs.umd.edu>
 *
 * All rights reserved.
 *
 * This code may not be resdistributed without the permission of the copyright holders.
 * Any student solutions using any of this code base constitute derviced work and may
 * not be redistributed in any form.  This includes (but is not limited to) posting on
 * public forums or web sites, providing copies to (past, present, or future) students
 * enrolled in similar operating systems courses the University of Maryland's CMSC412 course.
 */

/*
 * Source: _Protected Mode Software Architecture_ by Tom Shanley,
 * ISBN 020155447X.
 */

#include <geekos/kassert.h>
#include <geekos/string.h>
#include <geekos/tss.h>
#include <geekos/segment.h>

static __inline__ void Set_Size_And_Base_Pages(struct Segment_Descriptor
                                               *desc, ulong_t baseAddr,
                                               ulong_t numPages) {
    /*
     * There are 20 bits in the size fields of a segment descriptor.
     * The maximum possible value is thus 0xFFFFF, which in terms of
     * pages is one page less than 4GB.  So, I conclude that the
     * number of pages in the segment is one greater than the
     * value specified in the descriptor.
     */
    KASSERT(numPages > 0);
    numPages -= 1;

    desc->sizeLow = numPages & 0xFFFF;
    desc->sizeHigh = (numPages >> 16) & 0x0F;
    desc->baseLow = baseAddr & 0xFFFFFF;
    desc->baseHigh = (baseAddr >> 24) & 0xFF;
    desc->granularity = 1;      /* size in pages */
}

static __inline__ void Set_Size_And_Base_Bytes(struct Segment_Descriptor
                                               *desc, ulong_t baseAddr,
                                               ulong_t numBytes) {
    desc->sizeLow = numBytes & 0xFFFF;
    desc->sizeHigh = (numBytes >> 16) & 0x0F;
    desc->baseLow = baseAddr & 0xFFFFFF;
    desc->baseHigh = (baseAddr >> 24) & 0xFF;
    desc->granularity = 0;      /* size in bytes */
}

/*
 * Initialize an unused segment descriptor.
 */
void Init_Null_Segment_Descriptor(struct Segment_Descriptor *desc) {
    memset(desc, '\0', sizeof(*desc));
}

/*
 * Initialize a code segment descriptor.
 */
void Init_Code_Segment_Descriptor(struct Segment_Descriptor *desc,
                                  ulong_t baseAddr,
                                  ulong_t numPages, int privilegeLevel) {
    KASSERT(privilegeLevel >= 0 && privilegeLevel <= 3);

    Set_Size_And_Base_Pages(desc, baseAddr, numPages);
    desc->type = 0x0A;          /* 1010b: code, !conforming, readable, !accessed */
    desc->system = 1;
    desc->dpl = privilegeLevel;
    desc->present = 1;
    desc->reserved = 0;
    desc->dbBit = 1;            /* 32 bit code segment */
}

/*
 * Initialize a data segment descriptor.
 */
void Init_Data_Segment_Descriptor(struct Segment_Descriptor *desc,
                                  ulong_t baseAddr,
                                  ulong_t numPages, int privilegeLevel) {
    KASSERT(privilegeLevel >= 0 && privilegeLevel <= 3);

    Set_Size_And_Base_Pages(desc, baseAddr, numPages);
    desc->type = 0x02;          /* 0010b: data, expand-up, writable, !accessed */
    desc->system = 1;
    desc->dpl = privilegeLevel;
    desc->present = 1;
    desc->reserved = 0;
    desc->dbBit = 1;            /* 32 bit operands */
}

/*
 * Initialize a TSS descriptor.
 */
void Init_TSS_Descriptor(struct Segment_Descriptor *desc,
                         struct TSS *theTSS) {
    Set_Size_And_Base_Bytes(desc, (ulong_t) theTSS, sizeof(struct TSS));
    desc->type = 0x09;          /* 1001b: 32 bit, !busy */
    desc->system = 0;
    desc->dpl = 0;
    desc->present = 1;
    desc->reserved = 0;
    desc->dbBit = 0;            /* must be 0 in TSS */
}

/*
 * Initialize an LDT (Local Descriptor Table) descriptor.
 */
void Init_LDT_Descriptor(struct Segment_Descriptor *desc,
                         struct Segment_Descriptor theLDT[],
                         int numEntries) {
    Set_Size_And_Base_Bytes(desc, (ulong_t) theLDT,
                            sizeof(struct Segment_Descriptor) *
                            numEntries);

    desc->type = 0x02;          /* 0010b */
    desc->system = 0;
    desc->dpl = 0;
    desc->present = 1;
    desc->reserved = 0;
    desc->dbBit = 0;
}

/* // TODO(PERCPU, "write a function based on Init_LDT_Descriptor that initializes an LDT on each per-cpu GDT") */
