/*
 * libfdt - Flat Device Tree manipulation
 * Copyright (C) 2006 David Gibson, IBM Corporation.
 *
 * libfdt is dual licensed: you can use it either under the terms of
 * the GPL, or the BSD license, at your option.
 *
 *  a) This library is free software; you can redistribute it and/or
 *     modify it under the terms of the GNU General Public License as
 *     published by the Free Software Foundation; either version 2 of the
 *     License, or (at your option) any later version.
 *
 *     This library is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public
 *     License along with this library; if not, write to the Free
 *     Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
 *     MA 02110-1301 USA
 *
 * Alternatively,
 *
 *  b) Redistribution and use in source and binary forms, with or
 *     without modification, are permitted provided that the following
 *     conditions are met:
 *
 *     1. Redistributions of source code must retain the above
 *        copyright notice, this list of conditions and the following
 *        disclaimer.
 *     2. 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.
 *
 *     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.
 */
#include "libfdt_env.h"

#include <fdt.h>
#include <libfdt.h>

#include "libfdt_internal.h"

static int
_fdt_sw_check_header (
  void  *fdt
  )
{
  if (fdt_magic (fdt) != FDT_SW_MAGIC) {
    return -FDT_ERR_BADMAGIC;
  }

  /* FIXME: should check more details about the header state */
  return 0;
}

#define FDT_SW_CHECK_HEADER(fdt) \
        { \
                int err; \
                if ((err = _fdt_sw_check_header(fdt)) != 0) \
                        return err; \
        }

static void *
_fdt_grab_space (
  void    *fdt,
  size_t  len
  )
{
  int  offset = fdt_size_dt_struct (fdt);
  int  spaceleft;

  spaceleft = fdt_totalsize (fdt) - fdt_off_dt_struct (fdt)
              - fdt_size_dt_strings (fdt);

  if ((offset + len < offset) || (offset + len > spaceleft)) {
    return NULL;
  }

  fdt_set_size_dt_struct (fdt, offset + len);
  return _fdt_offset_ptr_w (fdt, offset);
}

int
fdt_create (
  void  *buf,
  int   bufsize
  )
{
  void  *fdt = buf;

  if (bufsize < sizeof (struct fdt_header)) {
    return -FDT_ERR_NOSPACE;
  }

  memset (buf, 0, bufsize);

  fdt_set_magic (fdt, FDT_SW_MAGIC);
  fdt_set_version (fdt, FDT_LAST_SUPPORTED_VERSION);
  fdt_set_last_comp_version (fdt, FDT_FIRST_SUPPORTED_VERSION);
  fdt_set_totalsize (fdt, bufsize);

  fdt_set_off_mem_rsvmap (
    fdt,
    FDT_ALIGN (
      sizeof (struct fdt_header),
      sizeof (struct fdt_reserve_entry)
      )
    );
  fdt_set_off_dt_struct (fdt, fdt_off_mem_rsvmap (fdt));
  fdt_set_off_dt_strings (fdt, bufsize);

  return 0;
}

int
fdt_resize (
  void  *fdt,
  void  *buf,
  int   bufsize
  )
{
  size_t  headsize, tailsize;
  char    *oldtail, *newtail;

  FDT_SW_CHECK_HEADER (fdt);

  headsize = fdt_off_dt_struct (fdt);
  tailsize = fdt_size_dt_strings (fdt);

  if ((headsize + tailsize) > bufsize) {
    return -FDT_ERR_NOSPACE;
  }

  oldtail = (char *)fdt + fdt_totalsize (fdt) - tailsize;
  newtail = (char *)buf + bufsize - tailsize;

  /* Two cases to avoid clobbering data if the old and new
   * buffers partially overlap */
  if (buf <= fdt) {
    memmove (buf, fdt, headsize);
    memmove (newtail, oldtail, tailsize);
  } else {
    memmove (newtail, oldtail, tailsize);
    memmove (buf, fdt, headsize);
  }

  fdt_set_off_dt_strings (buf, bufsize);
  fdt_set_totalsize (buf, bufsize);

  return 0;
}

int
fdt_add_reservemap_entry (
  void      *fdt,
  uint64_t  addr,
  uint64_t  size
  )
{
  struct fdt_reserve_entry  *re;
  int                       offset;

  FDT_SW_CHECK_HEADER (fdt);

  if (fdt_size_dt_struct (fdt)) {
    return -FDT_ERR_BADSTATE;
  }

  offset = fdt_off_dt_struct (fdt);
  if ((offset + sizeof (*re)) > fdt_totalsize (fdt)) {
    return -FDT_ERR_NOSPACE;
  }

  re          = (struct fdt_reserve_entry *)((char *)fdt + offset);
  re->address = cpu_to_fdt64 (addr);
  re->size    = cpu_to_fdt64 (size);

  fdt_set_off_dt_struct (fdt, offset + sizeof (*re));

  return 0;
}

int
fdt_finish_reservemap (
  void  *fdt
  )
{
  return fdt_add_reservemap_entry (fdt, 0, 0);
}

int
fdt_begin_node (
  void        *fdt,
  const char  *name
  )
{
  struct fdt_node_header  *nh;
  int                     namelen = strlen (name) + 1;

  FDT_SW_CHECK_HEADER (fdt);

  nh = _fdt_grab_space (fdt, sizeof (*nh) + FDT_TAGALIGN (namelen));
  if (!nh) {
    return -FDT_ERR_NOSPACE;
  }

  nh->tag = cpu_to_fdt32 (FDT_BEGIN_NODE);
  memcpy (nh->name, name, namelen);
  return 0;
}

int
fdt_end_node (
  void  *fdt
  )
{
  fdt32_t  *en;

  FDT_SW_CHECK_HEADER (fdt);

  en = _fdt_grab_space (fdt, FDT_TAGSIZE);
  if (!en) {
    return -FDT_ERR_NOSPACE;
  }

  *en = cpu_to_fdt32 (FDT_END_NODE);
  return 0;
}

static int
_fdt_find_add_string (
  void        *fdt,
  const char  *s
  )
{
  char        *strtab = (char *)fdt + fdt_totalsize (fdt);
  const char  *p;
  int         strtabsize = fdt_size_dt_strings (fdt);
  int         len = strlen (s) + 1;
  int         struct_top, offset;

  p = _fdt_find_string (strtab - strtabsize, strtabsize, s);
  if (p) {
    return p - strtab;
  }

  /* Add it */
  offset     = -strtabsize - len;
  struct_top = fdt_off_dt_struct (fdt) + fdt_size_dt_struct (fdt);
  if (fdt_totalsize (fdt) + offset < struct_top) {
    return 0;             /* no more room :( */
  }

  memcpy (strtab + offset, s, len);
  fdt_set_size_dt_strings (fdt, strtabsize + len);
  return offset;
}

int
fdt_property_placeholder (
  void        *fdt,
  const char  *name,
  int         len,
  void        **valp
  )
{
  struct fdt_property  *prop;
  int                  nameoff;

  FDT_SW_CHECK_HEADER (fdt);

  nameoff = _fdt_find_add_string (fdt, name);
  if (nameoff == 0) {
    return -FDT_ERR_NOSPACE;
  }

  prop = _fdt_grab_space (fdt, sizeof (*prop) + FDT_TAGALIGN (len));
  if (!prop) {
    return -FDT_ERR_NOSPACE;
  }

  prop->tag     = cpu_to_fdt32 (FDT_PROP);
  prop->nameoff = cpu_to_fdt32 (nameoff);
  prop->len     = cpu_to_fdt32 (len);
  *valp         = prop->data;
  return 0;
}

int
fdt_property (
  void        *fdt,
  const char  *name,
  const void  *val,
  int         len
  )
{
  void  *ptr;
  int   ret;

  ret = fdt_property_placeholder (fdt, name, len, &ptr);
  if (ret) {
    return ret;
  }

  memcpy (ptr, val, len);
  return 0;
}

int
fdt_finish (
  void  *fdt
  )
{
  char      *p = (char *)fdt;
  fdt32_t   *end;
  int       oldstroffset, newstroffset;
  uint32_t  tag;
  int       offset, nextoffset;

  FDT_SW_CHECK_HEADER (fdt);

  /* Add terminator */
  end = _fdt_grab_space (fdt, sizeof (*end));
  if (!end) {
    return -FDT_ERR_NOSPACE;
  }

  *end = cpu_to_fdt32 (FDT_END);

  /* Relocate the string table */
  oldstroffset = fdt_totalsize (fdt) - fdt_size_dt_strings (fdt);
  newstroffset = fdt_off_dt_struct (fdt) + fdt_size_dt_struct (fdt);
  memmove (p + newstroffset, p + oldstroffset, fdt_size_dt_strings (fdt));
  fdt_set_off_dt_strings (fdt, newstroffset);

  /* Walk the structure, correcting string offsets */
  offset = 0;
  while ((tag = fdt_next_tag (fdt, offset, &nextoffset)) != FDT_END) {
    if (tag == FDT_PROP) {
      struct fdt_property  *prop =
        _fdt_offset_ptr_w (fdt, offset);
      int  nameoff;

      nameoff       = fdt32_to_cpu (prop->nameoff);
      nameoff      += fdt_size_dt_strings (fdt);
      prop->nameoff = cpu_to_fdt32 (nameoff);
    }

    offset = nextoffset;
  }

  if (nextoffset < 0) {
    return nextoffset;
  }

  /* Finally, adjust the header */
  fdt_set_totalsize (fdt, newstroffset + fdt_size_dt_strings (fdt));
  fdt_set_magic (fdt, FDT_MAGIC);
  return 0;
}
