/*
 * Copyright (c) 2010 ARM Limited
 * All rights reserved
 *
 * The license below extends only to copyright in the software and shall
 * not be construed as granting a license to any other intellectual
 * property including but not limited to intellectual property relating
 * to a hardware implementation of the functionality of the software
 * licensed hereunder.  You may use the software subject to the license
 * terms below provided that you ensure that this notice is replicated
 * unmodified and in its entirety in all distributions of the software,
 * modified or unmodified, in source code or in binary form.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Ali Saidi
 */

#ifndef __ARCH_ARM_LINUX_ATAG_HH__
#define __ARCH_ARM_LINUX_ATAG_HH__

#include <cstring>
#include <string>

#include "base/types.hh"

enum {
    CoreTag   = 0x54410001,
    MemTag    = 0x54410002,
    RevTag    = 0x54410007,
    SerialTag = 0x54410006,
    CmdTag    = 0x54410009,
    NoneTag   = 0x00000000
};

class AtagHeader
{
  protected:
    uint32_t *storage;
    uint32_t _size;

  public:
    /** Tag (normally starts with 'T''A' and 16 bits of number */
    virtual uint32_t tag() = 0;
    /** If the header should be 0 size */
    virtual bool null() { return false; }

    uint32_t size() const { return _size; }

    AtagHeader(uint32_t s)
        : _size(s)
    {
        storage = new uint32_t[size()];
    }

    virtual ~AtagHeader()
    {
        delete[] storage;
    }

    uint32_t copyOut(uint8_t *p)
    {
        storage[0] = null() ? 0 : size();
        storage[1] = tag();
        memcpy(p, storage, size() << 2);
        return size() << 2;
    }
};

class AtagCore : public AtagHeader
{
  public:
    static const uint32_t Size = 5;
    uint32_t tag() { return CoreTag; }

    void flags(uint32_t i) { storage[2] = i; }
    void pagesize(uint32_t i) { storage[3] = i; }
    void rootdev(uint32_t i) { storage[4] = i; }
    AtagCore()
        : AtagHeader(Size)
    {}
};

class AtagMem : public AtagHeader
{
  public:
    static const uint32_t Size = 4;
    uint32_t tag() { return MemTag; }

    void memSize(uint32_t i) { storage[2] = i; }
    void memStart(uint32_t i) { storage[3] = i; }
    AtagMem()
        : AtagHeader(Size)
    {}
};

class AtagRev : public AtagHeader
{
  public:
    static const uint32_t Size = 3;
    uint32_t tag() { return RevTag; }

    void rev(uint32_t i) { storage[2] = i; }
    AtagRev()
        : AtagHeader(Size)
    {}
};


class AtagSerial : public AtagHeader
{
  public:
    static const uint32_t Size = 4;
    uint32_t tag() { return SerialTag; }

    void sn(uint64_t i) { storage[2] = (uint32_t)i; storage[3] = i >> 32; }
    AtagSerial()
        : AtagHeader(Size)
    {}
};

class AtagCmdline : public AtagHeader
{
  public:
    static const uint32_t Size = 3;
    uint32_t tag() { return CmdTag; }

    void cmdline(const std::string &s)
    {
        // Add one for null terminator
        int len = s.length() + 1;

        // 2 + ceiling(len/4)
        _size = 2 + ((len + 3) >> 2);

        delete[] storage;
        storage = new uint32_t[size()];
        // Initialize the last byte of memory here beacuse it might be slightly
        // longer than needed and mis-speculation of the NULL in the O3 CPU can
        // change stats ever so slightly when that happens.
        storage[size() - 1] = 0;
        strcpy((char*)&storage[2] , s.c_str());
    }
    AtagCmdline()
        : AtagHeader(Size)
    {}
};

class AtagNone : public AtagHeader
{
  public:
    static const uint32_t Size = 2;
    virtual bool null() { return true; }
    uint32_t tag() { return NoneTag; }
    AtagNone()
        : AtagHeader(Size)
    {}
};
/*
//
// example ARM Linux bootloader code
// this example is distributed under the BSD licence
// Code taken from http://www.simtec.co.uk/products/SWLINUX/files/booting_article.html
///

// list of possible tags
#define ATAG_NONE       0x00000000
#define ATAG_CORE       0x54410001
#define ATAG_MEM        0x54410002
#define ATAG_VIDEOTEXT  0x54410003
#define ATAG_RAMDISK    0x54410004
#define ATAG_INITRD2    0x54420005
#define ATAG_SERIAL     0x54410006
#define ATAG_REVISION   0x54410007
#define ATAG_VIDEOLFB   0x54410008
#define ATAG_CMDLINE    0x54410009

// structures for each atag
struct atag_header {
        u32 size; // length of tag in words including this header
        u32 tag;  // tag type
};

struct atag_core {
        u32 flags;
        u32 pagesize;
        u32 rootdev;
};

struct atag_mem {
        u32     size;
        u32     start;
};

struct atag_videotext {
        u8              x;
        u8              y;
        u16             video_page;
        u8              video_mode;
        u8              video_cols;
        u16             video_ega_bx;
        u8              video_lines;
        u8              video_isvga;
        u16             video_points;
};

struct atag_ramdisk {
        u32 flags;
        u32 size;
        u32 start;
};

struct atag_initrd2 {
        u32 start;
        u32 size;
};

struct atag_serialnr {
        u32 low;
        u32 high;
};

struct atag_revision {
        u32 rev;
};

struct atag_videolfb {
        u16             lfb_width;
        u16             lfb_height;
        u16             lfb_depth;
        u16             lfb_linelength;
        u32             lfb_base;
        u32             lfb_size;
        u8              red_size;
        u8              red_pos;
        u8              green_size;
        u8              green_pos;
        u8              blue_size;
        u8              blue_pos;
        u8              rsvd_size;
        u8              rsvd_pos;
};

struct atag_cmdline {
        char    cmdline[1];
};

struct atag {
        struct atag_header hdr;
        union {
                struct atag_core         core;
                struct atag_mem          mem;
                struct atag_videotext    videotext;
                struct atag_ramdisk      ramdisk;
                struct atag_initrd2      initrd2;
                struct atag_serialnr     serialnr;
                struct atag_revision     revision;
                struct atag_videolfb     videolfb;
                struct atag_cmdline      cmdline;
        } u;
};
*/


#endif // __ARCH_ARM_LINUX_ATAG_HH__
