/*
 *	The PCI Utilities -- Bus Mapping Mode
 *
 *	Copyright (c) 1997--2008 Martin Mares <mj@ucw.cz>
 *
 *	Can be freely distributed and used under the terms of the GNU GPL v2+.
 *
 *	SPDX-License-Identifier: GPL-2.0-or-later
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "lspci.h"

struct bus_bridge {
  struct bus_bridge *next;
  byte this, dev, func, first, last, bug;
};

struct bus_info {
  byte exists;
  byte guestbook;
  struct bus_bridge *bridges, *via;
};

static struct bus_info *bus_info;

static void
map_bridge(struct bus_info *bi, struct device *d, int np, int ns, int nl)
{
  struct bus_bridge *b = xmalloc(sizeof(struct bus_bridge));
  struct pci_dev *p = d->dev;

  b->next = bi->bridges;
  bi->bridges = b;
  b->this = get_conf_byte(d, np);
  b->dev = p->dev;
  b->func = p->func;
  b->first = get_conf_byte(d, ns);
  b->last = get_conf_byte(d, nl);
  printf("## %02x:%02x.%d is a bridge from %02x to %02x-%02x\n",
	 p->bus, p->dev, p->func, b->this, b->first, b->last);
  if (b->this != p->bus)
    printf("!!! Bridge points to invalid primary bus.\n");
  if (b->first > b->last)
    {
      printf("!!! Bridge points to invalid bus range.\n");
      b->last = b->first;
    }
}

static void
do_map_bus(int bus)
{
  int domain = (filter.domain >= 0 ? filter.domain : 0);
  int dev, func;
  int verbose = pacc->debugging;
  struct bus_info *bi = bus_info + bus;
  struct device *d;

  if (verbose)
    printf("Mapping bus %04x:%02x\n", domain, bus);
  for (dev = 0; dev < 32; dev++)
    if (filter.slot < 0 || filter.slot == dev)
      {
	int func_limit = 1;
	for (func = 0; func < func_limit; func++)
	  if (filter.func < 0 || filter.func == func)
	    {
	      struct pci_dev *p = pci_get_dev(pacc, domain, bus, dev, func);
	      u16 vendor = pci_read_word(p, PCI_VENDOR_ID);
	      if (vendor && vendor != 0xffff)
		{
		  if (!func && (pci_read_byte(p, PCI_HEADER_TYPE) & 0x80))
		    func_limit = 8;
		  if (verbose)
		    printf("Discovered device %04x:%02x:%02x.%d\n", domain, bus, dev, func);
		  bi->exists = 1;
		  if (d = scan_device(p))
		    {
		      show_device(d);
		      switch (get_conf_byte(d, PCI_HEADER_TYPE) & 0x7f)
			{
			case PCI_HEADER_TYPE_BRIDGE:
			  map_bridge(bi, d, PCI_PRIMARY_BUS, PCI_SECONDARY_BUS, PCI_SUBORDINATE_BUS);
			  break;
			case PCI_HEADER_TYPE_CARDBUS:
			  map_bridge(bi, d, PCI_CB_PRIMARY_BUS, PCI_CB_CARD_BUS, PCI_CB_SUBORDINATE_BUS);
			  break;
			}
		      free(d);
		    }
		  else if (verbose)
		    printf("But it was filtered out.\n");
		}
	      pci_free_dev(p);
	    }
      }
}

static void
do_map_bridges(int bus, int min, int max)
{
  struct bus_info *bi = bus_info + bus;
  struct bus_bridge *b;

  bi->guestbook = 1;
  for (b=bi->bridges; b; b=b->next)
    {
      if (bus_info[b->first].guestbook)
	b->bug = 1;
      else if (b->first < min || b->last > max)
	b->bug = 2;
      else
	{
	  bus_info[b->first].via = b;
	  do_map_bridges(b->first, b->first, b->last);
	}
    }
}

static void
map_bridges(void)
{
  int i;

  printf("\nSummary of buses:\n\n");
  for (i=0; i<256; i++)
    if (bus_info[i].exists && !bus_info[i].guestbook)
      do_map_bridges(i, 0, 255);
  for (i=0; i<256; i++)
    {
      struct bus_info *bi = bus_info + i;
      struct bus_bridge *b = bi->via;

      if (bi->exists)
	{
	  printf("%02x: ", i);
	  if (b)
	    printf("Entered via %02x:%02x.%d\n", b->this, b->dev, b->func);
	  else if (!i)
	    printf("Primary host bus\n");
	  else
	    printf("Secondary host bus (?)\n");
	}
      for (b=bi->bridges; b; b=b->next)
	{
	  printf("\t%02x.%d Bridge to %02x-%02x", b->dev, b->func, b->first, b->last);
	  switch (b->bug)
	    {
	    case 1:
	      printf(" <overlap bug>");
	      break;
	    case 2:
	      printf(" <crossing bug>");
	      break;
	    }
	  putchar('\n');
	}
    }
}

void
map_the_bus(void)
{
  if (pacc->method == PCI_ACCESS_PROC_BUS_PCI ||
      pacc->method == PCI_ACCESS_SYS_BUS_PCI ||
      pacc->method == PCI_ACCESS_WIN32_CFGMGR32 ||
      pacc->method == PCI_ACCESS_DUMP)
    printf("WARNING: Bus mapping can be reliable only with direct hardware access enabled.\n\n");
  bus_info = xmalloc(sizeof(struct bus_info) * 256);
  memset(bus_info, 0, sizeof(struct bus_info) * 256);
  if (filter.bus >= 0)
    do_map_bus(filter.bus);
  else
    {
      int bus;
      for (bus=0; bus<256; bus++)
	do_map_bus(bus);
    }
  map_bridges();
}
