/* ----------------------------------------------------------------------- *
 *
 *   Copyright 2006-2007 Erwan Velu - All Rights Reserved
 *
 *   Permission is hereby granted, free of charge, to any person
 *   obtaining a copy of this software and associated documentation
 *   files (the "Software"), to deal in the Software without
 *   restriction, including without limitation the rights to use,
 *   copy, modify, merge, publish, distribute, sublicense, and/or
 *   sell copies of the Software, and to permit persons to whom
 *   the Software is furnished to do so, subject to the following
 *   conditions:
 *
 *   The above copyright notice and this permission notice shall
 *   be included in all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 *   OTHER DEALINGS IN THE SOFTWARE.
 *
 * ----------------------------------------------------------------------- */

/*
 * pci.c
 *
 * A module to extract pci informations
 */

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <console.h>
#include <sys/pci.h>
#include <com32.h>
#include <stdbool.h>

#ifdef DEBUG
# define dprintf printf
#else
# define dprintf(...) ((void)0)
#endif

#define MAX_LINE 512

/* searching the next char that is not a space */
static char *skipspace(char *p)
{
    while (*p && *p <= ' ')
	p++;

    return p;
}

/* removing any \n found in a string */
static void remove_eol(char *string)
{
    int j = strlen(string);
    int i = 0;
    for (i = 0; i < j; i++)
	if (string[i] == '\n')
	    string[i] = 0;
}

/* converting a hexa string into its numerical value */
static int hex_to_int(char *hexa)
{
    return strtoul(hexa, NULL, 16);
}

/* Try to match any pci device to the appropriate kernel module */
/* it uses the modules.pcimap from the boot device */
int get_module_name_from_pcimap(struct pci_domain *domain,
				char *modules_pcimap_path)
{
  char line[MAX_LINE];
  char module_name[21]; // the module name field is 21 char long
  char delims[]=" ";    // colums are separated by spaces
  char vendor_id[16];
  char product_id[16];
  char sub_vendor_id[16];
  char sub_product_id[16];
  FILE *f;
  struct pci_device *dev=NULL;

  /* Intializing the linux_kernel_module for each pci device to "unknown" */
  /* adding a dev_info member if needed */
  for_each_pci_func(dev, domain) {
    /* initialize the dev_info structure if it doesn't exist yet. */
    if (! dev->dev_info) {
      dev->dev_info = zalloc(sizeof *dev->dev_info);
      if (!dev->dev_info)
	return -1;
    }
    for (int i=0;i<MAX_KERNEL_MODULES_PER_PCI_DEVICE;i++) {
     if (strlen(dev->dev_info->linux_kernel_module[i])==0)
       strlcpy(dev->dev_info->linux_kernel_module[i], "unknown",7);
    }
  }

  /* Opening the modules.pcimap (of a linux kernel) from the boot device */
  f=fopen(modules_pcimap_path, "r");
  if (!f)
    return -ENOMODULESPCIMAP;

  strcpy(vendor_id,"0000");
  strcpy(product_id,"0000");
  strcpy(sub_product_id,"0000");
  strcpy(sub_vendor_id,"0000");
  dev->dev_info->linux_kernel_module_count=0;

  /* for each line we found in the modules.pcimap */
  while ( fgets(line, sizeof line, f) ) {
    /* skipping unecessary lines */
    if ((line[0] == '#') || (line[0] == ' ') || (line[0] == 10))
        continue;

    char *result = NULL;
    int field=0;

    /* looking for the next field */
    result = strtok(line, delims);
    while( result != NULL ) {
       /* if the column is larger than 1 char */
       /* multiple spaces generates some empty fields */
       if (strlen(result)>1) {
	 switch (field) {
	 case 0:strcpy(module_name,result); break;
	 case 1:strcpy(vendor_id,result); break;
	 case 2:strcpy(product_id,result); break;
	 case 3:strcpy(sub_vendor_id,result); break;
	 case 4:strcpy(sub_product_id,result); break;
	 }
	 field++;
       }
       /* Searching the next field */
       result = strtok( NULL, delims );
   }
    int int_vendor_id=hex_to_int(vendor_id);
    int int_sub_vendor_id=hex_to_int(sub_vendor_id);
    int int_product_id=hex_to_int(product_id);
    int int_sub_product_id=hex_to_int(sub_product_id);
    /* if a pci_device matches an entry, fill the linux_kernel_module with
       the appropriate kernel module */
    for_each_pci_func(dev, domain) {
      if (int_vendor_id == dev->vendor &&
	  int_product_id == dev->product &&
	  (int_sub_product_id & dev->sub_product)
	  == dev->sub_product &&
	  (int_sub_vendor_id & dev->sub_vendor)
	  == dev->sub_vendor) {
	strcpy(dev->dev_info->linux_kernel_module[dev->dev_info->linux_kernel_module_count], module_name);
	dev->dev_info->linux_kernel_module_count++;
      }
    }
  }
  fclose(f);
  return 0;
}

/* Try to match any pci device to the appropriate class name */
/* it uses the pci.ids from the boot device */
int get_class_name_from_pci_ids(struct pci_domain *domain, char *pciids_path)
{
    char line[MAX_LINE];
    char class_name[PCI_CLASS_NAME_SIZE];
    char sub_class_name[PCI_CLASS_NAME_SIZE];
    char class_id_str[5];
    char sub_class_id_str[5];
    FILE *f;
    struct pci_device *dev;
    bool class_mode = false;

    /* Intializing the vendor/product name for each pci device to "unknown" */
    /* adding a dev_info member if needed */
    for_each_pci_func(dev, domain) {
	/* initialize the dev_info structure if it doesn't exist yet. */
	if (!dev->dev_info) {
	    dev->dev_info = zalloc(sizeof *dev->dev_info);
	    if (!dev->dev_info)
		return -1;
	}
	strlcpy(dev->dev_info->class_name, "unknown", 7);
    }

    /* Opening the pci.ids from the boot device */
    f = fopen(pciids_path, "r");
    if (!f)
	return -ENOPCIIDS;

    /* for each line we found in the pci.ids */
    while (fgets(line, sizeof line, f)) {
	/* Skipping uncessary lines */
	if ((line[0] == '#') || (line[0] == ' ') || (line[0] == 10))
	    continue;

	/* Until we found a line starting with a 'C', we are not parsing classes */
	if (line[0] == 'C')
	    class_mode = true;
	if (class_mode == false)
	    continue;
	strlcpy(class_name, "unknown", 7);
	/* If the line doesn't start with a tab, it means that's a class name */
	if (line[0] != '\t') {

	    /* ignore the two first char and then copy 2 chars (class id) */
	    strlcpy(class_id_str, &line[2], 2);
	    class_id_str[2] = 0;

	    /* the class name is the next field */
	    strlcpy(class_name, skipspace(strstr(line, " ")),
		    PCI_CLASS_NAME_SIZE - 1);
	    remove_eol(class_name);

	    int int_class_id_str = hex_to_int(class_id_str);
	    /* assign the class_name to any matching pci device */
	    for_each_pci_func(dev, domain) {
		if (int_class_id_str == dev->class[2]) {
		    strlcpy(dev->dev_info->class_name, class_name,
			    PCI_CLASS_NAME_SIZE - 1);
		    /* This value is usually the main category */
		    strlcpy(dev->dev_info->category_name, class_name + 4,
			    PCI_CLASS_NAME_SIZE - 1);
		}
	    }
	    /* if we have a tab + a char, it means this is a sub class name */
	} else if ((line[0] == '\t') && (line[1] != '\t')) {

	    /* the sub class name the second field */
	    strlcpy(sub_class_name, skipspace(strstr(line, " ")),
		    PCI_CLASS_NAME_SIZE - 1);
	    remove_eol(sub_class_name);

	    /* the sub class id is first field */
	    strlcpy(sub_class_id_str, &line[1], 2);
	    sub_class_id_str[2] = 0;

	    int int_class_id_str = hex_to_int(class_id_str);
	    int int_sub_class_id_str = hex_to_int(sub_class_id_str);
	    /* assign the product_name to any matching pci device */
	    for_each_pci_func(dev, domain) {
		if (int_class_id_str == dev->class[2] &&
		    int_sub_class_id_str == dev->class[1])
		    strlcpy(dev->dev_info->class_name, sub_class_name,
			    PCI_CLASS_NAME_SIZE - 1);
	    }

	}
    }
    fclose(f);
    return 0;
}

/* Try to match any pci device to the appropriate vendor and product name */
/* it uses the pci.ids from the boot device */
int get_name_from_pci_ids(struct pci_domain *domain, char *pciids_path)
{
    char line[MAX_LINE];
    char vendor[PCI_VENDOR_NAME_SIZE];
    char vendor_id[5];
    char product[PCI_PRODUCT_NAME_SIZE];
    char product_id[5];
    char sub_product_id[5];
    char sub_vendor_id[5];
    FILE *f;
    struct pci_device *dev;
    bool skip_to_next_vendor = false;
    uint16_t int_vendor_id;
    uint16_t int_product_id;
    uint16_t int_sub_product_id;
    uint16_t int_sub_vendor_id;

    /* Intializing the vendor/product name for each pci device to "unknown" */
    /* adding a dev_info member if needed */
    for_each_pci_func(dev, domain) {
	/* initialize the dev_info structure if it doesn't exist yet. */
	if (!dev->dev_info) {
	    dev->dev_info = zalloc(sizeof *dev->dev_info);
	    if (!dev->dev_info)
		return -1;
	}
	strlcpy(dev->dev_info->vendor_name, "unknown", 7);
	strlcpy(dev->dev_info->product_name, "unknown", 7);
    }

    /* Opening the pci.ids from the boot device */
    f = fopen(pciids_path, "r");
    if (!f)
	return -ENOPCIIDS;

    strlcpy(vendor_id, "0000", 4);
    strlcpy(product_id, "0000", 4);
    strlcpy(sub_product_id, "0000", 4);
    strlcpy(sub_vendor_id, "0000", 4);

    /* for each line we found in the pci.ids */
    while (fgets(line, sizeof line, f)) {
	/* Skipping uncessary lines */
	if ((line[0] == '#') || (line[0] == ' ') || (line[0] == 'C') ||
	    (line[0] == 10))
	    continue;

	/* If the line doesn't start with a tab, it means that's a vendor id */
	if (line[0] != '\t') {

	    /* the 4 first chars are the vendor_id */
	    strlcpy(vendor_id, line, 4);

	    /* the vendor name is the next field */
	    vendor_id[4] = 0;
	    strlcpy(vendor, skipspace(strstr(line, " ")),
		    PCI_VENDOR_NAME_SIZE - 1);

	    remove_eol(vendor);
	    /* init product_id, sub_product and sub_vendor */
	    strlcpy(product_id, "0000", 4);
	    strlcpy(sub_product_id, "0000", 4);
	    strlcpy(sub_vendor_id, "0000", 4);

	    /* Unless we found a matching device, we have to skip to the next vendor */
	    skip_to_next_vendor = true;

	    int_vendor_id = hex_to_int(vendor_id);
	    /* Iterate in all pci devices to find a matching vendor */
	    for_each_pci_func(dev, domain) {
		/* if one device that match this vendor */
		if (int_vendor_id == dev->vendor) {
		    /* copy the vendor name for this device */
		    strlcpy(dev->dev_info->vendor_name, vendor,
			    PCI_VENDOR_NAME_SIZE - 1);
		    /* Some pci devices match this vendor, so we have to found them */
		    skip_to_next_vendor = false;
		    /* Let's loop on the other devices as some may have the same vendor */
		}
	    }
	    /* if we have a tab + a char, it means this is a product id
	     * but we only look at it if we own some pci devices of the current vendor*/
	} else if ((line[0] == '\t') && (line[1] != '\t')
		   && (skip_to_next_vendor == false)) {

	    /* the product name the second field */
	    strlcpy(product, skipspace(strstr(line, " ")),
		    PCI_PRODUCT_NAME_SIZE - 1);
	    remove_eol(product);

	    /* the product id is first field */
	    strlcpy(product_id, &line[1], 4);
	    product_id[4] = 0;

	    /* init sub_product and sub_vendor */
	    strlcpy(sub_product_id, "0000", 4);
	    strlcpy(sub_vendor_id, "0000", 4);

	    int_vendor_id = hex_to_int(vendor_id);
	    int_product_id = hex_to_int(product_id);
	    /* assign the product_name to any matching pci device */
	    for_each_pci_func(dev, domain) {
		if (int_vendor_id == dev->vendor &&
		    int_product_id == dev->product) {
		    strlcpy(dev->dev_info->vendor_name, vendor,
			    PCI_VENDOR_NAME_SIZE - 1);
		    strlcpy(dev->dev_info->product_name, product,
			    PCI_PRODUCT_NAME_SIZE - 1);
		}
	    }

	    /* if we have two tabs, it means this is a sub product
	     * but we only look at it if we own some pci devices of the current vendor*/
	} else if ((line[0] == '\t') && (line[1] == '\t')
		   && (skip_to_next_vendor == false)) {

	    /* the product name is last field */
	    strlcpy(product, skipspace(strstr(line, " ")),
		    PCI_PRODUCT_NAME_SIZE - 1);
	    strlcpy(product, skipspace(strstr(product, " ")),
		    PCI_PRODUCT_NAME_SIZE - 1);
	    remove_eol(product);

	    /* the sub_vendor id is first field */
	    strlcpy(sub_vendor_id, &line[2], 4);
	    sub_vendor_id[4] = 0;

	    /* the sub_vendor id is second field */
	    strlcpy(sub_product_id, &line[7], 4);
	    sub_product_id[4] = 0;

	    int_vendor_id = hex_to_int(vendor_id);
	    int_sub_vendor_id = hex_to_int(sub_vendor_id);
	    int_product_id = hex_to_int(product_id);
	    int_sub_product_id = hex_to_int(sub_product_id);
	    /* assign the product_name to any matching pci device */
	    for_each_pci_func(dev, domain) {
		if (int_vendor_id == dev->vendor &&
		    int_product_id == dev->product &&
		    int_sub_product_id == dev->sub_product &&
		    int_sub_vendor_id == dev->sub_vendor) {
		    strlcpy(dev->dev_info->vendor_name, vendor,
			    PCI_VENDOR_NAME_SIZE - 1);
		    strlcpy(dev->dev_info->product_name, product,
			    PCI_PRODUCT_NAME_SIZE - 1);
		}
	    }
	}
    }
    fclose(f);
    return 0;
}

/* searching if any pcidevice match our query */
struct match *find_pci_device(const struct pci_domain *domain,
			      struct match *list)
{
    uint32_t did, sid;
    struct match *m;
    const struct pci_device *dev;

    /* for all matches we have to search */
    for (m = list; m; m = m->next) {
	/* for each pci device we know */
	for_each_pci_func(dev, domain) {
	    /* sid & did are the easiest way to compare devices */
	    /* they are made of vendor/product subvendor/subproduct ids */
	    sid = dev->svid_sdid;
	    did = dev->vid_did;
	    /* if the current device match */
	    if (((did ^ m->did) & m->did_mask) == 0 &&
		((sid ^ m->sid) & m->sid_mask) == 0 &&
		dev->revision >= m->rid_min && dev->revision <= m->rid_max) {
		dprintf
		    ("PCI Match: Vendor=%04x Product=%04x Sub_vendor=%04x Sub_Product=%04x Release=%02x\n",
		     dev->vendor, dev->product, dev->sub_vendor,
		     dev->sub_product, dev->revision);
		/* returning the matched pci device */
		return m;
	    }
	}
    }
    return NULL;
}

/* scanning the pci bus to find pci devices */
struct pci_domain *pci_scan(void)
{
    struct pci_domain *domain = NULL;
    struct pci_bus *bus = NULL;
    struct pci_slot *slot = NULL;
    struct pci_device *func = NULL;
    unsigned int nbus, ndev, nfunc, maxfunc;
    uint32_t did, sid, rcid;
    uint8_t hdrtype;
    pciaddr_t a;
    int cfgtype;

    cfgtype = pci_set_config_type(PCI_CFG_AUTO);

    dprintf("PCI configuration type %d\n", cfgtype);

    if (cfgtype == PCI_CFG_NONE)
	return NULL;

    dprintf("Scanning PCI Buses\n");

    for (nbus = 0; nbus < MAX_PCI_BUSES; nbus++) {
	dprintf("Probing bus 0x%02x... \n", nbus);
	bus = NULL;

	for (ndev = 0; ndev < MAX_PCI_DEVICES; ndev++) {
	    maxfunc = 1;	/* Assume a single-function device */
	    slot = NULL;

	    for (nfunc = 0; nfunc < maxfunc; nfunc++) {
		a = pci_mkaddr(nbus, ndev, nfunc, 0);
		did = pci_readl(a);

		if (did == 0xffffffff || did == 0xffff0000 ||
		    did == 0x0000ffff || did == 0x00000000)
		    continue;

		hdrtype = pci_readb(a + 0x0e);

		if (hdrtype & 0x80)
		    maxfunc = MAX_PCI_FUNC;	/* Multifunction device */

		rcid = pci_readl(a + 0x08);
		sid = pci_readl(a + 0x2c);

		if (!domain) {
		    domain = zalloc(sizeof *domain);
		    if (!domain)
			goto bail;
		}
		if (!bus) {
		    bus = zalloc(sizeof *bus);
		    if (!bus)
			goto bail;
		    domain->bus[nbus] = bus;
		}
		if (!slot) {
		    slot = zalloc(sizeof *slot);
		    if (!slot)
			goto bail;
		    bus->slot[ndev] = slot;
		}
		func = zalloc(sizeof *func);
		if (!func)
		    goto bail;

		slot->func[nfunc] = func;

		func->vid_did = did;
		func->svid_sdid = sid;
		func->rid_class = rcid;

		dprintf
		    ("Scanning: BUS %02x DID %08x (%04x:%04x) SID %08x RID %02x\n",
		     nbus, did, did >> 16, (did << 16) >> 16, sid, rcid & 0xff);
	    }
	}
    }

    return domain;

bail:
    free_pci_domain(domain);
    return NULL;
}

/* gathering additional configuration*/
void gather_additional_pci_config(struct pci_domain *domain)
{
    struct pci_device *dev;
    pciaddr_t pci_addr;
    int cfgtype;

    cfgtype = pci_set_config_type(PCI_CFG_AUTO);
    if (cfgtype == PCI_CFG_NONE)
	return;

    for_each_pci_func3(dev, domain, pci_addr) {
	if (!dev->dev_info) {
	    dev->dev_info = zalloc(sizeof *dev->dev_info);
	    if (!dev->dev_info) {
		return;
	    }
	}
	dev->dev_info->irq = pci_readb(pci_addr + 0x3c);
	dev->dev_info->latency = pci_readb(pci_addr + 0x0d);
    }
}

void free_pci_domain(struct pci_domain *domain)
{
    struct pci_bus *bus;
    struct pci_slot *slot;
    struct pci_device *func;
    unsigned int nbus, ndev, nfunc;

    if (domain) {
	for (nbus = 0; nbus < MAX_PCI_BUSES; nbus++) {
	    bus = domain->bus[nbus];
	    if (bus) {
		for (ndev = 0; ndev < MAX_PCI_DEVICES; ndev++) {
		    slot = bus->slot[ndev];
		    if (slot) {
			for (nfunc = 0; nfunc < MAX_PCI_FUNC; nfunc++) {
			    func = slot->func[nfunc];
			    if (func) {
				if (func->dev_info)
				    free(func->dev_info);
				free(func);
			    }
			    free(slot);
			}
		    }
		    free(bus);
		}
	    }
	    free(domain);
	}
    }
}

/* Try to match any pci device to the appropriate kernel module */
/* it uses the modules.alias from the boot device */
int get_module_name_from_alias(struct pci_domain *domain, char *modules_alias_path)
{
  char line[MAX_LINE];
  char module_name[21]; // the module name field is 21 char long
  char delims[]="*";    // colums are separated by spaces
  char vendor_id[16];
  char product_id[16];
  char sub_vendor_id[16];
  char sub_product_id[16];
  FILE *f;
  struct pci_device *dev=NULL;

  /* Intializing the linux_kernel_module for each pci device to "unknown" */
  /* adding a dev_info member if needed */
  for_each_pci_func(dev, domain) {
    /* initialize the dev_info structure if it doesn't exist yet. */
    if (! dev->dev_info) {
      dev->dev_info = zalloc(sizeof *dev->dev_info);
      if (!dev->dev_info)
	return -1;
    }
    for (int i=0;i<MAX_KERNEL_MODULES_PER_PCI_DEVICE;i++) {
     if (strlen(dev->dev_info->linux_kernel_module[i])==0)
       strlcpy(dev->dev_info->linux_kernel_module[i], "unknown",7);
    }
  }

  /* Opening the modules.pcimap (of a linux kernel) from the boot device */
  f=fopen(modules_alias_path, "r");
  if (!f)
    return -ENOMODULESALIAS;

  dev->dev_info->linux_kernel_module_count=0;

  /* for each line we found in the modules.pcimap */
  while ( fgets(line, sizeof line, f) ) {
    /* skipping unecessary lines */
    if ((line[0] == '#') || (strstr(line,"alias pci:v")==NULL))
        continue;

    /* Resetting temp buffer*/
    memset(module_name,0,sizeof(module_name));
    memset(vendor_id,0,sizeof(vendor_id));
    memset(sub_vendor_id,0,sizeof(sub_vendor_id));
    memset(product_id,0,sizeof(product_id));
    memset(sub_product_id,0,sizeof(sub_product_id));
    strcpy(vendor_id,"0000");
    strcpy(product_id,"0000");
    /* ffff will be used to match any device as in modules.alias
     * a missing subvendor/product have to be considered as  0xFFFF*/
    strcpy(sub_product_id,"ffff");
    strcpy(sub_vendor_id,"ffff");

    char *result = NULL;
    int field=0;

    /* looking for the next field */
    result = strtok(line+strlen("alias pci:v"), delims);
    while( result != NULL ) {
	if (field==0) {

		/* Searching for the vendor separator*/
		char *temp = strstr(result,"d");
		if (temp != NULL) {
			strncpy(vendor_id,result,temp-result);
			result+=strlen(vendor_id)+1;
		}

		/* Searching for the product separator*/
		temp = strstr(result,"sv");
		if (temp != NULL) {
			strncpy(product_id,result,temp-result);
			result+=strlen(product_id)+1;
		}

		/* Searching for the sub vendor separator*/
		temp = strstr(result,"sd");
		if (temp != NULL) {
			strncpy(sub_vendor_id,result,temp-result);
			result+=strlen(sub_vendor_id)+1;
		}

		/* Searching for the sub product separator*/
		temp = strstr(result,"bc");
		if (temp != NULL) {
			strncpy(sub_product_id,result,temp-result);
			result+=strlen(sub_product_id)+1;
		}
	/* That's the module name */
	} else if ((strlen(result)>2) &&
			(result[0]==0x20))
		strcpy(module_name,result+1);
		/* We have to replace \n by \0*/
		module_name[strlen(module_name)-1]='\0';
	field++;

	/* Searching the next field */
        result = strtok( NULL, delims );
    }

    /* Now we have extracted informations from the modules.alias
     * Let's compare it with the devices we know*/
    int int_vendor_id=hex_to_int(vendor_id);
    int int_sub_vendor_id=hex_to_int(sub_vendor_id);
    int int_product_id=hex_to_int(product_id);
    int int_sub_product_id=hex_to_int(sub_product_id);
    /* if a pci_device matches an entry, fill the linux_kernel_module with
       the appropriate kernel module */
    for_each_pci_func(dev, domain) {
      if (int_vendor_id == dev->vendor &&
	  int_product_id == dev->product &&
	  (int_sub_product_id & dev->sub_product)
	  == dev->sub_product &&
	  (int_sub_vendor_id & dev->sub_vendor)
	  == dev->sub_vendor) {
	strcpy(dev->dev_info->linux_kernel_module[dev->dev_info->linux_kernel_module_count], module_name);
	dev->dev_info->linux_kernel_module_count++;
      }
    }
  }
  fclose(f);
  return 0;
}
