/*
 * lzo1x.S86  -  LZO1X decompression
 *
 * Copyright (C) 2004-2007 Gero Kuhlmann   <gero@gkminix.han.de>
 *
 * This code is based on a source file named lzo1x_1f.asm found in the
 * LZO source package version 1.08 (lzo-1.08.tar.gz), written by Markus
 * Oberhumer. It carries the following copyright:
 *
 *       Copyright (C) 1996-2002 Markus F.X.J. Oberhumer
 *
 * The original source file has been modified in the following ways:
 *	- The whole code has been rewritten to run in 16-bit real code
 *	  mode, using 16-bit registers wherever possible and using
 *	  segment registers for accessing data
 *	- The programming interface has been modified for register
 *	  based parameters, and to return errors through the carry flag
 *	  instead of an error code
 *	- The source code layout has been modified to make it compilable
 *	  using the CPP/GAS tools used by netboot
 *	- The netboot coding style has been applied, and comments have
 *	  been added
 *	- Minor modifications to the source code to make it easier to
 *	  understand, and to decrease the size of the resulting binary
 *
 * The author placed the whole LZO source code under the GNU GPL, which
 * also applies for netboot. The full LZO sources can be retrieved from
 * http://www.oberhumer.com
 *
 *====================================================================
 *
 *  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: lzo1x.S86,v 1.4 2007/01/06 18:30:51 gkminix Exp $
 *
 *====================================================================
 */


#include <common.i86>

.file __FILE__
.line __LINE__



/*
 *====================================================================
 *
 * Declarations for LZO1X algorithm (see original LZO source code for
 * an explanation):
 */

#define MIN_OUTBUF	16		/* min. output buffer size */
#define EOF_LENGTH	3		/* length of EOF code */
#define NN		3		/* 3 for LZO_FAST */

#define M1_MARKER	0		/* marker for M1 match */
#define M2_MARKER	64		/* marker for M2 match */
#define M3_MARKER	32		/* marker for M3 match */
#define M4_MARKER	16		/* marker for M4 match */

#define M2_MAX_OFFSET	0x0800
#define M3_MAX_OFFSET	0x4000




.line __LINE__
/*
 *====================================================================
 *
 * BSS segment:
 */

	.bss

	.lcomm	inend,2			# end of input block
	.lcomm	outstart,2		# start of output block
	.lcomm	outend,2		# end of output block




/*
 *====================================================================
 *
 * Start of the text segment and definition of all public entry points
 */

	.text

	.global	lzo1x_decompress	# define global symbols



/*
 *====================================================================
 *
 * Decompress a memory block using the LZO1X algorithm. The original
 * C prototype looks like this:
 *
 *    LZO_EXTERN(int)
 *    lzo1x_decompress   ( const lzo_byte *src, lzo_uint  src_len,
 *                               lzo_byte *dst, lzo_uintp dst_len,
 *                               lzo_voidp wrkmem );
 *
 * But because this routine gets called by assembly code we use a
 * register based programming interface. In addition we just return
 * an error condition in the carry flag instead of an error code.
 *
 * Input:  FS:SI -  Pointer to compressed data
 *         ES:DI -  Pointer to decompressed data buffer
 *         CX    -  Size of compressed data
 *         DX    -  Size of decompressed data buffer
 * Output: AX    -  Length of decompressed data
 *         Carry flag set if error
 * Changed registers: EAX
 */
lzo1x_decompress:

	cld
	push	ebx
	push	ecx
	push	edx
	push	di
	push	si

/*
 * Check input parameters and compute the pointer to the last bytes of the
 * input and output buffers
 */

	mov	ax,cx
	neg	ax			# check if the input data exceeds the
	cmp	si,ax			# 64 kB boundary
	ja	8f
	cmp	cx,EOF_LENGTH		# check minimum input data size
	jb	8f
	mov	ax,si
	add	ax,cx			# compute end of compressed data
	mov	[inend],ax

	mov	ax,dx
	neg	ax			# check if the output data buffer
	cmp	di,ax			# exceeds the 64 kB boundary
	ja	8f
	cmp	dx,MIN_OUTBUF		# check minimum output data buffer size
	jb	8f
	mov	ax,di			# compute end of decompressed data
	add	ax,dx			# buffer
	mov	[outend],ax
	mov	[outstart],di

/*
 * Initialize and check if the compressed image starts with a literal run
 */

	movzx	ax,byte ptr fs:[si]
	inc	si
	cmp	al,M4_MARKER + 1	# check if the compressed image starts
	jbe	6f			# with a byte string
	sub	al,M4_MARKER + 1 - NN	# compute length of byte string
	jmp	LFLR			# copy byte string into destination

/*
 * This is the actual start of the decompression loop, so read the next
 * byte out of the compressed image and check if it identifies a literal
 * run.
 */

Lloop:	cmp	si,[inend]		# check if end of input buffer reached
	jae	8f
	movzx	ax,byte ptr fs:[si]	# get next byte out of input buffer
	inc	si
6:	cmp	al,16			# check if a literal run follows
	jae	Lm2_match
	or	al,al			# check if string is longer than 255
	jz	2f			# bytes
	add	ax,2 * NN
	jmp	LFLR

1:	add	ax,255
	jc	8f
2:	cmp	si,[inend]		# check if end of input buffer reached
	jae	8f
	movzx	bx,byte ptr fs:[si]	# get next literal length
	inc	si
	or	bx,bx			# a zero length actually means 255
	jz	1b			# bytes
	add	bx,15 + 2 * NN
	add	ax,bx			# compute total literal length
	jc	8f

/*
 * Copy a literal run from the input buffer into the output buffer.
 * First we check that the literal doesn't exceed either the input or
 * output buffer size.
 */

LFLR:	lea	dx,[di - NN]
	add	dx,ax			# add offset to destination pointer
	cmp	dx,[outend]		# check that it doesn't exceed the
	ja	8f			# output buffer size
	lea	dx,[si - NN + 1]
	add	dx,ax			# add offset to source pointer
	cmp	dx,[inend]		# check that it doesn't exceed the
	ja	8f			# input buffer size

	mov	cx,ax
	shr	cx,2			# compute number of dwords to copy
/* rep	movsd	es:[di],fs:[si] */
	.byte	0xF3,0x66,0x64,0xA5	# GAS produces wrong code
	xor	ax,0x0003
	and	ax,0x0003
	sub	si,ax			# let SI and DI point to first
	sub	di,ax			# byte following copy string

Lfirst_literal_run:
	cmp	si,[inend]		# check if end of input buffer reached
	jae	8f
	movzx	ax,byte ptr fs:[si]	# get first byte following literal out
	inc	si			# of the input buffer
	cmp	al,16
	jae	Lm2_match

	cmp	si,[inend]		# check if end of input buffer reached
	jae	8f
	movzx	bx,byte ptr fs:[si]	# get next byte out of input buffer
	inc	si
	shr	ax,2			# compute destination buffer offset
	shl	bx,2
	add	ax,bx
	add	ax,M2_MAX_OFFSET + 1	# compute new offset into destination
	mov	bx,di			# buffer
	sub	bx,ax
	jc	8f
	cmp	bx,[outstart]		# check that it doesn't go below start
	jb	8f			# of output buffer
	add	di,3
	jc	8f			# check if we have space for 3 bytes in
	cmp	di,[outend]		# output buffer
	ja	8f
	mov	cx,es:[bx]
	mov	es:[di - 3],cx		# copy three bytes within output buffer
	mov	cl,es:[bx + 2]
	mov	es:[di - 1],cl
	jmp	Lmatch_done

/*
 * M2: Handle all function codes above 64
 */

Lm2_match:
	cmp	al,M2_MARKER
	jb	Lm3_match
	cmp	si,[inend]		# check if end of input buffer reached
	jae	8f
	movzx	bx,byte ptr fs:[si]	# get next byte out of input buffer
	inc	si
	mov	cx,ax
	shr	ax,2
	and	ax,0x0007
	shl	bx,3
	add	ax,bx
	lea	dx,[di - 1]
	sub	dx,ax			# compute beginning of destination
	jc	8f			# area
	shr	cx,5			# compute number of bytes to copy
	dec	cx
	jmp	Lcopy_match

/*
 * M3: Handle all function codes between 32 and 63
 */

Lm3_match:
	cmp	al,M3_MARKER
	jb	Lm4_match
	and	ax,M3_MARKER - 1	# a zero value actually means 255 bytes
	mov	cx,ax			# compute length of M3 match
	jz	4f
	jmp	5f

3:	add	cx,255
4:	cmp	si,[inend]		# check if end of input buffer reached
	jae	8f
	movzx	bx,byte ptr fs:[si]	# get next length byte out of input
	inc	si			# buffer
	or	bx,bx			# check if we have to add another 255
	jz	3b			# bytes
	add	cx,bx			# adjust byte number properly
	add	cx,31

5:	mov	dx,si
	add	dx,2			# check if input buffer exceeded
	jc	8f
	cmp	dx,[inend]
	ja	8f
	mov	ax,fs:[si]		# get next word out of input buffer
	add	si,2
	shr	ax,2			# compute destination pointer for
	lea	dx,[di - 1]		# match
	sub	dx,ax
	jc	8f

/*
 * Copy match
 * Registers have to contain the following C variable values
 *   DX  -  m_pos
 *   CX  -  t
 *   AX  -  distance between match and destination
 *
 * If the distance between match and destination is below 4, the copy
 * areas overlap in a way so that we cant use double-word copying.
 */

Lcopy_match:
	add	cx,2 + NN
	cmp	dx,[outstart]		# check that destination is above
	jb	8f			# start of output buffer
	lea	bx,[di - NN]
	add	bx,cx			# check that the end of the copy is
	cmp	bx,[outend]		# still within output buffer
	ja	8f
	cmp	ax,3			# check for overlapping copy
	jb	Lcopybyte

/*
 * Copy match using longwords
 */

Lcopylong:
	shr	cx,2			# now actually copy the match using
	xchg	si,dx			# longwords for speed
/* rep	movsd	es:[di],es:[si] */
	.byte	0xF3,0x66,0x26,0xA5	# GAS produces invalid code
	mov	si,dx
	mov	di,bx			# re-adjust buffer pointers

/*
 * After copying a match string, copy the final bytes, and continue with
 * the next opcode of the compressed data.
 */

Lmatch_done:
	mov	al,fs:[si - 2]
	and	ax,0x0003		# re-read length word
	jz	Lloop

Lmatch_next:
	mov	dx,di
	add	dx,ax			# check that it doesn't exceed the end
	jc	8f			# of the output buffer
	cmp	dx,[outend]
	ja	8f
	mov	dx,si
	add	dx,ax			# check that it doesn't exceed the end
	jc	8f			# of the input buffer
	inc	dx
	cmp	dx,[inend]
	ja	8f
	mov	cx,ax
/* rep	movsb	es:[di],fs:[si] */
	.byte	0xF3,0x66,0x64,0xA4	# GAS produces invalid code
	movzx	ax,byte ptr fs:[si]	# read next byte from input buffer
	inc	si
	jmp	Lm2_match

/*
 * Copy match using single-byte transfer
 */

Lcopybyte:
	xchg	dx,si
	sub	cx,NN
/* rep	movsb	es:[di],es:[si] */
	.byte	0xF3,0x26,0xA4		# GAS produces invalid code
	mov	si,dx
	jmp	Lmatch_done

/*
 * M4: Handle function codes between 16 and 31
 */

Lm4_match:
	cmp	al,M4_MARKER
	jb	Lm1_match
	mov	cx,ax
	and	eax,0x00000008
	shl	eax,13			# save in bit 16
	and	cx,0x0007
	jz	2f
	jmp	3f

1:	add	cx,255
2:	cmp	si,[inend]		# check if end of input buffer reached
	jae	8f
	movzx	bx,byte ptr fs:[si]	# get next byte out of input buffer
	inc	si
	or	bx,bx			# check if we have to add another 255
	jz	1b			# bytes
	add	cx,bx
	add	cx,7			# adjust number of bytes

3:	mov	dx,si
	add	dx,2			# check that we don't read past end
	jc	8f			# of input buffer
	cmp	dx,[inend]
	ja	8f
	mov	ax,fs:[si]		# get next word out of input buffer
	add	si,2
	shr	eax,2
	jz	Leof			# check if at end of compressed data
	add	ax,M3_MAX_OFFSET
	mov	dx,di
	sub	dx,ax
	jc	8f
	jmp	Lcopy_match

/*
 * M1: Handle function codes below 32
 */

Lm1_match:
	add	di,2			# check that we don't exceed the end
	jc	8f			# of the output buffer
	cmp	di,[outend]
	ja	8f
	cmp	si,[inend]		# check if end of input buffer reached
	jae	8f
	movzx	dx,byte ptr fs:[si]	# get next byte out of input buffer
	inc	si
	shl	dx,2
	shr	ax,2			# compute offset to source word
	add	ax,dx
	lea	bx,[di - 3]
	sub	bx,ax
	jc	8f
	cmp	bx,[outstart]		# check that offset is within buffer
	jb	8f			# limits
	mov	al,es:[bx]
	mov	es:[di - 2],al		# we must use byte copy because BX can
	mov	al,es:[bx + 1]		# be (DI - 1)
	mov	es:[di - 1],al
	jmp	Lmatch_done

/*
 * Check input and output buffer sizes and check return status
 */

Leof:	cmp	cx,1			# CX must be 1
	jne	8f
	cmp	di,[outend]		# check that end of output buffer
	ja	8f			# not exceeded
	cmp	si,[inend]		# check that input has been consumed
	jne	8f			# completely
	sub	di,[outstart]		# compute output length, this will
	mov	ax,di			# clear the carry flag (or set it
	jmp	9f			# in case of error)

/*
 * Return with/without error
 */

8:	stc
9:	pop	si
	pop	di
	pop	edx
	pop	ecx
	pop	ebx
	ret




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

