/*
 **************************************************************************
 *
 * Network driver interface for netboot bootrom
 *
 * Module:  pci.S86
 * Purpose: PCI handling routines
 * Entries: initpci, cleanpci
 *
 **************************************************************************
 *
 * Copyright (C) 2002-2007 Gero Kuhlmann <gero@gkminix.han.de>
 *
 *  This program 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
 *  any later version.
 *
 *  This program 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 program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: pci.S86,v 1.11 2007/01/06 18:30:48 gkminix Exp $
 *
 */


/*
 **************************************************************************
 *
 * Load assembler macros and other include files.
 *
 */
#include <common.i86>
#include <romconfig.i86>
#include <pxe/general.i86>
#include <pxe/entry.i86>
#include <pxe/undi.i86>
#include <system/bios.i86>
#include <system/pci.i86>
#include <system/i8259.i86>

.file __FILE__
.line __LINE__



/*
 **************************************************************************
 *
 * Start BSS segment.
 *
 */
	.bss

	.extern	devinfo
	.extern	irqnum




/*
 **************************************************************************
 *
 * Start code segment.
 *
 */
	.text

	.global	initpci
	.global	cleanpci

	.extern	pxestruct
	.extern	bustyp



/*
 **************************************************************************
 *
 * Pointer to old PCI BIOS handler routine
 */
oldpci:	.long	0



/*
 **************************************************************************
 *
 * New PCI BIOS interrupt handler. This routine will modify the PCI BIOS
 * calls so that it looks like there is just one PCI network interface card
 * installed.
 * Input:  AX    -  PCI BIOS function code
 * Output: depends on called function
 * Registers changed: depends on called function
 */

pcihandler:

/*
 * Check if we have a function which tries to determine the PCI PFA
 * of our network interface card.
 */

	push	ds
	mov	ds,word ptr cs:o_desc_segment[pxestruct + pxe_undi_data_sel]
	cmp	ax,PCI_FUNC_FIND_DEV
	je	2f
	cmp	ax,PCI_FUNC_FIND_CLASS
	je	3f
1:	pop	ds
	ljmp	cs:[oldpci]

/*
 * Return the PCI PFA of our network interface device (ID search)
 */

2:	cmp	cx,o_ugnt_pci_devid[devinfo]
	jne	1b
	cmp	dx,o_ugnt_pci_vendid[devinfo]
	jne	1b
	jmp	4f

/*
 * Return the PCI PFA of our network interface device (class search)
 */

3:	mov	eax,ecx
	shr	eax,16			# check PCI base class code
	cmp	al,PCI_BASE_NETWORK
	jne	1b

4:	mov	ah,0x86
	or	si,si			# only return PFA value for first
	jnz	8f			# PCI device
	xor	ah,ah
	mov	bx,o_ugnt_pci_busdevfunc[devinfo]
8:	pop	ds

	push	bp
	mov	bp,sp
	and	byte ptr [bp + 6],0xFE
	or	ah,ah			# set carry flag on stack according
	jz	9f			# to operation result
	or	byte ptr [bp + 6],0x01
9:	pop	bp
	iret



/*
 **************************************************************************
 *
 * Initialize PCI hardware handling. This involves setting the latency
 * timer of the PCI network card, if its a bus master card, and also
 * redirect the PCI BIOS interrupt handler.
 * Input:  None
 * Output: Carry flag set if error
 * Registers changed: EAX, EBX, ECX, EDX
 *
 */
initpci:

	push	di

/*
 * Check that we have a PCI network card and get its PFN number. Then check
 * that its a bus master device.
 */

	mov	al,cs:[bustyp]
	cmp	al,PXENV_BUS_PCI	# check network card type
	jne	7f			# return without error

	mov	bx,o_ugnt_pci_busdevfunc[devinfo]
	cmp	bx,0xFFFF		# we must have a PCI PFN
	je	8f
	or	bx,bx			# devinfo initialized?
	jz	8f

	mov	di,PCI_REG_COMMAND
	mov	ax,PCI_FUNC_READ_WORD
	int	INT_PCI			# get command word
	jc	8f
	test	cx,PCI_CMD_MASTER	# check that its a bus master
	jz	1f

/*
 * Now read the PCI latency timer and check that its above 16. If not, we
 * set it to a fixed value of 32. This is similar to the way the Linux
 * kernel is doing it.
 */

	mov	di,PCI_REG_LATENCY_TIMER
	mov	ax,PCI_FUNC_READ_BYTE
	int	INT_PCI			# get value of latency timer
	jc	8f
	cmp	cl,16			# it has to be greater than 16
	jae	1f
	mov	cl,32			# if not, set it to 32 as a default
	mov	ax,PCI_FUNC_WRITE_BYTE
	int	INT_PCI
	jc	8f

/*
 * Determine the network card hardware interrupt number. This can be used
 * if autodetection fails for some reason.
 */

1:	mov	word ptr [irqnum],0
	mov	di,PCI_REG_HEADER_TYPE
	mov	ax,PCI_FUNC_READ_BYTE
	int	INT_PCI			# get PCI header type
	jc	8f
	or	cl,cl			# we have to have header type 0
	jnz	2f

	mov	di,PCI_REG_INT_LINE
	mov	ax,PCI_FUNC_READ_BYTE
	int	INT_PCI			# get interrupt number
	jc	8f
	movzx	ax,cl
	cmp	ax,SLAVE_IRQ_NUM	# redirect slave interrupt vector
	jne	3f
	mov	ax,SLAVE_IRQ_REDIR
	jmp	4f

3:	cmp	ax,HWIRQ_MIN		# check if interrupt number is
	jb	2f			# suitable
	cmp	ax,HWIRQ_MAX
	ja	2f
4:	mov	[irqnum],ax		# save interrupt number

/*
 * Redirect the PCI BIOS interrupt handler. This causes the PCI BIOS to
 * only know one single network interface controller.
 */

2:	cli
	push	ds
	xor	ax,ax
	mov	ds,ax
	mov	eax,dword ptr ds:[INT_PCI * 4]
	mov	cs:[oldpci],eax
	mov	word ptr ds:[INT_PCI * 4 + 0],offset pcihandler
	mov	word ptr ds:[INT_PCI * 4 + 2],cs
	pop	ds
7:	clc
	jmp	9f

8:	stc
9:	sti				# enable interrupts in any case
	pop	di
	ret



/*
 **************************************************************************
 *
 * Clean all PCI interrupts from interrupt vector buffer
 * Input:  SS:BP  -  buffer of interrupt vectors
 * Output: None
 * Registers changed: none
 *
 */
cleanpci:

	mov	dword ptr [bp + INT_PCI * 4],0
	ret




/*
 **************************************************************************
 *
 */
	.end

