/*
 * Copyright (c) 2009-2011, Broadcom Corporation
 * Copyright (c) 2014, QLogic Corporation
 *
 * Written by:  Benjamin Li  (benli@broadcom.com)
 *
 * All rights reserved.
 *
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Adam Dunkels.
 * 4. The name of the author may not be used to endorse or promote
 *    products derived from this software without specific prior
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
 *
 * nic_id.c - Using sysfs to determine the PCI vendor, device, subvendor and
 *            subdevice ID's
 *
 */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>

#include "logger.h"
#include "nic.h"

#define PFX "nic_id "

/*******************************************************************************
 * Sysfs constant strings used to get PCI vendor, and device ID's
 ******************************************************************************/
const char uio_vendor_id_template[] = "/sys/class/uio/uio%d/device/vendor";
const char uio_subvendor_id_template[] =
	"/sys/class/uio/uio%d/device/subsystem_vendor";
const char uio_device_id_template[] = "/sys/class/uio/uio%d/device/device";
const char uio_subdevice_id_template[] =
	"/sys/class/uio/uio%d/device/subsystem_device";
const char uio_device_symlink_template[] = "/sys/class/uio/uio%d/device";

/**
 *  get_id() - Utility function to read hex values from sysfs
 *  @param nic - NIC device to use
 *  @param sysfs_template - sysfs path template to use
 *  @param sysfs_template_size - sysfs path template size in bytes
 *  @parm id - this is the value returned from the sysfs entry
 *  @return 0 on success <0 on failure
 */
static int get_id(nic_t *nic,
		  const char *sysfs_template,
		  const size_t sysfs_template_size, uint32_t *id)
{
	int rc = 0;
	FILE *fp;
	size_t chars_read;
	char buf[7];
	char *path;
	size_t path_size;

	path_size = sysfs_template_size + 4;
	path = malloc(path_size);
	if (path == NULL) {
		ILOG_ERR("Could not allocate memory for %s", sysfs_template);
		return -ENOMEM;
	}

	snprintf(path, path_size, sysfs_template, nic->uio_minor);

	fp = fopen(path, "r");
	if (fp == NULL) {
		ILOG_ERR(PFX "%s: Could not open path: %s [%s]",
			nic->log_name, path, strerror(errno));
		rc = -EIO;
		goto error_fopen;
	}

	chars_read = fread(buf, sizeof(buf), 1, fp);
	if (chars_read != 1) {
		ILOG_ERR(PFX "%s: Could not read from: %s [%s]",
			nic->log_name, path, strerror(ferror(fp)));
		rc = -EIO;
		goto error;
	}

	chars_read = sscanf(buf, "%x", id);
	if (chars_read != 1) {
		ILOG_ERR(PFX "%s: Could interpret value: %s from: %s [%s]",
			nic->log_name, buf, path, strerror(errno));
		rc = -EIO;
		goto error;
	}

error:
	fclose(fp);

error_fopen:
	free(path);

	return rc;
}

static int get_vendor(nic_t *nic, uint32_t *id)
{
	return get_id(nic,
		      uio_vendor_id_template, sizeof(uio_vendor_id_template),
		      id);
}

static int get_subvendor(nic_t *nic, uint32_t *id)
{
	return get_id(nic,
		      uio_subvendor_id_template,
		      sizeof(uio_subvendor_id_template), id);
}

static int get_device(nic_t *nic, uint32_t *id)
{
	return get_id(nic,
		      uio_device_id_template,
		      sizeof(uio_device_id_template), id);
}

static int get_subdevice(nic_t *nic, uint32_t *id)
{
	return get_id(nic,
		      uio_subdevice_id_template,
		      sizeof(uio_subdevice_id_template), id);
}

int get_bus_slot_func_num(nic_t *nic,
			  uint32_t *bus, uint32_t *slot, uint32_t *func)
{
	size_t size;
	char *path, *tok, *tok2;
	int path_tokens, i;
	size_t path_size;
	char *read_pci_bus_slot_func_str;
	char pci_bus_slot_func_str[32];
	int rc;
	char *saveptr;

	path_size = sizeof(uio_device_symlink_template) + 4;
	path = malloc(path_size);
	if (path == NULL) {
		ILOG_ERR(PFX "%s: Could not allocate path memory for %s",
			nic->log_name, uio_device_symlink_template);
		rc = -ENOMEM;
		goto error_alloc_path;
	}

	read_pci_bus_slot_func_str = malloc(128);
	if (read_pci_bus_slot_func_str == NULL) {
		ILOG_ERR(PFX "%s: Could not allocate read pci bus memory for %s",
			nic->log_name, uio_device_symlink_template);
		rc = -ENOMEM;
		goto error_alloc_read_pci_bus;
	}

	snprintf(path, path_size, uio_device_symlink_template, nic->uio_minor);

	size = readlink(path, read_pci_bus_slot_func_str, 128);
	if (size == -1) {
		ILOG_ERR(PFX "%s: Error with %s: %s",
			nic->log_name, path, strerror(errno));
		rc = errno;
		goto error;
	}

	if (size > ((128) - 1)) {
		read_pci_bus_slot_func_str[128 - 1] = '\0';
		ILOG_ERR(PFX "%s: not enough space (%d) for reading PCI slot:bus.func %s: %s",
			nic->log_name, size, path, strerror(errno));
		rc = -EIO;
		goto error;
	}

	/*  readlink() doesn't NULL terminate the string */
	read_pci_bus_slot_func_str[size] = '\0';

	path_tokens = 0;
	tok = strtok_r(read_pci_bus_slot_func_str, "/", &saveptr);
	while (tok != NULL) {
		path_tokens++;
		tok = strtok_r(NULL, "/", &saveptr);
	}

	size = readlink(path, read_pci_bus_slot_func_str, 128);
	if (size == -1) {
		ILOG_ERR(PFX "%s: Error with %s: %s",
			nic->log_name, path, strerror(errno));
		rc = errno;
		goto error;
	}

	if (size > ((128) - 1)) {
		read_pci_bus_slot_func_str[128 - 1] = '\0';
		ILOG_ERR(PFX "%s: not enough space for reading PCI slot:bus.func %s: %s",
			nic->log_name, path, strerror(errno));
		rc = -EIO;
		goto error;
	}

	/*  readlink() doesn't NULL terminate the string */
	read_pci_bus_slot_func_str[size] = '\0';

	tok = strtok_r(read_pci_bus_slot_func_str, "/", &saveptr);
	for (i = 0; i < path_tokens - 1; i++)
		tok = strtok_r(NULL, "/", &saveptr);
	strcpy(pci_bus_slot_func_str, tok);

	tok = strtok_r(pci_bus_slot_func_str, ":", &saveptr);
	if (tok == NULL) {
		ILOG_ERR(PFX "%s: Error with slot string: %s",
			nic->log_name, pci_bus_slot_func_str);
		rc = -EIO;
		goto error;
	}

	tok = strtok_r(NULL, ":", &saveptr);
	if (tok == NULL) {
		ILOG_ERR(PFX "%s: Error parsing slot: %s",
			nic->log_name, pci_bus_slot_func_str);
		rc = -EIO;
		goto error;
	}

	sscanf(tok, "%x", bus);

	/*  Need to extract the next token "xx.x" */
	tok = strtok_r(NULL, ":", &saveptr);
	if (tok == NULL) {
		ILOG_ERR(PFX "%s: Error extracing bus.func: %s",
			nic->log_name, pci_bus_slot_func_str);
		rc = -EIO;
		goto error;
	}

	tok2 = strtok_r(tok, ".", &saveptr);
	if (tok2 == NULL) {
		ILOG_ERR(PFX "%s: Error parsing bus: %s",
			nic->log_name, pci_bus_slot_func_str);
		rc = -EIO;
		goto error;
	}

	sscanf(tok2, "%x", slot);

	tok2 = strtok_r(NULL, ".", &saveptr);
	if (tok2 == NULL) {
		ILOG_ERR(PFX "%s: Error parsing func: %s",
			nic->log_name, pci_bus_slot_func_str);
		rc = -EIO;
		goto error;
	}

	sscanf(tok2, "%x", func);
	ILOG_INFO(PFX "%s: is found at %02x:%02x.%02x", nic->log_name,
		 *bus, *slot, *func);
	rc = 0;
error:
	free(read_pci_bus_slot_func_str);
error_alloc_read_pci_bus:
	free(path);
error_alloc_path:
	return rc;
}

/**
 *  find_set_nic_lib() - Match the NIC library to the NIC
 *  @param nic - NIC device to determine which NIC library to use
 *  @return 0 on success <0 on failure
 */
int find_set_nic_lib(nic_t *nic)
{
	uint32_t vendor;
	uint32_t subvendor;
	uint32_t device;
	uint32_t subdevice;

	uint32_t pci_bus;
	uint32_t pci_slot;
	uint32_t pci_func;
	int rc = 0;

	nic_lib_handle_t *handle;
	struct pci_device_id *pci_entry;
	size_t name_size;

	rc = get_vendor(nic, &vendor);
	if (rc != 0) {
		ILOG_ERR(PFX "%s: Could not get vendor id [0x%x]",
			nic->log_name, rc);
		return rc;
	}

	rc = get_subvendor(nic, &subvendor);
	if (rc != 0) {
		ILOG_ERR(PFX "%s: Could not get subvendor id [0x%x]",
			nic->log_name, rc);
		return rc;
	}

	rc = get_device(nic, &device);
	if (rc != 0) {
		ILOG_ERR(PFX "%s: Could not get device id [0x%x]",
			nic->log_name, rc);
		return rc;
	}

	rc = get_subdevice(nic, &subdevice);
	if (rc != 0) {
		ILOG_ERR(PFX "%s: Could not get subdevice id [0x%x]",
			nic->log_name, rc);
		return rc;
	}

	get_bus_slot_func_num(nic, &pci_bus, &pci_slot, &pci_func);

	ILOG_DEBUG(PFX "%s: Looking for device vendor: 0x%x subvendor: 0x%x device: 0x%x subdevice: 0x%x",
		  nic->log_name, vendor, subvendor, device, subdevice);

	rc = find_nic_lib_using_pci_id(vendor, device, subvendor, subdevice,
				       &handle, &pci_entry);

	if (rc != 0) {
		ILOG_WARN(PFX "%s: Couldn't find proper NIC library",
			 nic->log_name);
		return rc;
	}

	nic->nic_library = handle;
	nic->pci_id = pci_entry;

	/*  Prepare the NIC library op table */
	nic->ops = handle->ops;
	(*nic->ops->lib_ops.get_library_name) (&nic->library_name, &name_size);

	return 0;
}
