From e7c2a72e2680827d6a733931273a93461c0d8d1b Mon Sep 17 00:00:00 2001 From: Ralf Baechle Date: Tue, 14 Nov 1995 08:00:00 +0000 Subject: Import of Linux/MIPS 1.3.0 --- drivers/FPU-emu/Makefile | 50 - drivers/FPU-emu/README | 436 --- drivers/FPU-emu/control_w.h | 45 - drivers/FPU-emu/div_Xsig.S | 369 -- drivers/FPU-emu/div_small.S | 50 - drivers/FPU-emu/errors.c | 671 ---- drivers/FPU-emu/exception.h | 53 - drivers/FPU-emu/fpu_arith.c | 179 - drivers/FPU-emu/fpu_asm.h | 30 - drivers/FPU-emu/fpu_aux.c | 184 - drivers/FPU-emu/fpu_emu.h | 171 - drivers/FPU-emu/fpu_entry.c | 690 ---- drivers/FPU-emu/fpu_etc.c | 129 - drivers/FPU-emu/fpu_proto.h | 137 - drivers/FPU-emu/fpu_system.h | 82 - drivers/FPU-emu/fpu_trig.c | 1718 --------- drivers/FPU-emu/get_address.c | 423 --- drivers/FPU-emu/load_store.c | 260 -- drivers/FPU-emu/mul_Xsig.S | 182 - drivers/FPU-emu/poly.h | 116 - drivers/FPU-emu/poly_2xm1.c | 152 - drivers/FPU-emu/poly_atan.c | 197 - drivers/FPU-emu/poly_l2.c | 255 -- drivers/FPU-emu/poly_sin.c | 408 -- drivers/FPU-emu/poly_tan.c | 213 -- drivers/FPU-emu/polynom_Xsig.S | 137 - drivers/FPU-emu/reg_add_sub.c | 318 -- drivers/FPU-emu/reg_compare.c | 378 -- drivers/FPU-emu/reg_constant.c | 116 - drivers/FPU-emu/reg_constant.h | 31 - drivers/FPU-emu/reg_div.S | 251 -- drivers/FPU-emu/reg_ld_str.c | 1438 ------- drivers/FPU-emu/reg_mul.c | 105 - drivers/FPU-emu/reg_norm.S | 150 - drivers/FPU-emu/reg_round.S | 701 ---- drivers/FPU-emu/reg_u_add.S | 189 - drivers/FPU-emu/reg_u_div.S | 477 --- drivers/FPU-emu/reg_u_mul.S | 163 - drivers/FPU-emu/reg_u_sub.S | 292 -- drivers/FPU-emu/round_Xsig.S | 148 - drivers/FPU-emu/shr_Xsig.S | 90 - drivers/FPU-emu/status_w.h | 65 - drivers/FPU-emu/version.h | 12 - drivers/FPU-emu/wm_shrx.S | 208 - drivers/FPU-emu/wm_sqrt.S | 474 --- drivers/Makefile | 9 +- drivers/block/MAKEDEV.ide1 | 24 + drivers/block/Makefile | 38 +- drivers/block/README.aztcd | 730 ++++ drivers/block/README.fd | 168 + drivers/block/README.ide | 278 ++ drivers/block/README.sbpcd | 823 +++- drivers/block/README.sonycd535 | 119 + drivers/block/aztcd.c | 1713 +++++++++ drivers/block/blk.h | 127 +- drivers/block/cdu31a.c | 410 +- drivers/block/floppy.c | 680 +++- drivers/block/genhd.c | 49 +- drivers/block/hd.c | 34 +- drivers/block/ide-cd.c | 2126 +++++++++++ drivers/block/ide.c | 2438 ++++++++++++ drivers/block/ll_rw_blk.c | 201 +- drivers/block/mcd.c | 25 +- drivers/block/ramdisk.c | 38 +- drivers/block/sbpcd.c | 7544 ++++++++++++++++++++++--------------- drivers/block/sbpcd2.c | 5 + drivers/block/sbpcd3.c | 5 + drivers/block/sbpcd4.c | 5 + drivers/block/sonycd535.c | 1743 +++++++++ drivers/block/xd.c | 3 +- drivers/char/8x16.fnt | 4097 ++++++++++++++++++++ drivers/char/ChangeLog | 192 +- drivers/char/Makefile | 40 +- drivers/char/README.scc | 933 +++++ drivers/char/atixlmouse.c | 2 +- drivers/char/busmouse.c | 3 +- drivers/char/console.c | 1337 +++---- drivers/char/consolemap.c | 286 ++ drivers/char/consolemap.h | 13 + drivers/char/cyclades.c | 2958 +++++++++++++++ drivers/char/defkeymap.c | 2 +- drivers/char/defkeymap.map | 3 +- drivers/char/fnt8x16.c | 7 + drivers/char/kbd_kern.h | 9 +- drivers/char/keyboard.c | 290 +- drivers/char/lp.c | 244 +- drivers/char/mem.c | 29 +- drivers/char/msbusmouse.c | 2 +- drivers/char/n_tty.c | 16 +- drivers/char/psaux.c | 4 +- drivers/char/scc.c | 2305 +++++++++++ drivers/char/scc_config.h | 67 + drivers/char/selection.c | 294 ++ drivers/char/selection.h | 91 + drivers/char/serial.c | 633 +++- drivers/char/tpqic02.c | 4 +- drivers/char/tty_io.c | 174 +- drivers/char/tty_ioctl.c | 8 +- drivers/char/uni_to_437.c | 169 - drivers/char/vc_screen.c | 212 ++ drivers/char/vesa_blank.c | 246 ++ drivers/char/vt.c | 176 +- drivers/net/3c501.c | 213 +- drivers/net/3c503.c | 11 +- drivers/net/3c505.c | 2324 ++++++------ drivers/net/3c505.h | 178 +- drivers/net/3c505dta.h | 119 - drivers/net/3c507.c | 8 +- drivers/net/3c509.c | 30 +- drivers/net/8390.c | 119 +- drivers/net/8390.h | 11 +- drivers/net/CONFIG | 25 + drivers/net/MODULES | 8 - drivers/net/Makefile | 148 +- drivers/net/README.3c505 | 39 +- drivers/net/README.arcnet | 204 + drivers/net/README.arcnet-jumpers | 1561 ++++++++ drivers/net/README.de4x5 | 48 + drivers/net/README.eql | 528 +++ drivers/net/README.tunnel | 123 + drivers/net/README.wavelan | 31 + drivers/net/Space.c | 162 +- drivers/net/ac3200.c | 1 - drivers/net/apricot.c | 311 +- drivers/net/arcnet.c | 2175 +++++++++++ drivers/net/at1700.c | 43 +- drivers/net/atp.c | 145 +- drivers/net/auto_irq.c | 5 +- drivers/net/de4x5.c | 2513 ++++++++++++ drivers/net/de4x5.h | 647 ++++ drivers/net/de600.c | 35 +- drivers/net/de620.c | 45 +- drivers/net/depca.c | 267 +- drivers/net/dummy.c | 72 +- drivers/net/e2100.c | 3 +- drivers/net/eexpress.c | 33 +- drivers/net/eql.c | 1153 ++++++ drivers/net/ewrk3.c | 173 +- drivers/net/hp-plus.c | 12 +- drivers/net/hp.c | 3 +- drivers/net/i82586.h | 410 ++ drivers/net/ibmtr.c | 1180 ++++++ drivers/net/ibmtr.h | 426 +++ drivers/net/lance.c | 223 +- drivers/net/loopback.c | 185 +- drivers/net/ne.c | 115 +- drivers/net/net_init.c | 85 +- drivers/net/ni52.c | 728 ++-- drivers/net/ni52.h | 2 +- drivers/net/ni65.c | 11 +- drivers/net/pi2.c | 1693 +++++++++ drivers/net/pi2.h | 133 + drivers/net/plip.c | 1568 ++++---- drivers/net/ppp.c | 143 +- drivers/net/sk_g16.c | 15 +- drivers/net/skeleton.c | 7 +- drivers/net/slhc.c | 72 +- drivers/net/slip.c | 1717 +++++---- drivers/net/slip.h | 79 +- drivers/net/smc-ultra.c | 56 +- drivers/net/sonic.c | 1120 ++++++ drivers/net/sonic.h | 359 ++ drivers/net/tulip.c | 737 ++++ drivers/net/tunnel.c | 311 ++ drivers/net/wavelan.c | 2448 ++++++++++++ drivers/net/wavelan.h | 254 ++ drivers/net/wd.c | 11 +- drivers/net/z8530.h | 220 ++ drivers/net/znet.c | 26 +- drivers/pci/Makefile | 40 + drivers/pci/pci.c | 752 ++++ drivers/scsi/53c7,8xx.c | 733 ++-- drivers/scsi/53c7,8xx.h | 66 +- drivers/scsi/53c7,8xx.scr | 33 +- drivers/scsi/53c8xx_d.h | 252 +- drivers/scsi/53c8xx_u.h | 4 + drivers/scsi/ChangeLog | 964 +++++ drivers/scsi/Makefile | 52 +- drivers/scsi/NCR5380.c | 26 +- drivers/scsi/NCR5380.h | 2 +- drivers/scsi/README.st | 21 +- drivers/scsi/aha152x.c | 189 +- drivers/scsi/aha152x.h | 4 +- drivers/scsi/aha1542.c | 74 +- drivers/scsi/aha1740.c | 35 +- drivers/scsi/aha1740.h | 42 +- drivers/scsi/aha274x.c | 30 +- drivers/scsi/buslogic.c | 690 ++-- drivers/scsi/buslogic.h | 27 +- drivers/scsi/constants.c | 2 +- drivers/scsi/eata.c | 456 ++- drivers/scsi/eata.h | 39 +- drivers/scsi/eata_dma.c | 1185 ++++++ drivers/scsi/eata_dma.h | 408 ++ drivers/scsi/fdomain.c | 245 +- drivers/scsi/fdomain.h | 9 +- drivers/scsi/hosts.c | 171 +- drivers/scsi/hosts.h | 65 +- drivers/scsi/in2000.c | 64 +- drivers/scsi/qlogic.c | 306 +- drivers/scsi/script_asm.pl | 19 +- drivers/scsi/scsi.c | 1615 +++++--- drivers/scsi/scsi.h | 65 +- drivers/scsi/scsi_debug.c | 26 +- drivers/scsi/scsi_ioctl.c | 21 +- drivers/scsi/scsi_module.c | 49 + drivers/scsi/sd.c | 218 +- drivers/scsi/sd_ioctl.c | 17 + drivers/scsi/seagate.c | 64 +- drivers/scsi/seagate.h | 2 + drivers/scsi/sg.c | 104 +- drivers/scsi/sg.h | 2 +- drivers/scsi/sr.c | 376 +- drivers/scsi/sr.h | 1 + drivers/scsi/sr_ioctl.c | 53 +- drivers/scsi/st.c | 455 ++- drivers/scsi/st.h | 8 + drivers/scsi/t128.c | 8 +- drivers/scsi/u14-34f.c | 316 +- drivers/scsi/u14-34f.h | 17 +- drivers/scsi/ultrastor.c | 18 +- drivers/scsi/ultrastor.h | 2 +- drivers/scsi/wd7000.c | 25 +- drivers/sound/.blurb | 27 - drivers/sound/.blurb.orig | 27 - drivers/sound/CHANGELOG | 2 +- drivers/sound/Makefile | 2 + drivers/sound/Readme | 12 +- drivers/sound/Readme.v30 | 2 +- drivers/sound/ad1848.c | 5 +- drivers/sound/configure.c | 2 +- drivers/sound/dma.h | 266 -- drivers/sound/dmabuf.c | 4 +- drivers/sound/experimental.txt | 2 +- drivers/sound/gus_card.c | 6 +- drivers/sound/gus_wave.c | 5 +- drivers/sound/mpu401.c | 4 +- drivers/sound/pas2_card.c | 2 +- drivers/sound/pss.c | 2 +- drivers/sound/sb_dsp.c | 4 +- drivers/sound/sound_calls.h | 6 +- drivers/sound/soundcard.c | 3 +- drivers/sound/uart6850.c | 2 +- 243 files changed, 60996 insertions(+), 24840 deletions(-) delete mode 100644 drivers/FPU-emu/Makefile delete mode 100644 drivers/FPU-emu/README delete mode 100644 drivers/FPU-emu/control_w.h delete mode 100644 drivers/FPU-emu/div_Xsig.S delete mode 100644 drivers/FPU-emu/div_small.S delete mode 100644 drivers/FPU-emu/errors.c delete mode 100644 drivers/FPU-emu/exception.h delete mode 100644 drivers/FPU-emu/fpu_arith.c delete mode 100644 drivers/FPU-emu/fpu_asm.h delete mode 100644 drivers/FPU-emu/fpu_aux.c delete mode 100644 drivers/FPU-emu/fpu_emu.h delete mode 100644 drivers/FPU-emu/fpu_entry.c delete mode 100644 drivers/FPU-emu/fpu_etc.c delete mode 100644 drivers/FPU-emu/fpu_proto.h delete mode 100644 drivers/FPU-emu/fpu_system.h delete mode 100644 drivers/FPU-emu/fpu_trig.c delete mode 100644 drivers/FPU-emu/get_address.c delete mode 100644 drivers/FPU-emu/load_store.c delete mode 100644 drivers/FPU-emu/mul_Xsig.S delete mode 100644 drivers/FPU-emu/poly.h delete mode 100644 drivers/FPU-emu/poly_2xm1.c delete mode 100644 drivers/FPU-emu/poly_atan.c delete mode 100644 drivers/FPU-emu/poly_l2.c delete mode 100644 drivers/FPU-emu/poly_sin.c delete mode 100644 drivers/FPU-emu/poly_tan.c delete mode 100644 drivers/FPU-emu/polynom_Xsig.S delete mode 100644 drivers/FPU-emu/reg_add_sub.c delete mode 100644 drivers/FPU-emu/reg_compare.c delete mode 100644 drivers/FPU-emu/reg_constant.c delete mode 100644 drivers/FPU-emu/reg_constant.h delete mode 100644 drivers/FPU-emu/reg_div.S delete mode 100644 drivers/FPU-emu/reg_ld_str.c delete mode 100644 drivers/FPU-emu/reg_mul.c delete mode 100644 drivers/FPU-emu/reg_norm.S delete mode 100644 drivers/FPU-emu/reg_round.S delete mode 100644 drivers/FPU-emu/reg_u_add.S delete mode 100644 drivers/FPU-emu/reg_u_div.S delete mode 100644 drivers/FPU-emu/reg_u_mul.S delete mode 100644 drivers/FPU-emu/reg_u_sub.S delete mode 100644 drivers/FPU-emu/round_Xsig.S delete mode 100644 drivers/FPU-emu/shr_Xsig.S delete mode 100644 drivers/FPU-emu/status_w.h delete mode 100644 drivers/FPU-emu/version.h delete mode 100644 drivers/FPU-emu/wm_shrx.S delete mode 100644 drivers/FPU-emu/wm_sqrt.S create mode 100644 drivers/block/MAKEDEV.ide1 create mode 100644 drivers/block/README.aztcd create mode 100644 drivers/block/README.fd create mode 100644 drivers/block/README.ide create mode 100644 drivers/block/README.sonycd535 create mode 100644 drivers/block/aztcd.c create mode 100644 drivers/block/ide-cd.c create mode 100644 drivers/block/ide.c create mode 100644 drivers/block/sbpcd2.c create mode 100644 drivers/block/sbpcd3.c create mode 100644 drivers/block/sbpcd4.c create mode 100644 drivers/block/sonycd535.c create mode 100644 drivers/char/8x16.fnt create mode 100644 drivers/char/README.scc create mode 100644 drivers/char/consolemap.c create mode 100644 drivers/char/consolemap.h create mode 100644 drivers/char/cyclades.c create mode 100644 drivers/char/fnt8x16.c create mode 100644 drivers/char/scc.c create mode 100644 drivers/char/scc_config.h create mode 100644 drivers/char/selection.c create mode 100644 drivers/char/selection.h delete mode 100644 drivers/char/uni_to_437.c create mode 100644 drivers/char/vc_screen.c create mode 100644 drivers/char/vesa_blank.c delete mode 100644 drivers/net/3c505dta.h delete mode 100644 drivers/net/MODULES create mode 100644 drivers/net/README.arcnet create mode 100644 drivers/net/README.arcnet-jumpers create mode 100644 drivers/net/README.de4x5 create mode 100644 drivers/net/README.eql create mode 100644 drivers/net/README.tunnel create mode 100644 drivers/net/README.wavelan create mode 100644 drivers/net/arcnet.c create mode 100644 drivers/net/de4x5.c create mode 100644 drivers/net/de4x5.h create mode 100644 drivers/net/eql.c create mode 100644 drivers/net/i82586.h create mode 100644 drivers/net/ibmtr.c create mode 100644 drivers/net/ibmtr.h create mode 100644 drivers/net/pi2.c create mode 100644 drivers/net/pi2.h create mode 100644 drivers/net/sonic.c create mode 100644 drivers/net/sonic.h create mode 100644 drivers/net/tulip.c create mode 100644 drivers/net/tunnel.c create mode 100644 drivers/net/wavelan.c create mode 100644 drivers/net/wavelan.h create mode 100644 drivers/net/z8530.h create mode 100644 drivers/pci/Makefile create mode 100644 drivers/pci/pci.c create mode 100644 drivers/scsi/eata_dma.c create mode 100644 drivers/scsi/eata_dma.h create mode 100644 drivers/scsi/scsi_module.c delete mode 100644 drivers/sound/.blurb.orig delete mode 100644 drivers/sound/dma.h (limited to 'drivers') diff --git a/drivers/FPU-emu/Makefile b/drivers/FPU-emu/Makefile deleted file mode 100644 index 46ff0c541..000000000 --- a/drivers/FPU-emu/Makefile +++ /dev/null @@ -1,50 +0,0 @@ -# -# Makefile for wm-FPU-emu -# - -#DEBUG = -DDEBUGGING -DEBUG = -PARANOID = -DPARANOID -CFLAGS := $(CFLAGS) $(PARANOID) $(DEBUG) -fno-builtin - -.c.o: - $(CC) $(CFLAGS) $(MATH_EMULATION) -c $< - -.S.o: - $(CC) -D__ASSEMBLER__ $(PARANOID) -c $< - -.s.o: - $(CC) -c $< - -OBJS = fpu_entry.o div_small.o errors.o \ - fpu_arith.o fpu_aux.o fpu_etc.o fpu_trig.o \ - load_store.o get_address.o \ - poly_atan.o poly_l2.o poly_2xm1.o poly_sin.o poly_tan.o \ - reg_add_sub.o reg_compare.o reg_constant.o reg_ld_str.o \ - reg_div.o reg_mul.o reg_norm.o \ - reg_u_add.o reg_u_div.o reg_u_mul.o reg_u_sub.o \ - reg_round.o \ - wm_shrx.o wm_sqrt.o \ - div_Xsig.o polynom_Xsig.o round_Xsig.o \ - shr_Xsig.o mul_Xsig.o - -math.a: $(OBJS) - rm -f math.a - $(AR) rcs math.a $(OBJS) - sync - -dep: - $(CPP) -M *.c > .depend - $(CPP) -D__ASSEMBLER__ -M *.S >> .depend - -proto: - cproto -e -DMAKING_PROTO *.c >fpu_proto.h - -dummy: - -# -# include a dependency file if one exists -# -ifeq (.depend,$(wildcard .depend)) -include .depend -endif diff --git a/drivers/FPU-emu/README b/drivers/FPU-emu/README deleted file mode 100644 index 2c0acb423..000000000 --- a/drivers/FPU-emu/README +++ /dev/null @@ -1,436 +0,0 @@ - +---------------------------------------------------------------------------+ - | wm-FPU-emu an FPU emulator for 80386 and 80486SX microprocessors. | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | This program is free software; you can redistribute it and/or modify | - | it under the terms of the GNU General Public License version 2 as | - | published by the Free Software Foundation. | - | | - | 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. | - | | - +---------------------------------------------------------------------------+ - - - -wm-FPU-emu is an FPU emulator for Linux. It is derived from wm-emu387 -which was my 80387 emulator for early versions of djgpp (gcc under -msdos); wm-emu387 was in turn based upon emu387 which was written by -DJ Delorie for djgpp. The interface to the Linux kernel is based upon -the original Linux math emulator by Linus Torvalds. - -My target FPU for wm-FPU-emu is that described in the Intel486 -Programmer's Reference Manual (1992 edition). Unfortunately, numerous -facets of the functioning of the FPU are not well covered in the -Reference Manual. The information in the manual has been supplemented -with measurements on real 80486's. Unfortunately, it is simply not -possible to be sure that all of the peculiarities of the 80486 have -been discovered, so there is always likely to be obscure differences -in the detailed behaviour of the emulator and a real 80486. - -wm-FPU-emu does not implement all of the behaviour of the 80486 FPU, -but is very close. See "Limitations" later in this file for a list of -some differences. - -Please report bugs, etc to me at: - billm@vaxc.cc.monash.edu.au - or at: - billm@jacobi.maths.monash.edu.au - - ---Bill Metzenthen - August 1994 - - ------------------------ Internals of wm-FPU-emu ----------------------- - -Numeric algorithms: -(1) Add, subtract, and multiply. Nothing remarkable in these. -(2) Divide has been tuned to get reasonable performance. The algorithm - is not the obvious one which most people seem to use, but is designed - to take advantage of the characteristics of the 80386. I expect that - it has been invented many times before I discovered it, but I have not - seen it. It is based upon one of those ideas which one carries around - for years without ever bothering to check it out. -(3) The sqrt function has been tuned to get good performance. It is based - upon Newton's classic method. Performance was improved by capitalizing - upon the properties of Newton's method, and the code is once again - structured taking account of the 80386 characteristics. -(4) The trig, log, and exp functions are based in each case upon quasi- - "optimal" polynomial approximations. My definition of "optimal" was - based upon getting good accuracy with reasonable speed. -(5) The argument reducing code for the trig function effectively uses - a value of pi which is accurate to more than 128 bits. As a consequence, - the reduced argument is accurate to more than 64 bits for arguments up - to a few pi, and accurate to more than 64 bits for most arguments, - even for arguments approaching 2^63. This is far superior to an - 80486, which uses a value of pi which is accurate to 66 bits. - -The code of the emulator is complicated slightly by the need to -account for a limited form of re-entrancy. Normally, the emulator will -emulate each FPU instruction to completion without interruption. -However, it may happen that when the emulator is accessing the user -memory space, swapping may be needed. In this case the emulator may be -temporarily suspended while disk i/o takes place. During this time -another process may use the emulator, thereby perhaps changing static -variables. The code which accesses user memory is confined to five -files: - fpu_entry.c - reg_ld_str.c - load_store.c - get_address.c - errors.c -As from version 1.12 of the emulator, no static variables are used -(apart from those in the kernel's per-process tables). The emulator is -therefore now fully re-entrant, rather than having just the restricted -form of re-entrancy which is required by the Linux kernel. - ------------------------ Limitations of wm-FPU-emu ----------------------- - -There are a number of differences between the current wm-FPU-emu -(version 1.20) and the 80486 FPU (apart from bugs). Some of the more -important differences are listed below: - -The Roundup flag does not have much meaning for the transcendental -functions and its 80486 value with these functions is likely to differ -from its emulator value. - -In a few rare cases the Underflow flag obtained with the emulator will -be different from that obtained with an 80486. This occurs when the -following conditions apply simultaneously: -(a) the operands have a higher precision than the current setting of the - precision control (PC) flags. -(b) the underflow exception is masked. -(c) the magnitude of the exact result (before rounding) is less than 2^-16382. -(d) the magnitude of the final result (after rounding) is exactly 2^-16382. -(e) the magnitude of the exact result would be exactly 2^-16382 if the - operands were rounded to the current precision before the arithmetic - operation was performed. -If all of these apply, the emulator will set the Underflow flag but a real -80486 will not. - -NOTE: Certain formats of Extended Real are UNSUPPORTED. They are -unsupported by the 80486. They are the Pseudo-NaNs, Pseudoinfinities, -and Unnormals. None of these will be generated by an 80486 or by the -emulator. Do not use them. The emulator treats them differently in -detail from the way an 80486 does. - -The emulator treats PseudoDenormals differently from an 80486. These -numbers are in fact properly normalised numbers with the exponent -offset by 1, and the emulator treats them as such. Unlike the 80486, -the emulator does not generate a Denormal Operand exception for these -numbers. The arithmetical results produced when using such a number as -an operand are the same for the emulator and a real 80486 (apart from -any slight precision difference for the transcendental functions). -Neither the emulator nor an 80486 produces one of these numbers as the -result of any arithmetic operation. An 80486 can keep one of these -numbers in an FPU register with its identity as a PseudoDenormal, but -the emulator will not; they are always converted to a valid number. - -Self modifying code can cause the emulator to fail. An example of such -code is: - movl %esp,[%ebx] - fld1 -The FPU instruction may be (usually will be) loaded into the pre-fetch -queue of the cpu before the mov instruction is executed. If the -destination of the 'movl' overlaps the FPU instruction then the bytes -in the prefetch queue and memory will be inconsistent when the FPU -instruction is executed. The emulator will be invoked but will not be -able to find the instruction which caused the device-not-present -exception. For this case, the emulator cannot emulate the behaviour of -an 80486DX. - -Handling of the address size override prefix byte (0x67) has not been -extensively tested yet. A major problem exists because using it in -vm86 mode can cause a general protection fault. Address offsets -greater than 0xffff appear to be illegal in vm86 mode but are quite -acceptable (and work) in real mode. A small test program developed to -check the addressing, and which runs successfully in real mode, -crashes dosemu under Linux and also brings Windows down with a general -protection fault message when run under the MS-DOS prompt of Windows -3.1. (The program simply reads data from a valid address). - -The emulator supports 16-bit protected mode, with one difference from -an 80486DX. A 80486DX will allow some floating point instructions to -write a few bytes below the lowest address of the stack. The emulator -will not allow this in 16-bit protected mode: no instructions are -allowed to write outside the bounds set by the protection. - ------------------------ Performance of wm-FPU-emu ----------------------- - -Speed. ------ - -The speed of floating point computation with the emulator will depend -upon instruction mix. Relative performance is best for the instructions -which require most computation. The simple instructions are adversely -affected by the fpu instruction trap overhead. - - -Timing: Some simple timing tests have been made on the emulator functions. -The times include load/store instructions. All times are in microseconds -measured on a 33MHz 386 with 64k cache. The Turbo C tests were under -ms-dos, the next two columns are for emulators running with the djgpp -ms-dos extender. The final column is for wm-FPU-emu in Linux 0.97, -using libm4.0 (hard). - -function Turbo C djgpp 1.06 WM-emu387 wm-FPU-emu - - + 60.5 154.8 76.5 139.4 - - 61.1-65.5 157.3-160.8 76.2-79.5 142.9-144.7 - * 71.0 190.8 79.6 146.6 - / 61.2-75.0 261.4-266.9 75.3-91.6 142.2-158.1 - - sin() 310.8 4692.0 319.0 398.5 - cos() 284.4 4855.2 308.0 388.7 - tan() 495.0 8807.1 394.9 504.7 - atan() 328.9 4866.4 601.1 419.5-491.9 - - sqrt() 128.7 crashed 145.2 227.0 - log() 413.1-419.1 5103.4-5354.21 254.7-282.2 409.4-437.1 - exp() 479.1 6619.2 469.1 850.8 - - -The performance under Linux is improved by the use of look-ahead code. -The following results show the improvement which is obtained under -Linux due to the look-ahead code. Also given are the times for the -original Linux emulator with the 4.1 'soft' lib. - - [ Linus' note: I changed look-ahead to be the default under linux, as - there was no reason not to use it after I had edited it to be - disabled during tracing ] - - wm-FPU-emu w original w - look-ahead 'soft' lib - + 106.4 190.2 - - 108.6-111.6 192.4-216.2 - * 113.4 193.1 - / 108.8-124.4 700.1-706.2 - - sin() 390.5 2642.0 - cos() 381.5 2767.4 - tan() 496.5 3153.3 - atan() 367.2-435.5 2439.4-3396.8 - - sqrt() 195.1 4732.5 - log() 358.0-387.5 3359.2-3390.3 - exp() 619.3 4046.4 - - -These figures are now somewhat out-of-date. The emulator has become -progressively slower for most functions as more of the 80486 features -have been implemented. - - ------------------------ Accuracy of wm-FPU-emu ----------------------- - - -The accuracy of the emulator is in almost all cases equal to or better -than that of an Intel 80486 FPU. - -The results of the basic arithmetic functions (+,-,*,/), and fsqrt -match those of an 80486 FPU. They are the best possible; the error for -these never exceeds 1/2 an lsb. The fprem and fprem1 instructions -return exact results; they have no error. - - -The following table compares the emulator accuracy for the sqrt(), -trig and log functions against the Turbo C "emulator". For this table, -each function was tested at about 400 points. Ideal worst-case results -would be 64 bits. The reduced Turbo C accuracy of cos() and tan() for -arguments greater than pi/4 can be thought of as being related to the -precision of the argument x; e.g. an argument of pi/2-(1e-10) which is -accurate to 64 bits can result in a relative accuracy in cos() of -about 64 + log2(cos(x)) = 31 bits. - - -Function Tested x range Worst result Turbo C - (relative bits) - -sqrt(x) 1 .. 2 64.1 63.2 -atan(x) 1e-10 .. 200 64.2 62.8 -cos(x) 0 .. pi/2-(1e-10) 64.4 (x <= pi/4) 62.4 - 64.1 (x = pi/2-(1e-10)) 31.9 -sin(x) 1e-10 .. pi/2 64.0 62.8 -tan(x) 1e-10 .. pi/2-(1e-10) 64.0 (x <= pi/4) 62.1 - 64.1 (x = pi/2-(1e-10)) 31.9 -exp(x) 0 .. 1 63.1 ** 62.9 -log(x) 1+1e-6 .. 2 63.8 ** 62.1 - -** The accuracy for exp() and log() is low because the FPU (emulator) -does not compute them directly; two operations are required. - - -The emulator passes the "paranoia" tests (compiled with gcc 2.3.3 or -later) for 'float' variables (24 bit precision numbers) when precision -control is set to 24, 53 or 64 bits, and for 'double' variables (53 -bit precision numbers) when precision control is set to 53 bits (a -properly performing FPU cannot pass the 'paranoia' tests for 'double' -variables when precision control is set to 64 bits). - -The code for reducing the argument for the trig functions (fsin, fcos, -fptan and fsincos) has been improved and now effectively uses a value -for pi which is accurate to more than 128 bits precision. As a -consequence, the accuracy of these functions for large arguments has -been dramatically improved (and is now very much better than an 80486 -FPU). There is also now no degradation of accuracy for fcos and fptan -for operands close to pi/2. Measured results are (note that the -definition of accuracy has changed slightly from that used for the -above table): - -Function Tested x range Worst result - (absolute bits) - -cos(x) 0 .. 9.22e+18 62.0 -sin(x) 1e-16 .. 9.22e+18 62.1 -tan(x) 1e-16 .. 9.22e+18 61.8 - -It is possible with some effort to find very large arguments which -give much degraded precision. For example, the integer number - 8227740058411162616.0 -is within about 10e-7 of a multiple of pi. To find the tan (for -example) of this number to 64 bits precision it would be necessary to -have a value of pi which had about 150 bits precision. The FPU -emulator computes the result to about 42.6 bits precision (the correct -result is about -9.739715e-8). On the other hand, an 80486 FPU returns -0.01059, which in relative terms is hopelessly inaccurate. - -For arguments close to critical angles (which occur at multiples of -pi/2) the emulator is more accurate than an 80486 FPU. For very large -arguments, the emulator is far more accurate. - - -Prior to version 1.20 of the emulator, the accuracy of the results for -the transcendental functions (in their principal range) was not as -good as the results from an 80486 FPU. From version 1.20, the accuracy -has been considerably improved and these functions now give measured -worst-case results which are better than the worst-case results given -by an 80486 FPU. - -The following table gives the measured results for the emulator. The -number of randomly selected arguments in each case is about half a -million. The group of three columns gives the frequency of the given -accuracy in number of times per million, thus the second of these -columns shows that an accuracy of between 63.80 and 63.89 bits was -found at a rate of 133 times per one million measurements for fsin. -The results show that the fsin, fcos and fptan instructions return -results which are in error (i.e. less accurate than the best possible -result (which is 64 bits)) for about one per cent of all arguments -between -pi/2 and +pi/2. The other instructions have a lower -frequency of results which are in error. The last two columns give -the worst accuracy which was found (in bits) and the approximate value -of the argument which produced it. - - frequency (per M) - ------------------- --------------- -instr arg range # tests 63.7 63.8 63.9 worst at arg - bits bits bits bits ------ ------------ ------- ---- ---- ----- ----- -------- -fsin (0,pi/2) 547756 0 133 10673 63.89 0.451317 -fcos (0,pi/2) 547563 0 126 10532 63.85 0.700801 -fptan (0,pi/2) 536274 11 267 10059 63.74 0.784876 -fpatan 4 quadrants 517087 0 8 1855 63.88 0.435121 (4q) -fyl2x (0,20) 541861 0 0 1323 63.94 1.40923 (x) -fyl2xp1 (-.293,.414) 520256 0 0 5678 63.93 0.408542 (x) -f2xm1 (-1,1) 538847 4 481 6488 63.79 0.167709 - - -Tests performed on an 80486 FPU showed results of lower accuracy. The -following table gives the results which were obtained with an AMD -486DX2/66 (other tests indicate that an Intel 486DX produces -identical results). The tests were basically the same as those used -to measure the emulator (the values, being random, were in general not -the same). The total number of tests for each instruction are given -at the end of the table, in case each about 100k tests were performed. -Another line of figures at the end of the table shows that most of the -instructions return results which are in error for more than 10 -percent of the arguments tested. - -The numbers in the body of the table give the approx number of times a -result of the given accuracy in bits (given in the left-most column) -was obtained per one million arguments. For three of the instructions, -two columns of results are given: * The second column for f2xm1 gives -the number cases where the results of the first column were for a -positive argument, this shows that this instruction gives better -results for positive arguments than it does for negative. * In the -cases of fcos and fptan, the first column gives the results when all -cases where arguments greater than 1.5 were removed from the results -given in the second column. Unlike the emulator, an 80486 FPU returns -results of relatively poor accuracy for these instructions when the -argument approaches pi/2. The table does not show those cases when the -accuracy of the results were less than 62 bits, which occurs quite -often for fsin and fptan when the argument approaches pi/2. This poor -accuracy is discussed above in relation to the Turbo C "emulator", and -the accuracy of the value of pi. - - -bits f2xm1 f2xm1 fpatan fcos fcos fyl2x fyl2xp1 fsin fptan fptan -62.0 0 0 0 0 437 0 0 0 0 925 -62.1 0 0 10 0 894 0 0 0 0 1023 -62.2 14 0 0 0 1033 0 0 0 0 945 -62.3 57 0 0 0 1202 0 0 0 0 1023 -62.4 385 0 0 10 1292 0 23 0 0 1178 -62.5 1140 0 0 119 1649 0 39 0 0 1149 -62.6 2037 0 0 189 1620 0 16 0 0 1169 -62.7 5086 14 0 646 2315 10 101 35 39 1402 -62.8 8818 86 0 984 3050 59 287 131 224 2036 -62.9 11340 1355 0 2126 4153 79 605 357 321 1948 -63.0 15557 4750 0 3319 5376 246 1281 862 808 2688 -63.1 20016 8288 0 4620 6628 511 2569 1723 1510 3302 -63.2 24945 11127 10 6588 8098 1120 4470 2968 2990 4724 -63.3 25686 12382 69 8774 10682 1906 6775 4482 5474 7236 -63.4 29219 14722 79 11109 12311 3094 9414 7259 8912 10587 -63.5 30458 14936 393 13802 15014 5874 12666 9609 13762 15262 -63.6 32439 16448 1277 17945 19028 10226 15537 14657 19158 20346 -63.7 35031 16805 4067 23003 23947 18910 20116 21333 25001 26209 -63.8 33251 15820 7673 24781 25675 24617 25354 24440 29433 30329 -63.9 33293 16833 18529 28318 29233 31267 31470 27748 29676 30601 - -Per cent with error: - 30.9 3.2 18.5 9.8 13.1 11.6 17.4 -Total arguments tested: - 70194 70099 101784 100641 100641 101799 128853 114893 102675 102675 - - -------------------------- Contributors ------------------------------- - -A number of people have contributed to the development of the -emulator, often by just reporting bugs, sometimes with suggested -fixes, and a few kind people have provided me with access in one way -or another to an 80486 machine. Contributors include (to those people -who I may have forgotten, please forgive me): - -Linus Torvalds -Tommy.Thorn@daimi.aau.dk -Andrew.Tridgell@anu.edu.au -Nick Holloway, alfie@dcs.warwick.ac.uk -Hermano Moura, moura@dcs.gla.ac.uk -Jon Jagger, J.Jagger@scp.ac.uk -Lennart Benschop -Brian Gallew, geek+@CMU.EDU -Thomas Staniszewski, ts3v+@andrew.cmu.edu -Martin Howell, mph@plasma.apana.org.au -M Saggaf, alsaggaf@athena.mit.edu -Peter Barker, PETER@socpsy.sci.fau.edu -tom@vlsivie.tuwien.ac.at -Dan Russel, russed@rpi.edu -Daniel Carosone, danielce@ee.mu.oz.au -cae@jpmorgan.com -Hamish Coleman, t933093@minyos.xx.rmit.oz.au -Bruce Evans, bde@kralizec.zeta.org.au -Timo Korvola, Timo.Korvola@hut.fi -Rick Lyons, rick@razorback.brisnet.org.au -Rick, jrs@world.std.com - -...and numerous others who responded to my request for help with -a real 80486. - diff --git a/drivers/FPU-emu/control_w.h b/drivers/FPU-emu/control_w.h deleted file mode 100644 index ef5fced39..000000000 --- a/drivers/FPU-emu/control_w.h +++ /dev/null @@ -1,45 +0,0 @@ -/*---------------------------------------------------------------------------+ - | control_w.h | - | | - | Copyright (C) 1992,1993 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - +---------------------------------------------------------------------------*/ - -#ifndef _CONTROLW_H_ -#define _CONTROLW_H_ - -#ifdef __ASSEMBLER__ -#define _Const_(x) $##x -#else -#define _Const_(x) x -#endif - -#define CW_RC _Const_(0x0C00) /* rounding control */ -#define CW_PC _Const_(0x0300) /* precision control */ - -#define CW_Precision Const_(0x0020) /* loss of precision mask */ -#define CW_Underflow Const_(0x0010) /* underflow mask */ -#define CW_Overflow Const_(0x0008) /* overflow mask */ -#define CW_ZeroDiv Const_(0x0004) /* divide by zero mask */ -#define CW_Denormal Const_(0x0002) /* denormalized operand mask */ -#define CW_Invalid Const_(0x0001) /* invalid operation mask */ - -#define CW_Exceptions _Const_(0x003f) /* all masks */ - -#define RC_RND _Const_(0x0000) -#define RC_DOWN _Const_(0x0400) -#define RC_UP _Const_(0x0800) -#define RC_CHOP _Const_(0x0C00) - -/* p 15-5: Precision control bits affect only the following: - ADD, SUB(R), MUL, DIV(R), and SQRT */ -#define PR_24_BITS _Const_(0x000) -#define PR_53_BITS _Const_(0x200) -#define PR_64_BITS _Const_(0x300) -#define PR_RESERVED_BITS _Const_(0x100) -/* FULL_PRECISION simulates all exceptions masked */ -#define FULL_PRECISION (PR_64_BITS | RC_RND | 0x3f) - -#endif _CONTROLW_H_ diff --git a/drivers/FPU-emu/div_Xsig.S b/drivers/FPU-emu/div_Xsig.S deleted file mode 100644 index 67d8be964..000000000 --- a/drivers/FPU-emu/div_Xsig.S +++ /dev/null @@ -1,369 +0,0 @@ - .file "div_Xsig.S" -/*---------------------------------------------------------------------------+ - | div_Xsig.S | - | | - | Division subroutine for 96 bit quantities | - | | - | Copyright (C) 1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------+ - | Divide the 96 bit quantity pointed to by a, by that pointed to by b, and | - | put the 96 bit result at the location d. | - | | - | The result may not be accurate to 96 bits. It is intended for use where | - | a result better than 64 bits is required. The result should usually be | - | good to at least 94 bits. | - | The returned result is actually divided by one half. This is done to | - | prevent overflow. | - | | - | .aaaaaaaaaaaaaa / .bbbbbbbbbbbbb -> .dddddddddddd | - | | - | void div_Xsig(Xsig *a, Xsig *b, Xsig *dest) | - | | - +---------------------------------------------------------------------------*/ - -#include "exception.h" -#include "fpu_asm.h" - - -#define XsigLL(x) (x) -#define XsigL(x) 4(x) -#define XsigH(x) 8(x) - - -#ifndef NON_REENTRANT_FPU -/* - Local storage on the stack: - Accumulator: FPU_accum_3:FPU_accum_2:FPU_accum_1:FPU_accum_0 - */ -#define FPU_accum_3 -4(%ebp) -#define FPU_accum_2 -8(%ebp) -#define FPU_accum_1 -12(%ebp) -#define FPU_accum_0 -16(%ebp) -#define FPU_result_3 -20(%ebp) -#define FPU_result_2 -24(%ebp) -#define FPU_result_1 -28(%ebp) - -#else -.data -/* - Local storage in a static area: - Accumulator: FPU_accum_3:FPU_accum_2:FPU_accum_1:FPU_accum_0 - */ - .align 2,0 -FPU_accum_3: - .long 0 -FPU_accum_2: - .long 0 -FPU_accum_1: - .long 0 -FPU_accum_0: - .long 0 -FPU_result_3: - .long 0 -FPU_result_2: - .long 0 -FPU_result_1: - .long 0 -#endif NON_REENTRANT_FPU - - -.text - .align 2,144 - -.globl _div_Xsig - -_div_Xsig: - pushl %ebp - movl %esp,%ebp -#ifndef NON_REENTRANT_FPU - subl $28,%esp -#endif NON_REENTRANT_FPU - - pushl %esi - pushl %edi - pushl %ebx - - movl PARAM1,%esi /* pointer to num */ - movl PARAM2,%ebx /* pointer to denom */ - -#ifdef PARANOID - testl $0x80000000, XsigH(%ebx) /* Divisor */ - je L_bugged -#endif PARANOID - - -/*---------------------------------------------------------------------------+ - | Divide: Return arg1/arg2 to arg3. | - | | - | The maximum returned value is (ignoring exponents) | - | .ffffffff ffffffff | - | ------------------ = 1.ffffffff fffffffe | - | .80000000 00000000 | - | and the minimum is | - | .80000000 00000000 | - | ------------------ = .80000000 00000001 (rounded) | - | .ffffffff ffffffff | - | | - +---------------------------------------------------------------------------*/ - - /* Save extended dividend in local register */ - - /* Divide by 2 to prevent overflow */ - clc - movl XsigH(%esi),%eax - rcrl %eax - movl %eax,FPU_accum_3 - movl XsigL(%esi),%eax - rcrl %eax - movl %eax,FPU_accum_2 - movl XsigLL(%esi),%eax - rcrl %eax - movl %eax,FPU_accum_1 - movl $0,%eax - rcrl %eax - movl %eax,FPU_accum_0 - - movl FPU_accum_2,%eax /* Get the current num */ - movl FPU_accum_3,%edx - -/*----------------------------------------------------------------------*/ -/* Initialization done. - Do the first 32 bits. */ - - /* We will divide by a number which is too large */ - movl XsigH(%ebx),%ecx - addl $1,%ecx - jnc LFirst_div_not_1 - - /* here we need to divide by 100000000h, - i.e., no division at all.. */ - mov %edx,%eax - jmp LFirst_div_done - -LFirst_div_not_1: - divl %ecx /* Divide the numerator by the augmented - denom ms dw */ - -LFirst_div_done: - movl %eax,FPU_result_3 /* Put the result in the answer */ - - mull XsigH(%ebx) /* mul by the ms dw of the denom */ - - subl %eax,FPU_accum_2 /* Subtract from the num local reg */ - sbbl %edx,FPU_accum_3 - - movl FPU_result_3,%eax /* Get the result back */ - mull XsigL(%ebx) /* now mul the ls dw of the denom */ - - subl %eax,FPU_accum_1 /* Subtract from the num local reg */ - sbbl %edx,FPU_accum_2 - sbbl $0,FPU_accum_3 - je LDo_2nd_32_bits /* Must check for non-zero result here */ - -#ifdef PARANOID - jb L_bugged_1 -#endif PARANOID - - /* need to subtract another once of the denom */ - incl FPU_result_3 /* Correct the answer */ - - movl XsigL(%ebx),%eax - movl XsigH(%ebx),%edx - subl %eax,FPU_accum_1 /* Subtract from the num local reg */ - sbbl %edx,FPU_accum_2 - -#ifdef PARANOID - sbbl $0,FPU_accum_3 - jne L_bugged_1 /* Must check for non-zero result here */ -#endif PARANOID - -/*----------------------------------------------------------------------*/ -/* Half of the main problem is done, there is just a reduced numerator - to handle now. - Work with the second 32 bits, FPU_accum_0 not used from now on */ -LDo_2nd_32_bits: - movl FPU_accum_2,%edx /* get the reduced num */ - movl FPU_accum_1,%eax - - /* need to check for possible subsequent overflow */ - cmpl XsigH(%ebx),%edx - jb LDo_2nd_div - ja LPrevent_2nd_overflow - - cmpl XsigL(%ebx),%eax - jb LDo_2nd_div - -LPrevent_2nd_overflow: -/* The numerator is greater or equal, would cause overflow */ - /* prevent overflow */ - subl XsigL(%ebx),%eax - sbbl XsigH(%ebx),%edx - movl %edx,FPU_accum_2 - movl %eax,FPU_accum_1 - - incl FPU_result_3 /* Reflect the subtraction in the answer */ - -#ifdef PARANOID - je L_bugged_2 /* Can't bump the result to 1.0 */ -#endif PARANOID - -LDo_2nd_div: - cmpl $0,%ecx /* augmented denom msw */ - jnz LSecond_div_not_1 - - /* %ecx == 0, we are dividing by 1.0 */ - mov %edx,%eax - jmp LSecond_div_done - -LSecond_div_not_1: - divl %ecx /* Divide the numerator by the denom ms dw */ - -LSecond_div_done: - movl %eax,FPU_result_2 /* Put the result in the answer */ - - mull XsigH(%ebx) /* mul by the ms dw of the denom */ - - subl %eax,FPU_accum_1 /* Subtract from the num local reg */ - sbbl %edx,FPU_accum_2 - -#ifdef PARANOID - jc L_bugged_2 -#endif PARANOID - - movl FPU_result_2,%eax /* Get the result back */ - mull XsigL(%ebx) /* now mul the ls dw of the denom */ - - subl %eax,FPU_accum_0 /* Subtract from the num local reg */ - sbbl %edx,FPU_accum_1 /* Subtract from the num local reg */ - sbbl $0,FPU_accum_2 - -#ifdef PARANOID - jc L_bugged_2 -#endif PARANOID - - jz LDo_3rd_32_bits - -#ifdef PARANOID - cmpl $1,FPU_accum_2 - jne L_bugged_2 -#endif PARANOID - - /* need to subtract another once of the denom */ - movl XsigL(%ebx),%eax - movl XsigH(%ebx),%edx - subl %eax,FPU_accum_0 /* Subtract from the num local reg */ - sbbl %edx,FPU_accum_1 - sbbl $0,FPU_accum_2 - -#ifdef PARANOID - jc L_bugged_2 - jne L_bugged_2 -#endif PARANOID - - addl $1,FPU_result_2 /* Correct the answer */ - adcl $0,FPU_result_3 - -#ifdef PARANOID - jc L_bugged_2 /* Must check for non-zero result here */ -#endif PARANOID - -/*----------------------------------------------------------------------*/ -/* The division is essentially finished here, we just need to perform - tidying operations. - Deal with the 3rd 32 bits */ -LDo_3rd_32_bits: - /* We use an approximation for the third 32 bits. - To take account of the 3rd 32 bits of the divisor - (call them del), we subtract del * (a/b) */ - - movl FPU_result_3,%eax /* a/b */ - mull XsigLL(%ebx) /* del */ - - subl %edx,FPU_accum_1 - - /* A borrow indicates that the result is negative */ - jnb LTest_over - - movl XsigH(%ebx),%edx - addl %edx,FPU_accum_1 - - subl $1,FPU_result_2 /* Adjust the answer */ - sbbl $0,FPU_result_3 - - /* The above addition might not have been enough, check again. */ - movl FPU_accum_1,%edx /* get the reduced num */ - cmpl XsigH(%ebx),%edx /* denom */ - jb LDo_3rd_div - - movl XsigH(%ebx),%edx - addl %edx,FPU_accum_1 - - subl $1,FPU_result_2 /* Adjust the answer */ - sbbl $0,FPU_result_3 - jmp LDo_3rd_div - -LTest_over: - movl FPU_accum_1,%edx /* get the reduced num */ - - /* need to check for possible subsequent overflow */ - cmpl XsigH(%ebx),%edx /* denom */ - jb LDo_3rd_div - - /* prevent overflow */ - subl XsigH(%ebx),%edx - movl %edx,FPU_accum_1 - - addl $1,FPU_result_2 /* Reflect the subtraction in the answer */ - adcl $0,FPU_result_3 - -LDo_3rd_div: - movl FPU_accum_0,%eax - movl FPU_accum_1,%edx - divl XsigH(%ebx) - - movl %eax,FPU_result_1 /* Rough estimate of third word */ - - movl PARAM3,%esi /* pointer to answer */ - - movl FPU_result_1,%eax - movl %eax,XsigLL(%esi) - movl FPU_result_2,%eax - movl %eax,XsigL(%esi) - movl FPU_result_3,%eax - movl %eax,XsigH(%esi) - -L_exit: - popl %ebx - popl %edi - popl %esi - - leave - ret - - -#ifdef PARANOID -/* The logic is wrong if we got here */ -L_bugged: - pushl EX_INTERNAL|0x240 - call EXCEPTION - pop %ebx - jmp L_exit - -L_bugged_1: - pushl EX_INTERNAL|0x241 - call EXCEPTION - pop %ebx - jmp L_exit - -L_bugged_2: - pushl EX_INTERNAL|0x242 - call EXCEPTION - pop %ebx - jmp L_exit -#endif PARANOID diff --git a/drivers/FPU-emu/div_small.S b/drivers/FPU-emu/div_small.S deleted file mode 100644 index 0225a96d4..000000000 --- a/drivers/FPU-emu/div_small.S +++ /dev/null @@ -1,50 +0,0 @@ - .file "div_small.S" -/*---------------------------------------------------------------------------+ - | div_small.S | - | | - | Divide a 64 bit integer by a 32 bit integer & return remainder. | - | | - | Copyright (C) 1992 W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------+ - | unsigned long div_small(unsigned long long *x, unsigned long y) | - +---------------------------------------------------------------------------*/ - -#include "fpu_asm.h" - -.text - .align 2,144 - -.globl _div_small - -_div_small: - pushl %ebp - movl %esp,%ebp - - pushl %esi - - movl PARAM1,%esi /* pointer to num */ - movl PARAM2,%ecx /* The denominator */ - - movl 4(%esi),%eax /* Get the current num msw */ - xorl %edx,%edx - divl %ecx - - movl %eax,4(%esi) - - movl (%esi),%eax /* Get the num lsw */ - divl %ecx - - movl %eax,(%esi) - - movl %edx,%eax /* Return the remainder in eax */ - - popl %esi - - leave - ret - diff --git a/drivers/FPU-emu/errors.c b/drivers/FPU-emu/errors.c deleted file mode 100644 index e34eec942..000000000 --- a/drivers/FPU-emu/errors.c +++ /dev/null @@ -1,671 +0,0 @@ -/*---------------------------------------------------------------------------+ - | errors.c | - | | - | The error handling functions for wm-FPU-emu | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------+ - | Note: | - | The file contains code which accesses user memory. | - | Emulator static data may change when user memory is accessed, due to | - | other processes using the emulator while swapping is in progress. | - +---------------------------------------------------------------------------*/ - -#include - -#include - -#include "fpu_system.h" -#include "exception.h" -#include "fpu_emu.h" -#include "status_w.h" -#include "control_w.h" -#include "reg_constant.h" -#include "version.h" - -/* */ -#undef PRINT_MESSAGES -/* */ - - -void Un_impl(void) -{ - unsigned char byte1, FPU_modrm; - unsigned long address = FPU_ORIG_EIP; - - RE_ENTRANT_CHECK_OFF; - /* No need to verify_area(), we have previously fetched these bytes. */ - printk("Unimplemented FPU Opcode at eip=%p : ", (void *) address); - if ( FPU_CS == USER_CS ) - { - while ( 1 ) - { - byte1 = get_fs_byte((unsigned char *) address); - if ( (byte1 & 0xf8) == 0xd8 ) break; - printk("[%02x]", byte1); - address++; - } - printk("%02x ", byte1); - FPU_modrm = get_fs_byte(1 + (unsigned char *) address); - - if (FPU_modrm >= 0300) - printk("%02x (%02x+%d)\n", FPU_modrm, FPU_modrm & 0xf8, FPU_modrm & 7); - else - printk("/%d\n", (FPU_modrm >> 3) & 7); - } - else - { - printk("cs selector = %04x\n", FPU_CS); - } - - RE_ENTRANT_CHECK_ON; - - EXCEPTION(EX_Invalid); - -} - - -/* - Called for opcodes which are illegal and which are known to result in a - SIGILL with a real 80486. - */ -void FPU_illegal(void) -{ - math_abort(FPU_info,SIGILL); -} - - - -void emu_printall() -{ - int i; - static char *tag_desc[] = { "Valid", "Zero", "ERROR", "ERROR", - "DeNorm", "Inf", "NaN", "Empty" }; - unsigned char byte1, FPU_modrm; - unsigned long address = FPU_ORIG_EIP; - - RE_ENTRANT_CHECK_OFF; - /* No need to verify_area(), we have previously fetched these bytes. */ - printk("At %p:", (void *) address); - if ( FPU_CS == USER_CS ) - { -#define MAX_PRINTED_BYTES 20 - for ( i = 0; i < MAX_PRINTED_BYTES; i++ ) - { - byte1 = get_fs_byte((unsigned char *) address); - if ( (byte1 & 0xf8) == 0xd8 ) - { - printk(" %02x", byte1); - break; - } - printk(" [%02x]", byte1); - address++; - } - if ( i == MAX_PRINTED_BYTES ) - printk(" [more..]\n"); - else - { - FPU_modrm = get_fs_byte(1 + (unsigned char *) address); - - if (FPU_modrm >= 0300) - printk(" %02x (%02x+%d)\n", FPU_modrm, FPU_modrm & 0xf8, FPU_modrm & 7); - else - printk(" /%d, mod=%d rm=%d\n", - (FPU_modrm >> 3) & 7, (FPU_modrm >> 6) & 3, FPU_modrm & 7); - } - } - else - { - printk("%04x\n", FPU_CS); - } - - partial_status = status_word(); - -#ifdef DEBUGGING -if ( partial_status & SW_Backward ) printk("SW: backward compatibility\n"); -if ( partial_status & SW_C3 ) printk("SW: condition bit 3\n"); -if ( partial_status & SW_C2 ) printk("SW: condition bit 2\n"); -if ( partial_status & SW_C1 ) printk("SW: condition bit 1\n"); -if ( partial_status & SW_C0 ) printk("SW: condition bit 0\n"); -if ( partial_status & SW_Summary ) printk("SW: exception summary\n"); -if ( partial_status & SW_Stack_Fault ) printk("SW: stack fault\n"); -if ( partial_status & SW_Precision ) printk("SW: loss of precision\n"); -if ( partial_status & SW_Underflow ) printk("SW: underflow\n"); -if ( partial_status & SW_Overflow ) printk("SW: overflow\n"); -if ( partial_status & SW_Zero_Div ) printk("SW: divide by zero\n"); -if ( partial_status & SW_Denorm_Op ) printk("SW: denormalized operand\n"); -if ( partial_status & SW_Invalid ) printk("SW: invalid operation\n"); -#endif DEBUGGING - - printk(" SW: b=%d st=%ld es=%d sf=%d cc=%d%d%d%d ef=%d%d%d%d%d%d\n", - partial_status & 0x8000 ? 1 : 0, /* busy */ - (partial_status & 0x3800) >> 11, /* stack top pointer */ - partial_status & 0x80 ? 1 : 0, /* Error summary status */ - partial_status & 0x40 ? 1 : 0, /* Stack flag */ - partial_status & SW_C3?1:0, partial_status & SW_C2?1:0, /* cc */ - partial_status & SW_C1?1:0, partial_status & SW_C0?1:0, /* cc */ - partial_status & SW_Precision?1:0, partial_status & SW_Underflow?1:0, - partial_status & SW_Overflow?1:0, partial_status & SW_Zero_Div?1:0, - partial_status & SW_Denorm_Op?1:0, partial_status & SW_Invalid?1:0); - -printk(" CW: ic=%d rc=%ld%ld pc=%ld%ld iem=%d ef=%d%d%d%d%d%d\n", - control_word & 0x1000 ? 1 : 0, - (control_word & 0x800) >> 11, (control_word & 0x400) >> 10, - (control_word & 0x200) >> 9, (control_word & 0x100) >> 8, - control_word & 0x80 ? 1 : 0, - control_word & SW_Precision?1:0, control_word & SW_Underflow?1:0, - control_word & SW_Overflow?1:0, control_word & SW_Zero_Div?1:0, - control_word & SW_Denorm_Op?1:0, control_word & SW_Invalid?1:0); - - for ( i = 0; i < 8; i++ ) - { - FPU_REG *r = &st(i); - switch (r->tag) - { - case TW_Empty: - continue; - break; - case TW_Zero: -#if 0 - printk("st(%d) %c .0000 0000 0000 0000 ", - i, r->sign ? '-' : '+'); - break; -#endif - case TW_Valid: - case TW_NaN: -/* case TW_Denormal: */ - case TW_Infinity: - printk("st(%d) %c .%04lx %04lx %04lx %04lx e%+-6ld ", i, - r->sign ? '-' : '+', - (long)(r->sigh >> 16), - (long)(r->sigh & 0xFFFF), - (long)(r->sigl >> 16), - (long)(r->sigl & 0xFFFF), - r->exp - EXP_BIAS + 1); - break; - default: - printk("Whoops! Error in errors.c "); - break; - } - printk("%s\n", tag_desc[(int) (unsigned) r->tag]); - } - -#ifdef OBSOLETE - printk("[data] %c .%04lx %04lx %04lx %04lx e%+-6ld ", - FPU_loaded_data.sign ? '-' : '+', - (long)(FPU_loaded_data.sigh >> 16), - (long)(FPU_loaded_data.sigh & 0xFFFF), - (long)(FPU_loaded_data.sigl >> 16), - (long)(FPU_loaded_data.sigl & 0xFFFF), - FPU_loaded_data.exp - EXP_BIAS + 1); - printk("%s\n", tag_desc[(int) (unsigned) FPU_loaded_data.tag]); -#endif OBSOLETE - RE_ENTRANT_CHECK_ON; - -} - -static struct { - int type; - char *name; -} exception_names[] = { - { EX_StackOver, "stack overflow" }, - { EX_StackUnder, "stack underflow" }, - { EX_Precision, "loss of precision" }, - { EX_Underflow, "underflow" }, - { EX_Overflow, "overflow" }, - { EX_ZeroDiv, "divide by zero" }, - { EX_Denormal, "denormalized operand" }, - { EX_Invalid, "invalid operation" }, - { EX_INTERNAL, "INTERNAL BUG in "FPU_VERSION }, - { 0, NULL } -}; - -/* - EX_INTERNAL is always given with a code which indicates where the - error was detected. - - Internal error types: - 0x14 in fpu_etc.c - 0x1nn in a *.c file: - 0x101 in reg_add_sub.c - 0x102 in reg_mul.c - 0x104 in poly_atan.c - 0x105 in reg_mul.c - 0x107 in fpu_trig.c - 0x108 in reg_compare.c - 0x109 in reg_compare.c - 0x110 in reg_add_sub.c - 0x111 in fpe_entry.c - 0x112 in fpu_trig.c - 0x113 in errors.c - 0x115 in fpu_trig.c - 0x116 in fpu_trig.c - 0x117 in fpu_trig.c - 0x118 in fpu_trig.c - 0x119 in fpu_trig.c - 0x120 in poly_atan.c - 0x121 in reg_compare.c - 0x122 in reg_compare.c - 0x123 in reg_compare.c - 0x125 in fpu_trig.c - 0x126 in fpu_entry.c - 0x127 in poly_2xm1.c - 0x128 in fpu_entry.c - 0x129 in fpu_entry.c - 0x130 in get_address.c - 0x131 in get_address.c - 0x132 in get_address.c - 0x133 in get_address.c - 0x140 in load_store.c - 0x141 in load_store.c - 0x150 in poly_sin.c - 0x151 in poly_sin.c - 0x160 in reg_ld_str.c - 0x161 in reg_ld_str.c - 0x162 in reg_ld_str.c - 0x163 in reg_ld_str.c - 0x2nn in an *.S file: - 0x201 in reg_u_add.S - 0x202 in reg_u_div.S - 0x203 in reg_u_div.S - 0x204 in reg_u_div.S - 0x205 in reg_u_mul.S - 0x206 in reg_u_sub.S - 0x207 in wm_sqrt.S - 0x208 in reg_div.S - 0x209 in reg_u_sub.S - 0x210 in reg_u_sub.S - 0x211 in reg_u_sub.S - 0x212 in reg_u_sub.S - 0x213 in wm_sqrt.S - 0x214 in wm_sqrt.S - 0x215 in wm_sqrt.S - 0x220 in reg_norm.S - 0x221 in reg_norm.S - 0x230 in reg_round.S - 0x231 in reg_round.S - 0x232 in reg_round.S - 0x233 in reg_round.S - 0x234 in reg_round.S - 0x235 in reg_round.S - 0x236 in reg_round.S - 0x240 in div_Xsig.S - 0x241 in div_Xsig.S - 0x242 in div_Xsig.S - */ - -void exception(int n) -{ - int i, int_type; - - int_type = 0; /* Needed only to stop compiler warnings */ - if ( n & EX_INTERNAL ) - { - int_type = n - EX_INTERNAL; - n = EX_INTERNAL; - /* Set lots of exception bits! */ - partial_status |= (SW_Exc_Mask | SW_Summary | SW_Backward); - } - else - { - /* Extract only the bits which we use to set the status word */ - n &= (SW_Exc_Mask); - /* Set the corresponding exception bit */ - partial_status |= n; - /* Set summary bits iff exception isn't masked */ - if ( partial_status & ~control_word & CW_Exceptions ) - partial_status |= (SW_Summary | SW_Backward); - if ( n & (SW_Stack_Fault | EX_Precision) ) - { - if ( !(n & SW_C1) ) - /* This bit distinguishes over- from underflow for a stack fault, - and roundup from round-down for precision loss. */ - partial_status &= ~SW_C1; - } - } - - RE_ENTRANT_CHECK_OFF; - if ( (~control_word & n & CW_Exceptions) || (n == EX_INTERNAL) ) - { -#ifdef PRINT_MESSAGES - /* My message from the sponsor */ - printk(FPU_VERSION" "__DATE__" (C) W. Metzenthen.\n"); -#endif PRINT_MESSAGES - - /* Get a name string for error reporting */ - for (i=0; exception_names[i].type; i++) - if ( (exception_names[i].type & n) == exception_names[i].type ) - break; - - if (exception_names[i].type) - { -#ifdef PRINT_MESSAGES - printk("FP Exception: %s!\n", exception_names[i].name); -#endif PRINT_MESSAGES - } - else - printk("FPU emulator: Unknown Exception: 0x%04x!\n", n); - - if ( n == EX_INTERNAL ) - { - printk("FPU emulator: Internal error type 0x%04x\n", int_type); - emu_printall(); - } -#ifdef PRINT_MESSAGES - else - emu_printall(); -#endif PRINT_MESSAGES - - /* - * The 80486 generates an interrupt on the next non-control FPU - * instruction. So we need some means of flagging it. - * We use the ES (Error Summary) bit for this, assuming that - * this is the way a real FPU does it (until I can check it out), - * if not, then some method such as the following kludge might - * be needed. - */ -/* regs[0].tag |= TW_FPU_Interrupt; */ - } - RE_ENTRANT_CHECK_ON; - -#ifdef __DEBUG__ - math_abort(FPU_info,SIGFPE); -#endif __DEBUG__ - -} - - -/* Real operation attempted on two operands, one a NaN. */ -/* Returns nz if the exception is unmasked */ -asmlinkage int real_2op_NaN(FPU_REG const *a, FPU_REG const *b, FPU_REG *dest) -{ - FPU_REG const *x; - int signalling; - - /* The default result for the case of two "equal" NaNs (signs may - differ) is chosen to reproduce 80486 behaviour */ - x = a; - if (a->tag == TW_NaN) - { - if (b->tag == TW_NaN) - { - signalling = !(a->sigh & b->sigh & 0x40000000); - /* find the "larger" */ - if ( significand(a) < significand(b) ) - x = b; - } - else - { - /* return the quiet version of the NaN in a */ - signalling = !(a->sigh & 0x40000000); - } - } - else -#ifdef PARANOID - if (b->tag == TW_NaN) -#endif PARANOID - { - signalling = !(b->sigh & 0x40000000); - x = b; - } -#ifdef PARANOID - else - { - signalling = 0; - EXCEPTION(EX_INTERNAL|0x113); - x = &CONST_QNaN; - } -#endif PARANOID - - if ( !signalling ) - { - if ( !(x->sigh & 0x80000000) ) /* pseudo-NaN ? */ - x = &CONST_QNaN; - reg_move(x, dest); - return 0; - } - - if ( control_word & CW_Invalid ) - { - /* The masked response */ - if ( !(x->sigh & 0x80000000) ) /* pseudo-NaN ? */ - x = &CONST_QNaN; - reg_move(x, dest); - /* ensure a Quiet NaN */ - dest->sigh |= 0x40000000; - } - - EXCEPTION(EX_Invalid); - - return !(control_word & CW_Invalid); -} - - -/* Invalid arith operation on Valid registers */ -/* Returns nz if the exception is unmasked */ -asmlinkage int arith_invalid(FPU_REG *dest) -{ - - EXCEPTION(EX_Invalid); - - if ( control_word & CW_Invalid ) - { - /* The masked response */ - reg_move(&CONST_QNaN, dest); - } - - return !(control_word & CW_Invalid); - -} - - -/* Divide a finite number by zero */ -asmlinkage int divide_by_zero(int sign, FPU_REG *dest) -{ - - if ( control_word & CW_ZeroDiv ) - { - /* The masked response */ - reg_move(&CONST_INF, dest); - dest->sign = (unsigned char)sign; - } - - EXCEPTION(EX_ZeroDiv); - - return !(control_word & CW_ZeroDiv); - -} - - -/* This may be called often, so keep it lean */ -int set_precision_flag(int flags) -{ - if ( control_word & CW_Precision ) - { - partial_status &= ~(SW_C1 & flags); - partial_status |= flags; /* The masked response */ - return 0; - } - else - { - exception(flags); - return 1; - } -} - - -/* This may be called often, so keep it lean */ -asmlinkage void set_precision_flag_up(void) -{ - if ( control_word & CW_Precision ) - partial_status |= (SW_Precision | SW_C1); /* The masked response */ - else - exception(EX_Precision | SW_C1); - -} - - -/* This may be called often, so keep it lean */ -asmlinkage void set_precision_flag_down(void) -{ - if ( control_word & CW_Precision ) - { /* The masked response */ - partial_status &= ~SW_C1; - partial_status |= SW_Precision; - } - else - exception(EX_Precision); -} - - -asmlinkage int denormal_operand(void) -{ - if ( control_word & CW_Denormal ) - { /* The masked response */ - partial_status |= SW_Denorm_Op; - return 0; - } - else - { - exception(EX_Denormal); - return 1; - } -} - - -asmlinkage int arith_overflow(FPU_REG *dest) -{ - - if ( control_word & CW_Overflow ) - { - char sign; - /* The masked response */ -/* ###### The response here depends upon the rounding mode */ - sign = dest->sign; - reg_move(&CONST_INF, dest); - dest->sign = sign; - } - else - { - /* Subtract the magic number from the exponent */ - dest->exp -= (3 * (1 << 13)); - } - - EXCEPTION(EX_Overflow); - if ( control_word & CW_Overflow ) - { - /* The overflow exception is masked. */ - /* By definition, precision is lost. - The roundup bit (C1) is also set because we have - "rounded" upwards to Infinity. */ - EXCEPTION(EX_Precision | SW_C1); - return !(control_word & CW_Precision); - } - - return !(control_word & CW_Overflow); - -} - - -asmlinkage int arith_underflow(FPU_REG *dest) -{ - - if ( control_word & CW_Underflow ) - { - /* The masked response */ - if ( dest->exp <= EXP_UNDER - 63 ) - { - reg_move(&CONST_Z, dest); - partial_status &= ~SW_C1; /* Round down. */ - } - } - else - { - /* Add the magic number to the exponent. */ - dest->exp += (3 * (1 << 13)); - } - - EXCEPTION(EX_Underflow); - if ( control_word & CW_Underflow ) - { - /* The underflow exception is masked. */ - EXCEPTION(EX_Precision); - return !(control_word & CW_Precision); - } - - return !(control_word & CW_Underflow); - -} - - -void stack_overflow(void) -{ - - if ( control_word & CW_Invalid ) - { - /* The masked response */ - top--; - reg_move(&CONST_QNaN, &st(0)); - } - - EXCEPTION(EX_StackOver); - - return; - -} - - -void stack_underflow(void) -{ - - if ( control_word & CW_Invalid ) - { - /* The masked response */ - reg_move(&CONST_QNaN, &st(0)); - } - - EXCEPTION(EX_StackUnder); - - return; - -} - - -void stack_underflow_i(int i) -{ - - if ( control_word & CW_Invalid ) - { - /* The masked response */ - reg_move(&CONST_QNaN, &(st(i))); - } - - EXCEPTION(EX_StackUnder); - - return; - -} - - -void stack_underflow_pop(int i) -{ - - if ( control_word & CW_Invalid ) - { - /* The masked response */ - reg_move(&CONST_QNaN, &(st(i))); - pop(); - } - - EXCEPTION(EX_StackUnder); - - return; - -} - diff --git a/drivers/FPU-emu/exception.h b/drivers/FPU-emu/exception.h deleted file mode 100644 index 2e629a30c..000000000 --- a/drivers/FPU-emu/exception.h +++ /dev/null @@ -1,53 +0,0 @@ -/*---------------------------------------------------------------------------+ - | exception.h | - | | - | Copyright (C) 1992 W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - +---------------------------------------------------------------------------*/ - -#ifndef _EXCEPTION_H_ -#define _EXCEPTION_H_ - - -#ifdef __ASSEMBLER__ -#define Const_(x) $##x -#else -#define Const_(x) x -#endif - -#ifndef SW_C1 -#include "fpu_emu.h" -#endif SW_C1 - -#define FPU_BUSY Const_(0x8000) /* FPU busy bit (8087 compatibility) */ -#define EX_ErrorSummary Const_(0x0080) /* Error summary status */ -/* Special exceptions: */ -#define EX_INTERNAL Const_(0x8000) /* Internal error in wm-FPU-emu */ -#define EX_StackOver Const_(0x0041|SW_C1) /* stack overflow */ -#define EX_StackUnder Const_(0x0041) /* stack underflow */ -/* Exception flags: */ -#define EX_Precision Const_(0x0020) /* loss of precision */ -#define EX_Underflow Const_(0x0010) /* underflow */ -#define EX_Overflow Const_(0x0008) /* overflow */ -#define EX_ZeroDiv Const_(0x0004) /* divide by zero */ -#define EX_Denormal Const_(0x0002) /* denormalized operand */ -#define EX_Invalid Const_(0x0001) /* invalid operation */ - - -#define PRECISION_LOST_UP Const_((EX_Precision | SW_C1)) -#define PRECISION_LOST_DOWN Const_(EX_Precision) - - -#ifndef __ASSEMBLER__ - -#ifdef DEBUG -#define EXCEPTION(x) { printk("exception in %s at line %d\n", \ - __FILE__, __LINE__); exception(x); } -#else -#define EXCEPTION(x) exception(x) -#endif - -#endif __ASSEMBLER__ - -#endif _EXCEPTION_H_ diff --git a/drivers/FPU-emu/fpu_arith.c b/drivers/FPU-emu/fpu_arith.c deleted file mode 100644 index 96e6bd89b..000000000 --- a/drivers/FPU-emu/fpu_arith.c +++ /dev/null @@ -1,179 +0,0 @@ -/*---------------------------------------------------------------------------+ - | fpu_arith.c | - | | - | Code to implement the FPU register/register arithmetic instructions | - | | - | Copyright (C) 1992,1993 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -#include "fpu_system.h" -#include "fpu_emu.h" -#include "control_w.h" -#include "status_w.h" - - -void fadd__() -{ - /* fadd st,st(i) */ - clear_C1(); - reg_add(&st(0), &st(FPU_rm), &st(0), control_word); -} - - -void fmul__() -{ - /* fmul st,st(i) */ - clear_C1(); - reg_mul(&st(0), &st(FPU_rm), &st(0), control_word); -} - - - -void fsub__() -{ - /* fsub st,st(i) */ - clear_C1(); - reg_sub(&st(0), &st(FPU_rm), &st(0), control_word); -} - - -void fsubr_() -{ - /* fsubr st,st(i) */ - clear_C1(); - reg_sub(&st(FPU_rm), &st(0), &st(0), control_word); -} - - -void fdiv__() -{ - /* fdiv st,st(i) */ - clear_C1(); - reg_div(&st(0), &st(FPU_rm), &st(0), control_word); -} - - -void fdivr_() -{ - /* fdivr st,st(i) */ - clear_C1(); - reg_div(&st(FPU_rm), &st(0), &st(0), control_word); -} - - - -void fadd_i() -{ - /* fadd st(i),st */ - clear_C1(); - reg_add(&st(0), &st(FPU_rm), &st(FPU_rm), control_word); -} - - -void fmul_i() -{ - /* fmul st(i),st */ - clear_C1(); - reg_mul(&st(0), &st(FPU_rm), &st(FPU_rm), control_word); -} - - -void fsubri() -{ - /* fsubr st(i),st */ - /* This is the sense of the 80486 manual - reg_sub(&st(FPU_rm), &st(0), &st(FPU_rm), control_word); */ - clear_C1(); - reg_sub(&st(0), &st(FPU_rm), &st(FPU_rm), control_word); -} - - -void fsub_i() -{ - /* fsub st(i),st */ - /* This is the sense of the 80486 manual - reg_sub(&st(0), &st(FPU_rm), &st(FPU_rm), control_word); */ - clear_C1(); - reg_sub(&st(FPU_rm), &st(0), &st(FPU_rm), control_word); -} - - -void fdivri() -{ - /* fdivr st(i),st */ - clear_C1(); - reg_div(&st(0), &st(FPU_rm), &st(FPU_rm), control_word); -} - - -void fdiv_i() -{ - /* fdiv st(i),st */ - clear_C1(); - reg_div(&st(FPU_rm), &st(0), &st(FPU_rm), control_word); -} - - - -void faddp_() -{ - /* faddp st(i),st */ - clear_C1(); - if ( !reg_add(&st(0), &st(FPU_rm), &st(FPU_rm), control_word) ) - pop(); -} - - -void fmulp_() -{ - /* fmulp st(i),st */ - clear_C1(); - if ( !reg_mul(&st(0), &st(FPU_rm), &st(FPU_rm), control_word) ) - pop(); -} - - - -void fsubrp() -{ - /* fsubrp st(i),st */ - /* This is the sense of the 80486 manual - reg_sub(&st(FPU_rm), &st(0), &st(FPU_rm), control_word); */ - clear_C1(); - if ( !reg_sub(&st(0), &st(FPU_rm), &st(FPU_rm), control_word) ) - pop(); -} - - -void fsubp_() -{ - /* fsubp st(i),st */ - /* This is the sense of the 80486 manual - reg_sub(&st(0), &st(FPU_rm), &st(FPU_rm), control_word); */ - clear_C1(); - if ( !reg_sub(&st(FPU_rm), &st(0), &st(FPU_rm), control_word) ) - pop(); -} - - -void fdivrp() -{ - /* fdivrp st(i),st */ - clear_C1(); - if ( !reg_div(&st(0), &st(FPU_rm), &st(FPU_rm), control_word) ) - pop(); -} - - -void fdivp_() -{ - /* fdivp st(i),st */ - clear_C1(); - if ( !reg_div(&st(FPU_rm), &st(0), &st(FPU_rm), control_word) ) - pop(); -} - diff --git a/drivers/FPU-emu/fpu_asm.h b/drivers/FPU-emu/fpu_asm.h deleted file mode 100644 index 8eb60148d..000000000 --- a/drivers/FPU-emu/fpu_asm.h +++ /dev/null @@ -1,30 +0,0 @@ -/*---------------------------------------------------------------------------+ - | fpu_asm.h | - | | - | Copyright (C) 1992 W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - +---------------------------------------------------------------------------*/ - -#ifndef _FPU_ASM_H_ -#define _FPU_ASM_H_ - -#include "fpu_emu.h" - -#define EXCEPTION _exception - - -#define PARAM1 8(%ebp) -#define PARAM2 12(%ebp) -#define PARAM3 16(%ebp) -#define PARAM4 20(%ebp) - -#define SIGL_OFFSET 8 -#define SIGN(x) (x) -#define TAG(x) 1(x) -#define EXP(x) 4(x) -#define SIG(x) SIGL_OFFSET##(x) -#define SIGL(x) SIGL_OFFSET##(x) -#define SIGH(x) 12(x) - -#endif _FPU_ASM_H_ diff --git a/drivers/FPU-emu/fpu_aux.c b/drivers/FPU-emu/fpu_aux.c deleted file mode 100644 index 0d35fe19b..000000000 --- a/drivers/FPU-emu/fpu_aux.c +++ /dev/null @@ -1,184 +0,0 @@ -/*---------------------------------------------------------------------------+ - | fpu_aux.c | - | | - | Code to implement some of the FPU auxiliary instructions. | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -#include "fpu_system.h" -#include "exception.h" -#include "fpu_emu.h" -#include "status_w.h" -#include "control_w.h" - - -static void fnop(void) -{ -} - -void fclex(void) -{ - partial_status &= ~(SW_Backward|SW_Summary|SW_Stack_Fault|SW_Precision| - SW_Underflow|SW_Overflow|SW_Zero_Div|SW_Denorm_Op| - SW_Invalid); - no_ip_update = 1; -} - -/* Needs to be externally visible */ -void finit() -{ - int r; - control_word = 0x037f; - partial_status = 0; - top = 0; /* We don't keep top in the status word internally. */ - for (r = 0; r < 8; r++) - { - regs[r].tag = TW_Empty; - } - /* The behaviour is different to that detailed in - Section 15.1.6 of the Intel manual */ - operand_address.offset = 0; - operand_address.selector = 0; - instruction_address.offset = 0; - instruction_address.selector = 0; - instruction_address.opcode = 0; - no_ip_update = 1; -} - -/* - * These are nops on the i387.. - */ -#define feni fnop -#define fdisi fnop -#define fsetpm fnop - -static FUNC const finit_table[] = { - feni, fdisi, fclex, finit, - fsetpm, FPU_illegal, FPU_illegal, FPU_illegal -}; - -void finit_() -{ - (finit_table[FPU_rm])(); -} - - -static void fstsw_ax(void) -{ - *(short *) &FPU_EAX = status_word(); - no_ip_update = 1; -} - -static FUNC const fstsw_table[] = { - fstsw_ax, FPU_illegal, FPU_illegal, FPU_illegal, - FPU_illegal, FPU_illegal, FPU_illegal, FPU_illegal -}; - -void fstsw_() -{ - (fstsw_table[FPU_rm])(); -} - - -static FUNC const fp_nop_table[] = { - fnop, FPU_illegal, FPU_illegal, FPU_illegal, - FPU_illegal, FPU_illegal, FPU_illegal, FPU_illegal -}; - -void fp_nop() -{ - (fp_nop_table[FPU_rm])(); -} - - -void fld_i_() -{ - FPU_REG *st_new_ptr; - - if ( STACK_OVERFLOW ) - { stack_overflow(); return; } - - /* fld st(i) */ - if ( NOT_EMPTY(FPU_rm) ) - { reg_move(&st(FPU_rm), st_new_ptr); push(); } - else - { - if ( control_word & CW_Invalid ) - { - /* The masked response */ - stack_underflow(); - } - else - EXCEPTION(EX_StackUnder); - } - -} - - -void fxch_i() -{ - /* fxch st(i) */ - FPU_REG t; - register FPU_REG *sti_ptr = &st(FPU_rm), *st0_ptr = &st(0); - - if ( st0_ptr->tag == TW_Empty ) - { - if ( sti_ptr->tag == TW_Empty ) - { - stack_underflow(); - stack_underflow_i(FPU_rm); - return; - } - if ( control_word & CW_Invalid ) - reg_move(sti_ptr, st0_ptr); /* Masked response */ - stack_underflow_i(FPU_rm); - return; - } - if ( sti_ptr->tag == TW_Empty ) - { - if ( control_word & CW_Invalid ) - reg_move(st0_ptr, sti_ptr); /* Masked response */ - stack_underflow(); - return; - } - clear_C1(); - reg_move(st0_ptr, &t); - reg_move(sti_ptr, st0_ptr); - reg_move(&t, sti_ptr); -} - - -void ffree_() -{ - /* ffree st(i) */ - st(FPU_rm).tag = TW_Empty; -} - - -void ffreep() -{ - /* ffree st(i) + pop - unofficial code */ - st(FPU_rm).tag = TW_Empty; - pop(); -} - - -void fst_i_() -{ - /* fst st(i) */ - reg_move(&st(0), &st(FPU_rm)); -} - - -void fstp_i() -{ - /* fstp st(i) */ - reg_move(&st(0), &st(FPU_rm)); - pop(); -} - diff --git a/drivers/FPU-emu/fpu_emu.h b/drivers/FPU-emu/fpu_emu.h deleted file mode 100644 index 9d2c5dd13..000000000 --- a/drivers/FPU-emu/fpu_emu.h +++ /dev/null @@ -1,171 +0,0 @@ -/*---------------------------------------------------------------------------+ - | fpu_emu.h | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - +---------------------------------------------------------------------------*/ - - -#ifndef _FPU_EMU_H_ -#define _FPU_EMU_H_ - -/* - * Define DENORM_OPERAND to make the emulator detect denormals - * and use the denormal flag of the status word. Note: this only - * affects the flag and corresponding interrupt, the emulator - * will always generate denormals and operate upon them as required. - */ -#define DENORM_OPERAND - -/* - * Define PECULIAR_486 to get a closer approximation to 80486 behaviour, - * rather than behaviour which appears to be cleaner. - * This is a matter of opinion: for all I know, the 80486 may simply - * be complying with the IEEE spec. Maybe one day I'll get to see the - * spec... - */ -#define PECULIAR_486 - -#ifdef __ASSEMBLER__ -#include "fpu_asm.h" -#define Const(x) $##x -#else -#define Const(x) x -#endif - -#define EXP_BIAS Const(0) -#define EXP_OVER Const(0x4000) /* smallest invalid large exponent */ -#define EXP_UNDER Const(-0x3fff) /* largest invalid small exponent */ -#define EXP_Infinity EXP_OVER -#define EXP_NaN EXP_OVER - -#define SIGN_POS Const(0) -#define SIGN_NEG Const(1) - -/* Keep the order TW_Valid, TW_Zero, TW_Denormal */ -#define TW_Valid Const(0) /* valid */ -#define TW_Zero Const(1) /* zero */ -/* The following fold to 2 (Special) in the Tag Word */ -/* #define TW_Denormal Const(4) */ /* De-normal */ -#define TW_Infinity Const(5) /* + or - infinity */ -#define TW_NaN Const(6) /* Not a Number */ - -#define TW_Empty Const(7) /* empty */ - - -#ifndef __ASSEMBLER__ - -#include -#include - -/* -#define RE_ENTRANT_CHECKING - */ - -#ifdef RE_ENTRANT_CHECKING -extern char emulating; -# define RE_ENTRANT_CHECK_OFF emulating = 0 -# define RE_ENTRANT_CHECK_ON emulating = 1 -#else -# define RE_ENTRANT_CHECK_OFF -# define RE_ENTRANT_CHECK_ON -#endif RE_ENTRANT_CHECKING - -#define FWAIT_OPCODE 0x9b -#define OP_SIZE_PREFIX 0x66 -#define ADDR_SIZE_PREFIX 0x67 -#define PREFIX_CS 0x2e -#define PREFIX_DS 0x3e -#define PREFIX_ES 0x26 -#define PREFIX_SS 0x36 -#define PREFIX_FS 0x64 -#define PREFIX_GS 0x65 -#define PREFIX_REPE 0xf3 -#define PREFIX_REPNE 0xf2 -#define PREFIX_LOCK 0xf0 -#define PREFIX_CS_ 1 -#define PREFIX_DS_ 2 -#define PREFIX_ES_ 3 -#define PREFIX_FS_ 4 -#define PREFIX_GS_ 5 -#define PREFIX_SS_ 6 -#define PREFIX_DEFAULT 7 - -struct address { - unsigned int offset; - unsigned int selector:16; - unsigned int opcode:11; - unsigned int empty:5; -}; -typedef void (*FUNC)(void); -typedef struct fpu_reg FPU_REG; -typedef void (*FUNC_ST0)(FPU_REG *st0_ptr); -typedef struct { unsigned char address_size, operand_size, segment; } - overrides; -/* This structure is 32 bits: */ -typedef struct { overrides override; - unsigned char default_mode; } fpu_addr_modes; -/* PROTECTED has a restricted meaning in the emulator; it is used - to signal that the emulator needs to do special things to ensure - that protection is respected in a segmented model. */ -#define PROTECTED 4 -#define SIXTEEN 1 /* We rely upon this being 1 (true) */ -#define VM86 SIXTEEN -#define PM16 (SIXTEEN | PROTECTED) -#define SEG32 PROTECTED -extern unsigned char const data_sizes_16[32]; - -#define st(x) ( regs[((top+x) &7 )] ) - -#define STACK_OVERFLOW (st_new_ptr = &st(-1), st_new_ptr->tag != TW_Empty) -#define NOT_EMPTY(i) (st(i).tag != TW_Empty) -#define NOT_EMPTY_ST0 (st0_tag ^ TW_Empty) - -#define pop() { regs[(top++ & 7 )].tag = TW_Empty; } -#define poppop() { regs[((top + 1) & 7 )].tag \ - = regs[(top & 7 )].tag = TW_Empty; \ - top += 2; } - -/* push() does not affect the tags */ -#define push() { top--; } - - -#define reg_move(x, y) { \ - *(short *)&((y)->sign) = *(short *)&((x)->sign); \ - *(long *)&((y)->exp) = *(long *)&((x)->exp); \ - *(long long *)&((y)->sigl) = *(long long *)&((x)->sigl); } - -#define significand(x) ( ((unsigned long long *)&((x)->sigl))[0] ) - - -/*----- Prototypes for functions written in assembler -----*/ -/* extern void reg_move(FPU_REG *a, FPU_REG *b); */ - -asmlinkage void normalize(FPU_REG *x); -asmlinkage void normalize_nuo(FPU_REG *x); -asmlinkage int reg_div(FPU_REG const *arg1, FPU_REG const *arg2, - FPU_REG *answ, unsigned int control_w); -asmlinkage int reg_u_sub(FPU_REG const *arg1, FPU_REG const *arg2, - FPU_REG *answ, unsigned int control_w); -asmlinkage int reg_u_mul(FPU_REG const *arg1, FPU_REG const *arg2, - FPU_REG *answ, unsigned int control_w); -asmlinkage int reg_u_div(FPU_REG const *arg1, FPU_REG const *arg2, - FPU_REG *answ, unsigned int control_w); -asmlinkage int reg_u_add(FPU_REG const *arg1, FPU_REG const *arg2, - FPU_REG *answ, unsigned int control_w); -asmlinkage int wm_sqrt(FPU_REG *n, unsigned int control_w); -asmlinkage unsigned shrx(void *l, unsigned x); -asmlinkage unsigned shrxs(void *v, unsigned x); -asmlinkage unsigned long div_small(unsigned long long *x, unsigned long y); -asmlinkage void round_reg(FPU_REG *arg, unsigned int extent, - unsigned int control_w); - -#ifndef MAKING_PROTO -#include "fpu_proto.h" -#endif - -#endif __ASSEMBLER__ - -#endif _FPU_EMU_H_ diff --git a/drivers/FPU-emu/fpu_entry.c b/drivers/FPU-emu/fpu_entry.c deleted file mode 100644 index b2777a722..000000000 --- a/drivers/FPU-emu/fpu_entry.c +++ /dev/null @@ -1,690 +0,0 @@ -/*---------------------------------------------------------------------------+ - | fpu_entry.c | - | | - | The entry function for wm-FPU-emu | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | See the files "README" and "COPYING" for further copyright and warranty | - | information. | - | | - +---------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------+ - | Note: | - | The file contains code which accesses user memory. | - | Emulator static data may change when user memory is accessed, due to | - | other processes using the emulator while swapping is in progress. | - +---------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------+ - | math_emulate() is the sole entry point for wm-FPU-emu | - +---------------------------------------------------------------------------*/ - -#include - -#include - -#include "fpu_system.h" -#include "fpu_emu.h" -#include "exception.h" -#include "control_w.h" -#include "status_w.h" - -#define __BAD__ FPU_illegal /* Illegal on an 80486, causes SIGILL */ - -#ifndef NO_UNDOC_CODE /* Un-documented FPU op-codes supported by default. */ - -/* WARNING: These codes are not documented by Intel in their 80486 manual - and may not work on FPU clones or later Intel FPUs. */ - -/* Changes to support the un-doc codes provided by Linus Torvalds. */ - -#define _d9_d8_ fstp_i /* unofficial code (19) */ -#define _dc_d0_ fcom_st /* unofficial code (14) */ -#define _dc_d8_ fcompst /* unofficial code (1c) */ -#define _dd_c8_ fxch_i /* unofficial code (0d) */ -#define _de_d0_ fcompst /* unofficial code (16) */ -#define _df_c0_ ffreep /* unofficial code (07) ffree + pop */ -#define _df_c8_ fxch_i /* unofficial code (0f) */ -#define _df_d0_ fstp_i /* unofficial code (17) */ -#define _df_d8_ fstp_i /* unofficial code (1f) */ - -static FUNC const st_instr_table[64] = { - fadd__, fld_i_, __BAD__, __BAD__, fadd_i, ffree_, faddp_, _df_c0_, - fmul__, fxch_i, __BAD__, __BAD__, fmul_i, _dd_c8_, fmulp_, _df_c8_, - fcom_st, fp_nop, __BAD__, __BAD__, _dc_d0_, fst_i_, _de_d0_, _df_d0_, - fcompst, _d9_d8_, __BAD__, __BAD__, _dc_d8_, fstp_i, fcompp, _df_d8_, - fsub__, fp_etc, __BAD__, finit_, fsubri, fucom_, fsubrp, fstsw_, - fsubr_, fconst, fucompp, __BAD__, fsub_i, fucomp, fsubp_, __BAD__, - fdiv__, trig_a, __BAD__, __BAD__, fdivri, __BAD__, fdivrp, __BAD__, - fdivr_, trig_b, __BAD__, __BAD__, fdiv_i, __BAD__, fdivp_, __BAD__, -}; - -#else /* Support only documented FPU op-codes */ - -static FUNC const st_instr_table[64] = { - fadd__, fld_i_, __BAD__, __BAD__, fadd_i, ffree_, faddp_, __BAD__, - fmul__, fxch_i, __BAD__, __BAD__, fmul_i, __BAD__, fmulp_, __BAD__, - fcom_st, fp_nop, __BAD__, __BAD__, __BAD__, fst_i_, __BAD__, __BAD__, - fcompst, __BAD__, __BAD__, __BAD__, __BAD__, fstp_i, fcompp, __BAD__, - fsub__, fp_etc, __BAD__, finit_, fsubri, fucom_, fsubrp, fstsw_, - fsubr_, fconst, fucompp, __BAD__, fsub_i, fucomp, fsubp_, __BAD__, - fdiv__, trig_a, __BAD__, __BAD__, fdivri, __BAD__, fdivrp, __BAD__, - fdivr_, trig_b, __BAD__, __BAD__, fdiv_i, __BAD__, fdivp_, __BAD__, -}; - -#endif NO_UNDOC_CODE - - -#define _NONE_ 0 /* Take no special action */ -#define _REG0_ 1 /* Need to check for not empty st(0) */ -#define _REGI_ 2 /* Need to check for not empty st(0) and st(rm) */ -#define _REGi_ 0 /* Uses st(rm) */ -#define _PUSH_ 3 /* Need to check for space to push onto stack */ -#define _null_ 4 /* Function illegal or not implemented */ -#define _REGIi 5 /* Uses st(0) and st(rm), result to st(rm) */ -#define _REGIp 6 /* Uses st(0) and st(rm), result to st(rm) then pop */ -#define _REGIc 0 /* Compare st(0) and st(rm) */ -#define _REGIn 0 /* Uses st(0) and st(rm), but handle checks later */ - -#ifndef NO_UNDOC_CODE - -/* Un-documented FPU op-codes supported by default. (see above) */ - -static unsigned char const type_table[64] = { - _REGI_, _NONE_, _null_, _null_, _REGIi, _REGi_, _REGIp, _REGi_, - _REGI_, _REGIn, _null_, _null_, _REGIi, _REGI_, _REGIp, _REGI_, - _REGIc, _NONE_, _null_, _null_, _REGIc, _REG0_, _REGIc, _REG0_, - _REGIc, _REG0_, _null_, _null_, _REGIc, _REG0_, _REGIc, _REG0_, - _REGI_, _NONE_, _null_, _NONE_, _REGIi, _REGIc, _REGIp, _NONE_, - _REGI_, _NONE_, _REGIc, _null_, _REGIi, _REGIc, _REGIp, _null_, - _REGI_, _NONE_, _null_, _null_, _REGIi, _null_, _REGIp, _null_, - _REGI_, _NONE_, _null_, _null_, _REGIi, _null_, _REGIp, _null_ -}; - -#else /* Support only documented FPU op-codes */ - -static unsigned char const type_table[64] = { - _REGI_, _NONE_, _null_, _null_, _REGIi, _REGi_, _REGIp, _null_, - _REGI_, _REGIn, _null_, _null_, _REGIi, _null_, _REGIp, _null_, - _REGIc, _NONE_, _null_, _null_, _null_, _REG0_, _null_, _null_, - _REGIc, _null_, _null_, _null_, _null_, _REG0_, _REGIc, _null_, - _REGI_, _NONE_, _null_, _NONE_, _REGIi, _REGIc, _REGIp, _NONE_, - _REGI_, _NONE_, _REGIc, _null_, _REGIi, _REGIc, _REGIp, _null_, - _REGI_, _NONE_, _null_, _null_, _REGIi, _null_, _REGIp, _null_, - _REGI_, _NONE_, _null_, _null_, _REGIi, _null_, _REGIp, _null_ -}; - -#endif NO_UNDOC_CODE - - -#ifdef RE_ENTRANT_CHECKING -char emulating=0; -#endif RE_ENTRANT_CHECKING - -static int valid_prefix(unsigned char *Byte, unsigned char **fpu_eip, - overrides *override); - -asmlinkage void math_emulate(long arg) -{ - unsigned char FPU_modrm, byte1; - unsigned short code; - fpu_addr_modes addr_modes; - int unmasked; - FPU_REG loaded_data; - void *data_address; - struct address data_sel_off; - struct address entry_sel_off; - unsigned long code_base = 0; - unsigned long code_limit = 0; /* Initialized to stop compiler warnings */ - char st0_tag; - FPU_REG *st0_ptr; - struct desc_struct code_descriptor; - -#ifdef RE_ENTRANT_CHECKING - if ( emulating ) - { - printk("ERROR: wm-FPU-emu is not RE-ENTRANT!\n"); - } - RE_ENTRANT_CHECK_ON; -#endif RE_ENTRANT_CHECKING - - if (!current->used_math) - { - int i; - for ( i = 0; i < 8; i++ ) - { - /* Make sure that the registers are compatible - with the assumptions of the emulator. */ - regs[i].exp = 0; - regs[i].sigh = 0x80000000; - } - finit(); - current->used_math = 1; - } - - SETUP_DATA_AREA(arg); - - FPU_ORIG_EIP = FPU_EIP; - - if ( (FPU_EFLAGS & 0x00020000) != 0 ) - { - /* Virtual 8086 mode */ - addr_modes.default_mode = VM86; - FPU_EIP += code_base = FPU_CS << 4; - code_limit = code_base + 0xffff; /* Assumes code_base <= 0xffff0000 */ - } - else if ( FPU_CS == USER_CS && FPU_DS == USER_DS ) - { - addr_modes.default_mode = 0; - } - else if ( FPU_CS == KERNEL_CS ) - { - printk("math_emulate: %04x:%08lx\n",FPU_CS,FPU_EIP); - panic("Math emulation needed in kernel"); - } - else - { - - if ( (FPU_CS & 4) != 4 ) /* Must be in the LDT */ - { - /* Can only handle segmented addressing via the LDT - for now, and it must be 16 bit */ - printk("FPU emulator: Unsupported addressing mode\n"); - math_abort(FPU_info, SIGILL); - } - - if ( SEG_D_SIZE(code_descriptor = LDT_DESCRIPTOR(FPU_CS)) ) - { - /* The above test may be wrong, the book is not clear */ - /* Segmented 32 bit protected mode */ - addr_modes.default_mode = SEG32; - } - else - { - /* 16 bit protected mode */ - addr_modes.default_mode = PM16; - } - FPU_EIP += code_base = SEG_BASE_ADDR(code_descriptor); - code_limit = code_base - + (SEG_LIMIT(code_descriptor)+1) * SEG_GRANULARITY(code_descriptor) - - 1; - if ( code_limit < code_base ) code_limit = 0xffffffff; - } - - FPU_lookahead = 1; - if (current->flags & PF_PTRACED) - FPU_lookahead = 0; - - if ( !valid_prefix(&byte1, (unsigned char **)&FPU_EIP, - &addr_modes.override) ) - { - RE_ENTRANT_CHECK_OFF; - printk("FPU emulator: Unknown prefix byte 0x%02x, probably due to\n" - "FPU emulator: self-modifying code! (emulation impossible)\n", - byte1); - RE_ENTRANT_CHECK_ON; - EXCEPTION(EX_INTERNAL|0x126); - math_abort(FPU_info,SIGILL); - } - -do_another_FPU_instruction: - - no_ip_update = 0; - - FPU_EIP++; /* We have fetched the prefix and first code bytes. */ - - if ( addr_modes.default_mode ) - { - /* This checks for the minimum instruction bytes. - We also need to check any extra (address mode) code access. */ - if ( FPU_EIP > code_limit ) - math_abort(FPU_info,SIGSEGV); - } - - if ( (byte1 & 0xf8) != 0xd8 ) - { - if ( byte1 == FWAIT_OPCODE ) - { - if (partial_status & SW_Summary) - goto do_the_FPU_interrupt; - else - goto FPU_fwait_done; - } -#ifdef PARANOID - EXCEPTION(EX_INTERNAL|0x128); - math_abort(FPU_info,SIGILL); -#endif PARANOID - } - - RE_ENTRANT_CHECK_OFF; - FPU_code_verify_area(1); - FPU_modrm = get_fs_byte((unsigned char *) FPU_EIP); - RE_ENTRANT_CHECK_ON; - FPU_EIP++; - - if (partial_status & SW_Summary) - { - /* Ignore the error for now if the current instruction is a no-wait - control instruction */ - /* The 80486 manual contradicts itself on this topic, - but a real 80486 uses the following instructions: - fninit, fnstenv, fnsave, fnstsw, fnstenv, fnclex. - */ - code = (FPU_modrm << 8) | byte1; - if ( ! ( (((code & 0xf803) == 0xe003) || /* fnclex, fninit, fnstsw */ - (((code & 0x3003) == 0x3001) && /* fnsave, fnstcw, fnstenv, - fnstsw */ - ((code & 0xc000) != 0xc000))) ) ) - { - /* - * We need to simulate the action of the kernel to FPU - * interrupts here. - * Currently, the "real FPU" part of the kernel (0.99.10) - * clears the exception flags, sets the registers to empty, - * and passes information back to the interrupted process - * via the cs selector and operand selector, so we do the same. - */ - do_the_FPU_interrupt: - instruction_address.selector = status_word(); - operand_address.selector = tag_word(); - partial_status = 0; - top = 0; - { - int r; - for (r = 0; r < 8; r++) - { - regs[r].tag = TW_Empty; - } - } - - FPU_EIP = FPU_ORIG_EIP; /* Point to current FPU instruction. */ - - RE_ENTRANT_CHECK_OFF; - current->tss.trap_no = 16; - current->tss.error_code = 0; - send_sig(SIGFPE, current, 1); - return; - } - } - - entry_sel_off.offset = FPU_ORIG_EIP; - entry_sel_off.selector = FPU_CS; - entry_sel_off.opcode = (byte1 << 8) | FPU_modrm; - - FPU_rm = FPU_modrm & 7; - - if ( FPU_modrm < 0300 ) - { - /* All of these instructions use the mod/rm byte to get a data address */ - - if ( (addr_modes.default_mode & SIXTEEN) - ^ (addr_modes.override.address_size == ADDR_SIZE_PREFIX) ) - data_address = get_address_16(FPU_modrm, &FPU_EIP, &data_sel_off, - addr_modes); - else - data_address = get_address(FPU_modrm, &FPU_EIP, &data_sel_off, - addr_modes); - - if ( addr_modes.default_mode ) - { - if ( FPU_EIP-1 > code_limit ) - math_abort(FPU_info,SIGSEGV); - } - - if ( !(byte1 & 1) ) - { - unsigned short status1 = partial_status; - - st0_ptr = &st(0); - st0_tag = st0_ptr->tag; - - /* Stack underflow has priority */ - if ( NOT_EMPTY_ST0 ) - { - if ( addr_modes.default_mode & PROTECTED ) - { - /* This table works for 16 and 32 bit protected mode */ - if ( access_limit < data_sizes_16[(byte1 >> 1) & 3] ) - math_abort(FPU_info,SIGSEGV); - } - - unmasked = 0; /* Do this here to stop compiler warnings. */ - switch ( (byte1 >> 1) & 3 ) - { - case 0: - unmasked = reg_load_single((float *)data_address, - &loaded_data); - break; - case 1: - reg_load_int32((long *)data_address, &loaded_data); - break; - case 2: - unmasked = reg_load_double((double *)data_address, - &loaded_data); - break; - case 3: - reg_load_int16((short *)data_address, &loaded_data); - break; - } - - /* No more access to user memory, it is safe - to use static data now */ - - /* NaN operands have the next priority. */ - /* We have to delay looking at st(0) until after - loading the data, because that data might contain an SNaN */ - if ( (st0_tag == TW_NaN) || - (loaded_data.tag == TW_NaN) ) - { - /* Restore the status word; we might have loaded a - denormal. */ - partial_status = status1; - if ( (FPU_modrm & 0x30) == 0x10 ) - { - /* fcom or fcomp */ - EXCEPTION(EX_Invalid); - setcc(SW_C3 | SW_C2 | SW_C0); - if ( (FPU_modrm & 0x08) && (control_word & CW_Invalid) ) - pop(); /* fcomp, masked, so we pop. */ - } - else - { -#ifdef PECULIAR_486 - /* This is not really needed, but gives behaviour - identical to an 80486 */ - if ( (FPU_modrm & 0x28) == 0x20 ) - /* fdiv or fsub */ - real_2op_NaN(&loaded_data, st0_ptr, - st0_ptr); - else -#endif PECULIAR_486 - /* fadd, fdivr, fmul, or fsubr */ - real_2op_NaN(st0_ptr, &loaded_data, - st0_ptr); - } - goto reg_mem_instr_done; - } - - if ( unmasked && !((FPU_modrm & 0x30) == 0x10) ) - { - /* Is not a comparison instruction. */ - if ( (FPU_modrm & 0x38) == 0x38 ) - { - /* fdivr */ - if ( (st0_tag == TW_Zero) && - (loaded_data.tag == TW_Valid) ) - { - if ( divide_by_zero(loaded_data.sign, - st0_ptr) ) - { - /* We use the fact here that the unmasked - exception in the loaded data was for a - denormal operand */ - /* Restore the state of the denormal op bit */ - partial_status &= ~SW_Denorm_Op; - partial_status |= status1 & SW_Denorm_Op; - } - } - } - goto reg_mem_instr_done; - } - - switch ( (FPU_modrm >> 3) & 7 ) - { - case 0: /* fadd */ - clear_C1(); - reg_add(st0_ptr, &loaded_data, st0_ptr, - control_word); - break; - case 1: /* fmul */ - clear_C1(); - reg_mul(st0_ptr, &loaded_data, st0_ptr, - control_word); - break; - case 2: /* fcom */ - compare_st_data(&loaded_data); - break; - case 3: /* fcomp */ - if ( !compare_st_data(&loaded_data) && !unmasked ) - pop(); - break; - case 4: /* fsub */ - clear_C1(); - reg_sub(st0_ptr, &loaded_data, st0_ptr, - control_word); - break; - case 5: /* fsubr */ - clear_C1(); - reg_sub(&loaded_data, st0_ptr, st0_ptr, - control_word); - break; - case 6: /* fdiv */ - clear_C1(); - reg_div(st0_ptr, &loaded_data, st0_ptr, - control_word); - break; - case 7: /* fdivr */ - clear_C1(); - if ( st0_tag == TW_Zero ) - partial_status = status1; /* Undo any denorm tag, - zero-divide has priority. */ - reg_div(&loaded_data, st0_ptr, st0_ptr, - control_word); - break; - } - } - else - { - if ( (FPU_modrm & 0x30) == 0x10 ) - { - /* The instruction is fcom or fcomp */ - EXCEPTION(EX_StackUnder); - setcc(SW_C3 | SW_C2 | SW_C0); - if ( (FPU_modrm & 0x08) && (control_word & CW_Invalid) ) - pop(); /* fcomp */ - } - else - stack_underflow(); - } - reg_mem_instr_done: - operand_address = data_sel_off; - } - else - { - if ( !(no_ip_update = - load_store_instr(((FPU_modrm & 0x38) | (byte1 & 6)) >> 1, - addr_modes, data_address)) ) - { - operand_address = data_sel_off; - } - } - - } - else - { - /* None of these instructions access user memory */ - unsigned char instr_index = (FPU_modrm & 0x38) | (byte1 & 7); - -#ifdef PECULIAR_486 - /* This is supposed to be undefined, but a real 80486 seems - to do this: */ - operand_address.offset = 0; - operand_address.selector = FPU_DS; -#endif PECULIAR_486 - - st0_ptr = &st(0); - st0_tag = st0_ptr->tag; - switch ( type_table[(int) instr_index] ) - { - case _NONE_: /* also _REGIc: _REGIn */ - break; - case _REG0_: - if ( !NOT_EMPTY_ST0 ) - { - stack_underflow(); - goto FPU_instruction_done; - } - break; - case _REGIi: - if ( !NOT_EMPTY_ST0 || !NOT_EMPTY(FPU_rm) ) - { - stack_underflow_i(FPU_rm); - goto FPU_instruction_done; - } - break; - case _REGIp: - if ( !NOT_EMPTY_ST0 || !NOT_EMPTY(FPU_rm) ) - { - stack_underflow_pop(FPU_rm); - goto FPU_instruction_done; - } - break; - case _REGI_: - if ( !NOT_EMPTY_ST0 || !NOT_EMPTY(FPU_rm) ) - { - stack_underflow(); - goto FPU_instruction_done; - } - break; - case _PUSH_: /* Only used by the fld st(i) instruction */ - break; - case _null_: - FPU_illegal(); - goto FPU_instruction_done; - default: - EXCEPTION(EX_INTERNAL|0x111); - goto FPU_instruction_done; - } - (*st_instr_table[(int) instr_index])(); - -FPU_instruction_done: - ; - } - - if ( ! no_ip_update ) - instruction_address = entry_sel_off; - -FPU_fwait_done: - -#ifdef DEBUG - RE_ENTRANT_CHECK_OFF; - emu_printall(); - RE_ENTRANT_CHECK_ON; -#endif DEBUG - - if (FPU_lookahead && !need_resched) - { - FPU_ORIG_EIP = FPU_EIP - code_base; - if ( valid_prefix(&byte1, (unsigned char **)&FPU_EIP, - &addr_modes.override) ) - goto do_another_FPU_instruction; - } - - if ( addr_modes.default_mode ) - FPU_EIP -= code_base; - - RE_ENTRANT_CHECK_OFF; -} - - -/* Support for prefix bytes is not yet complete. To properly handle - all prefix bytes, further changes are needed in the emulator code - which accesses user address space. Access to separate segments is - important for msdos emulation. */ -static int valid_prefix(unsigned char *Byte, unsigned char **fpu_eip, - overrides *override) -{ - unsigned char byte; - unsigned char *ip = *fpu_eip; - - *override = (overrides) { 0, 0, PREFIX_DEFAULT }; /* defaults */ - - RE_ENTRANT_CHECK_OFF; - FPU_code_verify_area(1); - byte = get_fs_byte(ip); - RE_ENTRANT_CHECK_ON; - - while ( 1 ) - { - switch ( byte ) - { - case ADDR_SIZE_PREFIX: - override->address_size = ADDR_SIZE_PREFIX; - goto do_next_byte; - - case OP_SIZE_PREFIX: - override->operand_size = OP_SIZE_PREFIX; - goto do_next_byte; - - case PREFIX_CS: - override->segment = PREFIX_CS_; - goto do_next_byte; - case PREFIX_ES: - override->segment = PREFIX_ES_; - goto do_next_byte; - case PREFIX_SS: - override->segment = PREFIX_SS_; - goto do_next_byte; - case PREFIX_FS: - override->segment = PREFIX_FS_; - goto do_next_byte; - case PREFIX_GS: - override->segment = PREFIX_GS_; - goto do_next_byte; - case PREFIX_DS: - override->segment = PREFIX_DS_; - goto do_next_byte; - -/* lock is not a valid prefix for FPU instructions, - let the cpu handle it to generate a SIGILL. */ -/* case PREFIX_LOCK: */ - - /* rep.. prefixes have no meaning for FPU instructions */ - case PREFIX_REPE: - case PREFIX_REPNE: - - do_next_byte: - ip++; - RE_ENTRANT_CHECK_OFF; - FPU_code_verify_area(1); - byte = get_fs_byte(ip); - RE_ENTRANT_CHECK_ON; - break; - case FWAIT_OPCODE: - *Byte = byte; - return 1; - default: - if ( (byte & 0xf8) == 0xd8 ) - { - *Byte = byte; - *fpu_eip = ip; - return 1; - } - else - { - /* Not a valid sequence of prefix bytes followed by - an FPU instruction. */ - *Byte = byte; /* Needed for error message. */ - return 0; - } - } - } -} - - -void math_abort(struct info * info, unsigned int signal) -{ - FPU_EIP = FPU_ORIG_EIP; - current->tss.trap_no = 16; - current->tss.error_code = 0; - send_sig(signal,current,1); - RE_ENTRANT_CHECK_OFF; - __asm__("movl %0,%%esp ; ret": :"g" (((long) info)-4)); -#ifdef PARANOID - printk("ERROR: wm-FPU-emu math_abort failed!\n"); -#endif PARANOID -} diff --git a/drivers/FPU-emu/fpu_etc.c b/drivers/FPU-emu/fpu_etc.c deleted file mode 100644 index 20e3294ca..000000000 --- a/drivers/FPU-emu/fpu_etc.c +++ /dev/null @@ -1,129 +0,0 @@ -/*---------------------------------------------------------------------------+ - | fpu_etc.c | - | | - | Implement a few FPU instructions. | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -#include "fpu_system.h" -#include "exception.h" -#include "fpu_emu.h" -#include "status_w.h" -#include "reg_constant.h" - - -static void fchs(FPU_REG *st0_ptr) -{ - if ( st0_ptr->tag ^ TW_Empty ) - { - st0_ptr->sign ^= SIGN_POS^SIGN_NEG; - clear_C1(); - } - else - stack_underflow(); -} - -static void fabs(FPU_REG *st0_ptr) -{ - if ( st0_ptr->tag ^ TW_Empty ) - { - st0_ptr->sign = SIGN_POS; - clear_C1(); - } - else - stack_underflow(); -} - - -static void ftst_(FPU_REG *st0_ptr) -{ - switch (st0_ptr->tag) - { - case TW_Zero: - setcc(SW_C3); - break; - case TW_Valid: - if (st0_ptr->sign == SIGN_POS) - setcc(0); - else - setcc(SW_C0); - -#ifdef DENORM_OPERAND - if ( (st0_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - { -#ifdef PECULIAR_486 - /* This is weird! */ - if (st0_ptr->sign == SIGN_POS) - setcc(SW_C3); -#endif PECULIAR_486 - return; - } -#endif DENORM_OPERAND - - break; - case TW_NaN: - setcc(SW_C0|SW_C2|SW_C3); /* Operand is not comparable */ - EXCEPTION(EX_Invalid); - break; - case TW_Infinity: - if (st0_ptr->sign == SIGN_POS) - setcc(0); - else - setcc(SW_C0); - break; - case TW_Empty: - setcc(SW_C0|SW_C2|SW_C3); - EXCEPTION(EX_StackUnder); - break; - default: - setcc(SW_C0|SW_C2|SW_C3); /* Operand is not comparable */ - EXCEPTION(EX_INTERNAL|0x14); - break; - } -} - -static void fxam(FPU_REG *st0_ptr) -{ - int c=0; - switch (st0_ptr->tag) - { - case TW_Empty: - c = SW_C3|SW_C0; - break; - case TW_Zero: - c = SW_C3; - break; - case TW_Valid: - /* This will need to be changed if TW_Denormal is ever used. */ - if ( st0_ptr->exp <= EXP_UNDER ) - c = SW_C2|SW_C3; /* Denormal */ - else - c = SW_C2; - break; - case TW_NaN: - c = SW_C0; - break; - case TW_Infinity: - c = SW_C2|SW_C0; - break; - } - if (st0_ptr->sign == SIGN_NEG) - c |= SW_C1; - setcc(c); -} - - -static FUNC_ST0 const fp_etc_table[] = { - fchs, fabs, (FUNC_ST0)FPU_illegal, (FUNC_ST0)FPU_illegal, - ftst_, fxam, (FUNC_ST0)FPU_illegal, (FUNC_ST0)FPU_illegal -}; - -void fp_etc() -{ - (fp_etc_table[FPU_rm])(&st(0)); -} diff --git a/drivers/FPU-emu/fpu_proto.h b/drivers/FPU-emu/fpu_proto.h deleted file mode 100644 index b4392fe57..000000000 --- a/drivers/FPU-emu/fpu_proto.h +++ /dev/null @@ -1,137 +0,0 @@ -/* errors.c */ -extern void Un_impl(void); -extern void FPU_illegal(void); -extern void emu_printall(void); -extern void stack_overflow(void); -extern void stack_underflow(void); -extern void stack_underflow_i(int i); -extern void stack_underflow_pop(int i); -extern int set_precision_flag(int flags); -asmlinkage void exception(int n); -asmlinkage int real_2op_NaN(FPU_REG const *a, FPU_REG const *b, FPU_REG *dest); -asmlinkage int arith_invalid(FPU_REG *dest); -asmlinkage int divide_by_zero(int sign, FPU_REG *dest); -asmlinkage void set_precision_flag_up(void); -asmlinkage void set_precision_flag_down(void); -asmlinkage int denormal_operand(void); -asmlinkage int arith_overflow(FPU_REG *dest); -asmlinkage int arith_underflow(FPU_REG *dest); - -/* fpu_arith.c */ -extern void fadd__(void); -extern void fmul__(void); -extern void fsub__(void); -extern void fsubr_(void); -extern void fdiv__(void); -extern void fdivr_(void); -extern void fadd_i(void); -extern void fmul_i(void); -extern void fsubri(void); -extern void fsub_i(void); -extern void fdivri(void); -extern void fdiv_i(void); -extern void faddp_(void); -extern void fmulp_(void); -extern void fsubrp(void); -extern void fsubp_(void); -extern void fdivrp(void); -extern void fdivp_(void); - -/* fpu_aux.c */ -extern void fclex(void); -extern void finit(void); -extern void finit_(void); -extern void fstsw_(void); -extern void fp_nop(void); -extern void fld_i_(void); -extern void fxch_i(void); -extern void ffree_(void); -extern void ffreep(void); -extern void fst_i_(void); -extern void fstp_i(void); - -/* fpu_entry.c */ -asmlinkage void math_emulate(long arg); -extern void math_abort(struct info *info, unsigned int signal); - -/* fpu_etc.c */ -extern void fp_etc(void); - -/* fpu_trig.c */ -extern void convert_l2reg(long const *arg, FPU_REG *dest); -extern void trig_a(void); -extern void trig_b(void); - -/* get_address.c */ -extern void *get_address(unsigned char FPU_modrm, unsigned long *fpu_eip, - struct address *addr, - fpu_addr_modes); -extern void *get_address_16(unsigned char FPU_modrm, unsigned long *fpu_eip, - struct address *addr, - fpu_addr_modes); - -/* load_store.c */ -extern int load_store_instr(unsigned char type, fpu_addr_modes addr_modes, - void *address); - -/* poly_2xm1.c */ -extern int poly_2xm1(FPU_REG const *arg, FPU_REG *result); - -/* poly_atan.c */ -extern void poly_atan(FPU_REG *arg1, FPU_REG *arg2, FPU_REG *result); - -/* poly_l2.c */ -extern void poly_l2(FPU_REG const *arg, FPU_REG const *y, FPU_REG *result); -extern int poly_l2p1(FPU_REG const *arg, FPU_REG const *y, FPU_REG *result); - -/* poly_sin.c */ -extern void poly_sine(FPU_REG const *arg, FPU_REG *result); -extern void poly_cos(FPU_REG const *arg, FPU_REG *result); - -/* poly_tan.c */ -extern void poly_tan(FPU_REG const *arg, FPU_REG *result); - -/* reg_add_sub.c */ -extern int reg_add(FPU_REG const *a, FPU_REG const *b, - FPU_REG *dest, int control_w); -extern int reg_sub(FPU_REG const *a, FPU_REG const *b, - FPU_REG *dest, int control_w); - -/* reg_compare.c */ -extern int compare(FPU_REG const *b); -extern int compare_st_data(FPU_REG const *b); -extern void fcom_st(void); -extern void fcompst(void); -extern void fcompp(void); -extern void fucom_(void); -extern void fucomp(void); -extern void fucompp(void); - -/* reg_constant.c */ -extern void fconst(void); - -/* reg_ld_str.c */ -extern int reg_load_extended(long double *addr, FPU_REG *loaded_data); -extern int reg_load_double(double *dfloat, FPU_REG *loaded_data); -extern int reg_load_single(float *single, FPU_REG *loaded_data); -extern void reg_load_int64(long long *_s, FPU_REG *loaded_data); -extern void reg_load_int32(long *_s, FPU_REG *loaded_data); -extern void reg_load_int16(short *_s, FPU_REG *loaded_data); -extern void reg_load_bcd(char *s, FPU_REG *loaded_data); -extern int reg_store_extended(long double *d, FPU_REG *st0_ptr); -extern int reg_store_double(double *dfloat, FPU_REG *st0_ptr); -extern int reg_store_single(float *single, FPU_REG *st0_ptr); -extern int reg_store_int64(long long *d, FPU_REG *st0_ptr); -extern int reg_store_int32(long *d, FPU_REG *st0_ptr); -extern int reg_store_int16(short *d, FPU_REG *st0_ptr); -extern int reg_store_bcd(char *d, FPU_REG *st0_ptr); -extern int round_to_int(FPU_REG *r); -extern char *fldenv(fpu_addr_modes addr_modes, char *address); -extern void frstor(fpu_addr_modes addr_modes, char *address); -extern unsigned short tag_word(void); -extern char *fstenv(fpu_addr_modes addr_modes, char *address); -extern void fsave(fpu_addr_modes addr_modes, char *address); - -/* reg_mul.c */ -extern int reg_mul(FPU_REG const *a, FPU_REG const *b, - FPU_REG *dest, unsigned int control_w); diff --git a/drivers/FPU-emu/fpu_system.h b/drivers/FPU-emu/fpu_system.h deleted file mode 100644 index e09e08b15..000000000 --- a/drivers/FPU-emu/fpu_system.h +++ /dev/null @@ -1,82 +0,0 @@ -/*---------------------------------------------------------------------------+ - | fpu_system.h | - | | - | Copyright (C) 1992,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - +---------------------------------------------------------------------------*/ - -#ifndef _FPU_SYSTEM_H -#define _FPU_SYSTEM_H - -/* system dependent definitions */ - -#include -#include - -/* This sets the pointer FPU_info to point to the argument part - of the stack frame of math_emulate() */ -#define SETUP_DATA_AREA(arg) FPU_info = (struct info *) &arg - -#define LDT_DESCRIPTOR(s) (current->ldt[(s) >> 3]) -#define SEG_D_SIZE(x) ((x).b & (3 << 21)) -#define SEG_G_BIT(x) ((x).b & (1 << 23)) -#define SEG_GRANULARITY(x) (((x).b & (1 << 23)) ? 4096 : 1) -#define SEG_286_MODE(x) ((x).b & ( 0xff000000 | 0xf0000 | (1 << 23))) -#define SEG_BASE_ADDR(s) (((s).b & 0xff000000) \ - | (((s).b & 0xff) << 16) | ((s).a >> 16)) -#define SEG_LIMIT(s) (((s).b & 0xff0000) | ((s).a & 0xffff)) -#define SEG_EXECUTE_ONLY(s) (((s).b & ((1 << 11) | (1 << 9))) == (1 << 11)) -#define SEG_WRITE_PERM(s) (((s).b & ((1 << 11) | (1 << 9))) == (1 << 9)) -#define SEG_EXPAND_DOWN(s) (((s).b & ((1 << 11) | (1 << 10))) \ - == (1 << 10)) - -#define I387 (current->tss.i387) -#define FPU_info (I387.soft.info) - -#define FPU_CS (*(unsigned short *) &(FPU_info->___cs)) -#define FPU_SS (*(unsigned short *) &(FPU_info->___ss)) -#define FPU_DS (*(unsigned short *) &(FPU_info->___ds)) -#define FPU_EAX (FPU_info->___eax) -#define FPU_EFLAGS (FPU_info->___eflags) -#define FPU_EIP (FPU_info->___eip) -#define FPU_ORIG_EIP (FPU_info->___orig_eip) - -#define FPU_lookahead (I387.soft.lookahead) - -/* nz if ip_offset and cs_selector are not to be set for the current - instruction. */ -#define no_ip_update (((char *)&(I387.soft.twd))[0]) -#define FPU_rm (((unsigned char *)&(I387.soft.twd))[1]) - -/* Number of bytes of data which can be legally accessed by the current - instruction. This only needs to hold a number <= 108, so a byte will do. */ -#define access_limit (((unsigned char *)&(I387.soft.twd))[2]) - -#define partial_status (I387.soft.swd) -#define control_word (I387.soft.cwd) -#define regs (I387.soft.regs) -#define top (I387.soft.top) - -#define instruction_address (*(struct address *)&I387.soft.fip) -#define operand_address (*(struct address *)&I387.soft.foo) - -#define FPU_verify_area(x,y,z) if ( verify_area(x,y,z) ) \ - math_abort(FPU_info,SIGSEGV) - -#undef FPU_IGNORE_CODE_SEGV -#ifdef FPU_IGNORE_CODE_SEGV -/* verify_area() is very expensive, and causes the emulator to run - about 20% slower if applied to the code. Anyway, errors due to bad - code addresses should be much rarer than errors due to bad data - addresses. */ -#define FPU_code_verify_area(z) -#else -/* A simpler test than verify_area() can probably be done for - FPU_code_verify_area() because the only possible error is to step - past the upper boundary of a legal code area. */ -#define FPU_code_verify_area(z) FPU_verify_area(VERIFY_READ,(void *)FPU_EIP,z) -#endif - -#endif diff --git a/drivers/FPU-emu/fpu_trig.c b/drivers/FPU-emu/fpu_trig.c deleted file mode 100644 index 05241f700..000000000 --- a/drivers/FPU-emu/fpu_trig.c +++ /dev/null @@ -1,1718 +0,0 @@ -/*---------------------------------------------------------------------------+ - | fpu_trig.c | - | | - | Implementation of the FPU "transcendental" functions. | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -#include "fpu_system.h" -#include "exception.h" -#include "fpu_emu.h" -#include "status_w.h" -#include "control_w.h" -#include "reg_constant.h" - - -static void rem_kernel(unsigned long long st0, unsigned long long *y, - unsigned long long st1, - unsigned long long q, int n); - -#define BETTER_THAN_486 - -#define FCOS 4 -/* Not needed now with new code -#define FPTAN 1 - */ - -/* Used only by fptan, fsin, fcos, and fsincos. */ -/* This routine produces very accurate results, similar to - using a value of pi with more than 128 bits precision. */ -/* Limited measurements show no results worse than 64 bit precision - except for the results for arguments close to 2^63, where the - precision of the result sometimes degrades to about 63.9 bits */ -static int trig_arg(FPU_REG *X, int even) -{ - FPU_REG tmp; - unsigned long long q; - int old_cw = control_word, saved_status = partial_status; - - if ( X->exp >= EXP_BIAS + 63 ) - { - partial_status |= SW_C2; /* Reduction incomplete. */ - return -1; - } - - control_word &= ~CW_RC; - control_word |= RC_CHOP; - - reg_div(X, &CONST_PI2, &tmp, PR_64_BITS | RC_CHOP | 0x3f); - round_to_int(&tmp); /* Fortunately, this can't overflow - to 2^64 */ - q = significand(&tmp); - if ( q ) - { - rem_kernel(significand(X), - &significand(&tmp), - significand(&CONST_PI2), - q, X->exp - CONST_PI2.exp); - tmp.exp = CONST_PI2.exp; - normalize(&tmp); - reg_move(&tmp, X); - } - -#ifdef FPTAN - if ( even == FPTAN ) - { - if ( ((X->exp >= EXP_BIAS) || - ((X->exp == EXP_BIAS-1) - && (X->sigh >= 0xc90fdaa2))) ^ (q & 1) ) - even = FCOS; - else - even = 0; - } -#endif FPTAN - - if ( (even && !(q & 1)) || (!even && (q & 1)) ) - { - reg_sub(&CONST_PI2, X, X, FULL_PRECISION); -#ifdef BETTER_THAN_486 - /* So far, the results are exact but based upon a 64 bit - precision approximation to pi/2. The technique used - now is equivalent to using an approximation to pi/2 which - is accurate to about 128 bits. */ - if ( (X->exp <= CONST_PI2extra.exp + 64) || (q > 1) ) - { - /* This code gives the effect of having p/2 to better than - 128 bits precision. */ - significand(&tmp) = q + 1; - tmp.exp = EXP_BIAS + 63; - tmp.tag = TW_Valid; - normalize(&tmp); - reg_mul(&CONST_PI2extra, &tmp, &tmp, FULL_PRECISION); - reg_add(X, &tmp, X, FULL_PRECISION); - if ( X->sign == SIGN_NEG ) - { - /* CONST_PI2extra is negative, so the result of the addition - can be negative. This means that the argument is actually - in a different quadrant. The correction is always < pi/2, - so it can't overflow into yet another quadrant. */ - X->sign = SIGN_POS; - q++; - } - } -#endif BETTER_THAN_486 - } -#ifdef BETTER_THAN_486 - else - { - /* So far, the results are exact but based upon a 64 bit - precision approximation to pi/2. The technique used - now is equivalent to using an approximation to pi/2 which - is accurate to about 128 bits. */ - if ( ((q > 0) && (X->exp <= CONST_PI2extra.exp + 64)) || (q > 1) ) - { - /* This code gives the effect of having p/2 to better than - 128 bits precision. */ - significand(&tmp) = q; - tmp.exp = EXP_BIAS + 63; - tmp.tag = TW_Valid; - normalize(&tmp); - reg_mul(&CONST_PI2extra, &tmp, &tmp, FULL_PRECISION); - reg_sub(X, &tmp, X, FULL_PRECISION); - if ( (X->exp == CONST_PI2.exp) && - ((X->sigh > CONST_PI2.sigh) - || ((X->sigh == CONST_PI2.sigh) - && (X->sigl > CONST_PI2.sigl))) ) - { - /* CONST_PI2extra is negative, so the result of the - subtraction can be larger than pi/2. This means - that the argument is actually in a different quadrant. - The correction is always < pi/2, so it can't overflow - into yet another quadrant. */ - reg_sub(&CONST_PI, X, X, FULL_PRECISION); - q++; - } - } - } -#endif BETTER_THAN_486 - - control_word = old_cw; - partial_status = saved_status & ~SW_C2; /* Reduction complete. */ - - return (q & 3) | even; -} - - -/* Convert a long to register */ -void convert_l2reg(long const *arg, FPU_REG *dest) -{ - long num = *arg; - - if (num == 0) - { reg_move(&CONST_Z, dest); return; } - - if (num > 0) - dest->sign = SIGN_POS; - else - { num = -num; dest->sign = SIGN_NEG; } - - dest->sigh = num; - dest->sigl = 0; - dest->exp = EXP_BIAS + 31; - dest->tag = TW_Valid; - normalize(dest); -} - - -static void single_arg_error(FPU_REG *st0_ptr) -{ - switch ( st0_ptr->tag ) - { - case TW_NaN: - if ( !(st0_ptr->sigh & 0x40000000) ) /* Signaling ? */ - { - EXCEPTION(EX_Invalid); - if ( control_word & CW_Invalid ) - st0_ptr->sigh |= 0x40000000; /* Convert to a QNaN */ - } - break; /* return with a NaN in st(0) */ - case TW_Empty: - stack_underflow(); /* Puts a QNaN in st(0) */ - break; -#ifdef PARANOID - default: - EXCEPTION(EX_INTERNAL|0x0112); -#endif PARANOID - } -} - - -static void single_arg_2_error(FPU_REG *st0_ptr) -{ - FPU_REG *st_new_ptr; - - switch ( st0_ptr->tag ) - { - case TW_NaN: - if ( !(st0_ptr->sigh & 0x40000000) ) /* Signaling ? */ - { - EXCEPTION(EX_Invalid); - if ( control_word & CW_Invalid ) - { - /* The masked response */ - /* Convert to a QNaN */ - st0_ptr->sigh |= 0x40000000; - st_new_ptr = &st(-1); - push(); - reg_move(&st(1), st_new_ptr); - } - } - else - { - /* A QNaN */ - st_new_ptr = &st(-1); - push(); - reg_move(&st(1), st_new_ptr); - } - break; /* return with a NaN in st(0) */ -#ifdef PARANOID - default: - EXCEPTION(EX_INTERNAL|0x0112); -#endif PARANOID - } -} - - -/*---------------------------------------------------------------------------*/ - -static void f2xm1(FPU_REG *st0_ptr) -{ - clear_C1(); - switch ( st0_ptr->tag ) - { - case TW_Valid: - { - if ( st0_ptr->exp >= 0 ) - { - /* For an 80486 FPU, the result is undefined. */ - } -#ifdef DENORM_OPERAND - else if ( (st0_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - else - { - /* poly_2xm1(x) requires 0 < x < 1. */ - poly_2xm1(st0_ptr, st0_ptr); - } - if ( st0_ptr->exp <= EXP_UNDER ) - { - /* A denormal result has been produced. - Precision must have been lost, this is always - an underflow. */ - arith_underflow(st0_ptr); - } - set_precision_flag_up(); /* 80486 appears to always do this */ - return; - } - case TW_Zero: - return; - case TW_Infinity: - if ( st0_ptr->sign == SIGN_NEG ) - { - /* -infinity gives -1 (p16-10) */ - reg_move(&CONST_1, st0_ptr); - st0_ptr->sign = SIGN_NEG; - } - return; - default: - single_arg_error(st0_ptr); - } -} - - -static void fptan(FPU_REG *st0_ptr) -{ - char st0_tag = st0_ptr->tag; - FPU_REG *st_new_ptr; - int q; - char arg_sign = st0_ptr->sign; - - /* Stack underflow has higher priority */ - if ( st0_tag == TW_Empty ) - { - stack_underflow(); /* Puts a QNaN in st(0) */ - if ( control_word & CW_Invalid ) - { - st_new_ptr = &st(-1); - push(); - stack_underflow(); /* Puts a QNaN in the new st(0) */ - } - return; - } - - if ( STACK_OVERFLOW ) - { stack_overflow(); return; } - - switch ( st0_tag ) - { - case TW_Valid: - if ( st0_ptr->exp > EXP_BIAS - 40 ) - { - st0_ptr->sign = SIGN_POS; - if ( (q = trig_arg(st0_ptr, 0)) != -1 ) - { - poly_tan(st0_ptr, st0_ptr); - st0_ptr->sign = (q & 1) ^ arg_sign; - } - else - { - /* Operand is out of range */ - st0_ptr->sign = arg_sign; /* restore st(0) */ - return; - } - set_precision_flag_up(); /* We do not really know if up or down */ - } - else - { - /* For a small arg, the result == the argument */ - /* Underflow may happen */ - - if ( st0_ptr->exp <= EXP_UNDER ) - { -#ifdef DENORM_OPERAND - if ( denormal_operand() ) - return; -#endif DENORM_OPERAND - /* A denormal result has been produced. - Precision must have been lost, this is always - an underflow. */ - if ( arith_underflow(st0_ptr) ) - return; - } - set_precision_flag_down(); /* Must be down. */ - } - push(); - reg_move(&CONST_1, st_new_ptr); - return; - break; - case TW_Infinity: - /* The 80486 treats infinity as an invalid operand */ - arith_invalid(st0_ptr); - if ( control_word & CW_Invalid ) - { - st_new_ptr = &st(-1); - push(); - arith_invalid(st_new_ptr); - } - return; - case TW_Zero: - push(); - reg_move(&CONST_1, st_new_ptr); - setcc(0); - break; - default: - single_arg_2_error(st0_ptr); - break; - } -} - - -static void fxtract(FPU_REG *st0_ptr) -{ - char st0_tag = st0_ptr->tag; - FPU_REG *st_new_ptr; - register FPU_REG *st1_ptr = st0_ptr; /* anticipate */ - - if ( STACK_OVERFLOW ) - { stack_overflow(); return; } - clear_C1(); - if ( !(st0_tag ^ TW_Valid) ) - { - long e; - -#ifdef DENORM_OPERAND - if ( (st0_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - push(); - reg_move(st1_ptr, st_new_ptr); - st_new_ptr->exp = EXP_BIAS; - e = st1_ptr->exp - EXP_BIAS; - convert_l2reg(&e, st1_ptr); - return; - } - else if ( st0_tag == TW_Zero ) - { - char sign = st0_ptr->sign; - if ( divide_by_zero(SIGN_NEG, st0_ptr) ) - return; - push(); - reg_move(&CONST_Z, st_new_ptr); - st_new_ptr->sign = sign; - return; - } - else if ( st0_tag == TW_Infinity ) - { - char sign = st0_ptr->sign; - st0_ptr->sign = SIGN_POS; - push(); - reg_move(&CONST_INF, st_new_ptr); - st_new_ptr->sign = sign; - return; - } - else if ( st0_tag == TW_NaN ) - { - if ( real_2op_NaN(st0_ptr, st0_ptr, st0_ptr) ) - return; - push(); - reg_move(st1_ptr, st_new_ptr); - return; - } - else if ( st0_tag == TW_Empty ) - { - /* Is this the correct behaviour? */ - if ( control_word & EX_Invalid ) - { - stack_underflow(); - push(); - stack_underflow(); - } - else - EXCEPTION(EX_StackUnder); - } -#ifdef PARANOID - else - EXCEPTION(EX_INTERNAL | 0x119); -#endif PARANOID -} - - -static void fdecstp(FPU_REG *st0_ptr) -{ - clear_C1(); - top--; /* st0_ptr will be fixed in math_emulate() before the next instr */ -} - -static void fincstp(FPU_REG *st0_ptr) -{ - clear_C1(); - top++; /* st0_ptr will be fixed in math_emulate() before the next instr */ -} - - -static void fsqrt_(FPU_REG *st0_ptr) -{ - char st0_tag = st0_ptr->tag; - - clear_C1(); - if ( !(st0_tag ^ TW_Valid) ) - { - int expon; - - if (st0_ptr->sign == SIGN_NEG) - { - arith_invalid(st0_ptr); /* sqrt(negative) is invalid */ - return; - } - -#ifdef DENORM_OPERAND - if ( (st0_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - expon = st0_ptr->exp - EXP_BIAS; - st0_ptr->exp = EXP_BIAS + (expon & 1); /* make st(0) in [1.0 .. 4.0) */ - - wm_sqrt(st0_ptr, control_word); /* Do the computation */ - - st0_ptr->exp += expon >> 1; - st0_ptr->sign = SIGN_POS; - } - else if ( st0_tag == TW_Zero ) - return; - else if ( st0_tag == TW_Infinity ) - { - if ( st0_ptr->sign == SIGN_NEG ) - arith_invalid(st0_ptr); /* sqrt(-Infinity) is invalid */ - return; - } - else - { single_arg_error(st0_ptr); return; } - -} - - -static void frndint_(FPU_REG *st0_ptr) -{ - char st0_tag = st0_ptr->tag; - int flags; - - if ( !(st0_tag ^ TW_Valid) ) - { - if (st0_ptr->exp > EXP_BIAS+63) - return; - -#ifdef DENORM_OPERAND - if ( (st0_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - /* Fortunately, this can't overflow to 2^64 */ - if ( (flags = round_to_int(st0_ptr)) ) - set_precision_flag(flags); - - st0_ptr->exp = EXP_BIAS + 63; - normalize(st0_ptr); - return; - } - else if ( (st0_tag == TW_Zero) || (st0_tag == TW_Infinity) ) - return; - else - single_arg_error(st0_ptr); -} - - -static void fsin(FPU_REG *st0_ptr) -{ - char st0_tag = st0_ptr->tag; - char arg_sign = st0_ptr->sign; - - if ( st0_tag == TW_Valid ) - { - FPU_REG rv; - int q; - - if ( st0_ptr->exp > EXP_BIAS - 40 ) - { - st0_ptr->sign = SIGN_POS; - if ( (q = trig_arg(st0_ptr, 0)) != -1 ) - { - - poly_sine(st0_ptr, &rv); - - if (q & 2) - rv.sign ^= SIGN_POS ^ SIGN_NEG; - rv.sign ^= arg_sign; - reg_move(&rv, st0_ptr); - - /* We do not really know if up or down */ - set_precision_flag_up(); - return; - } - else - { - /* Operand is out of range */ - st0_ptr->sign = arg_sign; /* restore st(0) */ - return; - } - } - else - { - /* For a small arg, the result == the argument */ - /* Underflow may happen */ - - if ( st0_ptr->exp <= EXP_UNDER ) - { -#ifdef DENORM_OPERAND - if ( denormal_operand() ) - return; -#endif DENORM_OPERAND - /* A denormal result has been produced. - Precision must have been lost, this is always - an underflow. */ - arith_underflow(st0_ptr); - return; - } - - set_precision_flag_up(); /* Must be up. */ - } - } - else if ( st0_tag == TW_Zero ) - { - setcc(0); - return; - } - else if ( st0_tag == TW_Infinity ) - { - /* The 80486 treats infinity as an invalid operand */ - arith_invalid(st0_ptr); - return; - } - else - single_arg_error(st0_ptr); -} - - -static int f_cos(FPU_REG *arg) -{ - char arg_sign = arg->sign; - - if ( arg->tag == TW_Valid ) - { - FPU_REG rv; - int q; - - if ( arg->exp > EXP_BIAS - 40 ) - { - arg->sign = SIGN_POS; - if ( (arg->exp < EXP_BIAS) - || ((arg->exp == EXP_BIAS) - && (significand(arg) <= 0xc90fdaa22168c234LL)) ) - { - poly_cos(arg, &rv); - reg_move(&rv, arg); - - /* We do not really know if up or down */ - set_precision_flag_down(); - - return 0; - } - else if ( (q = trig_arg(arg, FCOS)) != -1 ) - { - poly_sine(arg, &rv); - - if ((q+1) & 2) - rv.sign ^= SIGN_POS ^ SIGN_NEG; - reg_move(&rv, arg); - - /* We do not really know if up or down */ - set_precision_flag_down(); - - return 0; - } - else - { - /* Operand is out of range */ - arg->sign = arg_sign; /* restore st(0) */ - return 1; - } - } - else - { -#ifdef DENORM_OPERAND - if ( (arg->exp <= EXP_UNDER) && (denormal_operand()) ) - return 1; -#endif DENORM_OPERAND - - setcc(0); - reg_move(&CONST_1, arg); -#ifdef PECULIAR_486 - set_precision_flag_down(); /* 80486 appears to do this. */ -#else - set_precision_flag_up(); /* Must be up. */ -#endif PECULIAR_486 - return 0; - } - } - else if ( arg->tag == TW_Zero ) - { - reg_move(&CONST_1, arg); - setcc(0); - return 0; - } - else if ( arg->tag == TW_Infinity ) - { - /* The 80486 treats infinity as an invalid operand */ - arith_invalid(arg); - return 1; - } - else - { - single_arg_error(arg); /* requires arg == &st(0) */ - return 1; - } -} - - -static void fcos(FPU_REG *st0_ptr) -{ - f_cos(st0_ptr); -} - - -static void fsincos(FPU_REG *st0_ptr) -{ - char st0_tag = st0_ptr->tag; - FPU_REG *st_new_ptr; - FPU_REG arg; - - /* Stack underflow has higher priority */ - if ( st0_tag == TW_Empty ) - { - stack_underflow(); /* Puts a QNaN in st(0) */ - if ( control_word & CW_Invalid ) - { - st_new_ptr = &st(-1); - push(); - stack_underflow(); /* Puts a QNaN in the new st(0) */ - } - return; - } - - if ( STACK_OVERFLOW ) - { stack_overflow(); return; } - - if ( st0_tag == TW_NaN ) - { - single_arg_2_error(st0_ptr); - return; - } - else if ( st0_tag == TW_Infinity ) - { - /* The 80486 treats infinity as an invalid operand */ - if ( !arith_invalid(st0_ptr) ) - { - /* unmasked response */ - push(); - arith_invalid(st_new_ptr); - } - return; - } - - reg_move(st0_ptr,&arg); - if ( !f_cos(&arg) ) - { - fsin(st0_ptr); - push(); - reg_move(&arg,st_new_ptr); - } - -} - - -/*---------------------------------------------------------------------------*/ -/* The following all require two arguments: st(0) and st(1) */ - -/* A lean, mean kernel for the fprem instructions. This relies upon - the division and rounding to an integer in do_fprem giving an - exact result. Because of this, rem_kernel() needs to deal only with - the least significant 64 bits, the more significant bits of the - result must be zero. - */ -static void rem_kernel(unsigned long long st0, unsigned long long *y, - unsigned long long st1, - unsigned long long q, int n) -{ - unsigned long long x; - - x = st0 << n; - - /* Do the required multiplication and subtraction in the one operation */ - asm volatile ("movl %2,%%eax; mull %4; subl %%eax,%0; sbbl %%edx,%1; - movl %3,%%eax; mull %4; subl %%eax,%1; - movl %2,%%eax; mull %5; subl %%eax,%1;" - :"=m" (x), "=m" (((unsigned *)&x)[1]) - :"m" (st1),"m" (((unsigned *)&st1)[1]), - "m" (q),"m" (((unsigned *)&q)[1]) - :"%ax","%dx"); - - *y = x; -} - - -/* Remainder of st(0) / st(1) */ -/* This routine produces exact results, i.e. there is never any - rounding or truncation, etc of the result. */ -static void do_fprem(FPU_REG *st0_ptr, int round) -{ - FPU_REG *st1_ptr = &st(1); - char st1_tag = st1_ptr->tag; - char st0_tag = st0_ptr->tag; - char sign = st0_ptr->sign; - - if ( !((st0_tag ^ TW_Valid) | (st1_tag ^ TW_Valid)) ) - { - FPU_REG tmp; - int old_cw = control_word; - int expdif = st0_ptr->exp - st1_ptr->exp; - long long q; - unsigned short saved_status; - int cc = 0; - -#ifdef DENORM_OPERAND - if ( ((st0_ptr->exp <= EXP_UNDER) || - (st1_ptr->exp <= EXP_UNDER)) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - /* We want the status following the denorm tests, but don't want - the status changed by the arithmetic operations. */ - saved_status = partial_status; - control_word &= ~CW_RC; - control_word |= RC_CHOP; - - if (expdif < 64) - { - /* This should be the most common case */ - - if ( expdif > -2 ) - { - reg_div(st0_ptr, st1_ptr, &tmp, PR_64_BITS | RC_CHOP | 0x3f); - - if ( tmp.exp >= EXP_BIAS ) - { - round_to_int(&tmp); /* Fortunately, this can't overflow - to 2^64 */ - q = significand(&tmp); - - rem_kernel(significand(st0_ptr), - &significand(&tmp), - significand(st1_ptr), - q, expdif); - - tmp.exp = st1_ptr->exp; - } - else - { - reg_move(st0_ptr, &tmp); - q = 0; - } - tmp.sign = sign; - - if ( (round == RC_RND) && (tmp.sigh & 0xc0000000) ) - { - /* We may need to subtract st(1) once more, - to get a result <= 1/2 of st(1). */ - unsigned long long x; - expdif = st1_ptr->exp - tmp.exp; - if ( expdif <= 1 ) - { - if ( expdif == 0 ) - x = significand(st1_ptr) - significand(&tmp); - else /* expdif is 1 */ - x = (significand(st1_ptr) << 1) - significand(&tmp); - if ( (x < significand(&tmp)) || - /* or equi-distant (from 0 & st(1)) and q is odd */ - ((x == significand(&tmp)) && (q & 1) ) ) - { - tmp.sign ^= (SIGN_POS^SIGN_NEG); - significand(&tmp) = x; - q++; - } - } - } - - if (q & 4) cc |= SW_C0; - if (q & 2) cc |= SW_C3; - if (q & 1) cc |= SW_C1; - } - else - { - control_word = old_cw; - setcc(0); - return; - } - } - else - { - /* There is a large exponent difference ( >= 64 ) */ - /* To make much sense, the code in this section should - be done at high precision. */ - int exp_1; - - /* prevent overflow here */ - /* N is 'a number between 32 and 63' (p26-113) */ - reg_move(st0_ptr, &tmp); - tmp.exp = EXP_BIAS + 56; - exp_1 = st1_ptr->exp; st1_ptr->exp = EXP_BIAS; - expdif -= 56; - - reg_div(&tmp, st1_ptr, &tmp, PR_64_BITS | RC_CHOP | 0x3f); - st1_ptr->exp = exp_1; - - round_to_int(&tmp); /* Fortunately, this can't overflow to 2^64 */ - - rem_kernel(significand(st0_ptr), - &significand(&tmp), - significand(st1_ptr), - significand(&tmp), - tmp.exp - EXP_BIAS - ); - tmp.exp = exp_1 + expdif; - tmp.sign = sign; - - /* It is possible for the operation to be complete here. - What does the IEEE standard say? The Intel 80486 manual - implies that the operation will never be completed at this - point, and the behaviour of a real 80486 confirms this. - */ - if ( !(tmp.sigh | tmp.sigl) ) - { - /* The result is zero */ - control_word = old_cw; - partial_status = saved_status; - reg_move(&CONST_Z, st0_ptr); - st0_ptr->sign = sign; -#ifdef PECULIAR_486 - setcc(SW_C2); -#else - setcc(0); -#endif PECULIAR_486 - return; - } - cc = SW_C2; - } - - control_word = old_cw; - partial_status = saved_status; - normalize_nuo(&tmp); - reg_move(&tmp, st0_ptr); - setcc(cc); - - /* The only condition to be looked for is underflow, - and it can occur here only if underflow is unmasked. */ - if ( (st0_ptr->exp <= EXP_UNDER) && (st0_ptr->tag != TW_Zero) - && !(control_word & CW_Underflow) ) - arith_underflow(st0_ptr); - - return; - } - else if ( (st0_tag == TW_Empty) | (st1_tag == TW_Empty) ) - { - stack_underflow(); - return; - } - else if ( st0_tag == TW_Zero ) - { - if ( st1_tag == TW_Valid ) - { -#ifdef DENORM_OPERAND - if ( (st1_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - setcc(0); return; - } - else if ( st1_tag == TW_Zero ) - { arith_invalid(st0_ptr); return; } /* fprem(?,0) always invalid */ - else if ( st1_tag == TW_Infinity ) - { setcc(0); return; } - } - else if ( st0_tag == TW_Valid ) - { - if ( st1_tag == TW_Zero ) - { - arith_invalid(st0_ptr); /* fprem(Valid,Zero) is invalid */ - return; - } - else if ( st1_tag != TW_NaN ) - { -#ifdef DENORM_OPERAND - if ( (st0_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - if ( st1_tag == TW_Infinity ) - { - /* fprem(Valid,Infinity) is o.k. */ - setcc(0); return; - } - } - } - else if ( st0_tag == TW_Infinity ) - { - if ( st1_tag != TW_NaN ) - { - arith_invalid(st0_ptr); /* fprem(Infinity,?) is invalid */ - return; - } - } - - /* One of the registers must contain a NaN is we got here. */ - -#ifdef PARANOID - if ( (st0_tag != TW_NaN) && (st1_tag != TW_NaN) ) - EXCEPTION(EX_INTERNAL | 0x118); -#endif PARANOID - - real_2op_NaN(st1_ptr, st0_ptr, st0_ptr); - -} - - -/* ST(1) <- ST(1) * log ST; pop ST */ -static void fyl2x(FPU_REG *st0_ptr) -{ - char st0_tag = st0_ptr->tag; - FPU_REG *st1_ptr = &st(1), exponent; - char st1_tag = st1_ptr->tag; - int e; - - clear_C1(); - if ( !((st0_tag ^ TW_Valid) | (st1_tag ^ TW_Valid)) ) - { - if ( st0_ptr->sign == SIGN_POS ) - { -#ifdef DENORM_OPERAND - if ( ((st0_ptr->exp <= EXP_UNDER) || - (st1_ptr->exp <= EXP_UNDER)) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - if ( (st0_ptr->sigh == 0x80000000) && (st0_ptr->sigl == 0) ) - { - /* Special case. The result can be precise. */ - e = st0_ptr->exp - EXP_BIAS; - if ( e > 0 ) - { - exponent.sigh = e; - exponent.sign = SIGN_POS; - } - else - { - exponent.sigh = -e; - exponent.sign = SIGN_NEG; - } - exponent.sigl = 0; - exponent.exp = EXP_BIAS + 31; - exponent.tag = TW_Valid; - normalize_nuo(&exponent); - reg_mul(&exponent, st1_ptr, st1_ptr, FULL_PRECISION); - } - else - { - /* The usual case */ - poly_l2(st0_ptr, st1_ptr, st1_ptr); - if ( st1_ptr->exp <= EXP_UNDER ) - { - /* A denormal result has been produced. - Precision must have been lost, this is always - an underflow. */ - arith_underflow(st1_ptr); - } - else - set_precision_flag_up(); /* 80486 appears to always do this */ - } - pop(); - return; - } - else - { - /* negative */ - if ( !arith_invalid(st1_ptr) ) - pop(); - return; - } - } - else if ( (st0_tag == TW_Empty) || (st1_tag == TW_Empty) ) - { - stack_underflow_pop(1); - return; - } - else if ( (st0_tag == TW_NaN) || (st1_tag == TW_NaN) ) - { - if ( !real_2op_NaN(st0_ptr, st1_ptr, st1_ptr) ) - pop(); - return; - } - else if ( (st0_tag <= TW_Zero) && (st1_tag <= TW_Zero) ) - { - /* one of the args is zero, the other valid, or both zero */ - if ( st0_tag == TW_Zero ) - { - if ( st1_tag == TW_Zero ) - { - /* Both args zero is invalid */ - if ( !arith_invalid(st1_ptr) ) - pop(); - } -#ifdef PECULIAR_486 - /* This case is not specifically covered in the manual, - but divide-by-zero would seem to be the best response. - However, a real 80486 does it this way... */ - else if ( st0_ptr->tag == TW_Infinity ) - { - reg_move(&CONST_INF, st1_ptr); - pop(); - } -#endif PECULIAR_486 - else - { - if ( !divide_by_zero(st1_ptr->sign^SIGN_NEG^SIGN_POS, st1_ptr) ) - pop(); - } - return; - } - else - { - /* st(1) contains zero, st(0) valid <> 0 */ - /* Zero is the valid answer */ - char sign = st1_ptr->sign; - - if ( st0_ptr->sign == SIGN_NEG ) - { - /* log(negative) */ - if ( !arith_invalid(st1_ptr) ) - pop(); - return; - } - -#ifdef DENORM_OPERAND - if ( (st0_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - if ( st0_ptr->exp < EXP_BIAS ) sign ^= SIGN_NEG^SIGN_POS; - pop(); st0_ptr = &st(0); - reg_move(&CONST_Z, st0_ptr); - st0_ptr->sign = sign; - return; - } - } - /* One or both arg must be an infinity */ - else if ( st0_tag == TW_Infinity ) - { - if ( (st0_ptr->sign == SIGN_NEG) || (st1_tag == TW_Zero) ) - { - /* log(-infinity) or 0*log(infinity) */ - if ( !arith_invalid(st1_ptr) ) - pop(); - return; - } - else - { - char sign = st1_ptr->sign; - -#ifdef DENORM_OPERAND - if ( (st1_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - pop(); st0_ptr = &st(0); - reg_move(&CONST_INF, st0_ptr); - st0_ptr->sign = sign; - return; - } - } - /* st(1) must be infinity here */ - else if ( (st0_tag == TW_Valid) && (st0_ptr->sign == SIGN_POS) ) - { - if ( st0_ptr->exp >= EXP_BIAS ) - { - if ( (st0_ptr->exp == EXP_BIAS) && - (st0_ptr->sigh == 0x80000000) && - (st0_ptr->sigl == 0) ) - { - /* st(0) holds 1.0 */ - /* infinity*log(1) */ - if ( !arith_invalid(st1_ptr) ) - pop(); - return; - } - /* st(0) is positive and > 1.0 */ - pop(); - } - else - { - /* st(0) is positive and < 1.0 */ - -#ifdef DENORM_OPERAND - if ( (st0_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - st1_ptr->sign ^= SIGN_NEG; - pop(); - } - return; - } - else - { - /* st(0) must be zero or negative */ - if ( st0_ptr->tag == TW_Zero ) - { - /* This should be invalid, but a real 80486 is happy with it. */ -#ifndef PECULIAR_486 - if ( !divide_by_zero(st1_ptr->sign, st1_ptr) ) -#endif PECULIAR_486 - { - st1_ptr->sign ^= SIGN_NEG^SIGN_POS; - pop(); - } - } - else - { - /* log(negative) */ - if ( !arith_invalid(st1_ptr) ) - pop(); - } - return; - } -} - - -static void fpatan(FPU_REG *st0_ptr) -{ - char st0_tag = st0_ptr->tag; - FPU_REG *st1_ptr = &st(1); - char st1_tag = st1_ptr->tag; - - clear_C1(); - if ( !((st0_tag ^ TW_Valid) | (st1_tag ^ TW_Valid)) ) - { -#ifdef DENORM_OPERAND - if ( ((st0_ptr->exp <= EXP_UNDER) || - (st1_ptr->exp <= EXP_UNDER)) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - poly_atan(st0_ptr, st1_ptr, st1_ptr); - - if ( st1_ptr->exp <= EXP_UNDER ) - { - /* A denormal result has been produced. - Precision must have been lost. - This is by definition an underflow. */ - arith_underflow(st1_ptr); - pop(); - return; - } - } - else if ( (st0_tag == TW_Empty) || (st1_tag == TW_Empty) ) - { - stack_underflow_pop(1); - return; - } - else if ( (st0_tag == TW_NaN) || (st1_tag == TW_NaN) ) - { - if ( !real_2op_NaN(st0_ptr, st1_ptr, st1_ptr) ) - pop(); - return; - } - else if ( (st0_tag == TW_Infinity) || (st1_tag == TW_Infinity) ) - { - char sign = st1_ptr->sign; - if ( st0_tag == TW_Infinity ) - { - if ( st1_tag == TW_Infinity ) - { - if ( st0_ptr->sign == SIGN_POS ) - { reg_move(&CONST_PI4, st1_ptr); } - else - reg_add(&CONST_PI4, &CONST_PI2, st1_ptr, FULL_PRECISION); - } - else - { -#ifdef DENORM_OPERAND - if ( st1_tag != TW_Zero ) - { - if ( (st1_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; - } -#endif DENORM_OPERAND - - if ( st0_ptr->sign == SIGN_POS ) - { - reg_move(&CONST_Z, st1_ptr); - st1_ptr->sign = sign; /* An 80486 preserves the sign */ - pop(); - return; - } - else - reg_move(&CONST_PI, st1_ptr); - } - } - else - { - /* st(1) is infinity, st(0) not infinity */ -#ifdef DENORM_OPERAND - if ( st0_tag != TW_Zero ) - { - if ( (st0_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; - } -#endif DENORM_OPERAND - - reg_move(&CONST_PI2, st1_ptr); - } - st1_ptr->sign = sign; - } - else if ( st1_tag == TW_Zero ) - { - /* st(0) must be valid or zero */ - char sign = st1_ptr->sign; - -#ifdef DENORM_OPERAND - if ( st0_tag != TW_Zero ) - { - if ( (st0_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; - } -#endif DENORM_OPERAND - - if ( st0_ptr->sign == SIGN_POS ) - { /* An 80486 preserves the sign */ pop(); return; } - else - reg_move(&CONST_PI, st1_ptr); - st1_ptr->sign = sign; - } - else if ( st0_tag == TW_Zero ) - { - /* st(1) must be TW_Valid here */ - char sign = st1_ptr->sign; - -#ifdef DENORM_OPERAND - if ( (st1_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - reg_move(&CONST_PI2, st1_ptr); - st1_ptr->sign = sign; - } -#ifdef PARANOID - else - EXCEPTION(EX_INTERNAL | 0x125); -#endif PARANOID - - pop(); - set_precision_flag_up(); /* We do not really know if up or down */ -} - - -static void fprem(FPU_REG *st0_ptr) -{ - do_fprem(st0_ptr, RC_CHOP); -} - - -static void fprem1(FPU_REG *st0_ptr) -{ - do_fprem(st0_ptr, RC_RND); -} - - -static void fyl2xp1(FPU_REG *st0_ptr) -{ - char st0_tag = st0_ptr->tag, sign; - FPU_REG *st1_ptr = &st(1); - char st1_tag = st1_ptr->tag; - - clear_C1(); - if ( !((st0_tag ^ TW_Valid) | (st1_tag ^ TW_Valid)) ) - { -#ifdef DENORM_OPERAND - if ( ((st0_ptr->exp <= EXP_UNDER) || - (st1_ptr->exp <= EXP_UNDER)) && denormal_operand() ) - return; -#endif DENORM_OPERAND - - if ( poly_l2p1(st0_ptr, st1_ptr, st1_ptr) ) - { -#ifdef PECULIAR_486 /* Stupid 80486 doesn't worry about log(negative). */ - st1_ptr->sign ^= SIGN_POS^SIGN_NEG; -#else - if ( arith_invalid(st1_ptr) ) /* poly_l2p1() returned invalid */ - return; -#endif PECULIAR_486 - } - if ( st1_ptr->exp <= EXP_UNDER ) - { - /* A denormal result has been produced. - Precision must have been lost, this is always - an underflow. */ - sign = st1_ptr->sign; - arith_underflow(st1_ptr); - st1_ptr->sign = sign; - } - else - set_precision_flag_up(); /* 80486 appears to always do this */ - pop(); - return; - } - else if ( (st0_tag == TW_Empty) | (st1_tag == TW_Empty) ) - { - stack_underflow_pop(1); - return; - } - else if ( st0_tag == TW_Zero ) - { - if ( st1_tag <= TW_Zero ) - { -#ifdef DENORM_OPERAND - if ( (st1_tag == TW_Valid) && (st1_ptr->exp <= EXP_UNDER) && - (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - st0_ptr->sign ^= st1_ptr->sign; - reg_move(st0_ptr, st1_ptr); - } - else if ( st1_tag == TW_Infinity ) - { - /* Infinity*log(1) */ - if ( !arith_invalid(st1_ptr) ) - pop(); - return; - } - else if ( st1_tag == TW_NaN ) - { - if ( !real_2op_NaN(st0_ptr, st1_ptr, st1_ptr) ) - pop(); - return; - } -#ifdef PARANOID - else - { - EXCEPTION(EX_INTERNAL | 0x116); - return; - } -#endif PARANOID - pop(); return; - } - else if ( st0_tag == TW_Valid ) - { - if ( st1_tag == TW_Zero ) - { - if ( st0_ptr->sign == SIGN_NEG ) - { - if ( st0_ptr->exp >= EXP_BIAS ) - { - /* st(0) holds <= -1.0 */ -#ifdef PECULIAR_486 /* Stupid 80486 doesn't worry about log(negative). */ - st1_ptr->sign ^= SIGN_POS^SIGN_NEG; -#else - if ( arith_invalid(st1_ptr) ) return; -#endif PECULIAR_486 - pop(); return; - } -#ifdef DENORM_OPERAND - if ( (st0_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - st1_ptr->sign ^= SIGN_POS^SIGN_NEG; - pop(); return; - } -#ifdef DENORM_OPERAND - if ( (st0_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - pop(); return; - } - if ( st1_tag == TW_Infinity ) - { - if ( st0_ptr->sign == SIGN_NEG ) - { - if ( (st0_ptr->exp >= EXP_BIAS) && - !((st0_ptr->sigh == 0x80000000) && - (st0_ptr->sigl == 0)) ) - { - /* st(0) holds < -1.0 */ -#ifdef PECULIAR_486 /* Stupid 80486 doesn't worry about log(negative). */ - st1_ptr->sign ^= SIGN_POS^SIGN_NEG; -#else - if ( arith_invalid(st1_ptr) ) return; -#endif PECULIAR_486 - pop(); return; - } -#ifdef DENORM_OPERAND - if ( (st0_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - st1_ptr->sign ^= SIGN_POS^SIGN_NEG; - pop(); return; - } -#ifdef DENORM_OPERAND - if ( (st0_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - pop(); return; - } - if ( st1_tag == TW_NaN ) - { - if ( !real_2op_NaN(st0_ptr, st1_ptr, st1_ptr) ) - pop(); - return; - } - } - else if ( st0_tag == TW_NaN ) - { - if ( !real_2op_NaN(st0_ptr, st1_ptr, st1_ptr) ) - pop(); - return; - } - else if ( st0_tag == TW_Infinity ) - { - if ( st1_tag == TW_NaN ) - { - if ( !real_2op_NaN(st0_ptr, st1_ptr, st1_ptr) ) - pop(); - return; - } - else if ( st0_ptr->sign == SIGN_NEG ) - { - int exponent = st1_ptr->exp; -#ifndef PECULIAR_486 - /* This should have higher priority than denormals, but... */ - if ( arith_invalid(st1_ptr) ) /* log(-infinity) */ - return; -#endif PECULIAR_486 -#ifdef DENORM_OPERAND - if ( st1_tag != TW_Zero ) - { - if ( (exponent <= EXP_UNDER) && (denormal_operand()) ) - return; - } -#endif DENORM_OPERAND -#ifdef PECULIAR_486 - /* Denormal operands actually get higher priority */ - if ( arith_invalid(st1_ptr) ) /* log(-infinity) */ - return; -#endif PECULIAR_486 - pop(); - return; - } - else if ( st1_tag == TW_Zero ) - { - /* log(infinity) */ - if ( !arith_invalid(st1_ptr) ) - pop(); - return; - } - - /* st(1) must be valid here. */ - -#ifdef DENORM_OPERAND - if ( (st1_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - /* The Manual says that log(Infinity) is invalid, but a real - 80486 sensibly says that it is o.k. */ - { char sign = st1_ptr->sign; - reg_move(&CONST_INF, st1_ptr); - st1_ptr->sign = sign; - } - pop(); - return; - } -#ifdef PARANOID - else - { - EXCEPTION(EX_INTERNAL | 0x117); - } -#endif PARANOID -} - - -static void fscale(FPU_REG *st0_ptr) -{ - char st0_tag = st0_ptr->tag; - FPU_REG *st1_ptr = &st(1); - char st1_tag = st1_ptr->tag; - int old_cw = control_word; - char sign = st0_ptr->sign; - - clear_C1(); - if ( !((st0_tag ^ TW_Valid) | (st1_tag ^ TW_Valid)) ) - { - long scale; - FPU_REG tmp; - -#ifdef DENORM_OPERAND - if ( ((st0_ptr->exp <= EXP_UNDER) || - (st1_ptr->exp <= EXP_UNDER)) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - if ( st1_ptr->exp > EXP_BIAS + 30 ) - { - /* 2^31 is far too large, would require 2^(2^30) or 2^(-2^30) */ - char sign; - - if ( st1_ptr->sign == SIGN_POS ) - { - EXCEPTION(EX_Overflow); - sign = st0_ptr->sign; - reg_move(&CONST_INF, st0_ptr); - st0_ptr->sign = sign; - } - else - { - EXCEPTION(EX_Underflow); - sign = st0_ptr->sign; - reg_move(&CONST_Z, st0_ptr); - st0_ptr->sign = sign; - } - return; - } - - control_word &= ~CW_RC; - control_word |= RC_CHOP; - reg_move(st1_ptr, &tmp); - round_to_int(&tmp); /* This can never overflow here */ - control_word = old_cw; - scale = st1_ptr->sign ? -tmp.sigl : tmp.sigl; - scale += st0_ptr->exp; - st0_ptr->exp = scale; - - /* Use round_reg() to properly detect under/overflow etc */ - round_reg(st0_ptr, 0, control_word); - - return; - } - else if ( st0_tag == TW_Valid ) - { - if ( st1_tag == TW_Zero ) - { - -#ifdef DENORM_OPERAND - if ( (st0_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - return; - } - if ( st1_tag == TW_Infinity ) - { -#ifdef DENORM_OPERAND - if ( (st0_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - if ( st1_ptr->sign == SIGN_POS ) - { reg_move(&CONST_INF, st0_ptr); } - else - reg_move(&CONST_Z, st0_ptr); - st0_ptr->sign = sign; - return; - } - if ( st1_tag == TW_NaN ) - { real_2op_NaN(st0_ptr, st1_ptr, st0_ptr); return; } - } - else if ( st0_tag == TW_Zero ) - { - if ( st1_tag == TW_Valid ) - { - -#ifdef DENORM_OPERAND - if ( (st1_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - return; - } - else if ( st1_tag == TW_Zero ) { return; } - else if ( st1_tag == TW_Infinity ) - { - if ( st1_ptr->sign == SIGN_NEG ) - return; - else - { - arith_invalid(st0_ptr); /* Zero scaled by +Infinity */ - return; - } - } - else if ( st1_tag == TW_NaN ) - { real_2op_NaN(st0_ptr, st1_ptr, st0_ptr); return; } - } - else if ( st0_tag == TW_Infinity ) - { - if ( st1_tag == TW_Valid ) - { - -#ifdef DENORM_OPERAND - if ( (st1_ptr->exp <= EXP_UNDER) && (denormal_operand()) ) - return; -#endif DENORM_OPERAND - - return; - } - if ( ((st1_tag == TW_Infinity) && (st1_ptr->sign == SIGN_POS)) - || (st1_tag == TW_Zero) ) - return; - else if ( st1_tag == TW_Infinity ) - { - arith_invalid(st0_ptr); /* Infinity scaled by -Infinity */ - return; - } - else if ( st1_tag == TW_NaN ) - { real_2op_NaN(st0_ptr, st1_ptr, st0_ptr); return; } - } - else if ( st0_tag == TW_NaN ) - { - if ( st1_tag != TW_Empty ) - { real_2op_NaN(st0_ptr, st1_ptr, st0_ptr); return; } - } - -#ifdef PARANOID - if ( !((st0_tag == TW_Empty) || (st1_tag == TW_Empty)) ) - { - EXCEPTION(EX_INTERNAL | 0x115); - return; - } -#endif - - /* At least one of st(0), st(1) must be empty */ - stack_underflow(); - -} - - -/*---------------------------------------------------------------------------*/ - -static FUNC_ST0 const trig_table_a[] = { - f2xm1, fyl2x, fptan, fpatan, fxtract, fprem1, fdecstp, fincstp -}; - -void trig_a(void) -{ - (trig_table_a[FPU_rm])(&st(0)); -} - - -static FUNC_ST0 const trig_table_b[] = - { - fprem, fyl2xp1, fsqrt_, fsincos, frndint_, fscale, fsin, fcos - }; - -void trig_b(void) -{ - (trig_table_b[FPU_rm])(&st(0)); -} diff --git a/drivers/FPU-emu/get_address.c b/drivers/FPU-emu/get_address.c deleted file mode 100644 index 6f3270ae3..000000000 --- a/drivers/FPU-emu/get_address.c +++ /dev/null @@ -1,423 +0,0 @@ -/*---------------------------------------------------------------------------+ - | get_address.c | - | | - | Get the effective address from an FPU instruction. | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------+ - | Note: | - | The file contains code which accesses user memory. | - | Emulator static data may change when user memory is accessed, due to | - | other processes using the emulator while swapping is in progress. | - +---------------------------------------------------------------------------*/ - - -#include -#include - -#include - -#include "fpu_system.h" -#include "exception.h" -#include "fpu_emu.h" - - -#define FPU_WRITE_BIT 0x10 - -static int reg_offset[] = { - offsetof(struct info,___eax), - offsetof(struct info,___ecx), - offsetof(struct info,___edx), - offsetof(struct info,___ebx), - offsetof(struct info,___esp), - offsetof(struct info,___ebp), - offsetof(struct info,___esi), - offsetof(struct info,___edi) -}; - -#define REG_(x) (*(long *)(reg_offset[(x)]+(char *) FPU_info)) - -static int reg_offset_vm86[] = { - offsetof(struct info,___cs), - offsetof(struct info,___vm86_ds), - offsetof(struct info,___vm86_es), - offsetof(struct info,___vm86_fs), - offsetof(struct info,___vm86_gs), - offsetof(struct info,___ss), - offsetof(struct info,___vm86_ds) - }; - -#define VM86_REG_(x) (*(unsigned short *) \ - (reg_offset_vm86[((unsigned)x)]+(char *) FPU_info)) - -static int reg_offset_pm[] = { - offsetof(struct info,___cs), - offsetof(struct info,___ds), - offsetof(struct info,___es), - offsetof(struct info,___fs), - offsetof(struct info,___gs), - offsetof(struct info,___ss), - offsetof(struct info,___ds) - }; - -#define PM_REG_(x) (*(unsigned short *) \ - (reg_offset_pm[((unsigned)x)]+(char *) FPU_info)) - - -/* Decode the SIB byte. This function assumes mod != 0 */ -static int sib(int mod, unsigned long *fpu_eip) -{ - unsigned char ss,index,base; - long offset; - - RE_ENTRANT_CHECK_OFF; - FPU_code_verify_area(1); - base = get_fs_byte((char *) (*fpu_eip)); /* The SIB byte */ - RE_ENTRANT_CHECK_ON; - (*fpu_eip)++; - ss = base >> 6; - index = (base >> 3) & 7; - base &= 7; - - if ((mod == 0) && (base == 5)) - offset = 0; /* No base register */ - else - offset = REG_(base); - - if (index == 4) - { - /* No index register */ - /* A non-zero ss is illegal */ - if ( ss ) - EXCEPTION(EX_Invalid); - } - else - { - offset += (REG_(index)) << ss; - } - - if (mod == 1) - { - /* 8 bit signed displacement */ - RE_ENTRANT_CHECK_OFF; - FPU_code_verify_area(1); - offset += (signed char) get_fs_byte((char *) (*fpu_eip)); - RE_ENTRANT_CHECK_ON; - (*fpu_eip)++; - } - else if (mod == 2 || base == 5) /* The second condition also has mod==0 */ - { - /* 32 bit displacement */ - RE_ENTRANT_CHECK_OFF; - FPU_code_verify_area(4); - offset += (signed) get_fs_long((unsigned long *) (*fpu_eip)); - RE_ENTRANT_CHECK_ON; - (*fpu_eip) += 4; - } - - return offset; -} - - -static unsigned long vm86_segment(unsigned char segment, - unsigned short *selector) -{ - segment--; -#ifdef PARANOID - if ( segment > PREFIX_SS_ ) - { - EXCEPTION(EX_INTERNAL|0x130); - math_abort(FPU_info,SIGSEGV); - } -#endif PARANOID - *selector = VM86_REG_(segment); - return (unsigned long)VM86_REG_(segment) << 4; -} - - -/* This should work for 16 and 32 bit protected mode. */ -static long pm_address(unsigned char FPU_modrm, unsigned char segment, - unsigned short *selector, long offset) -{ - struct desc_struct descriptor; - unsigned long base_address, limit, address, seg_top; - - segment--; -#ifdef PARANOID - if ( segment > PREFIX_SS_ ) - { - EXCEPTION(EX_INTERNAL|0x132); - math_abort(FPU_info,SIGSEGV); - } -#endif PARANOID - - *selector = PM_REG_(segment); - - descriptor = LDT_DESCRIPTOR(PM_REG_(segment)); - base_address = SEG_BASE_ADDR(descriptor); - address = base_address + offset; - limit = base_address - + (SEG_LIMIT(descriptor)+1) * SEG_GRANULARITY(descriptor) - 1; - if ( limit < base_address ) limit = 0xffffffff; - - if ( SEG_EXPAND_DOWN(descriptor) ) - { - if ( SEG_G_BIT(descriptor) ) - seg_top = 0xffffffff; - else - { - seg_top = base_address + (1 << 20); - if ( seg_top < base_address ) seg_top = 0xffffffff; - } - access_limit = - (address <= limit) || (address >= seg_top) ? 0 : - ((seg_top-address) >= 255 ? 255 : seg_top-address); - } - else - { - access_limit = - (address > limit) || (address < base_address) ? 0 : - ((limit-address) >= 254 ? 255 : limit-address+1); - } - if ( SEG_EXECUTE_ONLY(descriptor) || - (!SEG_WRITE_PERM(descriptor) && (FPU_modrm & FPU_WRITE_BIT)) ) - { - access_limit = 0; - } - return address; -} - - -/* - MOD R/M byte: MOD == 3 has a special use for the FPU - SIB byte used iff R/M = 100b - - 7 6 5 4 3 2 1 0 - ..... ......... ......... - MOD OPCODE(2) R/M - - - SIB byte - - 7 6 5 4 3 2 1 0 - ..... ......... ......... - SS INDEX BASE - -*/ - -void *get_address(unsigned char FPU_modrm, unsigned long *fpu_eip, - struct address *addr, -/* unsigned short *selector, unsigned long *offset, */ - fpu_addr_modes addr_modes) -{ - unsigned char mod; - unsigned rm = FPU_modrm & 7; - long *cpu_reg_ptr; - int address = 0; /* Initialized just to stop compiler warnings. */ - - /* Memory accessed via the cs selector is write protected - in `non-segmented' 32 bit protected mode. */ - if ( !addr_modes.default_mode && (FPU_modrm & FPU_WRITE_BIT) - && (addr_modes.override.segment == PREFIX_CS_) ) - { - math_abort(FPU_info,SIGSEGV); - } - - addr->selector = FPU_DS; /* Default, for 32 bit non-segmented mode. */ - - mod = (FPU_modrm >> 6) & 3; - - if (rm == 4 && mod != 3) - { - address = sib(mod, fpu_eip); - } - else - { - cpu_reg_ptr = & REG_(rm); - switch (mod) - { - case 0: - if (rm == 5) - { - /* Special case: disp32 */ - RE_ENTRANT_CHECK_OFF; - FPU_code_verify_area(4); - address = get_fs_long((unsigned long *) (*fpu_eip)); - (*fpu_eip) += 4; - RE_ENTRANT_CHECK_ON; - addr->offset = address; - return (void *) address; - } - else - { - address = *cpu_reg_ptr; /* Just return the contents - of the cpu register */ - addr->offset = address; - return (void *) address; - } - case 1: - /* 8 bit signed displacement */ - RE_ENTRANT_CHECK_OFF; - FPU_code_verify_area(1); - address = (signed char) get_fs_byte((char *) (*fpu_eip)); - RE_ENTRANT_CHECK_ON; - (*fpu_eip)++; - break; - case 2: - /* 32 bit displacement */ - RE_ENTRANT_CHECK_OFF; - FPU_code_verify_area(4); - address = (signed) get_fs_long((unsigned long *) (*fpu_eip)); - (*fpu_eip) += 4; - RE_ENTRANT_CHECK_ON; - break; - case 3: - /* Not legal for the FPU */ - EXCEPTION(EX_Invalid); - } - address += *cpu_reg_ptr; - } - - addr->offset = address; - - switch ( addr_modes.default_mode ) - { - case 0: - break; - case VM86: - address += vm86_segment(addr_modes.override.segment, - (unsigned short *)&(addr->selector)); - break; - case PM16: - case SEG32: - address = pm_address(FPU_modrm, addr_modes.override.segment, - (unsigned short *)&(addr->selector), address); - break; - default: - EXCEPTION(EX_INTERNAL|0x133); - } - - return (void *)address; -} - - -void *get_address_16(unsigned char FPU_modrm, unsigned long *fpu_eip, - struct address *addr, -/* unsigned short *selector, unsigned long *offset, */ - fpu_addr_modes addr_modes) -{ - unsigned char mod; - unsigned rm = FPU_modrm & 7; - int address = 0; /* Default used for mod == 0 */ - - /* Memory accessed via the cs selector is write protected - in `non-segmented' 32 bit protected mode. */ - if ( !addr_modes.default_mode && (FPU_modrm & FPU_WRITE_BIT) - && (addr_modes.override.segment == PREFIX_CS_) ) - { - math_abort(FPU_info,SIGSEGV); - } - - addr->selector = FPU_DS; /* Default, for 32 bit non-segmented mode. */ - - mod = (FPU_modrm >> 6) & 3; - - switch (mod) - { - case 0: - if (rm == 6) - { - /* Special case: disp16 */ - RE_ENTRANT_CHECK_OFF; - FPU_code_verify_area(2); - address = (unsigned short)get_fs_word((unsigned short *) (*fpu_eip)); - (*fpu_eip) += 2; - RE_ENTRANT_CHECK_ON; - goto add_segment; - } - break; - case 1: - /* 8 bit signed displacement */ - RE_ENTRANT_CHECK_OFF; - FPU_code_verify_area(1); - address = (signed char) get_fs_byte((signed char *) (*fpu_eip)); - RE_ENTRANT_CHECK_ON; - (*fpu_eip)++; - break; - case 2: - /* 16 bit displacement */ - RE_ENTRANT_CHECK_OFF; - FPU_code_verify_area(2); - address = (unsigned) get_fs_word((unsigned short *) (*fpu_eip)); - (*fpu_eip) += 2; - RE_ENTRANT_CHECK_ON; - break; - case 3: - /* Not legal for the FPU */ - EXCEPTION(EX_Invalid); - break; - } - switch ( rm ) - { - case 0: - address += FPU_info->___ebx + FPU_info->___esi; - break; - case 1: - address += FPU_info->___ebx + FPU_info->___edi; - break; - case 2: - address += FPU_info->___ebp + FPU_info->___esi; - if ( addr_modes.override.segment == PREFIX_DEFAULT ) - addr_modes.override.segment = PREFIX_SS_; - break; - case 3: - address += FPU_info->___ebp + FPU_info->___edi; - if ( addr_modes.override.segment == PREFIX_DEFAULT ) - addr_modes.override.segment = PREFIX_SS_; - break; - case 4: - address += FPU_info->___esi; - break; - case 5: - address += FPU_info->___edi; - break; - case 6: - address += FPU_info->___ebp; - if ( addr_modes.override.segment == PREFIX_DEFAULT ) - addr_modes.override.segment = PREFIX_SS_; - break; - case 7: - address += FPU_info->___ebx; - break; - } - - add_segment: - address &= 0xffff; - - addr->offset = address; - - switch ( addr_modes.default_mode ) - { - case 0: - break; - case VM86: - address += vm86_segment(addr_modes.override.segment, - (unsigned short *)&(addr->selector)); - break; - case PM16: - case SEG32: - address = pm_address(FPU_modrm, addr_modes.override.segment, - (unsigned short *)&(addr->selector), address); - break; - default: - EXCEPTION(EX_INTERNAL|0x131); - } - - return (void *)address ; -} diff --git a/drivers/FPU-emu/load_store.c b/drivers/FPU-emu/load_store.c deleted file mode 100644 index 6f0e167d6..000000000 --- a/drivers/FPU-emu/load_store.c +++ /dev/null @@ -1,260 +0,0 @@ -/*---------------------------------------------------------------------------+ - | load_store.c | - | | - | This file contains most of the code to interpret the FPU instructions | - | which load and store from user memory. | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------+ - | Note: | - | The file contains code which accesses user memory. | - | Emulator static data may change when user memory is accessed, due to | - | other processes using the emulator while swapping is in progress. | - +---------------------------------------------------------------------------*/ - -#include - -#include "fpu_system.h" -#include "exception.h" -#include "fpu_emu.h" -#include "status_w.h" -#include "control_w.h" - - -#define _NONE_ 0 /* st0_ptr etc not needed */ -#define _REG0_ 1 /* Will be storing st(0) */ -#define _PUSH_ 3 /* Need to check for space to push onto stack */ -#define _null_ 4 /* Function illegal or not implemented */ - -#define pop_0() { st0_ptr->tag = TW_Empty; top++; } - - -static unsigned char const type_table[32] = { - _PUSH_, _PUSH_, _PUSH_, _PUSH_, - _null_, _null_, _null_, _null_, - _REG0_, _REG0_, _REG0_, _REG0_, - _REG0_, _REG0_, _REG0_, _REG0_, - _NONE_, _null_, _NONE_, _PUSH_, - _NONE_, _PUSH_, _null_, _PUSH_, - _NONE_, _null_, _NONE_, _REG0_, - _NONE_, _REG0_, _NONE_, _REG0_ - }; - -unsigned char const data_sizes_16[32] = { - 4, 4, 8, 2, 0, 0, 0, 0, - 4, 4, 8, 2, 4, 4, 8, 2, - 14, 0, 94, 10, 2, 10, 0, 8, - 14, 0, 94, 10, 2, 10, 2, 8 -}; - -unsigned char const data_sizes_32[32] = { - 4, 4, 8, 2, 0, 0, 0, 0, - 4, 4, 8, 2, 4, 4, 8, 2, - 28, 0,108, 10, 2, 10, 0, 8, - 28, 0,108, 10, 2, 10, 2, 8 -}; - -int load_store_instr(unsigned char type, fpu_addr_modes addr_modes, - void *data_address) -{ - FPU_REG loaded_data; - FPU_REG *st0_ptr; - - st0_ptr = NULL; /* Initialized just to stop compiler warnings. */ - - if ( addr_modes.default_mode & PROTECTED ) - { - if ( addr_modes.default_mode == SEG32 ) - { - if ( access_limit < data_sizes_32[type] ) - math_abort(FPU_info,SIGSEGV); - } - else if ( addr_modes.default_mode == PM16 ) - { - if ( access_limit < data_sizes_16[type] ) - math_abort(FPU_info,SIGSEGV); - } -#ifdef PARANOID - else - EXCEPTION(EX_INTERNAL|0x140); -#endif PARANOID - } - - switch ( type_table[type] ) - { - case _NONE_: - break; - case _REG0_: - st0_ptr = &st(0); /* Some of these instructions pop after - storing */ - break; - case _PUSH_: - { - st0_ptr = &st(-1); - if ( st0_ptr->tag != TW_Empty ) - { stack_overflow(); return 0; } - top--; - } - break; - case _null_: - FPU_illegal(); - return 0; -#ifdef PARANOID - default: - EXCEPTION(EX_INTERNAL|0x141); - return 0; -#endif PARANOID - } - - switch ( type ) - { - case 000: /* fld m32real */ - clear_C1(); - reg_load_single((float *)data_address, &loaded_data); - if ( (loaded_data.tag == TW_NaN) && - real_2op_NaN(&loaded_data, &loaded_data, &loaded_data) ) - { - top++; - break; - } - reg_move(&loaded_data, st0_ptr); - break; - case 001: /* fild m32int */ - clear_C1(); - reg_load_int32((long *)data_address, st0_ptr); - break; - case 002: /* fld m64real */ - clear_C1(); - reg_load_double((double *)data_address, &loaded_data); - if ( (loaded_data.tag == TW_NaN) && - real_2op_NaN(&loaded_data, &loaded_data, &loaded_data) ) - { - top++; - break; - } - reg_move(&loaded_data, st0_ptr); - break; - case 003: /* fild m16int */ - clear_C1(); - reg_load_int16((short *)data_address, st0_ptr); - break; - case 010: /* fst m32real */ - clear_C1(); - reg_store_single((float *)data_address, st0_ptr); - break; - case 011: /* fist m32int */ - clear_C1(); - reg_store_int32((long *)data_address, st0_ptr); - break; - case 012: /* fst m64real */ - clear_C1(); - reg_store_double((double *)data_address, st0_ptr); - break; - case 013: /* fist m16int */ - clear_C1(); - reg_store_int16((short *)data_address, st0_ptr); - break; - case 014: /* fstp m32real */ - clear_C1(); - if ( reg_store_single((float *)data_address, st0_ptr) ) - pop_0(); /* pop only if the number was actually stored - (see the 80486 manual p16-28) */ - break; - case 015: /* fistp m32int */ - clear_C1(); - if ( reg_store_int32((long *)data_address, st0_ptr) ) - pop_0(); /* pop only if the number was actually stored - (see the 80486 manual p16-28) */ - break; - case 016: /* fstp m64real */ - clear_C1(); - if ( reg_store_double((double *)data_address, st0_ptr) ) - pop_0(); /* pop only if the number was actually stored - (see the 80486 manual p16-28) */ - break; - case 017: /* fistp m16int */ - clear_C1(); - if ( reg_store_int16((short *)data_address, st0_ptr) ) - pop_0(); /* pop only if the number was actually stored - (see the 80486 manual p16-28) */ - break; - case 020: /* fldenv m14/28byte */ - fldenv(addr_modes, (char *)data_address); - /* Ensure that the values just loaded are not changed by - fix-up operations. */ - return 1; - case 022: /* frstor m94/108byte */ - frstor(addr_modes, (char *)data_address); - /* Ensure that the values just loaded are not changed by - fix-up operations. */ - return 1; - case 023: /* fbld m80dec */ - clear_C1(); - reg_load_bcd((char *)data_address, st0_ptr); - break; - case 024: /* fldcw */ - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_READ, data_address, 2); - control_word = get_fs_word((unsigned short *) data_address); - RE_ENTRANT_CHECK_ON; - if ( partial_status & ~control_word & CW_Exceptions ) - partial_status |= (SW_Summary | SW_Backward); - else - partial_status &= ~(SW_Summary | SW_Backward); -#ifdef PECULIAR_486 - control_word |= 0x40; /* An 80486 appears to always set this bit */ -#endif PECULIAR_486 - return 1; - case 025: /* fld m80real */ - clear_C1(); - reg_load_extended((long double *)data_address, st0_ptr); - break; - case 027: /* fild m64int */ - clear_C1(); - reg_load_int64((long long *)data_address, st0_ptr); - break; - case 030: /* fstenv m14/28byte */ - fstenv(addr_modes, (char *)data_address); - return 1; - case 032: /* fsave */ - fsave(addr_modes, (char *)data_address); - return 1; - case 033: /* fbstp m80dec */ - clear_C1(); - if ( reg_store_bcd((char *)data_address, st0_ptr) ) - pop_0(); /* pop only if the number was actually stored - (see the 80486 manual p16-28) */ - break; - case 034: /* fstcw m16int */ - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_WRITE,data_address,2); - put_fs_word(control_word, (short *) data_address); - RE_ENTRANT_CHECK_ON; - return 1; - case 035: /* fstp m80real */ - clear_C1(); - if ( reg_store_extended((long double *)data_address, st0_ptr) ) - pop_0(); /* pop only if the number was actually stored - (see the 80486 manual p16-28) */ - break; - case 036: /* fstsw m2byte */ - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_WRITE,data_address,2); - put_fs_word(status_word(),(short *) data_address); - RE_ENTRANT_CHECK_ON; - return 1; - case 037: /* fistp m64int */ - clear_C1(); - if ( reg_store_int64((long long *)data_address, st0_ptr) ) - pop_0(); /* pop only if the number was actually stored - (see the 80486 manual p16-28) */ - break; - } - return 0; -} diff --git a/drivers/FPU-emu/mul_Xsig.S b/drivers/FPU-emu/mul_Xsig.S deleted file mode 100644 index 1d88d4466..000000000 --- a/drivers/FPU-emu/mul_Xsig.S +++ /dev/null @@ -1,182 +0,0 @@ -/*---------------------------------------------------------------------------+ - | mul_Xsig.S | - | | - | Multiply a 12 byte fixed point number by another fixed point number. | - | | - | Copyright (C) 1992,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | Call from C as: | - | void mul32_Xsig(Xsig *x, unsigned b) | - | | - | void mul64_Xsig(Xsig *x, unsigned long long *b) | - | | - | void mul_Xsig_Xsig(Xsig *x, unsigned *b) | - | | - | The result is neither rounded nor normalized, and the ls bit or so may | - | be wrong. | - | | - +---------------------------------------------------------------------------*/ - .file "mul_Xsig.S" - - -#include "fpu_asm.h" - -.text - .align 2,144 -.globl _mul32_Xsig -_mul32_Xsig: - pushl %ebp - movl %esp,%ebp - subl $16,%esp - pushl %esi - - movl PARAM1,%esi - movl PARAM2,%ecx - - xor %eax,%eax - movl %eax,-4(%ebp) - movl %eax,-8(%ebp) - - movl (%esi),%eax /* lsl of Xsig */ - mull %ecx /* msl of b */ - movl %edx,-12(%ebp) - - movl 4(%esi),%eax /* midl of Xsig */ - mull %ecx /* msl of b */ - addl %eax,-12(%ebp) - adcl %edx,-8(%ebp) - adcl $0,-4(%ebp) - - movl 8(%esi),%eax /* msl of Xsig */ - mull %ecx /* msl of b */ - addl %eax,-8(%ebp) - adcl %edx,-4(%ebp) - - movl -12(%ebp),%eax - movl %eax,(%esi) - movl -8(%ebp),%eax - movl %eax,4(%esi) - movl -4(%ebp),%eax - movl %eax,8(%esi) - - popl %esi - leave - ret - - - .align 2,144 -.globl _mul64_Xsig -_mul64_Xsig: - pushl %ebp - movl %esp,%ebp - subl $16,%esp - pushl %esi - - movl PARAM1,%esi - movl PARAM2,%ecx - - xor %eax,%eax - movl %eax,-4(%ebp) - movl %eax,-8(%ebp) - - movl (%esi),%eax /* lsl of Xsig */ - mull 4(%ecx) /* msl of b */ - movl %edx,-12(%ebp) - - movl 4(%esi),%eax /* midl of Xsig */ - mull (%ecx) /* lsl of b */ - addl %edx,-12(%ebp) - adcl $0,-8(%ebp) - adcl $0,-4(%ebp) - - movl 4(%esi),%eax /* midl of Xsig */ - mull 4(%ecx) /* msl of b */ - addl %eax,-12(%ebp) - adcl %edx,-8(%ebp) - adcl $0,-4(%ebp) - - movl 8(%esi),%eax /* msl of Xsig */ - mull (%ecx) /* lsl of b */ - addl %eax,-12(%ebp) - adcl %edx,-8(%ebp) - adcl $0,-4(%ebp) - - movl 8(%esi),%eax /* msl of Xsig */ - mull 4(%ecx) /* msl of b */ - addl %eax,-8(%ebp) - adcl %edx,-4(%ebp) - - movl -12(%ebp),%eax - movl %eax,(%esi) - movl -8(%ebp),%eax - movl %eax,4(%esi) - movl -4(%ebp),%eax - movl %eax,8(%esi) - - popl %esi - leave - ret - - - - .align 2,144 -.globl _mul_Xsig_Xsig -_mul_Xsig_Xsig: - pushl %ebp - movl %esp,%ebp - subl $16,%esp - pushl %esi - - movl PARAM1,%esi - movl PARAM2,%ecx - - xor %eax,%eax - movl %eax,-4(%ebp) - movl %eax,-8(%ebp) - - movl (%esi),%eax /* lsl of Xsig */ - mull 8(%ecx) /* msl of b */ - movl %edx,-12(%ebp) - - movl 4(%esi),%eax /* midl of Xsig */ - mull 4(%ecx) /* midl of b */ - addl %edx,-12(%ebp) - adcl $0,-8(%ebp) - adcl $0,-4(%ebp) - - movl 8(%esi),%eax /* msl of Xsig */ - mull (%ecx) /* lsl of b */ - addl %edx,-12(%ebp) - adcl $0,-8(%ebp) - adcl $0,-4(%ebp) - - movl 4(%esi),%eax /* midl of Xsig */ - mull 8(%ecx) /* msl of b */ - addl %eax,-12(%ebp) - adcl %edx,-8(%ebp) - adcl $0,-4(%ebp) - - movl 8(%esi),%eax /* msl of Xsig */ - mull 4(%ecx) /* midl of b */ - addl %eax,-12(%ebp) - adcl %edx,-8(%ebp) - adcl $0,-4(%ebp) - - movl 8(%esi),%eax /* msl of Xsig */ - mull 8(%ecx) /* msl of b */ - addl %eax,-8(%ebp) - adcl %edx,-4(%ebp) - - movl -12(%ebp),%edx - movl %edx,(%esi) - movl -8(%ebp),%edx - movl %edx,4(%esi) - movl -4(%ebp),%edx - movl %edx,8(%esi) - - popl %esi - leave - ret - diff --git a/drivers/FPU-emu/poly.h b/drivers/FPU-emu/poly.h deleted file mode 100644 index 397cb9e3e..000000000 --- a/drivers/FPU-emu/poly.h +++ /dev/null @@ -1,116 +0,0 @@ -/*---------------------------------------------------------------------------+ - | poly.h | - | | - | Header file for the FPU-emu poly*.c source files. | - | | - | Copyright (C) 1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | Declarations and definitions for functions operating on Xsig (12-byte | - | extended-significand) quantities. | - | | - +---------------------------------------------------------------------------*/ - -#ifndef _POLY_H -#define _POLY_H - -/* This 12-byte structure is used to improve the accuracy of computation - of transcendental functions. - Intended to be used to get results better than 8-byte computation - allows. 9-byte would probably be sufficient. - */ -typedef struct { - unsigned long lsw; - unsigned long midw; - unsigned long msw; -} Xsig; - -asmlinkage void mul64(unsigned long long const *a, unsigned long long const *b, - unsigned long long *result); -asmlinkage void polynomial_Xsig(Xsig *, const unsigned long long *x, - const unsigned long long terms[], const int n); - -asmlinkage void mul32_Xsig(Xsig *, const unsigned long mult); -asmlinkage void mul64_Xsig(Xsig *, const unsigned long long *mult); -asmlinkage void mul_Xsig_Xsig(Xsig *dest, const Xsig *mult); - -asmlinkage void shr_Xsig(Xsig *, const int n); -asmlinkage int round_Xsig(Xsig *); -asmlinkage int norm_Xsig(Xsig *); -asmlinkage void div_Xsig(Xsig *x1, const Xsig *x2, const Xsig *dest); - -/* Macro to extract the most significant 32 bits from a long long */ -#define LL_MSW(x) (((unsigned long *)&x)[1]) - -/* Macro to initialize an Xsig struct */ -#define MK_XSIG(a,b,c) { c, b, a } - -/* Macro to access the 8 ms bytes of an Xsig as a long long */ -#define XSIG_LL(x) (*(unsigned long long *)&x.midw) - - -/* - Need to run gcc with optimizations on to get these to - actually be in-line. - */ - -/* Multiply two fixed-point 32 bit numbers. */ -extern inline void mul_32_32(const unsigned long arg1, - const unsigned long arg2, - unsigned long *out) -{ - asm volatile ("movl %1,%%eax; mull %2; movl %%edx,%0" \ - :"=g" (*out) \ - :"g" (arg1), "g" (arg2) \ - :"ax","dx"); -} - - -/* Add the 12 byte Xsig x2 to Xsig dest, with no checks for overflow. */ -extern inline void add_Xsig_Xsig(Xsig *dest, const Xsig *x2) -{ - asm volatile ("movl %1,%%edi; movl %2,%%esi; - movl (%%esi),%%eax; addl %%eax,(%%edi); - movl 4(%%esi),%%eax; adcl %%eax,4(%%edi); - movl 8(%%esi),%%eax; adcl %%eax,8(%%edi);" - :"=g" (*dest):"g" (dest), "g" (x2) - :"ax","si","di"); -} - - -/* Add the 12 byte Xsig x2 to Xsig dest, adjust exp if overflow occurs. */ -/* Note: the constraints in the asm statement didn't always work properly - with gcc 2.5.8. Changing from using edi to using ecx got around the - problem, but keep fingers crossed! */ -extern inline int add_two_Xsig(Xsig *dest, const Xsig *x2, long int *exp) -{ - asm volatile ("movl %2,%%ecx; movl %3,%%esi; - movl (%%esi),%%eax; addl %%eax,(%%ecx); - movl 4(%%esi),%%eax; adcl %%eax,4(%%ecx); - movl 8(%%esi),%%eax; adcl %%eax,8(%%ecx); - jnc 0f; - rcrl 8(%%ecx); rcrl 4(%%ecx); rcrl (%%ecx) - movl %4,%%ecx; incl (%%ecx) - movl $1,%%eax; jmp 1f; - 0: xorl %%eax,%%eax; - 1:" - :"=g" (*exp), "=g" (*dest) - :"g" (dest), "g" (x2), "g" (exp) - :"cx","si","ax"); -} - - -/* Negate (subtract from 1.0) the 12 byte Xsig */ -/* This is faster in a loop on my 386 than using the "neg" instruction. */ -extern inline void negate_Xsig(Xsig *x) -{ - asm volatile("movl %1,%%esi; " - "xorl %%ecx,%%ecx; " - "movl %%ecx,%%eax; subl (%%esi),%%eax; movl %%eax,(%%esi); " - "movl %%ecx,%%eax; sbbl 4(%%esi),%%eax; movl %%eax,4(%%esi); " - "movl %%ecx,%%eax; sbbl 8(%%esi),%%eax; movl %%eax,8(%%esi); " - :"=g" (*x):"g" (x):"si","ax","cx"); -} - -#endif _POLY_H diff --git a/drivers/FPU-emu/poly_2xm1.c b/drivers/FPU-emu/poly_2xm1.c deleted file mode 100644 index f7c585d60..000000000 --- a/drivers/FPU-emu/poly_2xm1.c +++ /dev/null @@ -1,152 +0,0 @@ -/*---------------------------------------------------------------------------+ - | poly_2xm1.c | - | | - | Function to compute 2^x-1 by a polynomial approximation. | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -#include "exception.h" -#include "reg_constant.h" -#include "fpu_emu.h" -#include "control_w.h" -#include "poly.h" - - -#define HIPOWER 11 -static const unsigned long long lterms[HIPOWER] = -{ - 0x0000000000000000LL, /* This term done separately as 12 bytes */ - 0xf5fdeffc162c7543LL, - 0x1c6b08d704a0bfa6LL, - 0x0276556df749cc21LL, - 0x002bb0ffcf14f6b8LL, - 0x0002861225ef751cLL, - 0x00001ffcbfcd5422LL, - 0x00000162c005d5f1LL, - 0x0000000da96ccb1bLL, - 0x0000000078d1b897LL, - 0x000000000422b029LL -}; - -static const Xsig hiterm = MK_XSIG(0xb17217f7, 0xd1cf79ab, 0xc8a39194); - -/* Four slices: 0.0 : 0.25 : 0.50 : 0.75 : 1.0, - These numbers are 2^(1/4), 2^(1/2), and 2^(3/4) - */ -static const Xsig shiftterm0 = MK_XSIG(0, 0, 0); -static const Xsig shiftterm1 = MK_XSIG(0x9837f051, 0x8db8a96f, 0x46ad2318); -static const Xsig shiftterm2 = MK_XSIG(0xb504f333, 0xf9de6484, 0x597d89b3); -static const Xsig shiftterm3 = MK_XSIG(0xd744fcca, 0xd69d6af4, 0x39a68bb9); - -static const Xsig *shiftterm[] = { &shiftterm0, &shiftterm1, - &shiftterm2, &shiftterm3 }; - - -/*--- poly_2xm1() -----------------------------------------------------------+ - | Requires an argument which is TW_Valid and < 1. | - +---------------------------------------------------------------------------*/ -int poly_2xm1(FPU_REG const *arg, FPU_REG *result) -{ - long int exponent, shift; - unsigned long long Xll; - Xsig accumulator, Denom, argSignif; - - - exponent = arg->exp - EXP_BIAS; - -#ifdef PARANOID - if ( (exponent >= 0) /* Don't want a |number| >= 1.0 */ - || (arg->tag != TW_Valid) ) - { - /* Number negative, too large, or not Valid. */ - EXCEPTION(EX_INTERNAL|0x127); - return 1; - } -#endif PARANOID - - argSignif.lsw = 0; - XSIG_LL(argSignif) = Xll = significand(arg); - - if ( exponent == -1 ) - { - shift = (argSignif.msw & 0x40000000) ? 3 : 2; - /* subtract 0.5 or 0.75 */ - exponent -= 2; - XSIG_LL(argSignif) <<= 2; - Xll <<= 2; - } - else if ( exponent == -2 ) - { - shift = 1; - /* subtract 0.25 */ - exponent--; - XSIG_LL(argSignif) <<= 1; - Xll <<= 1; - } - else - shift = 0; - - if ( exponent < -2 ) - { - /* Shift the argument right by the required places. */ - if ( shrx(&Xll, -2-exponent) >= 0x80000000U ) - Xll++; /* round up */ - } - - accumulator.lsw = accumulator.midw = accumulator.msw = 0; - polynomial_Xsig(&accumulator, &Xll, lterms, HIPOWER-1); - mul_Xsig_Xsig(&accumulator, &argSignif); - shr_Xsig(&accumulator, 3); - - mul_Xsig_Xsig(&argSignif, &hiterm); /* The leading term */ - add_two_Xsig(&accumulator, &argSignif, &exponent); - - if ( shift ) - { - /* The argument is large, use the identity: - f(x+a) = f(a) * (f(x) + 1) - 1; - */ - shr_Xsig(&accumulator, - exponent); - accumulator.msw |= 0x80000000; /* add 1.0 */ - mul_Xsig_Xsig(&accumulator, shiftterm[shift]); - accumulator.msw &= 0x3fffffff; /* subtract 1.0 */ - exponent = 1; - } - - if ( arg->sign != SIGN_POS ) - { - /* The argument is negative, use the identity: - f(-x) = -f(x) / (1 + f(x)) - */ - Denom.lsw = accumulator.lsw; - XSIG_LL(Denom) = XSIG_LL(accumulator); - if ( exponent < 0 ) - shr_Xsig(&Denom, - exponent); - else if ( exponent > 0 ) - { - /* exponent must be 1 here */ - XSIG_LL(Denom) <<= 1; - if ( Denom.lsw & 0x80000000 ) - XSIG_LL(Denom) |= 1; - (Denom.lsw) <<= 1; - } - Denom.msw |= 0x80000000; /* add 1.0 */ - div_Xsig(&accumulator, &Denom, &accumulator); - } - - /* Convert to 64 bit signed-compatible */ - exponent += round_Xsig(&accumulator); - - significand(result) = XSIG_LL(accumulator); - result->tag = TW_Valid; - result->exp = exponent + EXP_BIAS; - result->sign = arg->sign; - - return 0; - -} diff --git a/drivers/FPU-emu/poly_atan.c b/drivers/FPU-emu/poly_atan.c deleted file mode 100644 index 6edca625f..000000000 --- a/drivers/FPU-emu/poly_atan.c +++ /dev/null @@ -1,197 +0,0 @@ -/*---------------------------------------------------------------------------+ - | poly_atan.c | - | | - | Compute the arctan of a FPU_REG, using a polynomial approximation. | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -#include "exception.h" -#include "reg_constant.h" -#include "fpu_emu.h" -#include "status_w.h" -#include "control_w.h" -#include "poly.h" - - -#define HIPOWERon 6 /* odd poly, negative terms */ -static const unsigned long long oddnegterms[HIPOWERon] = -{ - 0x0000000000000000LL, /* Dummy (not for - 1.0) */ - 0x015328437f756467LL, - 0x0005dda27b73dec6LL, - 0x0000226bf2bfb91aLL, - 0x000000ccc439c5f7LL, - 0x0000000355438407LL -} ; - -#define HIPOWERop 6 /* odd poly, positive terms */ -static const unsigned long long oddplterms[HIPOWERop] = -{ -/* 0xaaaaaaaaaaaaaaabLL, transferred to fixedpterm[] */ - 0x0db55a71875c9ac2LL, - 0x0029fce2d67880b0LL, - 0x0000dfd3908b4596LL, - 0x00000550fd61dab4LL, - 0x0000001c9422b3f9LL, - 0x000000003e3301e1LL -}; - -static const unsigned long long denomterm = 0xebd9b842c5c53a0eLL; - -static const Xsig fixedpterm = MK_XSIG(0xaaaaaaaa, 0xaaaaaaaa, 0xaaaaaaaa); - -static const Xsig pi_signif = MK_XSIG(0xc90fdaa2, 0x2168c234, 0xc4c6628b); - - -/*--- poly_atan() -----------------------------------------------------------+ - | | - +---------------------------------------------------------------------------*/ -void poly_atan(FPU_REG *arg1, FPU_REG *arg2, FPU_REG *result) -{ - char transformed, inverted, - sign1 = arg1->sign, sign2 = arg2->sign; - long int exponent, dummy_exp; - Xsig accumulator, Numer, Denom, accumulatore, argSignif, - argSq, argSqSq; - - - arg1->sign = arg2->sign = SIGN_POS; - if ( (compare(arg2) & ~COMP_Denormal) == COMP_A_lt_B ) - { - inverted = 1; - exponent = arg1->exp - arg2->exp; - Numer.lsw = Denom.lsw = 0; - XSIG_LL(Numer) = significand(arg1); - XSIG_LL(Denom) = significand(arg2); - } - else - { - inverted = 0; - exponent = arg2->exp - arg1->exp; - Numer.lsw = Denom.lsw = 0; - XSIG_LL(Numer) = significand(arg2); - XSIG_LL(Denom) = significand(arg1); - } - div_Xsig(&Numer, &Denom, &argSignif); - exponent += norm_Xsig(&argSignif); - - if ( (exponent >= -1) - || ((exponent == -2) && (argSignif.msw > 0xd413ccd0)) ) - { - /* The argument is greater than sqrt(2)-1 (=0.414213562...) */ - /* Convert the argument by an identity for atan */ - transformed = 1; - - if ( exponent >= 0 ) - { -#ifdef PARANOID - if ( !( (exponent == 0) && - (argSignif.lsw == 0) && (argSignif.midw == 0) && - (argSignif.msw == 0x80000000) ) ) - { - EXCEPTION(EX_INTERNAL|0x104); /* There must be a logic error */ - return; - } -#endif PARANOID - argSignif.msw = 0; /* Make the transformed arg -> 0.0 */ - } - else - { - Numer.lsw = Denom.lsw = argSignif.lsw; - XSIG_LL(Numer) = XSIG_LL(Denom) = XSIG_LL(argSignif); - - if ( exponent < -1 ) - shr_Xsig(&Numer, -1-exponent); - negate_Xsig(&Numer); - - shr_Xsig(&Denom, -exponent); - Denom.msw |= 0x80000000; - - div_Xsig(&Numer, &Denom, &argSignif); - - exponent = -1 + norm_Xsig(&argSignif); - } - } - else - { - transformed = 0; - } - - argSq.lsw = argSignif.lsw; argSq.midw = argSignif.midw; - argSq.msw = argSignif.msw; - mul_Xsig_Xsig(&argSq, &argSq); - - argSqSq.lsw = argSq.lsw; argSqSq.midw = argSq.midw; argSqSq.msw = argSq.msw; - mul_Xsig_Xsig(&argSqSq, &argSqSq); - - accumulatore.lsw = argSq.lsw; - XSIG_LL(accumulatore) = XSIG_LL(argSq); - - shr_Xsig(&argSq, 2*(-1-exponent-1)); - shr_Xsig(&argSqSq, 4*(-1-exponent-1)); - - /* Now have argSq etc with binary point at the left - .1xxxxxxxx */ - - /* Do the basic fixed point polynomial evaluation */ - accumulator.msw = accumulator.midw = accumulator.lsw = 0; - polynomial_Xsig(&accumulator, &XSIG_LL(argSqSq), - oddplterms, HIPOWERop-1); - mul64_Xsig(&accumulator, &XSIG_LL(argSq)); - negate_Xsig(&accumulator); - polynomial_Xsig(&accumulator, &XSIG_LL(argSqSq), oddnegterms, HIPOWERon-1); - negate_Xsig(&accumulator); - add_two_Xsig(&accumulator, &fixedpterm, &dummy_exp); - - mul64_Xsig(&accumulatore, &denomterm); - shr_Xsig(&accumulatore, 1 + 2*(-1-exponent)); - accumulatore.msw |= 0x80000000; - - div_Xsig(&accumulator, &accumulatore, &accumulator); - - mul_Xsig_Xsig(&accumulator, &argSignif); - mul_Xsig_Xsig(&accumulator, &argSq); - - shr_Xsig(&accumulator, 3); - negate_Xsig(&accumulator); - add_Xsig_Xsig(&accumulator, &argSignif); - - if ( transformed ) - { - /* compute pi/4 - accumulator */ - shr_Xsig(&accumulator, -1-exponent); - negate_Xsig(&accumulator); - add_Xsig_Xsig(&accumulator, &pi_signif); - exponent = -1; - } - - if ( inverted ) - { - /* compute pi/2 - accumulator */ - shr_Xsig(&accumulator, -exponent); - negate_Xsig(&accumulator); - add_Xsig_Xsig(&accumulator, &pi_signif); - exponent = 0; - } - - if ( sign1 ) - { - /* compute pi - accumulator */ - shr_Xsig(&accumulator, 1 - exponent); - negate_Xsig(&accumulator); - add_Xsig_Xsig(&accumulator, &pi_signif); - exponent = 1; - } - - exponent += round_Xsig(&accumulator); - significand(result) = XSIG_LL(accumulator); - result->exp = exponent + EXP_BIAS; - result->tag = TW_Valid; - result->sign = sign2; - -} diff --git a/drivers/FPU-emu/poly_l2.c b/drivers/FPU-emu/poly_l2.c deleted file mode 100644 index 1677f4aff..000000000 --- a/drivers/FPU-emu/poly_l2.c +++ /dev/null @@ -1,255 +0,0 @@ -/*---------------------------------------------------------------------------+ - | poly_l2.c | - | | - | Compute the base 2 log of a FPU_REG, using a polynomial approximation. | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - - -#include "exception.h" -#include "reg_constant.h" -#include "fpu_emu.h" -#include "control_w.h" -#include "poly.h" - - - -static void log2_kernel(FPU_REG const *arg, - Xsig *accum_result, long int *expon); - - -/*--- poly_l2() -------------------------------------------------------------+ - | Base 2 logarithm by a polynomial approximation. | - +---------------------------------------------------------------------------*/ -void poly_l2(FPU_REG const *arg, FPU_REG const *y, FPU_REG *result) -{ - long int exponent, expon, expon_expon; - Xsig accumulator, expon_accum, yaccum; - char sign; - FPU_REG x; - - - exponent = arg->exp - EXP_BIAS; - - /* From arg, make a number > sqrt(2)/2 and < sqrt(2) */ - if ( arg->sigh > (unsigned)0xb504f334 ) - { - /* Treat as sqrt(2)/2 < arg < 1 */ - significand(&x) = - significand(arg); - x.sign = SIGN_NEG; - x.tag = TW_Valid; - x.exp = EXP_BIAS-1; - exponent++; - normalize(&x); - } - else - { - /* Treat as 1 <= arg < sqrt(2) */ - x.sigh = arg->sigh - 0x80000000; - x.sigl = arg->sigl; - x.sign = SIGN_POS; - x.tag = TW_Valid; - x.exp = EXP_BIAS; - normalize(&x); - } - - if ( x.tag == TW_Zero ) - { - expon = 0; - accumulator.msw = accumulator.midw = accumulator.lsw = 0; - } - else - { - log2_kernel(&x, &accumulator, &expon); - } - - sign = exponent < 0; - if ( sign ) exponent = -exponent; - expon_accum.msw = exponent; expon_accum.midw = expon_accum.lsw = 0; - if ( exponent ) - { - expon_expon = 31 + norm_Xsig(&expon_accum); - shr_Xsig(&accumulator, expon_expon - expon); - - if ( sign ^ (x.sign == SIGN_NEG) ) - negate_Xsig(&accumulator); - add_Xsig_Xsig(&accumulator, &expon_accum); - } - else - { - expon_expon = expon; - sign = x.sign; - } - - yaccum.lsw = 0; XSIG_LL(yaccum) = significand(y); - mul_Xsig_Xsig(&accumulator, &yaccum); - - expon_expon += round_Xsig(&accumulator); - - if ( accumulator.msw == 0 ) - { - reg_move(&CONST_Z, y); - } - else - { - result->exp = expon_expon + y->exp + 1; - significand(result) = XSIG_LL(accumulator); - result->tag = TW_Valid; /* set the tags to Valid */ - result->sign = sign ^ y->sign; - } - - return; -} - - -/*--- poly_l2p1() -----------------------------------------------------------+ - | Base 2 logarithm by a polynomial approximation. | - | log2(x+1) | - +---------------------------------------------------------------------------*/ -int poly_l2p1(FPU_REG const *arg, FPU_REG const *y, FPU_REG *result) -{ - char sign; - long int exponent; - Xsig accumulator, yaccum; - - - sign = arg->sign; - - if ( arg->exp < EXP_BIAS ) - { - log2_kernel(arg, &accumulator, &exponent); - - yaccum.lsw = 0; - XSIG_LL(yaccum) = significand(y); - mul_Xsig_Xsig(&accumulator, &yaccum); - - exponent += round_Xsig(&accumulator); - - result->exp = exponent + y->exp + 1; - significand(result) = XSIG_LL(accumulator); - result->tag = TW_Valid; /* set the tags to Valid */ - result->sign = sign ^ y->sign; - - return 0; - } - else - { - /* The magnitude of arg is far too large. */ - reg_move(y, result); - if ( sign != SIGN_POS ) - { - /* Trying to get the log of a negative number. */ - return 1; - } - else - { - return 0; - } - } - -} - - - - -#undef HIPOWER -#define HIPOWER 10 -static const unsigned long long logterms[HIPOWER] = -{ - 0x2a8eca5705fc2ef0LL, - 0xf6384ee1d01febceLL, - 0x093bb62877cdf642LL, - 0x006985d8a9ec439bLL, - 0x0005212c4f55a9c8LL, - 0x00004326a16927f0LL, - 0x0000038d1d80a0e7LL, - 0x0000003141cc80c6LL, - 0x00000002b1668c9fLL, - 0x000000002c7a46aaLL -}; - -static const unsigned long leadterm = 0xb8000000; - - -/*--- log2_kernel() ---------------------------------------------------------+ - | Base 2 logarithm by a polynomial approximation. | - | log2(x+1) | - +---------------------------------------------------------------------------*/ -static void log2_kernel(FPU_REG const *arg, Xsig *accum_result, - long int *expon) -{ - char sign; - long int exponent, adj; - unsigned long long Xsq; - Xsig accumulator, Numer, Denom, argSignif, arg_signif; - - sign = arg->sign; - - exponent = arg->exp - EXP_BIAS; - Numer.lsw = Denom.lsw = 0; - XSIG_LL(Numer) = XSIG_LL(Denom) = significand(arg); - if ( sign == SIGN_POS ) - { - shr_Xsig(&Denom, 2 - (1 + exponent)); - Denom.msw |= 0x80000000; - div_Xsig(&Numer, &Denom, &argSignif); - } - else - { - shr_Xsig(&Denom, 1 - (1 + exponent)); - negate_Xsig(&Denom); - if ( Denom.msw & 0x80000000 ) - { - div_Xsig(&Numer, &Denom, &argSignif); - exponent ++; - } - else - { - /* Denom must be 1.0 */ - argSignif.lsw = Numer.lsw; argSignif.midw = Numer.midw; - argSignif.msw = Numer.msw; - } - } - -#ifndef PECULIAR_486 - /* Should check here that |local_arg| is within the valid range */ - if ( exponent >= -2 ) - { - if ( (exponent > -2) || - (argSignif.msw > (unsigned)0xafb0ccc0) ) - { - /* The argument is too large */ - } - } -#endif PECULIAR_486 - - arg_signif.lsw = argSignif.lsw; XSIG_LL(arg_signif) = XSIG_LL(argSignif); - adj = norm_Xsig(&argSignif); - accumulator.lsw = argSignif.lsw; XSIG_LL(accumulator) = XSIG_LL(argSignif); - mul_Xsig_Xsig(&accumulator, &accumulator); - shr_Xsig(&accumulator, 2*(-1 - (1 + exponent + adj))); - Xsq = XSIG_LL(accumulator); - if ( accumulator.lsw & 0x80000000 ) - Xsq++; - - accumulator.msw = accumulator.midw = accumulator.lsw = 0; - /* Do the basic fixed point polynomial evaluation */ - polynomial_Xsig(&accumulator, &Xsq, logterms, HIPOWER-1); - - mul_Xsig_Xsig(&accumulator, &argSignif); - shr_Xsig(&accumulator, 6 - adj); - - mul32_Xsig(&arg_signif, leadterm); - add_two_Xsig(&accumulator, &arg_signif, &exponent); - - *expon = exponent + 1; - accum_result->lsw = accumulator.lsw; - accum_result->midw = accumulator.midw; - accum_result->msw = accumulator.msw; - -} diff --git a/drivers/FPU-emu/poly_sin.c b/drivers/FPU-emu/poly_sin.c deleted file mode 100644 index 03db5b6aa..000000000 --- a/drivers/FPU-emu/poly_sin.c +++ /dev/null @@ -1,408 +0,0 @@ -/*---------------------------------------------------------------------------+ - | poly_sin.c | - | | - | Computation of an approximation of the sin function and the cosine | - | function by a polynomial. | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - - -#include "exception.h" -#include "reg_constant.h" -#include "fpu_emu.h" -#include "control_w.h" -#include "poly.h" - - -#define N_COEFF_P 4 -#define N_COEFF_N 4 - -static const unsigned long long pos_terms_l[N_COEFF_P] = -{ - 0xaaaaaaaaaaaaaaabLL, - 0x00d00d00d00cf906LL, - 0x000006b99159a8bbLL, - 0x000000000d7392e6LL -}; - -static const unsigned long long neg_terms_l[N_COEFF_N] = -{ - 0x2222222222222167LL, - 0x0002e3bc74aab624LL, - 0x0000000b09229062LL, - 0x00000000000c7973LL -}; - - - -#define N_COEFF_PH 4 -#define N_COEFF_NH 4 -static const unsigned long long pos_terms_h[N_COEFF_PH] = -{ - 0x0000000000000000LL, - 0x05b05b05b05b0406LL, - 0x000049f93edd91a9LL, - 0x00000000c9c9ed62LL -}; - -static const unsigned long long neg_terms_h[N_COEFF_NH] = -{ - 0xaaaaaaaaaaaaaa98LL, - 0x001a01a01a019064LL, - 0x0000008f76c68a77LL, - 0x0000000000d58f5eLL -}; - - -/*--- poly_sine() -----------------------------------------------------------+ - | | - +---------------------------------------------------------------------------*/ -void poly_sine(FPU_REG const *arg, FPU_REG *result) -{ - int exponent, echange; - Xsig accumulator, argSqrd, argTo4; - unsigned long fix_up, adj; - unsigned long long fixed_arg; - - -#ifdef PARANOID - if ( arg->tag == TW_Zero ) - { - /* Return 0.0 */ - reg_move(&CONST_Z, result); - return; - } -#endif PARANOID - - exponent = arg->exp - EXP_BIAS; - - accumulator.lsw = accumulator.midw = accumulator.msw = 0; - - /* Split into two ranges, for arguments below and above 1.0 */ - /* The boundary between upper and lower is approx 0.88309101259 */ - if ( (exponent < -1) || ((exponent == -1) && (arg->sigh <= 0xe21240aa)) ) - { - /* The argument is <= 0.88309101259 */ - - argSqrd.msw = arg->sigh; argSqrd.midw = arg->sigl; argSqrd.lsw = 0; - mul64_Xsig(&argSqrd, &significand(arg)); - shr_Xsig(&argSqrd, 2*(-1-exponent)); - argTo4.msw = argSqrd.msw; argTo4.midw = argSqrd.midw; - argTo4.lsw = argSqrd.lsw; - mul_Xsig_Xsig(&argTo4, &argTo4); - - polynomial_Xsig(&accumulator, &XSIG_LL(argTo4), neg_terms_l, - N_COEFF_N-1); - mul_Xsig_Xsig(&accumulator, &argSqrd); - negate_Xsig(&accumulator); - - polynomial_Xsig(&accumulator, &XSIG_LL(argTo4), pos_terms_l, - N_COEFF_P-1); - - shr_Xsig(&accumulator, 2); /* Divide by four */ - accumulator.msw |= 0x80000000; /* Add 1.0 */ - - mul64_Xsig(&accumulator, &significand(arg)); - mul64_Xsig(&accumulator, &significand(arg)); - mul64_Xsig(&accumulator, &significand(arg)); - - /* Divide by four, FPU_REG compatible, etc */ - exponent = 3*exponent + EXP_BIAS; - - /* The minimum exponent difference is 3 */ - shr_Xsig(&accumulator, arg->exp - exponent); - - negate_Xsig(&accumulator); - XSIG_LL(accumulator) += significand(arg); - - echange = round_Xsig(&accumulator); - - result->exp = arg->exp + echange; - } - else - { - /* The argument is > 0.88309101259 */ - /* We use sin(arg) = cos(pi/2-arg) */ - - fixed_arg = significand(arg); - - if ( exponent == 0 ) - { - /* The argument is >= 1.0 */ - - /* Put the binary point at the left. */ - fixed_arg <<= 1; - } - /* pi/2 in hex is: 1.921fb54442d18469 898CC51701B839A2 52049C1 */ - fixed_arg = 0x921fb54442d18469LL - fixed_arg; - - XSIG_LL(argSqrd) = fixed_arg; argSqrd.lsw = 0; - mul64_Xsig(&argSqrd, &fixed_arg); - - XSIG_LL(argTo4) = XSIG_LL(argSqrd); argTo4.lsw = argSqrd.lsw; - mul_Xsig_Xsig(&argTo4, &argTo4); - - polynomial_Xsig(&accumulator, &XSIG_LL(argTo4), neg_terms_h, - N_COEFF_NH-1); - mul_Xsig_Xsig(&accumulator, &argSqrd); - negate_Xsig(&accumulator); - - polynomial_Xsig(&accumulator, &XSIG_LL(argTo4), pos_terms_h, - N_COEFF_PH-1); - negate_Xsig(&accumulator); - - mul64_Xsig(&accumulator, &fixed_arg); - mul64_Xsig(&accumulator, &fixed_arg); - - shr_Xsig(&accumulator, 3); - negate_Xsig(&accumulator); - - add_Xsig_Xsig(&accumulator, &argSqrd); - - shr_Xsig(&accumulator, 1); - - accumulator.lsw |= 1; /* A zero accumulator here would cause problems */ - negate_Xsig(&accumulator); - - /* The basic computation is complete. Now fix the answer to - compensate for the error due to the approximation used for - pi/2 - */ - - /* This has an exponent of -65 */ - fix_up = 0x898cc517; - /* The fix-up needs to be improved for larger args */ - if ( argSqrd.msw & 0xffc00000 ) - { - /* Get about 32 bit precision in these: */ - mul_32_32(0x898cc517, argSqrd.msw, &adj); - fix_up -= adj/6; - } - mul_32_32(fix_up, LL_MSW(fixed_arg), &fix_up); - - adj = accumulator.lsw; /* temp save */ - accumulator.lsw -= fix_up; - if ( accumulator.lsw > adj ) - XSIG_LL(accumulator) --; - - echange = round_Xsig(&accumulator); - - result->exp = EXP_BIAS - 1 + echange; - } - - significand(result) = XSIG_LL(accumulator); - result->tag = TW_Valid; - result->sign = arg->sign; - -#ifdef PARANOID - if ( (result->exp >= EXP_BIAS) - && (significand(result) > 0x8000000000000000LL) ) - { - EXCEPTION(EX_INTERNAL|0x150); - } -#endif PARANOID - -} - - - -/*--- poly_cos() ------------------------------------------------------------+ - | | - +---------------------------------------------------------------------------*/ -void poly_cos(FPU_REG const *arg, FPU_REG *result) -{ - long int exponent, exp2, echange; - Xsig accumulator, argSqrd, fix_up, argTo4; - unsigned long adj; - unsigned long long fixed_arg; - - -#ifdef PARANOID - if ( arg->tag == TW_Zero ) - { - /* Return 1.0 */ - reg_move(&CONST_1, result); - return; - } - - if ( (arg->exp > EXP_BIAS) - || ((arg->exp == EXP_BIAS) - && (significand(arg) > 0xc90fdaa22168c234LL)) ) - { - EXCEPTION(EX_Invalid); - reg_move(&CONST_QNaN, result); - return; - } -#endif PARANOID - - exponent = arg->exp - EXP_BIAS; - - accumulator.lsw = accumulator.midw = accumulator.msw = 0; - - if ( (exponent < -1) || ((exponent == -1) && (arg->sigh <= 0xb00d6f54)) ) - { - /* arg is < 0.687705 */ - - argSqrd.msw = arg->sigh; argSqrd.midw = arg->sigl; argSqrd.lsw = 0; - mul64_Xsig(&argSqrd, &significand(arg)); - - if ( exponent < -1 ) - { - /* shift the argument right by the required places */ - shr_Xsig(&argSqrd, 2*(-1-exponent)); - } - - argTo4.msw = argSqrd.msw; argTo4.midw = argSqrd.midw; - argTo4.lsw = argSqrd.lsw; - mul_Xsig_Xsig(&argTo4, &argTo4); - - polynomial_Xsig(&accumulator, &XSIG_LL(argTo4), neg_terms_h, - N_COEFF_NH-1); - mul_Xsig_Xsig(&accumulator, &argSqrd); - negate_Xsig(&accumulator); - - polynomial_Xsig(&accumulator, &XSIG_LL(argTo4), pos_terms_h, - N_COEFF_PH-1); - negate_Xsig(&accumulator); - - mul64_Xsig(&accumulator, &significand(arg)); - mul64_Xsig(&accumulator, &significand(arg)); - shr_Xsig(&accumulator, -2*(1+exponent)); - - shr_Xsig(&accumulator, 3); - negate_Xsig(&accumulator); - - add_Xsig_Xsig(&accumulator, &argSqrd); - - shr_Xsig(&accumulator, 1); - - /* It doesn't matter if accumulator is all zero here, the - following code will work ok */ - negate_Xsig(&accumulator); - - if ( accumulator.lsw & 0x80000000 ) - XSIG_LL(accumulator) ++; - if ( accumulator.msw == 0 ) - { - /* The result is 1.0 */ - reg_move(&CONST_1, result); - } - else - { - significand(result) = XSIG_LL(accumulator); - - /* will be a valid positive nr with expon = -1 */ - *(short *)&(result->sign) = 0; - result->exp = EXP_BIAS - 1; - } - } - else - { - fixed_arg = significand(arg); - - if ( exponent == 0 ) - { - /* The argument is >= 1.0 */ - - /* Put the binary point at the left. */ - fixed_arg <<= 1; - } - /* pi/2 in hex is: 1.921fb54442d18469 898CC51701B839A2 52049C1 */ - fixed_arg = 0x921fb54442d18469LL - fixed_arg; - - exponent = -1; - exp2 = -1; - - /* A shift is needed here only for a narrow range of arguments, - i.e. for fixed_arg approx 2^-32, but we pick up more... */ - if ( !(LL_MSW(fixed_arg) & 0xffff0000) ) - { - fixed_arg <<= 16; - exponent -= 16; - exp2 -= 16; - } - - XSIG_LL(argSqrd) = fixed_arg; argSqrd.lsw = 0; - mul64_Xsig(&argSqrd, &fixed_arg); - - if ( exponent < -1 ) - { - /* shift the argument right by the required places */ - shr_Xsig(&argSqrd, 2*(-1-exponent)); - } - - argTo4.msw = argSqrd.msw; argTo4.midw = argSqrd.midw; - argTo4.lsw = argSqrd.lsw; - mul_Xsig_Xsig(&argTo4, &argTo4); - - polynomial_Xsig(&accumulator, &XSIG_LL(argTo4), neg_terms_l, - N_COEFF_N-1); - mul_Xsig_Xsig(&accumulator, &argSqrd); - negate_Xsig(&accumulator); - - polynomial_Xsig(&accumulator, &XSIG_LL(argTo4), pos_terms_l, - N_COEFF_P-1); - - shr_Xsig(&accumulator, 2); /* Divide by four */ - accumulator.msw |= 0x80000000; /* Add 1.0 */ - - mul64_Xsig(&accumulator, &fixed_arg); - mul64_Xsig(&accumulator, &fixed_arg); - mul64_Xsig(&accumulator, &fixed_arg); - - /* Divide by four, FPU_REG compatible, etc */ - exponent = 3*exponent; - - /* The minimum exponent difference is 3 */ - shr_Xsig(&accumulator, exp2 - exponent); - - negate_Xsig(&accumulator); - XSIG_LL(accumulator) += fixed_arg; - - /* The basic computation is complete. Now fix the answer to - compensate for the error due to the approximation used for - pi/2 - */ - - /* This has an exponent of -65 */ - XSIG_LL(fix_up) = 0x898cc51701b839a2ll; - fix_up.lsw = 0; - - /* The fix-up needs to be improved for larger args */ - if ( argSqrd.msw & 0xffc00000 ) - { - /* Get about 32 bit precision in these: */ - mul_32_32(0x898cc517, argSqrd.msw, &adj); - fix_up.msw -= adj/2; - mul_32_32(0x898cc517, argTo4.msw, &adj); - fix_up.msw += adj/24; - } - - exp2 += norm_Xsig(&accumulator); - shr_Xsig(&accumulator, 1); /* Prevent overflow */ - exp2++; - shr_Xsig(&fix_up, 65 + exp2); - - add_Xsig_Xsig(&accumulator, &fix_up); - - echange = round_Xsig(&accumulator); - - result->exp = exp2 + EXP_BIAS + echange; - *(short *)&(result->sign) = 0; /* Is a valid positive nr */ - significand(result) = XSIG_LL(accumulator); - } - -#ifdef PARANOID - if ( (result->exp >= EXP_BIAS) - && (significand(result) > 0x8000000000000000LL) ) - { - EXCEPTION(EX_INTERNAL|0x151); - } -#endif PARANOID - -} diff --git a/drivers/FPU-emu/poly_tan.c b/drivers/FPU-emu/poly_tan.c deleted file mode 100644 index d9b09e438..000000000 --- a/drivers/FPU-emu/poly_tan.c +++ /dev/null @@ -1,213 +0,0 @@ -/*---------------------------------------------------------------------------+ - | poly_tan.c | - | | - | Compute the tan of a FPU_REG, using a polynomial approximation. | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -#include "exception.h" -#include "reg_constant.h" -#include "fpu_emu.h" -#include "control_w.h" -#include "poly.h" - - -#define HiPOWERop 3 /* odd poly, positive terms */ -static const unsigned long long oddplterm[HiPOWERop] = -{ - 0x0000000000000000LL, - 0x0051a1cf08fca228LL, - 0x0000000071284ff7LL -}; - -#define HiPOWERon 2 /* odd poly, negative terms */ -static const unsigned long long oddnegterm[HiPOWERon] = -{ - 0x1291a9a184244e80LL, - 0x0000583245819c21LL -}; - -#define HiPOWERep 2 /* even poly, positive terms */ -static const unsigned long long evenplterm[HiPOWERep] = -{ - 0x0e848884b539e888LL, - 0x00003c7f18b887daLL -}; - -#define HiPOWERen 2 /* even poly, negative terms */ -static const unsigned long long evennegterm[HiPOWERen] = -{ - 0xf1f0200fd51569ccLL, - 0x003afb46105c4432LL -}; - -static const unsigned long long twothirds = 0xaaaaaaaaaaaaaaabLL; - - -/*--- poly_tan() ------------------------------------------------------------+ - | | - +---------------------------------------------------------------------------*/ -void poly_tan(FPU_REG const *arg, FPU_REG *result) -{ - long int exponent; - int invert; - Xsig argSq, argSqSq, accumulatoro, accumulatore, accum, - argSignif, fix_up; - unsigned long adj; - - exponent = arg->exp - EXP_BIAS; - -#ifdef PARANOID - if ( arg->sign != 0 ) /* Can't hack a number < 0.0 */ - { arith_invalid(result); return; } /* Need a positive number */ -#endif PARANOID - - /* Split the problem into two domains, smaller and larger than pi/4 */ - if ( (exponent == 0) || ((exponent == -1) && (arg->sigh > 0xc90fdaa2)) ) - { - /* The argument is greater than (approx) pi/4 */ - invert = 1; - accum.lsw = 0; - XSIG_LL(accum) = significand(arg); - - if ( exponent == 0 ) - { - /* The argument is >= 1.0 */ - /* Put the binary point at the left. */ - XSIG_LL(accum) <<= 1; - } - /* pi/2 in hex is: 1.921fb54442d18469 898CC51701B839A2 52049C1 */ - XSIG_LL(accum) = 0x921fb54442d18469LL - XSIG_LL(accum); - - argSignif.lsw = accum.lsw; - XSIG_LL(argSignif) = XSIG_LL(accum); - exponent = -1 + norm_Xsig(&argSignif); - } - else - { - invert = 0; - argSignif.lsw = 0; - XSIG_LL(accum) = XSIG_LL(argSignif) = significand(arg); - - if ( exponent < -1 ) - { - /* shift the argument right by the required places */ - if ( shrx(&XSIG_LL(accum), -1-exponent) >= 0x80000000U ) - XSIG_LL(accum) ++; /* round up */ - } - } - - XSIG_LL(argSq) = XSIG_LL(accum); argSq.lsw = accum.lsw; - mul_Xsig_Xsig(&argSq, &argSq); - XSIG_LL(argSqSq) = XSIG_LL(argSq); argSqSq.lsw = argSq.lsw; - mul_Xsig_Xsig(&argSqSq, &argSqSq); - - /* Compute the negative terms for the numerator polynomial */ - accumulatoro.msw = accumulatoro.midw = accumulatoro.lsw = 0; - polynomial_Xsig(&accumulatoro, &XSIG_LL(argSqSq), oddnegterm, HiPOWERon-1); - mul_Xsig_Xsig(&accumulatoro, &argSq); - negate_Xsig(&accumulatoro); - /* Add the positive terms */ - polynomial_Xsig(&accumulatoro, &XSIG_LL(argSqSq), oddplterm, HiPOWERop-1); - - - /* Compute the positive terms for the denominator polynomial */ - accumulatore.msw = accumulatore.midw = accumulatore.lsw = 0; - polynomial_Xsig(&accumulatore, &XSIG_LL(argSqSq), evenplterm, HiPOWERep-1); - mul_Xsig_Xsig(&accumulatore, &argSq); - negate_Xsig(&accumulatore); - /* Add the negative terms */ - polynomial_Xsig(&accumulatore, &XSIG_LL(argSqSq), evennegterm, HiPOWERen-1); - /* Multiply by arg^2 */ - mul64_Xsig(&accumulatore, &XSIG_LL(argSignif)); - mul64_Xsig(&accumulatore, &XSIG_LL(argSignif)); - /* de-normalize and divide by 2 */ - shr_Xsig(&accumulatore, -2*(1+exponent) + 1); - negate_Xsig(&accumulatore); /* This does 1 - accumulator */ - - /* Now find the ratio. */ - if ( accumulatore.msw == 0 ) - { - /* accumulatoro must contain 1.0 here, (actually, 0) but it - really doesn't matter what value we use because it will - have negligible effect in later calculations - */ - XSIG_LL(accum) = 0x8000000000000000LL; - accum.lsw = 0; - } - else - { - div_Xsig(&accumulatoro, &accumulatore, &accum); - } - - /* Multiply by 1/3 * arg^3 */ - mul64_Xsig(&accum, &XSIG_LL(argSignif)); - mul64_Xsig(&accum, &XSIG_LL(argSignif)); - mul64_Xsig(&accum, &XSIG_LL(argSignif)); - mul64_Xsig(&accum, &twothirds); - shr_Xsig(&accum, -2*(exponent+1)); - - /* tan(arg) = arg + accum */ - add_two_Xsig(&accum, &argSignif, &exponent); - - if ( invert ) - { - /* We now have the value of tan(pi_2 - arg) where pi_2 is an - approximation for pi/2 - */ - /* The next step is to fix the answer to compensate for the - error due to the approximation used for pi/2 - */ - - /* This is (approx) delta, the error in our approx for pi/2 - (see above). It has an exponent of -65 - */ - XSIG_LL(fix_up) = 0x898cc51701b839a2LL; - fix_up.lsw = 0; - - if ( exponent == 0 ) - adj = 0xffffffff; /* We want approx 1.0 here, but - this is close enough. */ - else if ( exponent > -30 ) - { - adj = accum.msw >> -(exponent+1); /* tan */ - mul_32_32(adj, adj, &adj); /* tan^2 */ - } - else - adj = 0; - mul_32_32(0x898cc517, adj, &adj); /* delta * tan^2 */ - - fix_up.msw += adj; - if ( !(fix_up.msw & 0x80000000) ) /* did fix_up overflow ? */ - { - /* Yes, we need to add an msb */ - shr_Xsig(&fix_up, 1); - fix_up.msw |= 0x80000000; - shr_Xsig(&fix_up, 64 + exponent); - } - else - shr_Xsig(&fix_up, 65 + exponent); - - add_two_Xsig(&accum, &fix_up, &exponent); - - /* accum now contains tan(pi/2 - arg). - Use tan(arg) = 1.0 / tan(pi/2 - arg) - */ - accumulatoro.lsw = accumulatoro.midw = 0; - accumulatoro.msw = 0x80000000; - div_Xsig(&accumulatoro, &accum, &accum); - exponent = - exponent - 1; - } - - /* Transfer the result */ - round_Xsig(&accum); - *(short *)&(result->sign) = 0; - significand(result) = XSIG_LL(accum); - result->exp = EXP_BIAS + exponent; - -} diff --git a/drivers/FPU-emu/polynom_Xsig.S b/drivers/FPU-emu/polynom_Xsig.S deleted file mode 100644 index 585221f96..000000000 --- a/drivers/FPU-emu/polynom_Xsig.S +++ /dev/null @@ -1,137 +0,0 @@ -/*---------------------------------------------------------------------------+ - | polynomial_Xsig.S | - | | - | Fixed point arithmetic polynomial evaluation. | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | Call from C as: | - | void polynomial_Xsig(Xsig *accum, unsigned long long x, | - | unsigned long long terms[], int n) | - | | - | Computes: | - | terms[0] + (terms[1] + (terms[2] + ... + (terms[n-1]*x)*x)*x)*x) ... )*x | - | and adds the result to the 12 byte Xsig. | - | The terms[] are each 8 bytes, but all computation is performed to 12 byte | - | precision. | - | | - | This function must be used carefully: most overflow of intermediate | - | results is controlled, but overflow of the result is not. | - | | - +---------------------------------------------------------------------------*/ - .file "polynomial_Xsig.S" - -#include "fpu_asm.h" - - -#define TERM_SIZE $8 -#define SUM_MS -20(%ebp) /* sum ms long */ -#define SUM_MIDDLE -24(%ebp) /* sum middle long */ -#define SUM_LS -28(%ebp) /* sum ls long */ -#define ACCUM_MS -4(%ebp) /* accum ms long */ -#define ACCUM_MIDDLE -8(%ebp) /* accum middle long */ -#define ACCUM_LS -12(%ebp) /* accum ls long */ -#define OVERFLOWED -16(%ebp) /* addition overflow flag */ - -.text - .align 2,144 -.globl _polynomial_Xsig -_polynomial_Xsig: - pushl %ebp - movl %esp,%ebp - subl $32,%esp - pushl %esi - pushl %edi - pushl %ebx - - movl PARAM2,%esi /* x */ - movl PARAM3,%edi /* terms */ - - movl TERM_SIZE,%eax - mull PARAM4 /* n */ - addl %eax,%edi - - movl 4(%edi),%edx /* terms[n] */ - movl %edx,SUM_MS - movl (%edi),%edx /* terms[n] */ - movl %edx,SUM_MIDDLE - xor %eax,%eax - movl %eax,SUM_LS - movb %al,OVERFLOWED - - subl TERM_SIZE,%edi - decl PARAM4 - js L_accum_done - -L_accum_loop: - xor %eax,%eax - movl %eax,ACCUM_MS - movl %eax,ACCUM_MIDDLE - - movl SUM_MIDDLE,%eax - mull (%esi) /* x ls long */ - movl %edx,ACCUM_LS - - movl SUM_MIDDLE,%eax - mull 4(%esi) /* x ms long */ - addl %eax,ACCUM_LS - adcl %edx,ACCUM_MIDDLE - adcl $0,ACCUM_MS - - movl SUM_MS,%eax - mull (%esi) /* x ls long */ - addl %eax,ACCUM_LS - adcl %edx,ACCUM_MIDDLE - adcl $0,ACCUM_MS - - movl SUM_MS,%eax - mull 4(%esi) /* x ms long */ - addl %eax,ACCUM_MIDDLE - adcl %edx,ACCUM_MS - - testb $0xff,OVERFLOWED - jz L_no_overflow - - movl (%esi),%eax - addl %eax,ACCUM_MIDDLE - movl 4(%esi),%eax - adcl %eax,ACCUM_MS /* This could overflow too */ - -L_no_overflow: - -/* - * Now put the sum of next term and the accumulator - * into the sum register - */ - movl ACCUM_LS,%eax - addl (%edi),%eax /* term ls long */ - movl %eax,SUM_LS - movl ACCUM_MIDDLE,%eax - adcl (%edi),%eax /* term ls long */ - movl %eax,SUM_MIDDLE - movl ACCUM_MS,%eax - adcl 4(%edi),%eax /* term ms long */ - movl %eax,SUM_MS - sbbb %al,%al - movb %al,OVERFLOWED /* Used in the next iteration */ - - subl TERM_SIZE,%edi - decl PARAM4 - jns L_accum_loop - -L_accum_done: - movl PARAM1,%edi /* accum */ - movl SUM_LS,%eax - addl %eax,(%edi) - movl SUM_MIDDLE,%eax - adcl %eax,4(%edi) - movl SUM_MS,%eax - adcl %eax,8(%edi) - - popl %ebx - popl %edi - popl %esi - leave - ret diff --git a/drivers/FPU-emu/reg_add_sub.c b/drivers/FPU-emu/reg_add_sub.c deleted file mode 100644 index d70889b40..000000000 --- a/drivers/FPU-emu/reg_add_sub.c +++ /dev/null @@ -1,318 +0,0 @@ -/*---------------------------------------------------------------------------+ - | reg_add_sub.c | - | | - | Functions to add or subtract two registers and put the result in a third. | - | | - | Copyright (C) 1992,1993 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------+ - | For each function, the destination may be any FPU_REG, including one of | - | the source FPU_REGs. | - +---------------------------------------------------------------------------*/ - -#include "exception.h" -#include "reg_constant.h" -#include "fpu_emu.h" -#include "control_w.h" -#include "fpu_system.h" - - -int reg_add(FPU_REG const *a, FPU_REG const *b, FPU_REG *dest, int control_w) -{ - char saved_sign = dest->sign; - int diff; - - if ( !(a->tag | b->tag) ) - { - /* Both registers are valid */ - if (!(a->sign ^ b->sign)) - { - /* signs are the same */ - dest->sign = a->sign; - if ( reg_u_add(a, b, dest, control_w) ) - { - dest->sign = saved_sign; - return 1; - } - return 0; - } - - /* The signs are different, so do a subtraction */ - diff = a->exp - b->exp; - if (!diff) - { - diff = a->sigh - b->sigh; /* Works only if ms bits are identical */ - if (!diff) - { - diff = a->sigl > b->sigl; - if (!diff) - diff = -(a->sigl < b->sigl); - } - } - - if (diff > 0) - { - dest->sign = a->sign; - if ( reg_u_sub(a, b, dest, control_w) ) - { - dest->sign = saved_sign; - return 1; - } - } - else if ( diff == 0 ) - { -#ifdef DENORM_OPERAND - if ( (b->tag == TW_Valid) && (b->exp <= EXP_UNDER) && - denormal_operand() ) - return 1; -#endif DENORM_OPERAND - reg_move(&CONST_Z, dest); - /* sign depends upon rounding mode */ - dest->sign = ((control_w & CW_RC) != RC_DOWN) - ? SIGN_POS : SIGN_NEG; - } - else - { - dest->sign = b->sign; - if ( reg_u_sub(b, a, dest, control_w) ) - { - dest->sign = saved_sign; - return 1; - } - } - return 0; - } - else - { - if ( (a->tag == TW_NaN) || (b->tag == TW_NaN) ) - { return real_2op_NaN(a, b, dest); } - else if (a->tag == TW_Zero) - { - if (b->tag == TW_Zero) - { - char different_signs = a->sign ^ b->sign; - /* Both are zero, result will be zero. */ - reg_move(a, dest); - if (different_signs) - { - /* Signs are different. */ - /* Sign of answer depends upon rounding mode. */ - dest->sign = ((control_w & CW_RC) != RC_DOWN) - ? SIGN_POS : SIGN_NEG; - } - } - else - { -#ifdef DENORM_OPERAND - if ( (b->tag == TW_Valid) && (b->exp <= EXP_UNDER) && - denormal_operand() ) - return 1; -#endif DENORM_OPERAND - reg_move(b, dest); - } - return 0; - } - else if (b->tag == TW_Zero) - { -#ifdef DENORM_OPERAND - if ( (a->tag == TW_Valid) && (a->exp <= EXP_UNDER) && - denormal_operand() ) - return 1; -#endif DENORM_OPERAND - reg_move(a, dest); return 0; - } - else if (a->tag == TW_Infinity) - { - if (b->tag != TW_Infinity) - { -#ifdef DENORM_OPERAND - if ( (b->tag == TW_Valid) && (b->exp <= EXP_UNDER) && - denormal_operand() ) - return 1; -#endif DENORM_OPERAND - reg_move(a, dest); return 0; - } - if (a->sign == b->sign) - { - /* They are both + or - infinity */ - reg_move(a, dest); return 0; - } - return arith_invalid(dest); /* Infinity-Infinity is undefined. */ - } - else if (b->tag == TW_Infinity) - { -#ifdef DENORM_OPERAND - if ( (a->tag == TW_Valid) && (a->exp <= EXP_UNDER) && - denormal_operand() ) - return 1; -#endif DENORM_OPERAND - reg_move(b, dest); return 0; - } - } -#ifdef PARANOID - EXCEPTION(EX_INTERNAL|0x101); -#endif - return 1; -} - - -/* Subtract b from a. (a-b) -> dest */ -int reg_sub(FPU_REG const *a, FPU_REG const *b, FPU_REG *dest, int control_w) -{ - char saved_sign = dest->sign; - int diff; - - if ( !(a->tag | b->tag) ) - { - /* Both registers are valid */ - diff = a->exp - b->exp; - if (!diff) - { - diff = a->sigh - b->sigh; /* Works only if ms bits are identical */ - if (!diff) - { - diff = a->sigl > b->sigl; - if (!diff) - diff = -(a->sigl < b->sigl); - } - } - - switch (a->sign*2 + b->sign) - { - case 0: /* P - P */ - case 3: /* N - N */ - if (diff > 0) - { - /* |a| > |b| */ - dest->sign = a->sign; - if ( reg_u_sub(a, b, dest, control_w) ) - { - dest->sign = saved_sign; - return 1; - } - return 0; - } - else if ( diff == 0 ) - { -#ifdef DENORM_OPERAND - if ( (b->tag == TW_Valid) && (b->exp <= EXP_UNDER) && - denormal_operand() ) - return 1; -#endif DENORM_OPERAND - reg_move(&CONST_Z, dest); - /* sign depends upon rounding mode */ - dest->sign = ((control_w & CW_RC) != RC_DOWN) - ? SIGN_POS : SIGN_NEG; - } - else - { - dest->sign = a->sign ^ SIGN_POS^SIGN_NEG; - if ( reg_u_sub(b, a, dest, control_w) ) - { - dest->sign = saved_sign; - return 1; - } - } - break; - case 1: /* P - N */ - dest->sign = SIGN_POS; - if ( reg_u_add(a, b, dest, control_w) ) - { - dest->sign = saved_sign; - return 1; - } - break; - case 2: /* N - P */ - dest->sign = SIGN_NEG; - if ( reg_u_add(a, b, dest, control_w) ) - { - dest->sign = saved_sign; - return 1; - } - break; - } - return 0; - } - else - { - if ( (a->tag == TW_NaN) || (b->tag == TW_NaN) ) - { return real_2op_NaN(b, a, dest); } - else if (b->tag == TW_Zero) - { - if (a->tag == TW_Zero) - { - char same_signs = !(a->sign ^ b->sign); - /* Both are zero, result will be zero. */ - reg_move(a, dest); /* Answer for different signs. */ - if (same_signs) - { - /* Sign depends upon rounding mode */ - dest->sign = ((control_w & CW_RC) != RC_DOWN) - ? SIGN_POS : SIGN_NEG; - } - } - else - { -#ifdef DENORM_OPERAND - if ( (a->tag == TW_Valid) && (a->exp <= EXP_UNDER) && - denormal_operand() ) - return 1; -#endif DENORM_OPERAND - reg_move(a, dest); - } - return 0; - } - else if (a->tag == TW_Zero) - { -#ifdef DENORM_OPERAND - if ( (b->tag == TW_Valid) && (b->exp <= EXP_UNDER) && - denormal_operand() ) - return 1; -#endif DENORM_OPERAND - reg_move(b, dest); - dest->sign ^= SIGN_POS^SIGN_NEG; - return 0; - } - else if (a->tag == TW_Infinity) - { - if (b->tag != TW_Infinity) - { -#ifdef DENORM_OPERAND - if ( (b->tag == TW_Valid) && (b->exp <= EXP_UNDER) && - denormal_operand() ) - return 1; -#endif DENORM_OPERAND - reg_move(a, dest); return 0; - } - /* Both args are Infinity */ - if (a->sign == b->sign) - { - /* Infinity-Infinity is undefined. */ - return arith_invalid(dest); - } - reg_move(a, dest); - return 0; - } - else if (b->tag == TW_Infinity) - { -#ifdef DENORM_OPERAND - if ( (a->tag == TW_Valid) && (a->exp <= EXP_UNDER) && - denormal_operand() ) - return 1; -#endif DENORM_OPERAND - reg_move(b, dest); - dest->sign ^= SIGN_POS^SIGN_NEG; - return 0; - } - } -#ifdef PARANOID - EXCEPTION(EX_INTERNAL|0x110); -#endif - return 1; -} - diff --git a/drivers/FPU-emu/reg_compare.c b/drivers/FPU-emu/reg_compare.c deleted file mode 100644 index eb4a1fa99..000000000 --- a/drivers/FPU-emu/reg_compare.c +++ /dev/null @@ -1,378 +0,0 @@ -/*---------------------------------------------------------------------------+ - | reg_compare.c | - | | - | Compare two floating point registers | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------+ - | compare() is the core FPU_REG comparison function | - +---------------------------------------------------------------------------*/ - -#include "fpu_system.h" -#include "exception.h" -#include "fpu_emu.h" -#include "control_w.h" -#include "status_w.h" - - -int compare(FPU_REG const *b) -{ - int diff; - char st0_tag; - FPU_REG *st0_ptr; - - st0_ptr = &st(0); - st0_tag = st0_ptr->tag; - - if ( st0_tag | b->tag ) - { - if ( st0_tag == TW_Zero ) - { - if ( b->tag == TW_Zero ) return COMP_A_eq_B; - if ( b->tag == TW_Valid ) - { - return ((b->sign == SIGN_POS) ? COMP_A_lt_B : COMP_A_gt_B) -#ifdef DENORM_OPERAND - | ((b->exp <= EXP_UNDER) ? - COMP_Denormal : 0) -#endif DENORM_OPERAND - ; - } - } - else if ( b->tag == TW_Zero ) - { - if ( st0_tag == TW_Valid ) - { - return ((st0_ptr->sign == SIGN_POS) ? COMP_A_gt_B - : COMP_A_lt_B) -#ifdef DENORM_OPERAND - | ((st0_ptr->exp <= EXP_UNDER ) - ? COMP_Denormal : 0 ) -#endif DENORM_OPERAND - ; - } - } - - if ( st0_tag == TW_Infinity ) - { - if ( (b->tag == TW_Valid) || (b->tag == TW_Zero) ) - { - return ((st0_ptr->sign == SIGN_POS) ? COMP_A_gt_B - : COMP_A_lt_B) -#ifdef DENORM_OPERAND - | (((b->tag == TW_Valid) && (b->exp <= EXP_UNDER)) ? - COMP_Denormal : 0 ) -#endif DENORM_OPERAND -; - } - else if ( b->tag == TW_Infinity ) - { - /* The 80486 book says that infinities can be equal! */ - return (st0_ptr->sign == b->sign) ? COMP_A_eq_B : - ((st0_ptr->sign == SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B); - } - /* Fall through to the NaN code */ - } - else if ( b->tag == TW_Infinity ) - { - if ( (st0_tag == TW_Valid) || (st0_tag == TW_Zero) ) - { - return ((b->sign == SIGN_POS) ? COMP_A_lt_B : COMP_A_gt_B) -#ifdef DENORM_OPERAND - | (((st0_tag == TW_Valid) - && (st0_ptr->exp <= EXP_UNDER)) ? - COMP_Denormal : 0) -#endif DENORM_OPERAND - ; - } - /* Fall through to the NaN code */ - } - - /* The only possibility now should be that one of the arguments - is a NaN */ - if ( (st0_tag == TW_NaN) || (b->tag == TW_NaN) ) - { - if ( ((st0_tag == TW_NaN) && !(st0_ptr->sigh & 0x40000000)) - || ((b->tag == TW_NaN) && !(b->sigh & 0x40000000)) ) - /* At least one arg is a signaling NaN */ - return COMP_No_Comp | COMP_SNaN | COMP_NaN; - else - /* Neither is a signaling NaN */ - return COMP_No_Comp | COMP_NaN; - } - - EXCEPTION(EX_Invalid); - } - -#ifdef PARANOID - if (!(st0_ptr->sigh & 0x80000000)) EXCEPTION(EX_Invalid); - if (!(b->sigh & 0x80000000)) EXCEPTION(EX_Invalid); -#endif PARANOID - - - if (st0_ptr->sign != b->sign) - { - return ((st0_ptr->sign == SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B) -#ifdef DENORM_OPERAND - | - ( ((st0_ptr->exp <= EXP_UNDER) || (b->exp <= EXP_UNDER)) ? - COMP_Denormal : 0) -#endif DENORM_OPERAND - ; - } - - diff = st0_ptr->exp - b->exp; - if ( diff == 0 ) - { - diff = st0_ptr->sigh - b->sigh; /* Works only if ms bits are - identical */ - if ( diff == 0 ) - { - diff = st0_ptr->sigl > b->sigl; - if ( diff == 0 ) - diff = -(st0_ptr->sigl < b->sigl); - } - } - - if ( diff > 0 ) - { - return ((st0_ptr->sign == SIGN_POS) ? COMP_A_gt_B : COMP_A_lt_B) -#ifdef DENORM_OPERAND - | - ( ((st0_ptr->exp <= EXP_UNDER) || (b->exp <= EXP_UNDER)) ? - COMP_Denormal : 0) -#endif DENORM_OPERAND - ; - } - if ( diff < 0 ) - { - return ((st0_ptr->sign == SIGN_POS) ? COMP_A_lt_B : COMP_A_gt_B) -#ifdef DENORM_OPERAND - | - ( ((st0_ptr->exp <= EXP_UNDER) || (b->exp <= EXP_UNDER)) ? - COMP_Denormal : 0) -#endif DENORM_OPERAND - ; - } - - return COMP_A_eq_B -#ifdef DENORM_OPERAND - | - ( ((st0_ptr->exp <= EXP_UNDER) || (b->exp <= EXP_UNDER)) ? - COMP_Denormal : 0) -#endif DENORM_OPERAND - ; - -} - - -/* This function requires that st(0) is not empty */ -int compare_st_data(FPU_REG const *loaded_data) -{ - int f, c; - - c = compare(loaded_data); - - if (c & COMP_NaN) - { - EXCEPTION(EX_Invalid); - f = SW_C3 | SW_C2 | SW_C0; - } - else - switch (c & 7) - { - case COMP_A_lt_B: - f = SW_C0; - break; - case COMP_A_eq_B: - f = SW_C3; - break; - case COMP_A_gt_B: - f = 0; - break; - case COMP_No_Comp: - f = SW_C3 | SW_C2 | SW_C0; - break; -#ifdef PARANOID - default: - EXCEPTION(EX_INTERNAL|0x121); - f = SW_C3 | SW_C2 | SW_C0; - break; -#endif PARANOID - } - setcc(f); - if (c & COMP_Denormal) - { - return denormal_operand(); - } - return 0; -} - - -static int compare_st_st(int nr) -{ - int f, c; - - if ( !NOT_EMPTY(0) || !NOT_EMPTY(nr) ) - { - setcc(SW_C3 | SW_C2 | SW_C0); - /* Stack fault */ - EXCEPTION(EX_StackUnder); - return !(control_word & CW_Invalid); - } - - c = compare(&st(nr)); - if (c & COMP_NaN) - { - setcc(SW_C3 | SW_C2 | SW_C0); - EXCEPTION(EX_Invalid); - return !(control_word & CW_Invalid); - } - else - switch (c & 7) - { - case COMP_A_lt_B: - f = SW_C0; - break; - case COMP_A_eq_B: - f = SW_C3; - break; - case COMP_A_gt_B: - f = 0; - break; - case COMP_No_Comp: - f = SW_C3 | SW_C2 | SW_C0; - break; -#ifdef PARANOID - default: - EXCEPTION(EX_INTERNAL|0x122); - f = SW_C3 | SW_C2 | SW_C0; - break; -#endif PARANOID - } - setcc(f); - if (c & COMP_Denormal) - { - return denormal_operand(); - } - return 0; -} - - -static int compare_u_st_st(int nr) -{ - int f, c; - - if ( !NOT_EMPTY(0) || !NOT_EMPTY(nr) ) - { - setcc(SW_C3 | SW_C2 | SW_C0); - /* Stack fault */ - EXCEPTION(EX_StackUnder); - return !(control_word & CW_Invalid); - } - - c = compare(&st(nr)); - if (c & COMP_NaN) - { - setcc(SW_C3 | SW_C2 | SW_C0); - if (c & COMP_SNaN) /* This is the only difference between - un-ordered and ordinary comparisons */ - { - EXCEPTION(EX_Invalid); - return !(control_word & CW_Invalid); - } - return 0; - } - else - switch (c & 7) - { - case COMP_A_lt_B: - f = SW_C0; - break; - case COMP_A_eq_B: - f = SW_C3; - break; - case COMP_A_gt_B: - f = 0; - break; - case COMP_No_Comp: - f = SW_C3 | SW_C2 | SW_C0; - break; -#ifdef PARANOID - default: - EXCEPTION(EX_INTERNAL|0x123); - f = SW_C3 | SW_C2 | SW_C0; - break; -#endif PARANOID - } - setcc(f); - if (c & COMP_Denormal) - { - return denormal_operand(); - } - return 0; -} - -/*---------------------------------------------------------------------------*/ - -void fcom_st() -{ - /* fcom st(i) */ - compare_st_st(FPU_rm); -} - - -void fcompst() -{ - /* fcomp st(i) */ - if ( !compare_st_st(FPU_rm) ) - pop(); -} - - -void fcompp() -{ - /* fcompp */ - if (FPU_rm != 1) - { - FPU_illegal(); - return; - } - if ( !compare_st_st(1) ) - poppop(); -} - - -void fucom_() -{ - /* fucom st(i) */ - compare_u_st_st(FPU_rm); - -} - - -void fucomp() -{ - /* fucomp st(i) */ - if ( !compare_u_st_st(FPU_rm) ) - pop(); -} - - -void fucompp() -{ - /* fucompp */ - if (FPU_rm == 1) - { - if ( !compare_u_st_st(1) ) - poppop(); - } - else - FPU_illegal(); -} diff --git a/drivers/FPU-emu/reg_constant.c b/drivers/FPU-emu/reg_constant.c deleted file mode 100644 index c1981ce24..000000000 --- a/drivers/FPU-emu/reg_constant.c +++ /dev/null @@ -1,116 +0,0 @@ -/*---------------------------------------------------------------------------+ - | reg_constant.c | - | | - | All of the constant FPU_REGs | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -#include "fpu_system.h" -#include "fpu_emu.h" -#include "status_w.h" -#include "reg_constant.h" - - -FPU_REG const CONST_1 = { SIGN_POS, TW_Valid, EXP_BIAS, - 0x00000000, 0x80000000 }; -FPU_REG const CONST_2 = { SIGN_POS, TW_Valid, EXP_BIAS+1, - 0x00000000, 0x80000000 }; -FPU_REG const CONST_HALF = { SIGN_POS, TW_Valid, EXP_BIAS-1, - 0x00000000, 0x80000000 }; -FPU_REG const CONST_L2T = { SIGN_POS, TW_Valid, EXP_BIAS+1, - 0xcd1b8afe, 0xd49a784b }; -FPU_REG const CONST_L2E = { SIGN_POS, TW_Valid, EXP_BIAS, - 0x5c17f0bc, 0xb8aa3b29 }; -FPU_REG const CONST_PI = { SIGN_POS, TW_Valid, EXP_BIAS+1, - 0x2168c235, 0xc90fdaa2 }; -FPU_REG const CONST_PI2 = { SIGN_POS, TW_Valid, EXP_BIAS, - 0x2168c235, 0xc90fdaa2 }; -FPU_REG const CONST_PI4 = { SIGN_POS, TW_Valid, EXP_BIAS-1, - 0x2168c235, 0xc90fdaa2 }; -FPU_REG const CONST_LG2 = { SIGN_POS, TW_Valid, EXP_BIAS-2, - 0xfbcff799, 0x9a209a84 }; -FPU_REG const CONST_LN2 = { SIGN_POS, TW_Valid, EXP_BIAS-1, - 0xd1cf79ac, 0xb17217f7 }; - -/* Extra bits to take pi/2 to more than 128 bits precision. */ -FPU_REG const CONST_PI2extra = { SIGN_NEG, TW_Valid, EXP_BIAS-66, - 0xfc8f8cbb, 0xece675d1 }; - -/* Only the sign (and tag) is used in internal zeroes */ -FPU_REG const CONST_Z = { SIGN_POS, TW_Zero, EXP_UNDER, 0x0, 0x0 }; - -/* Only the sign and significand (and tag) are used in internal NaNs */ -/* The 80486 never generates one of these -FPU_REG const CONST_SNAN = { SIGN_POS, TW_NaN, EXP_OVER, 0x00000001, 0x80000000 }; - */ -/* This is the real indefinite QNaN */ -FPU_REG const CONST_QNaN = { SIGN_NEG, TW_NaN, EXP_OVER, 0x00000000, 0xC0000000 }; - -/* Only the sign (and tag) is used in internal infinities */ -FPU_REG const CONST_INF = { SIGN_POS, TW_Infinity, EXP_OVER, 0x00000000, 0x80000000 }; - - - -static void fld_const(FPU_REG const *c) -{ - FPU_REG *st_new_ptr; - - if ( STACK_OVERFLOW ) - { - stack_overflow(); - return; - } - push(); - reg_move(c, st_new_ptr); - clear_C1(); -} - - -static void fld1(void) -{ - fld_const(&CONST_1); -} - -static void fldl2t(void) -{ - fld_const(&CONST_L2T); -} - -static void fldl2e(void) -{ - fld_const(&CONST_L2E); -} - -static void fldpi(void) -{ - fld_const(&CONST_PI); -} - -static void fldlg2(void) -{ - fld_const(&CONST_LG2); -} - -static void fldln2(void) -{ - fld_const(&CONST_LN2); -} - -static void fldz(void) -{ - fld_const(&CONST_Z); -} - -static FUNC constants_table[] = { - fld1, fldl2t, fldl2e, fldpi, fldlg2, fldln2, fldz, FPU_illegal -}; - -void fconst(void) -{ - (constants_table[FPU_rm])(); -} diff --git a/drivers/FPU-emu/reg_constant.h b/drivers/FPU-emu/reg_constant.h deleted file mode 100644 index b7db97e34..000000000 --- a/drivers/FPU-emu/reg_constant.h +++ /dev/null @@ -1,31 +0,0 @@ -/*---------------------------------------------------------------------------+ - | reg_constant.h | - | | - | Copyright (C) 1992 W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - +---------------------------------------------------------------------------*/ - -#ifndef _REG_CONSTANT_H_ -#define _REG_CONSTANT_H_ - -#include "fpu_emu.h" - -extern FPU_REG const CONST_1; -extern FPU_REG const CONST_2; -extern FPU_REG const CONST_HALF; -extern FPU_REG const CONST_L2T; -extern FPU_REG const CONST_L2E; -extern FPU_REG const CONST_PI; -extern FPU_REG const CONST_PI2; -extern FPU_REG const CONST_PI2extra; -extern FPU_REG const CONST_PI4; -extern FPU_REG const CONST_LG2; -extern FPU_REG const CONST_LN2; -extern FPU_REG const CONST_Z; -extern FPU_REG const CONST_PINF; -extern FPU_REG const CONST_INF; -extern FPU_REG const CONST_MINF; -extern FPU_REG const CONST_QNaN; - -#endif _REG_CONSTANT_H_ diff --git a/drivers/FPU-emu/reg_div.S b/drivers/FPU-emu/reg_div.S deleted file mode 100644 index 2fbc5f7c4..000000000 --- a/drivers/FPU-emu/reg_div.S +++ /dev/null @@ -1,251 +0,0 @@ - .file "reg_div.S" -/*---------------------------------------------------------------------------+ - | reg_div.S | - | | - | Divide one FPU_REG by another and put the result in a destination FPU_REG.| - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | Call from C as: | - | void reg_div(FPU_REG *a, FPU_REG *b, FPU_REG *dest, | - | unsigned int control_word) | - | | - +---------------------------------------------------------------------------*/ - -#include "exception.h" -#include "fpu_asm.h" - - -.text - .align 2 - -.globl _reg_div -_reg_div: - pushl %ebp - movl %esp,%ebp -#ifndef NON_REENTRANT_FPU - subl $28,%esp /* Needed by divide_kernel */ -#endif NON_REENTRANT_FPU - - pushl %esi - pushl %edi - pushl %ebx - - movl PARAM1,%esi - movl PARAM2,%ebx - movl PARAM3,%edi - - movb TAG(%esi),%al - orb TAG(%ebx),%al - - jne L_div_special /* Not (both numbers TW_Valid) */ - -#ifdef DENORM_OPERAND -/* Check for denormals */ - cmpl EXP_UNDER,EXP(%esi) - jg xL_arg1_not_denormal - - call _denormal_operand - orl %eax,%eax - jnz fpu_Arith_exit - -xL_arg1_not_denormal: - cmpl EXP_UNDER,EXP(%ebx) - jg xL_arg2_not_denormal - - call _denormal_operand - orl %eax,%eax - jnz fpu_Arith_exit - -xL_arg2_not_denormal: -#endif DENORM_OPERAND - -/* Both arguments are TW_Valid */ - movb TW_Valid,TAG(%edi) - - movb SIGN(%esi),%cl - cmpb %cl,SIGN(%ebx) - setne (%edi) /* Set the sign, requires SIGN_NEG=1, SIGN_POS=0 */ - - movl EXP(%esi),%edx - movl EXP(%ebx),%eax - subl %eax,%edx - addl EXP_BIAS,%edx - movl %edx,EXP(%edi) - - jmp _divide_kernel - - -/*-----------------------------------------------------------------------*/ -L_div_special: - cmpb TW_NaN,TAG(%esi) /* A NaN with anything to give NaN */ - je L_arg1_NaN - - cmpb TW_NaN,TAG(%ebx) /* A NaN with anything to give NaN */ - jne L_no_NaN_arg - -/* Operations on NaNs */ -L_arg1_NaN: -L_arg2_NaN: - pushl %edi /* Destination */ - pushl %esi - pushl %ebx /* Ordering is important here */ - call _real_2op_NaN - jmp LDiv_exit - -/* Invalid operations */ -L_zero_zero: -L_inf_inf: - pushl %edi /* Destination */ - call _arith_invalid /* 0/0 or Infinity/Infinity */ - jmp LDiv_exit - -L_no_NaN_arg: - cmpb TW_Infinity,TAG(%esi) - jne L_arg1_not_inf - - cmpb TW_Infinity,TAG(%ebx) - je L_inf_inf /* invalid operation */ - - cmpb TW_Valid,TAG(%ebx) - je L_inf_valid - -#ifdef PARANOID - /* arg2 must be zero or valid */ - cmpb TW_Zero,TAG(%ebx) - ja L_unknown_tags -#endif PARANOID - - /* Note that p16-9 says that infinity/0 returns infinity */ - jmp L_copy_arg1 /* Answer is Inf */ - -L_inf_valid: -#ifdef DENORM_OPERAND - cmpl EXP_UNDER,EXP(%ebx) - jg L_copy_arg1 /* Answer is Inf */ - - call _denormal_operand - orl %eax,%eax - jnz fpu_Arith_exit -#endif DENORM_OPERAND - - jmp L_copy_arg1 /* Answer is Inf */ - -L_arg1_not_inf: - cmpb TW_Zero,TAG(%ebx) /* Priority to div-by-zero error */ - jne L_arg2_not_zero - - cmpb TW_Zero,TAG(%esi) - je L_zero_zero /* invalid operation */ - -#ifdef PARANOID - /* arg1 must be valid */ - cmpb TW_Valid,TAG(%esi) - ja L_unknown_tags -#endif PARANOID - -/* Division by zero error */ - pushl %edi /* destination */ - movb SIGN(%esi),%al - xorb SIGN(%ebx),%al - pushl %eax /* lower 8 bits have the sign */ - call _divide_by_zero - jmp LDiv_exit - -L_arg2_not_zero: - cmpb TW_Infinity,TAG(%ebx) - jne L_arg2_not_inf - -#ifdef DENORM_OPERAND - cmpb TW_Valid,TAG(%esi) - jne L_return_zero - - cmpl EXP_UNDER,EXP(%esi) - jg L_return_zero /* Answer is zero */ - - call _denormal_operand - orl %eax,%eax - jnz fpu_Arith_exit -#endif DENORM_OPERAND - - jmp L_return_zero /* Answer is zero */ - -L_arg2_not_inf: - -#ifdef PARANOID - cmpb TW_Zero,TAG(%esi) - jne L_unknown_tags -#endif PARANOID - - /* arg1 is zero, arg2 is not Infinity or a NaN */ - -#ifdef DENORM_OPERAND - cmpl EXP_UNDER,EXP(%ebx) - jg L_copy_arg1 /* Answer is zero */ - - call _denormal_operand - orl %eax,%eax - jnz fpu_Arith_exit -#endif DENORM_OPERAND - -L_copy_arg1: - movb TAG(%esi),%ax - movb %ax,TAG(%edi) - movl EXP(%esi),%eax - movl %eax,EXP(%edi) - movl SIGL(%esi),%eax - movl %eax,SIGL(%edi) - movl SIGH(%esi),%eax - movl %eax,SIGH(%edi) - -LDiv_set_result_sign: - movb SIGN(%esi),%cl - cmpb %cl,SIGN(%ebx) - jne LDiv_negative_result - - movb SIGN_POS,SIGN(%edi) - xorl %eax,%eax /* Valid result */ - jmp LDiv_exit - -LDiv_negative_result: - movb SIGN_NEG,SIGN(%edi) - xorl %eax,%eax /* Valid result */ - -LDiv_exit: -#ifndef NON_REENTRANT_FPU - leal -40(%ebp),%esp -#else - leal -12(%ebp),%esp -#endif NON_REENTRANT_FPU - - popl %ebx - popl %edi - popl %esi - leave - ret - - -L_return_zero: - xorl %eax,%eax - movl %eax,SIGH(%edi) - movl %eax,SIGL(%edi) - movl EXP_UNDER,EXP(%edi) - movb TW_Zero,TAG(%edi) - jmp LDiv_set_result_sign - -#ifdef PARANOID -L_unknown_tags: - pushl EX_INTERNAL | 0x208 - call EXCEPTION - - /* Generate a NaN for unknown tags */ - movl _CONST_QNaN,%eax - movl %eax,(%edi) - movl _CONST_QNaN+4,%eax - movl %eax,SIGL(%edi) - movl _CONST_QNaN+8,%eax - movl %eax,SIGH(%edi) - jmp LDiv_exit /* %eax is nz */ -#endif PARANOID diff --git a/drivers/FPU-emu/reg_ld_str.c b/drivers/FPU-emu/reg_ld_str.c deleted file mode 100644 index efec9e010..000000000 --- a/drivers/FPU-emu/reg_ld_str.c +++ /dev/null @@ -1,1438 +0,0 @@ -/*---------------------------------------------------------------------------+ - | reg_ld_str.c | - | | - | All of the functions which transfer data between user memory and FPU_REGs.| - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------+ - | Note: | - | The file contains code which accesses user memory. | - | Emulator static data may change when user memory is accessed, due to | - | other processes using the emulator while swapping is in progress. | - +---------------------------------------------------------------------------*/ - -#include - -#include "fpu_system.h" -#include "exception.h" -#include "reg_constant.h" -#include "fpu_emu.h" -#include "control_w.h" -#include "status_w.h" - - -#define EXTENDED_Ebias 0x3fff -#define EXTENDED_Emin (-0x3ffe) /* smallest valid exponent */ - -#define DOUBLE_Emax 1023 /* largest valid exponent */ -#define DOUBLE_Ebias 1023 -#define DOUBLE_Emin (-1022) /* smallest valid exponent */ - -#define SINGLE_Emax 127 /* largest valid exponent */ -#define SINGLE_Ebias 127 -#define SINGLE_Emin (-126) /* smallest valid exponent */ - -static void write_to_extended(FPU_REG *rp, char *d); - - -/* Get a long double from user memory */ -int reg_load_extended(long double *s, FPU_REG *loaded_data) -{ - unsigned long sigl, sigh, exp; - - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_READ, s, 10); - sigl = get_fs_long((unsigned long *) s); - sigh = get_fs_long(1 + (unsigned long *) s); - exp = get_fs_word(4 + (unsigned short *) s); - RE_ENTRANT_CHECK_ON; - - loaded_data->tag = TW_Valid; /* Default */ - loaded_data->sigl = sigl; - loaded_data->sigh = sigh; - if (exp & 0x8000) - loaded_data->sign = SIGN_NEG; - else - loaded_data->sign = SIGN_POS; - exp &= 0x7fff; - loaded_data->exp = exp - EXTENDED_Ebias + EXP_BIAS; - - if ( exp == 0 ) - { - if ( !(sigh | sigl) ) - { - loaded_data->tag = TW_Zero; - return 0; - } - /* The number is a de-normal or pseudodenormal. */ - if (sigh & 0x80000000) - { - /* Is a pseudodenormal. */ - /* Convert it for internal use. */ - /* This is non-80486 behaviour because the number - loses its 'denormal' identity. */ - loaded_data->exp++; - return 1; - } - else - { - /* Is a denormal. */ - /* Convert it for internal use. */ - loaded_data->exp++; - normalize_nuo(loaded_data); - return 0; - } - } - else if ( exp == 0x7fff ) - { - if ( !((sigh ^ 0x80000000) | sigl) ) - { - /* Matches the bit pattern for Infinity. */ - loaded_data->exp = EXP_Infinity; - loaded_data->tag = TW_Infinity; - return 0; - } - - loaded_data->exp = EXP_NaN; - loaded_data->tag = TW_NaN; - if ( !(sigh & 0x80000000) ) - { - /* NaNs have the ms bit set to 1. */ - /* This is therefore an Unsupported NaN data type. */ - /* This is non 80486 behaviour */ - /* This should generate an Invalid Operand exception - later, so we convert it to a SNaN */ - loaded_data->sigh = 0x80000000; - loaded_data->sigl = 0x00000001; - loaded_data->sign = SIGN_NEG; - return 1; - } - return 0; - } - - if ( !(sigh & 0x80000000) ) - { - /* Unsupported data type. */ - /* Valid numbers have the ms bit set to 1. */ - /* Unnormal. */ - /* Convert it for internal use. */ - /* This is non-80486 behaviour */ - /* This should generate an Invalid Operand exception - later, so we convert it to a SNaN */ - loaded_data->sigh = 0x80000000; - loaded_data->sigl = 0x00000001; - loaded_data->sign = SIGN_NEG; - loaded_data->exp = EXP_NaN; - loaded_data->tag = TW_NaN; - return 1; - } - return 0; -} - - -/* Get a double from user memory */ -int reg_load_double(double *dfloat, FPU_REG *loaded_data) -{ - int exp; - unsigned m64, l64; - - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_READ, dfloat, 8); - m64 = get_fs_long(1 + (unsigned long *) dfloat); - l64 = get_fs_long((unsigned long *) dfloat); - RE_ENTRANT_CHECK_ON; - - if (m64 & 0x80000000) - loaded_data->sign = SIGN_NEG; - else - loaded_data->sign = SIGN_POS; - exp = ((m64 & 0x7ff00000) >> 20) - DOUBLE_Ebias; - m64 &= 0xfffff; - if (exp > DOUBLE_Emax) - { - /* Infinity or NaN */ - if ((m64 == 0) && (l64 == 0)) - { - /* +- infinity */ - loaded_data->sigh = 0x80000000; - loaded_data->sigl = 0x00000000; - loaded_data->exp = EXP_Infinity; - loaded_data->tag = TW_Infinity; - return 0; - } - else - { - /* Must be a signaling or quiet NaN */ - loaded_data->exp = EXP_NaN; - loaded_data->tag = TW_NaN; - loaded_data->sigh = (m64 << 11) | 0x80000000; - loaded_data->sigh |= l64 >> 21; - loaded_data->sigl = l64 << 11; - return 0; /* The calling function must look for NaNs */ - } - } - else if ( exp < DOUBLE_Emin ) - { - /* Zero or de-normal */ - if ((m64 == 0) && (l64 == 0)) - { - /* Zero */ - int c = loaded_data->sign; - reg_move(&CONST_Z, loaded_data); - loaded_data->sign = c; - return 0; - } - else - { - /* De-normal */ - loaded_data->exp = DOUBLE_Emin + EXP_BIAS; - loaded_data->tag = TW_Valid; - loaded_data->sigh = m64 << 11; - loaded_data->sigh |= l64 >> 21; - loaded_data->sigl = l64 << 11; - normalize_nuo(loaded_data); - return denormal_operand(); - } - } - else - { - loaded_data->exp = exp + EXP_BIAS; - loaded_data->tag = TW_Valid; - loaded_data->sigh = (m64 << 11) | 0x80000000; - loaded_data->sigh |= l64 >> 21; - loaded_data->sigl = l64 << 11; - - return 0; - } -} - - -/* Get a float from user memory */ -int reg_load_single(float *single, FPU_REG *loaded_data) -{ - unsigned m32; - int exp; - - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_READ, single, 4); - m32 = get_fs_long((unsigned long *) single); - RE_ENTRANT_CHECK_ON; - - if (m32 & 0x80000000) - loaded_data->sign = SIGN_NEG; - else - loaded_data->sign = SIGN_POS; - if (!(m32 & 0x7fffffff)) - { - /* Zero */ - int c = loaded_data->sign; - reg_move(&CONST_Z, loaded_data); - loaded_data->sign = c; - return 0; - } - exp = ((m32 & 0x7f800000) >> 23) - SINGLE_Ebias; - m32 = (m32 & 0x7fffff) << 8; - if ( exp < SINGLE_Emin ) - { - /* De-normals */ - loaded_data->exp = SINGLE_Emin + EXP_BIAS; - loaded_data->tag = TW_Valid; - loaded_data->sigh = m32; - loaded_data->sigl = 0; - normalize_nuo(loaded_data); - return denormal_operand(); - } - else if ( exp > SINGLE_Emax ) - { - /* Infinity or NaN */ - if ( m32 == 0 ) - { - /* +- infinity */ - loaded_data->sigh = 0x80000000; - loaded_data->sigl = 0x00000000; - loaded_data->exp = EXP_Infinity; - loaded_data->tag = TW_Infinity; - return 0; - } - else - { - /* Must be a signaling or quiet NaN */ - loaded_data->exp = EXP_NaN; - loaded_data->tag = TW_NaN; - loaded_data->sigh = m32 | 0x80000000; - loaded_data->sigl = 0; - return 0; /* The calling function must look for NaNs */ - } - } - else - { - loaded_data->exp = exp + EXP_BIAS; - loaded_data->sigh = m32 | 0x80000000; - loaded_data->sigl = 0; - loaded_data->tag = TW_Valid; - return 0; - } -} - - -/* Get a long long from user memory */ -void reg_load_int64(long long *_s, FPU_REG *loaded_data) -{ - int e; - long long s; - - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_READ, _s, 8); - ((unsigned long *)&s)[0] = get_fs_long((unsigned long *) _s); - ((unsigned long *)&s)[1] = get_fs_long(1 + (unsigned long *) _s); - RE_ENTRANT_CHECK_ON; - - if (s == 0) - { reg_move(&CONST_Z, loaded_data); return; } - - if (s > 0) - loaded_data->sign = SIGN_POS; - else - { - s = -s; - loaded_data->sign = SIGN_NEG; - } - - e = EXP_BIAS + 63; - significand(loaded_data) = s; - loaded_data->exp = e; - loaded_data->tag = TW_Valid; - normalize_nuo(loaded_data); -} - - -/* Get a long from user memory */ -void reg_load_int32(long *_s, FPU_REG *loaded_data) -{ - long s; - int e; - - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_READ, _s, 4); - s = (long)get_fs_long((unsigned long *) _s); - RE_ENTRANT_CHECK_ON; - - if (s == 0) - { reg_move(&CONST_Z, loaded_data); return; } - - if (s > 0) - loaded_data->sign = SIGN_POS; - else - { - s = -s; - loaded_data->sign = SIGN_NEG; - } - - e = EXP_BIAS + 31; - loaded_data->sigh = s; - loaded_data->sigl = 0; - loaded_data->exp = e; - loaded_data->tag = TW_Valid; - normalize_nuo(loaded_data); -} - - -/* Get a short from user memory */ -void reg_load_int16(short *_s, FPU_REG *loaded_data) -{ - int s, e; - - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_READ, _s, 2); - /* Cast as short to get the sign extended. */ - s = (short)get_fs_word((unsigned short *) _s); - RE_ENTRANT_CHECK_ON; - - if (s == 0) - { reg_move(&CONST_Z, loaded_data); return; } - - if (s > 0) - loaded_data->sign = SIGN_POS; - else - { - s = -s; - loaded_data->sign = SIGN_NEG; - } - - e = EXP_BIAS + 15; - loaded_data->sigh = s << 16; - - loaded_data->sigl = 0; - loaded_data->exp = e; - loaded_data->tag = TW_Valid; - normalize_nuo(loaded_data); -} - - -/* Get a packed bcd array from user memory */ -void reg_load_bcd(char *s, FPU_REG *loaded_data) -{ - int pos; - unsigned char bcd; - long long l=0; - - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_READ, s, 10); - RE_ENTRANT_CHECK_ON; - for ( pos = 8; pos >= 0; pos--) - { - l *= 10; - RE_ENTRANT_CHECK_OFF; - bcd = (unsigned char)get_fs_byte((unsigned char *) s+pos); - RE_ENTRANT_CHECK_ON; - l += bcd >> 4; - l *= 10; - l += bcd & 0x0f; - } - - RE_ENTRANT_CHECK_OFF; - loaded_data->sign = - ((unsigned char)get_fs_byte((unsigned char *) s+9)) & 0x80 ? - SIGN_NEG : SIGN_POS; - RE_ENTRANT_CHECK_ON; - - if (l == 0) - { - char sign = loaded_data->sign; - reg_move(&CONST_Z, loaded_data); - loaded_data->sign = sign; - } - else - { - significand(loaded_data) = l; - loaded_data->exp = EXP_BIAS + 63; - loaded_data->tag = TW_Valid; - normalize_nuo(loaded_data); - } -} - -/*===========================================================================*/ - -/* Put a long double into user memory */ -int reg_store_extended(long double *d, FPU_REG *st0_ptr) -{ - /* - The only exception raised by an attempt to store to an - extended format is the Invalid Stack exception, i.e. - attempting to store from an empty register. - */ - - if ( st0_ptr->tag != TW_Empty ) - { - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_WRITE, d, 10); - RE_ENTRANT_CHECK_ON; - write_to_extended(st0_ptr, (char *) d); - return 1; - } - - /* Empty register (stack underflow) */ - EXCEPTION(EX_StackUnder); - if ( control_word & CW_Invalid ) - { - /* The masked response */ - /* Put out the QNaN indefinite */ - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_WRITE,d,10); - put_fs_long(0, (unsigned long *) d); - put_fs_long(0xc0000000, 1 + (unsigned long *) d); - put_fs_word(0xffff, 4 + (short *) d); - RE_ENTRANT_CHECK_ON; - return 1; - } - else - return 0; - -} - - -/* Put a double into user memory */ -int reg_store_double(double *dfloat, FPU_REG *st0_ptr) -{ - unsigned long l[2]; - unsigned long increment = 0; /* avoid gcc warnings */ - char st0_tag = st0_ptr->tag; - - if (st0_tag == TW_Valid) - { - int exp; - FPU_REG tmp; - - reg_move(st0_ptr, &tmp); - exp = tmp.exp - EXP_BIAS; - - if ( exp < DOUBLE_Emin ) /* It may be a denormal */ - { - int precision_loss; - - /* A denormal will always underflow. */ -#ifndef PECULIAR_486 - /* An 80486 is supposed to be able to generate - a denormal exception here, but... */ - if ( st0_ptr->exp <= EXP_UNDER ) - { - /* Underflow has priority. */ - if ( control_word & CW_Underflow ) - denormal_operand(); - } -#endif PECULIAR_486 - - tmp.exp += -DOUBLE_Emin + 52; /* largest exp to be 51 */ - - if ( (precision_loss = round_to_int(&tmp)) ) - { -#ifdef PECULIAR_486 - /* Did it round to a non-denormal ? */ - /* This behaviour might be regarded as peculiar, it appears - that the 80486 rounds to the dest precision, then - converts to decide underflow. */ - if ( !((tmp.sigh == 0x00100000) && (tmp.sigl == 0) && - (st0_ptr->sigl & 0x000007ff)) ) -#endif PECULIAR_486 - { - EXCEPTION(EX_Underflow); - /* This is a special case: see sec 16.2.5.1 of - the 80486 book */ - if ( !(control_word & CW_Underflow) ) - return 0; - } - EXCEPTION(precision_loss); - if ( !(control_word & CW_Precision) ) - return 0; - } - l[0] = tmp.sigl; - l[1] = tmp.sigh; - } - else - { - if ( tmp.sigl & 0x000007ff ) - { - switch (control_word & CW_RC) - { - case RC_RND: - /* Rounding can get a little messy.. */ - increment = ((tmp.sigl & 0x7ff) > 0x400) | /* nearest */ - ((tmp.sigl & 0xc00) == 0xc00); /* odd -> even */ - break; - case RC_DOWN: /* towards -infinity */ - increment = (tmp.sign == SIGN_POS) ? 0 : tmp.sigl & 0x7ff; - break; - case RC_UP: /* towards +infinity */ - increment = (tmp.sign == SIGN_POS) ? tmp.sigl & 0x7ff : 0; - break; - case RC_CHOP: - increment = 0; - break; - } - - /* Truncate the mantissa */ - tmp.sigl &= 0xfffff800; - - if ( increment ) - { - set_precision_flag_up(); - - if ( tmp.sigl >= 0xfffff800 ) - { - /* the sigl part overflows */ - if ( tmp.sigh == 0xffffffff ) - { - /* The sigh part overflows */ - tmp.sigh = 0x80000000; - exp++; - if (exp >= EXP_OVER) - goto overflow; - } - else - { - tmp.sigh ++; - } - tmp.sigl = 0x00000000; - } - else - { - /* We only need to increment sigl */ - tmp.sigl += 0x00000800; - } - } - else - set_precision_flag_down(); - } - - l[0] = (tmp.sigl >> 11) | (tmp.sigh << 21); - l[1] = ((tmp.sigh >> 11) & 0xfffff); - - if ( exp > DOUBLE_Emax ) - { - overflow: - EXCEPTION(EX_Overflow); - if ( !(control_word & CW_Overflow) ) - return 0; - set_precision_flag_up(); - if ( !(control_word & CW_Precision) ) - return 0; - - /* This is a special case: see sec 16.2.5.1 of the 80486 book */ - /* Overflow to infinity */ - l[0] = 0x00000000; /* Set to */ - l[1] = 0x7ff00000; /* + INF */ - } - else - { - /* Add the exponent */ - l[1] |= (((exp+DOUBLE_Ebias) & 0x7ff) << 20); - } - } - } - else if (st0_tag == TW_Zero) - { - /* Number is zero */ - l[0] = 0; - l[1] = 0; - } - else if (st0_tag == TW_Infinity) - { - l[0] = 0; - l[1] = 0x7ff00000; - } - else if (st0_tag == TW_NaN) - { - /* See if we can get a valid NaN from the FPU_REG */ - l[0] = (st0_ptr->sigl >> 11) | (st0_ptr->sigh << 21); - l[1] = ((st0_ptr->sigh >> 11) & 0xfffff); - if ( !(st0_ptr->sigh & 0x40000000) ) - { - /* It is a signalling NaN */ - EXCEPTION(EX_Invalid); - if ( !(control_word & CW_Invalid) ) - return 0; - l[1] |= (0x40000000 >> 11); - } - l[1] |= 0x7ff00000; - } - else if ( st0_tag == TW_Empty ) - { - /* Empty register (stack underflow) */ - EXCEPTION(EX_StackUnder); - if ( control_word & CW_Invalid ) - { - /* The masked response */ - /* Put out the QNaN indefinite */ - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_WRITE,(void *)dfloat,8); - put_fs_long(0, (unsigned long *) dfloat); - put_fs_long(0xfff80000, 1 + (unsigned long *) dfloat); - RE_ENTRANT_CHECK_ON; - return 1; - } - else - return 0; - } - if ( st0_ptr->sign ) - l[1] |= 0x80000000; - - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_WRITE,(void *)dfloat,8); - put_fs_long(l[0], (unsigned long *)dfloat); - put_fs_long(l[1], 1 + (unsigned long *)dfloat); - RE_ENTRANT_CHECK_ON; - - return 1; -} - - -/* Put a float into user memory */ -int reg_store_single(float *single, FPU_REG *st0_ptr) -{ - long templ; - unsigned long increment = 0; /* avoid gcc warnings */ - char st0_tag = st0_ptr->tag; - - if (st0_tag == TW_Valid) - { - int exp; - FPU_REG tmp; - - reg_move(st0_ptr, &tmp); - exp = tmp.exp - EXP_BIAS; - - if ( exp < SINGLE_Emin ) - { - int precision_loss; - - /* A denormal will always underflow. */ -#ifndef PECULIAR_486 - /* An 80486 is supposed to be able to generate - a denormal exception here, but... */ - if ( st0_ptr->exp <= EXP_UNDER ) - { - /* Underflow has priority. */ - if ( control_word & CW_Underflow ) - denormal_operand(); - } -#endif PECULIAR_486 - - tmp.exp += -SINGLE_Emin + 23; /* largest exp to be 22 */ - - if ( (precision_loss = round_to_int(&tmp)) ) - { -#ifdef PECULIAR_486 - /* Did it round to a non-denormal ? */ - /* This behaviour might be regarded as peculiar, it appears - that the 80486 rounds to the dest precision, then - converts to decide underflow. */ - if ( !((tmp.sigl == 0x00800000) && - ((st0_ptr->sigh & 0x000000ff) || st0_ptr->sigl)) ) -#endif PECULIAR_486 - { - EXCEPTION(EX_Underflow); - /* This is a special case: see sec 16.2.5.1 of - the 80486 book */ - if ( !(control_word & EX_Underflow) ) - return 0; - } - EXCEPTION(precision_loss); - if ( !(control_word & EX_Precision) ) - return 0; - } - templ = tmp.sigl; - } - else - { - if ( tmp.sigl | (tmp.sigh & 0x000000ff) ) - { - unsigned long sigh = tmp.sigh; - unsigned long sigl = tmp.sigl; - - switch (control_word & CW_RC) - { - case RC_RND: - increment = ((sigh & 0xff) > 0x80) /* more than half */ - || (((sigh & 0xff) == 0x80) && sigl) /* more than half */ - || ((sigh & 0x180) == 0x180); /* round to even */ - break; - case RC_DOWN: /* towards -infinity */ - increment = (tmp.sign == SIGN_POS) - ? 0 : (sigl | (sigh & 0xff)); - break; - case RC_UP: /* towards +infinity */ - increment = (tmp.sign == SIGN_POS) - ? (sigl | (sigh & 0xff)) : 0; - break; - case RC_CHOP: - increment = 0; - break; - } - - /* Truncate part of the mantissa */ - tmp.sigl = 0; - - if (increment) - { - set_precision_flag_up(); - - if ( sigh >= 0xffffff00 ) - { - /* The sigh part overflows */ - tmp.sigh = 0x80000000; - exp++; - if ( exp >= EXP_OVER ) - goto overflow; - } - else - { - tmp.sigh &= 0xffffff00; - tmp.sigh += 0x100; - } - } - else - { - set_precision_flag_down(); - tmp.sigh &= 0xffffff00; /* Finish the truncation */ - } - } - - templ = (tmp.sigh >> 8) & 0x007fffff; - - if ( exp > SINGLE_Emax ) - { - overflow: - EXCEPTION(EX_Overflow); - if ( !(control_word & CW_Overflow) ) - return 0; - set_precision_flag_up(); - if ( !(control_word & CW_Precision) ) - return 0; - - /* This is a special case: see sec 16.2.5.1 of the 80486 book. */ - /* Masked response is overflow to infinity. */ - templ = 0x7f800000; - } - else - templ |= ((exp+SINGLE_Ebias) & 0xff) << 23; - } - } - else if (st0_tag == TW_Zero) - { - templ = 0; - } - else if (st0_tag == TW_Infinity) - { - templ = 0x7f800000; - } - else if (st0_tag == TW_NaN) - { - /* See if we can get a valid NaN from the FPU_REG */ - templ = st0_ptr->sigh >> 8; - if ( !(st0_ptr->sigh & 0x40000000) ) - { - /* It is a signalling NaN */ - EXCEPTION(EX_Invalid); - if ( !(control_word & CW_Invalid) ) - return 0; - templ |= (0x40000000 >> 8); - } - templ |= 0x7f800000; - } - else if ( st0_tag == TW_Empty ) - { - /* Empty register (stack underflow) */ - EXCEPTION(EX_StackUnder); - if ( control_word & EX_Invalid ) - { - /* The masked response */ - /* Put out the QNaN indefinite */ - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_WRITE,(void *)single,4); - put_fs_long(0xffc00000, (unsigned long *) single); - RE_ENTRANT_CHECK_ON; - return 1; - } - else - return 0; - } -#ifdef PARANOID - else - { - EXCEPTION(EX_INTERNAL|0x163); - return 0; - } -#endif - if (st0_ptr->sign) - templ |= 0x80000000; - - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_WRITE,(void *)single,4); - put_fs_long(templ,(unsigned long *) single); - RE_ENTRANT_CHECK_ON; - - return 1; -} - - -/* Put a long long into user memory */ -int reg_store_int64(long long *d, FPU_REG *st0_ptr) -{ - FPU_REG t; - long long tll; - int precision_loss; - char st0_tag = st0_ptr->tag; - - if ( st0_tag == TW_Empty ) - { - /* Empty register (stack underflow) */ - EXCEPTION(EX_StackUnder); - goto invalid_operand; - } - else if ( (st0_tag == TW_Infinity) || - (st0_tag == TW_NaN) ) - { - EXCEPTION(EX_Invalid); - goto invalid_operand; - } - - reg_move(st0_ptr, &t); - precision_loss = round_to_int(&t); - ((long *)&tll)[0] = t.sigl; - ((long *)&tll)[1] = t.sigh; - if ( (precision_loss == 1) || - ((t.sigh & 0x80000000) && - !((t.sigh == 0x80000000) && (t.sigl == 0) && - (t.sign == SIGN_NEG))) ) - { - EXCEPTION(EX_Invalid); - /* This is a special case: see sec 16.2.5.1 of the 80486 book */ - invalid_operand: - if ( control_word & EX_Invalid ) - { - /* Produce something like QNaN "indefinite" */ - tll = 0x8000000000000000LL; - } - else - return 0; - } - else - { - if ( precision_loss ) - set_precision_flag(precision_loss); - if ( t.sign ) - tll = - tll; - } - - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_WRITE,(void *)d,8); - put_fs_long(((long *)&tll)[0],(unsigned long *) d); - put_fs_long(((long *)&tll)[1],1 + (unsigned long *) d); - RE_ENTRANT_CHECK_ON; - - return 1; -} - - -/* Put a long into user memory */ -int reg_store_int32(long *d, FPU_REG *st0_ptr) -{ - FPU_REG t; - int precision_loss; - char st0_tag = st0_ptr->tag; - - if ( st0_tag == TW_Empty ) - { - /* Empty register (stack underflow) */ - EXCEPTION(EX_StackUnder); - goto invalid_operand; - } - else if ( (st0_tag == TW_Infinity) || - (st0_tag == TW_NaN) ) - { - EXCEPTION(EX_Invalid); - goto invalid_operand; - } - - reg_move(st0_ptr, &t); - precision_loss = round_to_int(&t); - if (t.sigh || - ((t.sigl & 0x80000000) && - !((t.sigl == 0x80000000) && (t.sign == SIGN_NEG))) ) - { - EXCEPTION(EX_Invalid); - /* This is a special case: see sec 16.2.5.1 of the 80486 book */ - invalid_operand: - if ( control_word & EX_Invalid ) - { - /* Produce something like QNaN "indefinite" */ - t.sigl = 0x80000000; - } - else - return 0; - } - else - { - if ( precision_loss ) - set_precision_flag(precision_loss); - if ( t.sign ) - t.sigl = -(long)t.sigl; - } - - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_WRITE,d,4); - put_fs_long(t.sigl, (unsigned long *) d); - RE_ENTRANT_CHECK_ON; - - return 1; -} - - -/* Put a short into user memory */ -int reg_store_int16(short *d, FPU_REG *st0_ptr) -{ - FPU_REG t; - int precision_loss; - char st0_tag = st0_ptr->tag; - - if ( st0_tag == TW_Empty ) - { - /* Empty register (stack underflow) */ - EXCEPTION(EX_StackUnder); - goto invalid_operand; - } - else if ( (st0_tag == TW_Infinity) || - (st0_tag == TW_NaN) ) - { - EXCEPTION(EX_Invalid); - goto invalid_operand; - } - - reg_move(st0_ptr, &t); - precision_loss = round_to_int(&t); - if (t.sigh || - ((t.sigl & 0xffff8000) && - !((t.sigl == 0x8000) && (t.sign == SIGN_NEG))) ) - { - EXCEPTION(EX_Invalid); - /* This is a special case: see sec 16.2.5.1 of the 80486 book */ - invalid_operand: - if ( control_word & EX_Invalid ) - { - /* Produce something like QNaN "indefinite" */ - t.sigl = 0x8000; - } - else - return 0; - } - else - { - if ( precision_loss ) - set_precision_flag(precision_loss); - if ( t.sign ) - t.sigl = -t.sigl; - } - - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_WRITE,d,2); - put_fs_word((short)t.sigl,(short *) d); - RE_ENTRANT_CHECK_ON; - - return 1; -} - - -/* Put a packed bcd array into user memory */ -int reg_store_bcd(char *d, FPU_REG *st0_ptr) -{ - FPU_REG t; - unsigned long long ll; - unsigned char b; - int i, precision_loss; - unsigned char sign = (st0_ptr->sign == SIGN_NEG) ? 0x80 : 0; - char st0_tag = st0_ptr->tag; - - if ( st0_tag == TW_Empty ) - { - /* Empty register (stack underflow) */ - EXCEPTION(EX_StackUnder); - goto invalid_operand; - } - - reg_move(st0_ptr, &t); - precision_loss = round_to_int(&t); - ll = significand(&t); - - /* Check for overflow, by comparing with 999999999999999999 decimal. */ - if ( (t.sigh > 0x0de0b6b3) || - ((t.sigh == 0x0de0b6b3) && (t.sigl > 0xa763ffff)) ) - { - EXCEPTION(EX_Invalid); - /* This is a special case: see sec 16.2.5.1 of the 80486 book */ - invalid_operand: - if ( control_word & CW_Invalid ) - { - /* Produce the QNaN "indefinite" */ - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_WRITE,d,10); - for ( i = 0; i < 7; i++) - put_fs_byte(0, (unsigned char *) d+i); /* These bytes "undefined" */ - put_fs_byte(0xc0, (unsigned char *) d+7); /* This byte "undefined" */ - put_fs_byte(0xff, (unsigned char *) d+8); - put_fs_byte(0xff, (unsigned char *) d+9); - RE_ENTRANT_CHECK_ON; - return 1; - } - else - return 0; - } - else if ( precision_loss ) - { - /* Precision loss doesn't stop the data transfer */ - set_precision_flag(precision_loss); - } - - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_WRITE,d,10); - RE_ENTRANT_CHECK_ON; - for ( i = 0; i < 9; i++) - { - b = div_small(&ll, 10); - b |= (div_small(&ll, 10)) << 4; - RE_ENTRANT_CHECK_OFF; - put_fs_byte(b,(unsigned char *) d+i); - RE_ENTRANT_CHECK_ON; - } - RE_ENTRANT_CHECK_OFF; - put_fs_byte(sign,(unsigned char *) d+9); - RE_ENTRANT_CHECK_ON; - - return 1; -} - -/*===========================================================================*/ - -/* r gets mangled such that sig is int, sign: - it is NOT normalized */ -/* The return value (in eax) is zero if the result is exact, - if bits are changed due to rounding, truncation, etc, then - a non-zero value is returned */ -/* Overflow is signalled by a non-zero return value (in eax). - In the case of overflow, the returned significand always has the - largest possible value */ -int round_to_int(FPU_REG *r) -{ - char very_big; - unsigned eax; - - if (r->tag == TW_Zero) - { - /* Make sure that zero is returned */ - significand(r) = 0; - return 0; /* o.k. */ - } - - if (r->exp > EXP_BIAS + 63) - { - r->sigl = r->sigh = ~0; /* The largest representable number */ - return 1; /* overflow */ - } - - eax = shrxs(&r->sigl, EXP_BIAS + 63 - r->exp); - very_big = !(~(r->sigh) | ~(r->sigl)); /* test for 0xfff...fff */ -#define half_or_more (eax & 0x80000000) -#define frac_part (eax) -#define more_than_half ((eax & 0x80000001) == 0x80000001) - switch (control_word & CW_RC) - { - case RC_RND: - if ( more_than_half /* nearest */ - || (half_or_more && (r->sigl & 1)) ) /* odd -> even */ - { - if ( very_big ) return 1; /* overflow */ - significand(r) ++; - return PRECISION_LOST_UP; - } - break; - case RC_DOWN: - if (frac_part && r->sign) - { - if ( very_big ) return 1; /* overflow */ - significand(r) ++; - return PRECISION_LOST_UP; - } - break; - case RC_UP: - if (frac_part && !r->sign) - { - if ( very_big ) return 1; /* overflow */ - significand(r) ++; - return PRECISION_LOST_UP; - } - break; - case RC_CHOP: - break; - } - - return eax ? PRECISION_LOST_DOWN : 0; - -} - -/*===========================================================================*/ - -char *fldenv(fpu_addr_modes addr_modes, char *s) -{ - unsigned short tag_word = 0; - unsigned char tag; - int i; - - if ( (addr_modes.default_mode == VM86) || - ((addr_modes.default_mode == PM16) - ^ (addr_modes.override.operand_size == OP_SIZE_PREFIX)) ) - { - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_READ, s, 0x0e); - control_word = get_fs_word((unsigned short *) s); - partial_status = get_fs_word((unsigned short *) (s+2)); - tag_word = get_fs_word((unsigned short *) (s+4)); - instruction_address.offset = get_fs_word((unsigned short *) (s+6)); - instruction_address.selector = get_fs_word((unsigned short *) (s+8)); - operand_address.offset = get_fs_word((unsigned short *) (s+0x0a)); - operand_address.selector = get_fs_word((unsigned short *) (s+0x0c)); - RE_ENTRANT_CHECK_ON; - s += 0x0e; - if ( addr_modes.default_mode == VM86 ) - { - instruction_address.offset - += (instruction_address.selector & 0xf000) << 4; - operand_address.offset += (operand_address.selector & 0xf000) << 4; - } - } - else - { - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_READ, s, 0x1c); - control_word = get_fs_word((unsigned short *) s); - partial_status = get_fs_word((unsigned short *) (s+4)); - tag_word = get_fs_word((unsigned short *) (s+8)); - instruction_address.offset = get_fs_long((unsigned long *) (s+0x0c)); - instruction_address.selector = get_fs_word((unsigned short *) (s+0x10)); - instruction_address.opcode = get_fs_word((unsigned short *) (s+0x12)); - operand_address.offset = get_fs_long((unsigned long *) (s+0x14)); - operand_address.selector = get_fs_long((unsigned long *) (s+0x18)); - RE_ENTRANT_CHECK_ON; - s += 0x1c; - } - -#ifdef PECULIAR_486 - control_word &= ~0xe080; -#endif PECULIAR_486 - - top = (partial_status >> SW_Top_Shift) & 7; - - if ( partial_status & ~control_word & CW_Exceptions ) - partial_status |= (SW_Summary | SW_Backward); - else - partial_status &= ~(SW_Summary | SW_Backward); - - for ( i = 0; i < 8; i++ ) - { - tag = tag_word & 3; - tag_word >>= 2; - - if ( tag == 3 ) - /* New tag is empty. Accept it */ - regs[i].tag = TW_Empty; - else if ( regs[i].tag == TW_Empty ) - { - /* Old tag is empty and new tag is not empty. New tag is determined - by old reg contents */ - if ( regs[i].exp == EXP_BIAS - EXTENDED_Ebias ) - { - if ( !(regs[i].sigl | regs[i].sigh) ) - regs[i].tag = TW_Zero; - else - regs[i].tag = TW_Valid; - } - else if ( regs[i].exp == 0x7fff + EXP_BIAS - EXTENDED_Ebias ) - { - if ( !((regs[i].sigh & ~0x80000000) | regs[i].sigl) ) - regs[i].tag = TW_Infinity; - else - regs[i].tag = TW_NaN; - } - else - regs[i].tag = TW_Valid; - } - /* Else old tag is not empty and new tag is not empty. Old tag - remains correct */ - } - - return s; -} - - -void frstor(fpu_addr_modes addr_modes, char *data_address) -{ - int i, stnr; - unsigned char tag; - char *s = fldenv(addr_modes, data_address); - - for ( i = 0; i < 8; i++ ) - { - /* Load each register. */ - stnr = (i+top) & 7; - tag = regs[stnr].tag; /* Derived from the fldenv() loaded tag word. */ - reg_load_extended((long double *)(s+i*10), ®s[stnr]); - if ( tag == TW_Empty ) /* The loaded data over-rides all other cases. */ - regs[stnr].tag = tag; - } - -} - - -unsigned short tag_word(void) -{ - unsigned short word = 0; - unsigned char tag; - int i; - - for ( i = 7; i >= 0; i-- ) - { - switch ( tag = regs[i].tag ) - { - case TW_Valid: - if ( regs[i].exp <= (EXP_BIAS - EXTENDED_Ebias) ) - tag = 2; - break; - case TW_Infinity: - case TW_NaN: - tag = 2; - break; - case TW_Empty: - tag = 3; - break; - /* TW_Zero already has the correct value */ - } - word <<= 2; - word |= tag; - } - return word; -} - - -char *fstenv(fpu_addr_modes addr_modes, char *d) -{ - if ( (addr_modes.default_mode == VM86) || - ((addr_modes.default_mode == PM16) - ^ (addr_modes.override.operand_size == OP_SIZE_PREFIX)) ) - { - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_WRITE,d,14); -#ifdef PECULIAR_486 - put_fs_long(control_word & ~0xe080, (unsigned short *) d); -#else - put_fs_word(control_word, (unsigned short *) d); -#endif PECULIAR_486 - put_fs_word(status_word(), (unsigned short *) (d+2)); - put_fs_word(tag_word(), (unsigned short *) (d+4)); - put_fs_word(instruction_address.offset, (unsigned short *) (d+6)); - put_fs_word(operand_address.offset, (unsigned short *) (d+0x0a)); - if ( addr_modes.default_mode == VM86 ) - { - put_fs_word((instruction_address.offset & 0xf0000) >> 4, - (unsigned short *) (d+8)); - put_fs_word((operand_address.offset & 0xf0000) >> 4, - (unsigned short *) (d+0x0c)); - } - else - { - put_fs_word(instruction_address.selector, (unsigned short *) (d+8)); - put_fs_word(operand_address.selector, (unsigned short *) (d+0x0c)); - } - RE_ENTRANT_CHECK_ON; - d += 0x0e; - } - else - { - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_WRITE,d,28); -#ifdef PECULIAR_486 - /* An 80486 sets all the reserved bits to 1. */ - put_fs_long(0xffff0040 | (control_word & ~0xe080), (unsigned long *) d); - put_fs_long(0xffff0000 | status_word(), (unsigned long *) (d+4)); - put_fs_long(0xffff0000 | tag_word(), (unsigned long *) (d+8)); -#else - put_fs_word(control_word, (unsigned short *) d); - put_fs_word(status_word(), (unsigned short *) (d+4)); - put_fs_word(tag_word(), (unsigned short *) (d+8)); -#endif PECULIAR_486 - put_fs_long(instruction_address.offset, (unsigned long *) (d+0x0c)); - put_fs_word(instruction_address.selector, (unsigned short *) (d+0x10)); - put_fs_word(instruction_address.opcode, (unsigned short *) (d+0x12)); - put_fs_long(operand_address.offset, (unsigned long *) (d+0x14)); -#ifdef PECULIAR_486 - /* An 80486 sets all the reserved bits to 1. */ - put_fs_word(operand_address.selector, (unsigned short *) (d+0x18)); - put_fs_word(0xffff, (unsigned short *) (d+0x1a)); -#else - put_fs_long(operand_address.selector, (unsigned long *) (d+0x18)); -#endif PECULIAR_486 - RE_ENTRANT_CHECK_ON; - d += 0x1c; - } - - control_word |= CW_Exceptions; - partial_status &= ~(SW_Summary | SW_Backward); - - return d; -} - - -void fsave(fpu_addr_modes addr_modes, char *data_address) -{ - char *d; - int i; - - d = fstenv(addr_modes, data_address); - RE_ENTRANT_CHECK_OFF; - FPU_verify_area(VERIFY_WRITE,d,80); - RE_ENTRANT_CHECK_ON; - for ( i = 0; i < 8; i++ ) - write_to_extended(®s[(top + i) & 7], d + 10 * i); - - finit(); - -} - -/*===========================================================================*/ - -/* - A call to this function must be preceded by a call to - FPU_verify_area() to verify access to the 10 bytes at d - */ -static void write_to_extended(FPU_REG *rp, char *d) -{ - long e; - FPU_REG tmp; - - e = rp->exp - EXP_BIAS + EXTENDED_Ebias; - -#ifdef PARANOID - switch ( rp->tag ) - { - case TW_Zero: - if ( rp->sigh | rp->sigl | e ) - EXCEPTION(EX_INTERNAL | 0x160); - break; - case TW_Infinity: - case TW_NaN: - if ( (e ^ 0x7fff) | !(rp->sigh & 0x80000000) ) - EXCEPTION(EX_INTERNAL | 0x161); - break; - default: - if (e > 0x7fff || e < -63) - EXCEPTION(EX_INTERNAL | 0x162); - } -#endif PARANOID - - /* - All numbers except denormals are stored internally in a - format which is compatible with the extended real number - format. - */ - if ( e > 0 ) - { - /* just copy the reg */ - RE_ENTRANT_CHECK_OFF; - put_fs_long(rp->sigl, (unsigned long *) d); - put_fs_long(rp->sigh, (unsigned long *) (d + 4)); - RE_ENTRANT_CHECK_ON; - } - else - { - /* - The number is a de-normal stored as a normal using our - extra exponent range, or is Zero. - Convert it back to a de-normal, or leave it as Zero. - */ - reg_move(rp, &tmp); - tmp.exp += -EXTENDED_Emin + 63; /* largest exp to be 63 */ - round_to_int(&tmp); - e = 0; - RE_ENTRANT_CHECK_OFF; - put_fs_long(tmp.sigl, (unsigned long *) d); - put_fs_long(tmp.sigh, (unsigned long *) (d + 4)); - RE_ENTRANT_CHECK_ON; - } - e |= rp->sign == SIGN_POS ? 0 : 0x8000; - RE_ENTRANT_CHECK_OFF; - put_fs_word(e, (unsigned short *) (d + 8)); - RE_ENTRANT_CHECK_ON; -} diff --git a/drivers/FPU-emu/reg_mul.c b/drivers/FPU-emu/reg_mul.c deleted file mode 100644 index 75246187b..000000000 --- a/drivers/FPU-emu/reg_mul.c +++ /dev/null @@ -1,105 +0,0 @@ -/*---------------------------------------------------------------------------+ - | reg_mul.c | - | | - | Multiply one FPU_REG by another, put the result in a destination FPU_REG. | - | | - | Copyright (C) 1992,1993 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------+ - | The destination may be any FPU_REG, including one of the source FPU_REGs. | - +---------------------------------------------------------------------------*/ - -#include "exception.h" -#include "reg_constant.h" -#include "fpu_emu.h" -#include "fpu_system.h" - - -/* This routine must be called with non-empty source registers */ -int reg_mul(FPU_REG const *a, FPU_REG const *b, - FPU_REG *dest, unsigned int control_w) -{ - char saved_sign = dest->sign; - char sign = (a->sign ^ b->sign); - - if (!(a->tag | b->tag)) - { - /* Both regs Valid, this should be the most common case. */ - dest->sign = sign; - if ( reg_u_mul(a, b, dest, control_w) ) - { - dest->sign = saved_sign; - return 1; - } - return 0; - } - else if ((a->tag <= TW_Zero) && (b->tag <= TW_Zero)) - { -#ifdef DENORM_OPERAND - if ( ((b->tag == TW_Valid) && (b->exp <= EXP_UNDER)) || - ((a->tag == TW_Valid) && (a->exp <= EXP_UNDER)) ) - { - if ( denormal_operand() ) return 1; - } -#endif DENORM_OPERAND - /* Must have either both arguments == zero, or - one valid and the other zero. - The result is therefore zero. */ - reg_move(&CONST_Z, dest); - /* The 80486 book says that the answer is +0, but a real - 80486 behaves this way. - IEEE-754 apparently says it should be this way. */ - dest->sign = sign; - return 0; - } - else - { - /* Must have infinities, NaNs, etc */ - if ( (a->tag == TW_NaN) || (b->tag == TW_NaN) ) - { return real_2op_NaN(a, b, dest); } - else if (a->tag == TW_Infinity) - { - if (b->tag == TW_Zero) - { return arith_invalid(dest); } /* Zero*Infinity is invalid */ - else - { -#ifdef DENORM_OPERAND - if ( (b->tag == TW_Valid) && (b->exp <= EXP_UNDER) && - denormal_operand() ) - return 1; -#endif DENORM_OPERAND - reg_move(a, dest); - dest->sign = sign; - } - return 0; - } - else if (b->tag == TW_Infinity) - { - if (a->tag == TW_Zero) - { return arith_invalid(dest); } /* Zero*Infinity is invalid */ - else - { -#ifdef DENORM_OPERAND - if ( (a->tag == TW_Valid) && (a->exp <= EXP_UNDER) && - denormal_operand() ) - return 1; -#endif DENORM_OPERAND - reg_move(b, dest); - dest->sign = sign; - } - return 0; - } -#ifdef PARANOID - else - { - EXCEPTION(EX_INTERNAL|0x102); - return 1; - } -#endif PARANOID - } -} diff --git a/drivers/FPU-emu/reg_norm.S b/drivers/FPU-emu/reg_norm.S deleted file mode 100644 index 9b7a9d77d..000000000 --- a/drivers/FPU-emu/reg_norm.S +++ /dev/null @@ -1,150 +0,0 @@ -/*---------------------------------------------------------------------------+ - | reg_norm.S | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | Normalize the value in a FPU_REG. | - | | - | Call from C as: | - | void normalize(FPU_REG *n) | - | | - | void normalize_nuo(FPU_REG *n) | - | | - +---------------------------------------------------------------------------*/ - -#include "fpu_asm.h" - - -.text - - .align 2,144 -.globl _normalize - -_normalize: - pushl %ebp - movl %esp,%ebp - pushl %ebx - - movl PARAM1,%ebx - -#ifdef PARANOID - cmpb TW_Valid,TAG(%ebx) - je L_ok - - pushl $0x220 - call _exception - addl $4,%esp - -L_ok: -#endif PARANOID - - movl SIGH(%ebx),%edx - movl SIGL(%ebx),%eax - - orl %edx,%edx /* ms bits */ - js L_done /* Already normalized */ - jnz L_shift_1 /* Shift left 1 - 31 bits */ - - orl %eax,%eax - jz L_zero /* The contents are zero */ - - movl %eax,%edx - xorl %eax,%eax - subl $32,EXP(%ebx) /* This can cause an underflow */ - -/* We need to shift left by 1 - 31 bits */ -L_shift_1: - bsrl %edx,%ecx /* get the required shift in %ecx */ - subl $31,%ecx - negl %ecx - shld %cl,%eax,%edx - shl %cl,%eax - subl %ecx,EXP(%ebx) /* This can cause an underflow */ - - movl %edx,SIGH(%ebx) - movl %eax,SIGL(%ebx) - -L_done: - cmpl EXP_OVER,EXP(%ebx) - jge L_overflow - - cmpl EXP_UNDER,EXP(%ebx) - jle L_underflow - -L_exit: - popl %ebx - leave - ret - - -L_zero: - movl EXP_UNDER,EXP(%ebx) - movb TW_Zero,TAG(%ebx) - jmp L_exit - -L_underflow: - push %ebx - call _arith_underflow - pop %ebx - jmp L_exit - -L_overflow: - push %ebx - call _arith_overflow - pop %ebx - jmp L_exit - - - -/* Normalise without reporting underflow or overflow */ - .align 2,144 -.globl _normalize_nuo - -_normalize_nuo: - pushl %ebp - movl %esp,%ebp - pushl %ebx - - movl PARAM1,%ebx - -#ifdef PARANOID - cmpb TW_Valid,TAG(%ebx) - je L_ok_nuo - - pushl $0x221 - call _exception - addl $4,%esp - -L_ok_nuo: -#endif PARANOID - - movl SIGH(%ebx),%edx - movl SIGL(%ebx),%eax - - orl %edx,%edx /* ms bits */ - js L_exit /* Already normalized */ - jnz L_nuo_shift_1 /* Shift left 1 - 31 bits */ - - orl %eax,%eax - jz L_zero /* The contents are zero */ - - movl %eax,%edx - xorl %eax,%eax - subl $32,EXP(%ebx) /* This can cause an underflow */ - -/* We need to shift left by 1 - 31 bits */ -L_nuo_shift_1: - bsrl %edx,%ecx /* get the required shift in %ecx */ - subl $31,%ecx - negl %ecx - shld %cl,%eax,%edx - shl %cl,%eax - subl %ecx,EXP(%ebx) /* This can cause an underflow */ - - movl %edx,SIGH(%ebx) - movl %eax,SIGL(%ebx) - jmp L_exit - - diff --git a/drivers/FPU-emu/reg_round.S b/drivers/FPU-emu/reg_round.S deleted file mode 100644 index bd8a40dc4..000000000 --- a/drivers/FPU-emu/reg_round.S +++ /dev/null @@ -1,701 +0,0 @@ - .file "reg_round.S" -/*---------------------------------------------------------------------------+ - | reg_round.S | - | | - | Rounding/truncation/etc for FPU basic arithmetic functions. | - | | - | Copyright (C) 1993 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | This code has four possible entry points. | - | The following must be entered by a jmp instruction: | - | fpu_reg_round, fpu_reg_round_sqrt, and fpu_Arith_exit. | - | | - | The _round_reg entry point is intended to be used by C code. | - | From C, call as: | - | void round_reg(FPU_REG *arg, unsigned int extent, unsigned int control_w) | - | | - | For correct "up" and "down" rounding, the argument must have the correct | - | sign. | - | | - +---------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------+ - | Four entry points. | - | | - | Needed by both the fpu_reg_round and fpu_reg_round_sqrt entry points: | - | %eax:%ebx 64 bit significand | - | %edx 32 bit extension of the significand | - | %edi pointer to an FPU_REG for the result to be stored | - | stack calling function must have set up a C stack frame and | - | pushed %esi, %edi, and %ebx | - | | - | Needed just for the fpu_reg_round_sqrt entry point: | - | %cx A control word in the same format as the FPU control word. | - | Otherwise, PARAM4 must give such a value. | - | | - | | - | The significand and its extension are assumed to be exact in the | - | following sense: | - | If the significand by itself is the exact result then the significand | - | extension (%edx) must contain 0, otherwise the significand extension | - | must be non-zero. | - | If the significand extension is non-zero then the significand is | - | smaller than the magnitude of the correct exact result by an amount | - | greater than zero and less than one ls bit of the significand. | - | The significand extension is only required to have three possible | - | non-zero values: | - | less than 0x80000000 <=> the significand is less than 1/2 an ls | - | bit smaller than the magnitude of the | - | true exact result. | - | exactly 0x80000000 <=> the significand is exactly 1/2 an ls bit | - | smaller than the magnitude of the true | - | exact result. | - | greater than 0x80000000 <=> the significand is more than 1/2 an ls | - | bit smaller than the magnitude of the | - | true exact result. | - | | - +---------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------+ - | The code in this module has become quite complex, but it should handle | - | all of the FPU flags which are set at this stage of the basic arithmetic | - | computations. | - | There are a few rare cases where the results are not set identically to | - | a real FPU. These require a bit more thought because at this stage the | - | results of the code here appear to be more consistent... | - | This may be changed in a future version. | - +---------------------------------------------------------------------------*/ - - -#include "fpu_asm.h" -#include "exception.h" -#include "control_w.h" - -/* Flags for FPU_bits_lost */ -#define LOST_DOWN $1 -#define LOST_UP $2 - -/* Flags for FPU_denormal */ -#define DENORMAL $1 -#define UNMASKED_UNDERFLOW $2 - - -#ifndef NON_REENTRANT_FPU -/* Make the code re-entrant by putting - local storage on the stack: */ -#define FPU_bits_lost (%esp) -#define FPU_denormal 1(%esp) - -#else -/* Not re-entrant, so we can gain speed by putting - local storage in a static area: */ -.data - .align 2,0 -FPU_bits_lost: - .byte 0 -FPU_denormal: - .byte 0 -#endif NON_REENTRANT_FPU - - -.text - .align 2,144 -.globl fpu_reg_round -.globl fpu_reg_round_sqrt -.globl fpu_Arith_exit -.globl _round_reg - -/* Entry point when called from C */ -_round_reg: - pushl %ebp - movl %esp,%ebp - pushl %esi - pushl %edi - pushl %ebx - - movl PARAM1,%edi - movl SIGH(%edi),%eax - movl SIGL(%edi),%ebx - movl PARAM2,%edx - movl PARAM3,%ecx - jmp fpu_reg_round_sqrt - -fpu_reg_round: /* Normal entry point */ - movl PARAM4,%ecx - -fpu_reg_round_sqrt: /* Entry point from wm_sqrt.S */ - -#ifndef NON_REENTRANT_FPU - pushl %ebx /* adjust the stack pointer */ -#endif NON_REENTRANT_FPU - -#ifdef PARANOID -/* Cannot use this here yet */ -/* orl %eax,%eax */ -/* jns L_entry_bugged */ -#endif PARANOID - - cmpl EXP_UNDER,EXP(%edi) - jle xMake_denorm /* The number is a de-normal */ - - movb $0,FPU_denormal /* 0 -> not a de-normal */ - -xDenorm_done: - movb $0,FPU_bits_lost /* No bits yet lost in rounding */ - - movl %ecx,%esi - andl CW_PC,%ecx - cmpl PR_64_BITS,%ecx - je LRound_To_64 - - cmpl PR_53_BITS,%ecx - je LRound_To_53 - - cmpl PR_24_BITS,%ecx - je LRound_To_24 - -#ifdef PECULIAR_486 -/* With the precision control bits set to 01 "(reserved)", a real 80486 - behaves as if the precision control bits were set to 11 "64 bits" */ - cmpl PR_RESERVED_BITS,%ecx - je LRound_To_64 -#ifdef PARANOID - jmp L_bugged_denorm_486 -#endif PARANOID -#else -#ifdef PARANOID - jmp L_bugged_denorm /* There is no bug, just a bad control word */ -#endif PARANOID -#endif PECULIAR_486 - - -/* Round etc to 24 bit precision */ -LRound_To_24: - movl %esi,%ecx - andl CW_RC,%ecx - cmpl RC_RND,%ecx - je LRound_nearest_24 - - cmpl RC_CHOP,%ecx - je LCheck_truncate_24 - - cmpl RC_UP,%ecx /* Towards +infinity */ - je LUp_24 - - cmpl RC_DOWN,%ecx /* Towards -infinity */ - je LDown_24 - -#ifdef PARANOID - jmp L_bugged_round24 -#endif PARANOID - -LUp_24: - cmpb SIGN_POS,SIGN(%edi) - jne LCheck_truncate_24 /* If negative then up==truncate */ - - jmp LCheck_24_round_up - -LDown_24: - cmpb SIGN_POS,SIGN(%edi) - je LCheck_truncate_24 /* If positive then down==truncate */ - -LCheck_24_round_up: - movl %eax,%ecx - andl $0x000000ff,%ecx - orl %ebx,%ecx - orl %edx,%ecx - jnz LDo_24_round_up - jmp LRe_normalise - -LRound_nearest_24: - /* Do rounding of the 24th bit if needed (nearest or even) */ - movl %eax,%ecx - andl $0x000000ff,%ecx - cmpl $0x00000080,%ecx - jc LCheck_truncate_24 /* less than half, no increment needed */ - - jne LGreater_Half_24 /* greater than half, increment needed */ - - /* Possibly half, we need to check the ls bits */ - orl %ebx,%ebx - jnz LGreater_Half_24 /* greater than half, increment needed */ - - orl %edx,%edx - jnz LGreater_Half_24 /* greater than half, increment needed */ - - /* Exactly half, increment only if 24th bit is 1 (round to even) */ - testl $0x00000100,%eax - jz LDo_truncate_24 - -LGreater_Half_24: /* Rounding: increment at the 24th bit */ -LDo_24_round_up: - andl $0xffffff00,%eax /* Truncate to 24 bits */ - xorl %ebx,%ebx - movb LOST_UP,FPU_bits_lost - addl $0x00000100,%eax - jmp LCheck_Round_Overflow - -LCheck_truncate_24: - movl %eax,%ecx - andl $0x000000ff,%ecx - orl %ebx,%ecx - orl %edx,%ecx - jz LRe_normalise /* No truncation needed */ - -LDo_truncate_24: - andl $0xffffff00,%eax /* Truncate to 24 bits */ - xorl %ebx,%ebx - movb LOST_DOWN,FPU_bits_lost - jmp LRe_normalise - - -/* Round etc to 53 bit precision */ -LRound_To_53: - movl %esi,%ecx - andl CW_RC,%ecx - cmpl RC_RND,%ecx - je LRound_nearest_53 - - cmpl RC_CHOP,%ecx - je LCheck_truncate_53 - - cmpl RC_UP,%ecx /* Towards +infinity */ - je LUp_53 - - cmpl RC_DOWN,%ecx /* Towards -infinity */ - je LDown_53 - -#ifdef PARANOID - jmp L_bugged_round53 -#endif PARANOID - -LUp_53: - cmpb SIGN_POS,SIGN(%edi) - jne LCheck_truncate_53 /* If negative then up==truncate */ - - jmp LCheck_53_round_up - -LDown_53: - cmpb SIGN_POS,SIGN(%edi) - je LCheck_truncate_53 /* If positive then down==truncate */ - -LCheck_53_round_up: - movl %ebx,%ecx - andl $0x000007ff,%ecx - orl %edx,%ecx - jnz LDo_53_round_up - jmp LRe_normalise - -LRound_nearest_53: - /* Do rounding of the 53rd bit if needed (nearest or even) */ - movl %ebx,%ecx - andl $0x000007ff,%ecx - cmpl $0x00000400,%ecx - jc LCheck_truncate_53 /* less than half, no increment needed */ - - jnz LGreater_Half_53 /* greater than half, increment needed */ - - /* Possibly half, we need to check the ls bits */ - orl %edx,%edx - jnz LGreater_Half_53 /* greater than half, increment needed */ - - /* Exactly half, increment only if 53rd bit is 1 (round to even) */ - testl $0x00000800,%ebx - jz LTruncate_53 - -LGreater_Half_53: /* Rounding: increment at the 53rd bit */ -LDo_53_round_up: - movb LOST_UP,FPU_bits_lost - andl $0xfffff800,%ebx /* Truncate to 53 bits */ - addl $0x00000800,%ebx - adcl $0,%eax - jmp LCheck_Round_Overflow - -LCheck_truncate_53: - movl %ebx,%ecx - andl $0x000007ff,%ecx - orl %edx,%ecx - jz LRe_normalise - -LTruncate_53: - movb LOST_DOWN,FPU_bits_lost - andl $0xfffff800,%ebx /* Truncate to 53 bits */ - jmp LRe_normalise - - -/* Round etc to 64 bit precision */ -LRound_To_64: - movl %esi,%ecx - andl CW_RC,%ecx - cmpl RC_RND,%ecx - je LRound_nearest_64 - - cmpl RC_CHOP,%ecx - je LCheck_truncate_64 - - cmpl RC_UP,%ecx /* Towards +infinity */ - je LUp_64 - - cmpl RC_DOWN,%ecx /* Towards -infinity */ - je LDown_64 - -#ifdef PARANOID - jmp L_bugged_round64 -#endif PARANOID - -LUp_64: - cmpb SIGN_POS,SIGN(%edi) - jne LCheck_truncate_64 /* If negative then up==truncate */ - - orl %edx,%edx - jnz LDo_64_round_up - jmp LRe_normalise - -LDown_64: - cmpb SIGN_POS,SIGN(%edi) - je LCheck_truncate_64 /* If positive then down==truncate */ - - orl %edx,%edx - jnz LDo_64_round_up - jmp LRe_normalise - -LRound_nearest_64: - cmpl $0x80000000,%edx - jc LCheck_truncate_64 - - jne LDo_64_round_up - - /* Now test for round-to-even */ - testb $1,%ebx - jz LCheck_truncate_64 - -LDo_64_round_up: - movb LOST_UP,FPU_bits_lost - addl $1,%ebx - adcl $0,%eax - -LCheck_Round_Overflow: - jnc LRe_normalise - - /* Overflow, adjust the result (significand to 1.0) */ - rcrl $1,%eax - rcrl $1,%ebx - incl EXP(%edi) - jmp LRe_normalise - -LCheck_truncate_64: - orl %edx,%edx - jz LRe_normalise - -LTruncate_64: - movb LOST_DOWN,FPU_bits_lost - -LRe_normalise: - testb $0xff,FPU_denormal - jnz xNormalise_result - -xL_Normalised: - cmpb LOST_UP,FPU_bits_lost - je xL_precision_lost_up - - cmpb LOST_DOWN,FPU_bits_lost - je xL_precision_lost_down - -xL_no_precision_loss: - /* store the result */ - movb TW_Valid,TAG(%edi) - -xL_Store_significand: - movl %eax,SIGH(%edi) - movl %ebx,SIGL(%edi) - - xorl %eax,%eax /* No errors detected. */ - - cmpl EXP_OVER,EXP(%edi) - jge L_overflow - -fpu_reg_round_exit: -#ifndef NON_REENTRANT_FPU - popl %ebx /* adjust the stack pointer */ -#endif NON_REENTRANT_FPU - -fpu_Arith_exit: - popl %ebx - popl %edi - popl %esi - leave - ret - - -/* - * Set the FPU status flags to represent precision loss due to - * round-up. - */ -xL_precision_lost_up: - push %eax - call _set_precision_flag_up - popl %eax - jmp xL_no_precision_loss - -/* - * Set the FPU status flags to represent precision loss due to - * truncation. - */ -xL_precision_lost_down: - push %eax - call _set_precision_flag_down - popl %eax - jmp xL_no_precision_loss - - -/* - * The number is a denormal (which might get rounded up to a normal) - * Shift the number right the required number of bits, which will - * have to be undone later... - */ -xMake_denorm: - /* The action to be taken depends upon whether the underflow - exception is masked */ - testb CW_Underflow,%cl /* Underflow mask. */ - jz xUnmasked_underflow /* Do not make a denormal. */ - - movb DENORMAL,FPU_denormal - - pushl %ecx /* Save */ - movl EXP_UNDER+1,%ecx - subl EXP(%edi),%ecx - - cmpl $64,%ecx /* shrd only works for 0..31 bits */ - jnc xDenorm_shift_more_than_63 - - cmpl $32,%ecx /* shrd only works for 0..31 bits */ - jnc xDenorm_shift_more_than_32 - -/* - * We got here without jumps by assuming that the most common requirement - * is for a small de-normalising shift. - * Shift by [1..31] bits - */ - addl %ecx,EXP(%edi) - orl %edx,%edx /* extension */ - setne %ch /* Save whether %edx is non-zero */ - xorl %edx,%edx - shrd %cl,%ebx,%edx - shrd %cl,%eax,%ebx - shr %cl,%eax - orb %ch,%dl - popl %ecx - jmp xDenorm_done - -/* Shift by [32..63] bits */ -xDenorm_shift_more_than_32: - addl %ecx,EXP(%edi) - subb $32,%cl - orl %edx,%edx - setne %ch - orb %ch,%bl - xorl %edx,%edx - shrd %cl,%ebx,%edx - shrd %cl,%eax,%ebx - shr %cl,%eax - orl %edx,%edx /* test these 32 bits */ - setne %cl - orb %ch,%bl - orb %cl,%bl - movl %ebx,%edx - movl %eax,%ebx - xorl %eax,%eax - popl %ecx - jmp xDenorm_done - -/* Shift by [64..) bits */ -xDenorm_shift_more_than_63: - cmpl $64,%ecx - jne xDenorm_shift_more_than_64 - -/* Exactly 64 bit shift */ - addl %ecx,EXP(%edi) - xorl %ecx,%ecx - orl %edx,%edx - setne %cl - orl %ebx,%ebx - setne %ch - orb %ch,%cl - orb %cl,%al - movl %eax,%edx - xorl %eax,%eax - xorl %ebx,%ebx - popl %ecx - jmp xDenorm_done - -xDenorm_shift_more_than_64: - movl EXP_UNDER+1,EXP(%edi) -/* This is easy, %eax must be non-zero, so.. */ - movl $1,%edx - xorl %eax,%eax - xorl %ebx,%ebx - popl %ecx - jmp xDenorm_done - - -xUnmasked_underflow: - movb UNMASKED_UNDERFLOW,FPU_denormal - jmp xDenorm_done - - -/* Undo the de-normalisation. */ -xNormalise_result: - cmpb UNMASKED_UNDERFLOW,FPU_denormal - je xSignal_underflow - -/* The number must be a denormal if we got here. */ -#ifdef PARANOID - /* But check it... just in case. */ - cmpl EXP_UNDER+1,EXP(%edi) - jne L_norm_bugged -#endif PARANOID - -#ifdef PECULIAR_486 - /* - * This implements a special feature of 80486 behaviour. - * Underflow will be signalled even if the number is - * not a denormal after rounding. - * This difference occurs only for masked underflow, and not - * in the unmasked case. - * Actual 80486 behaviour differs from this in some circumstances. - */ - orl %eax,%eax /* ms bits */ - js LNormalise_shift_done /* Will be masked underflow */ -#endif PECULIAR_486 - - orl %eax,%eax /* ms bits */ - js xL_Normalised /* No longer a denormal */ - - jnz LNormalise_shift_up_to_31 /* Shift left 0 - 31 bits */ - - orl %ebx,%ebx - jz L_underflow_to_zero /* The contents are zero */ - -/* Shift left 32 - 63 bits */ - movl %ebx,%eax - xorl %ebx,%ebx - subl $32,EXP(%edi) - -LNormalise_shift_up_to_31: - bsrl %eax,%ecx /* get the required shift in %ecx */ - subl $31,%ecx - negl %ecx - shld %cl,%ebx,%eax - shl %cl,%ebx - subl %ecx,EXP(%edi) - -LNormalise_shift_done: - testb $0xff,FPU_bits_lost /* bits lost == underflow */ - jz xL_Normalised - - /* There must be a masked underflow */ - push %eax - pushl EX_Underflow - call _exception - popl %eax - popl %eax - jmp xL_Normalised - - -/* - * The operations resulted in a number too small to represent. - * Masked response. - */ -L_underflow_to_zero: - push %eax - call _set_precision_flag_down - popl %eax - - push %eax - pushl EX_Underflow - call _exception - popl %eax - popl %eax - -/* Reduce the exponent to EXP_UNDER */ - movl EXP_UNDER,EXP(%edi) - movb TW_Zero,TAG(%edi) - jmp xL_Store_significand - - -/* The operations resulted in a number too large to represent. */ -L_overflow: - push %edi - call _arith_overflow - pop %edi - jmp fpu_reg_round_exit - - -xSignal_underflow: - /* The number may have been changed to a non-denormal */ - /* by the rounding operations. */ - cmpl EXP_UNDER,EXP(%edi) - jle xDo_unmasked_underflow - - jmp xL_Normalised - -xDo_unmasked_underflow: - /* Increase the exponent by the magic number */ - addl $(3*(1<<13)),EXP(%edi) - push %eax - pushl EX_Underflow - call EXCEPTION - popl %eax - popl %eax - jmp xL_Normalised - - -#ifdef PARANOID -#ifdef PECULIAR_486 -L_bugged_denorm_486: - pushl EX_INTERNAL|0x236 - call EXCEPTION - popl %ebx - jmp L_exception_exit -#else -L_bugged_denorm: - pushl EX_INTERNAL|0x230 - call EXCEPTION - popl %ebx - jmp L_exception_exit -#endif PECULIAR_486 - -L_bugged_round24: - pushl EX_INTERNAL|0x231 - call EXCEPTION - popl %ebx - jmp L_exception_exit - -L_bugged_round53: - pushl EX_INTERNAL|0x232 - call EXCEPTION - popl %ebx - jmp L_exception_exit - -L_bugged_round64: - pushl EX_INTERNAL|0x233 - call EXCEPTION - popl %ebx - jmp L_exception_exit - -L_norm_bugged: - pushl EX_INTERNAL|0x234 - call EXCEPTION - popl %ebx - jmp L_exception_exit - -L_entry_bugged: - pushl EX_INTERNAL|0x235 - call EXCEPTION - popl %ebx -L_exception_exit: - mov $1,%eax - jmp fpu_reg_round_exit -#endif PARANOID diff --git a/drivers/FPU-emu/reg_u_add.S b/drivers/FPU-emu/reg_u_add.S deleted file mode 100644 index 4410f8fd4..000000000 --- a/drivers/FPU-emu/reg_u_add.S +++ /dev/null @@ -1,189 +0,0 @@ - .file "reg_u_add.S" -/*---------------------------------------------------------------------------+ - | reg_u_add.S | - | | - | Add two valid (TW_Valid) FPU_REG numbers, of the same sign, and put the | - | result in a destination FPU_REG. | - | | - | Copyright (C) 1992,1993 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | Call from C as: | - | void reg_u_add(FPU_REG *arg1, FPU_REG *arg2, FPU_REG *answ, | - | int control_w) | - | | - +---------------------------------------------------------------------------*/ - -/* - | Kernel addition routine reg_u_add(reg *arg1, reg *arg2, reg *answ). - | Takes two valid reg f.p. numbers (TW_Valid), which are - | treated as unsigned numbers, - | and returns their sum as a TW_Valid or TW_S f.p. number. - | The returned number is normalized. - | Basic checks are performed if PARANOID is defined. - */ - -#include "exception.h" -#include "fpu_asm.h" -#include "control_w.h" - -.text - .align 2,144 -.globl _reg_u_add -_reg_u_add: - pushl %ebp - movl %esp,%ebp - pushl %esi - pushl %edi - pushl %ebx - - movl PARAM1,%esi /* source 1 */ - movl PARAM2,%edi /* source 2 */ - -#ifdef DENORM_OPERAND - cmpl EXP_UNDER,EXP(%esi) - jg xOp1_not_denorm - - call _denormal_operand - orl %eax,%eax - jnz fpu_Arith_exit - -xOp1_not_denorm: - cmpl EXP_UNDER,EXP(%edi) - jg xOp2_not_denorm - - call _denormal_operand - orl %eax,%eax - jnz fpu_Arith_exit - -xOp2_not_denorm: -#endif DENORM_OPERAND - - movl EXP(%esi),%ecx - subl EXP(%edi),%ecx /* exp1 - exp2 */ - jge L_arg1_larger - - /* num1 is smaller */ - movl SIGL(%esi),%ebx - movl SIGH(%esi),%eax - - movl %edi,%esi - negw %cx - jmp L_accum_loaded - -L_arg1_larger: - /* num1 has larger or equal exponent */ - movl SIGL(%edi),%ebx - movl SIGH(%edi),%eax - -L_accum_loaded: - movl PARAM3,%edi /* destination */ -/* movb SIGN(%esi),%dl - movb %dl,SIGN(%edi) */ /* Copy the sign from the first arg */ - - - movl EXP(%esi),%edx - movl %edx,EXP(%edi) /* Copy exponent to destination */ - - xorl %edx,%edx /* clear the extension */ - -#ifdef PARANOID - testl $0x80000000,%eax - je L_bugged - - testl $0x80000000,SIGH(%esi) - je L_bugged -#endif PARANOID - -/* The number to be shifted is in %eax:%ebx:%edx */ - cmpw $32,%cx /* shrd only works for 0..31 bits */ - jnc L_more_than_31 - -/* less than 32 bits */ - shrd %cl,%ebx,%edx - shrd %cl,%eax,%ebx - shr %cl,%eax - jmp L_shift_done - -L_more_than_31: - cmpw $64,%cx - jnc L_more_than_63 - - subb $32,%cl - jz L_exactly_32 - - shrd %cl,%eax,%edx - shr %cl,%eax - orl %ebx,%ebx - jz L_more_31_no_low /* none of the lowest bits is set */ - - orl $1,%edx /* record the fact in the extension */ - -L_more_31_no_low: - movl %eax,%ebx - xorl %eax,%eax - jmp L_shift_done - -L_exactly_32: - movl %ebx,%edx - movl %eax,%ebx - xorl %eax,%eax - jmp L_shift_done - -L_more_than_63: - cmpw $65,%cx - jnc L_more_than_64 - - movl %eax,%edx - orl %ebx,%ebx - jz L_more_63_no_low - - orl $1,%edx - jmp L_more_63_no_low - -L_more_than_64: - movl $1,%edx /* The shifted nr always at least one '1' */ - -L_more_63_no_low: - xorl %ebx,%ebx - xorl %eax,%eax - -L_shift_done: - /* Now do the addition */ - addl SIGL(%esi),%ebx - adcl SIGH(%esi),%eax - jnc L_round_the_result - - /* Overflow, adjust the result */ - rcrl $1,%eax - rcrl $1,%ebx - rcrl $1,%edx - jnc L_no_bit_lost - - orl $1,%edx - -L_no_bit_lost: - incl EXP(%edi) - -L_round_the_result: - jmp fpu_reg_round /* Round the result */ - - - -#ifdef PARANOID -/* If we ever get here then we have problems! */ -L_bugged: - pushl EX_INTERNAL|0x201 - call EXCEPTION - pop %ebx - jmp L_exit -#endif PARANOID - - -L_exit: - popl %ebx - popl %edi - popl %esi - leave - ret diff --git a/drivers/FPU-emu/reg_u_div.S b/drivers/FPU-emu/reg_u_div.S deleted file mode 100644 index 328e9116e..000000000 --- a/drivers/FPU-emu/reg_u_div.S +++ /dev/null @@ -1,477 +0,0 @@ - .file "reg_u_div.S" -/*---------------------------------------------------------------------------+ - | reg_u_div.S | - | | - | Core division routines | - | | - | Copyright (C) 1992,1993 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------+ - | Kernel for the division routines. | - | | - | void reg_u_div(FPU_REG *a, FPU_REG *a, | - | FPU_REG *dest, unsigned int control_word) | - | | - | Does not compute the destination exponent, but does adjust it. | - +---------------------------------------------------------------------------*/ - -#include "exception.h" -#include "fpu_asm.h" -#include "control_w.h" - - -/* #define dSIGL(x) (x) */ -/* #define dSIGH(x) 4(x) */ - - -#ifndef NON_REENTRANT_FPU -/* - Local storage on the stack: - Result: FPU_accum_3:FPU_accum_2:FPU_accum_1:FPU_accum_0 - Overflow flag: ovfl_flag - */ -#define FPU_accum_3 -4(%ebp) -#define FPU_accum_2 -8(%ebp) -#define FPU_accum_1 -12(%ebp) -#define FPU_accum_0 -16(%ebp) -#define FPU_result_1 -20(%ebp) -#define FPU_result_2 -24(%ebp) -#define FPU_ovfl_flag -28(%ebp) - -#else -.data -/* - Local storage in a static area: - Result: FPU_accum_3:FPU_accum_2:FPU_accum_1:FPU_accum_0 - Overflow flag: ovfl_flag - */ - .align 2,0 -FPU_accum_3: - .long 0 -FPU_accum_2: - .long 0 -FPU_accum_1: - .long 0 -FPU_accum_0: - .long 0 -FPU_result_1: - .long 0 -FPU_result_2: - .long 0 -FPU_ovfl_flag: - .byte 0 -#endif NON_REENTRANT_FPU - - -.text - .align 2,144 - -.globl _reg_u_div - -.globl _divide_kernel - -_reg_u_div: - pushl %ebp - movl %esp,%ebp -#ifndef NON_REENTRANT_FPU - subl $28,%esp -#endif NON_REENTRANT_FPU - - pushl %esi - pushl %edi - pushl %ebx - - movl PARAM1,%esi /* pointer to num */ - movl PARAM2,%ebx /* pointer to denom */ - movl PARAM3,%edi /* pointer to answer */ - -#ifdef DENORM_OPERAND - movl EXP(%esi),%eax - cmpl EXP_UNDER,%eax - jg xOp1_not_denorm - - call _denormal_operand - orl %eax,%eax - jnz fpu_Arith_exit - -xOp1_not_denorm: - movl EXP(%ebx),%eax - cmpl EXP_UNDER,%eax - jg xOp2_not_denorm - - call _denormal_operand - orl %eax,%eax - jnz fpu_Arith_exit - -xOp2_not_denorm: -#endif DENORM_OPERAND - -_divide_kernel: -#ifdef PARANOID -/* testl $0x80000000, SIGH(%esi) // Dividend */ -/* je L_bugged */ - testl $0x80000000, SIGH(%ebx) /* Divisor */ - je L_bugged -#endif PARANOID - -/* Check if the divisor can be treated as having just 32 bits */ - cmpl $0,SIGL(%ebx) - jnz L_Full_Division /* Can't do a quick divide */ - -/* We should be able to zip through the division here */ - movl SIGH(%ebx),%ecx /* The divisor */ - movl SIGH(%esi),%edx /* Dividend */ - movl SIGL(%esi),%eax /* Dividend */ - - cmpl %ecx,%edx - setaeb FPU_ovfl_flag /* Keep a record */ - jb L_no_adjust - - subl %ecx,%edx /* Prevent the overflow */ - -L_no_adjust: - /* Divide the 64 bit number by the 32 bit denominator */ - divl %ecx - movl %eax,FPU_result_2 - - /* Work on the remainder of the first division */ - xorl %eax,%eax - divl %ecx - movl %eax,FPU_result_1 - - /* Work on the remainder of the 64 bit division */ - xorl %eax,%eax - divl %ecx - - testb $255,FPU_ovfl_flag /* was the num > denom ? */ - je L_no_overflow - - /* Do the shifting here */ - /* increase the exponent */ - incl EXP(%edi) - - /* shift the mantissa right one bit */ - stc /* To set the ms bit */ - rcrl FPU_result_2 - rcrl FPU_result_1 - rcrl %eax - -L_no_overflow: - jmp LRound_precision /* Do the rounding as required */ - - -/*---------------------------------------------------------------------------+ - | Divide: Return arg1/arg2 to arg3. | - | | - | This routine does not use the exponents of arg1 and arg2, but does | - | adjust the exponent of arg3. | - | | - | The maximum returned value is (ignoring exponents) | - | .ffffffff ffffffff | - | ------------------ = 1.ffffffff fffffffe | - | .80000000 00000000 | - | and the minimum is | - | .80000000 00000000 | - | ------------------ = .80000000 00000001 (rounded) | - | .ffffffff ffffffff | - | | - +---------------------------------------------------------------------------*/ - - -L_Full_Division: - /* Save extended dividend in local register */ - movl SIGL(%esi),%eax - movl %eax,FPU_accum_2 - movl SIGH(%esi),%eax - movl %eax,FPU_accum_3 - xorl %eax,%eax - movl %eax,FPU_accum_1 /* zero the extension */ - movl %eax,FPU_accum_0 /* zero the extension */ - - movl SIGL(%esi),%eax /* Get the current num */ - movl SIGH(%esi),%edx - -/*----------------------------------------------------------------------*/ -/* Initialization done. - Do the first 32 bits. */ - - movb $0,FPU_ovfl_flag - cmpl SIGH(%ebx),%edx /* Test for imminent overflow */ - jb LLess_than_1 - ja LGreater_than_1 - - cmpl SIGL(%ebx),%eax - jb LLess_than_1 - -LGreater_than_1: -/* The dividend is greater or equal, would cause overflow */ - setaeb FPU_ovfl_flag /* Keep a record */ - - subl SIGL(%ebx),%eax - sbbl SIGH(%ebx),%edx /* Prevent the overflow */ - movl %eax,FPU_accum_2 - movl %edx,FPU_accum_3 - -LLess_than_1: -/* At this point, we have a dividend < divisor, with a record of - adjustment in FPU_ovfl_flag */ - - /* We will divide by a number which is too large */ - movl SIGH(%ebx),%ecx - addl $1,%ecx - jnc LFirst_div_not_1 - - /* here we need to divide by 100000000h, - i.e., no division at all.. */ - mov %edx,%eax - jmp LFirst_div_done - -LFirst_div_not_1: - divl %ecx /* Divide the numerator by the augmented - denom ms dw */ - -LFirst_div_done: - movl %eax,FPU_result_2 /* Put the result in the answer */ - - mull SIGH(%ebx) /* mul by the ms dw of the denom */ - - subl %eax,FPU_accum_2 /* Subtract from the num local reg */ - sbbl %edx,FPU_accum_3 - - movl FPU_result_2,%eax /* Get the result back */ - mull SIGL(%ebx) /* now mul the ls dw of the denom */ - - subl %eax,FPU_accum_1 /* Subtract from the num local reg */ - sbbl %edx,FPU_accum_2 - sbbl $0,FPU_accum_3 - je LDo_2nd_32_bits /* Must check for non-zero result here */ - -#ifdef PARANOID - jb L_bugged_1 -#endif PARANOID - - /* need to subtract another once of the denom */ - incl FPU_result_2 /* Correct the answer */ - - movl SIGL(%ebx),%eax - movl SIGH(%ebx),%edx - subl %eax,FPU_accum_1 /* Subtract from the num local reg */ - sbbl %edx,FPU_accum_2 - -#ifdef PARANOID - sbbl $0,FPU_accum_3 - jne L_bugged_1 /* Must check for non-zero result here */ -#endif PARANOID - -/*----------------------------------------------------------------------*/ -/* Half of the main problem is done, there is just a reduced numerator - to handle now. - Work with the second 32 bits, FPU_accum_0 not used from now on */ -LDo_2nd_32_bits: - movl FPU_accum_2,%edx /* get the reduced num */ - movl FPU_accum_1,%eax - - /* need to check for possible subsequent overflow */ - cmpl SIGH(%ebx),%edx - jb LDo_2nd_div - ja LPrevent_2nd_overflow - - cmpl SIGL(%ebx),%eax - jb LDo_2nd_div - -LPrevent_2nd_overflow: -/* The numerator is greater or equal, would cause overflow */ - /* prevent overflow */ - subl SIGL(%ebx),%eax - sbbl SIGH(%ebx),%edx - movl %edx,FPU_accum_2 - movl %eax,FPU_accum_1 - - incl FPU_result_2 /* Reflect the subtraction in the answer */ - -#ifdef PARANOID - je L_bugged_2 /* Can't bump the result to 1.0 */ -#endif PARANOID - -LDo_2nd_div: - cmpl $0,%ecx /* augmented denom msw */ - jnz LSecond_div_not_1 - - /* %ecx == 0, we are dividing by 1.0 */ - mov %edx,%eax - jmp LSecond_div_done - -LSecond_div_not_1: - divl %ecx /* Divide the numerator by the denom ms dw */ - -LSecond_div_done: - movl %eax,FPU_result_1 /* Put the result in the answer */ - - mull SIGH(%ebx) /* mul by the ms dw of the denom */ - - subl %eax,FPU_accum_1 /* Subtract from the num local reg */ - sbbl %edx,FPU_accum_2 - -#ifdef PARANOID - jc L_bugged_2 -#endif PARANOID - - movl FPU_result_1,%eax /* Get the result back */ - mull SIGL(%ebx) /* now mul the ls dw of the denom */ - - subl %eax,FPU_accum_0 /* Subtract from the num local reg */ - sbbl %edx,FPU_accum_1 /* Subtract from the num local reg */ - sbbl $0,FPU_accum_2 - -#ifdef PARANOID - jc L_bugged_2 -#endif PARANOID - - jz LDo_3rd_32_bits - -#ifdef PARANOID - cmpl $1,FPU_accum_2 - jne L_bugged_2 -#endif PARANOID - - /* need to subtract another once of the denom */ - movl SIGL(%ebx),%eax - movl SIGH(%ebx),%edx - subl %eax,FPU_accum_0 /* Subtract from the num local reg */ - sbbl %edx,FPU_accum_1 - sbbl $0,FPU_accum_2 - -#ifdef PARANOID - jc L_bugged_2 - jne L_bugged_2 -#endif PARANOID - - addl $1,FPU_result_1 /* Correct the answer */ - adcl $0,FPU_result_2 - -#ifdef PARANOID - jc L_bugged_2 /* Must check for non-zero result here */ -#endif PARANOID - -/*----------------------------------------------------------------------*/ -/* The division is essentially finished here, we just need to perform - tidying operations. - Deal with the 3rd 32 bits */ -LDo_3rd_32_bits: - movl FPU_accum_1,%edx /* get the reduced num */ - movl FPU_accum_0,%eax - - /* need to check for possible subsequent overflow */ - cmpl SIGH(%ebx),%edx /* denom */ - jb LRound_prep - ja LPrevent_3rd_overflow - - cmpl SIGL(%ebx),%eax /* denom */ - jb LRound_prep - -LPrevent_3rd_overflow: - /* prevent overflow */ - subl SIGL(%ebx),%eax - sbbl SIGH(%ebx),%edx - movl %edx,FPU_accum_1 - movl %eax,FPU_accum_0 - - addl $1,FPU_result_1 /* Reflect the subtraction in the answer */ - adcl $0,FPU_result_2 - jne LRound_prep - jnc LRound_prep - - /* This is a tricky spot, there is an overflow of the answer */ - movb $255,FPU_ovfl_flag /* Overflow -> 1.000 */ - -LRound_prep: -/* - * Prepare for rounding. - * To test for rounding, we just need to compare 2*accum with the - * denom. - */ - movl FPU_accum_0,%ecx - movl FPU_accum_1,%edx - movl %ecx,%eax - orl %edx,%eax - jz LRound_ovfl /* The accumulator contains zero. */ - - /* Multiply by 2 */ - clc - rcll $1,%ecx - rcll $1,%edx - jc LRound_large /* No need to compare, denom smaller */ - - subl SIGL(%ebx),%ecx - sbbl SIGH(%ebx),%edx - jnc LRound_not_small - - movl $0x70000000,%eax /* Denom was larger */ - jmp LRound_ovfl - -LRound_not_small: - jnz LRound_large - - movl $0x80000000,%eax /* Remainder was exactly 1/2 denom */ - jmp LRound_ovfl - -LRound_large: - movl $0xff000000,%eax /* Denom was smaller */ - -LRound_ovfl: -/* We are now ready to deal with rounding, but first we must get - the bits properly aligned */ - testb $255,FPU_ovfl_flag /* was the num > denom ? */ - je LRound_precision - - incl EXP(%edi) - - /* shift the mantissa right one bit */ - stc /* Will set the ms bit */ - rcrl FPU_result_2 - rcrl FPU_result_1 - rcrl %eax - -/* Round the result as required */ -LRound_precision: - decl EXP(%edi) /* binary point between 1st & 2nd bits */ - - movl %eax,%edx - movl FPU_result_1,%ebx - movl FPU_result_2,%eax - jmp fpu_reg_round - - -#ifdef PARANOID -/* The logic is wrong if we got here */ -L_bugged: - pushl EX_INTERNAL|0x202 - call EXCEPTION - pop %ebx - jmp L_exit - -L_bugged_1: - pushl EX_INTERNAL|0x203 - call EXCEPTION - pop %ebx - jmp L_exit - -L_bugged_2: - pushl EX_INTERNAL|0x204 - call EXCEPTION - pop %ebx - jmp L_exit - -L_exit: - popl %ebx - popl %edi - popl %esi - - leave - ret -#endif PARANOID diff --git a/drivers/FPU-emu/reg_u_mul.S b/drivers/FPU-emu/reg_u_mul.S deleted file mode 100644 index 8250666bd..000000000 --- a/drivers/FPU-emu/reg_u_mul.S +++ /dev/null @@ -1,163 +0,0 @@ - .file "reg_u_mul.S" -/*---------------------------------------------------------------------------+ - | reg_u_mul.S | - | | - | Core multiplication routine | - | | - | Copyright (C) 1992,1993 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------+ - | Basic multiplication routine. | - | Does not check the resulting exponent for overflow/underflow | - | | - | reg_u_mul(FPU_REG *a, FPU_REG *b, FPU_REG *c, unsigned int cw); | - | | - | Internal working is at approx 128 bits. | - | Result is rounded to nearest 53 or 64 bits, using "nearest or even". | - +---------------------------------------------------------------------------*/ - -#include "exception.h" -#include "fpu_asm.h" -#include "control_w.h" - - - -#ifndef NON_REENTRANT_FPU -/* Local storage on the stack: */ -#define FPU_accum_0 -4(%ebp) /* ms word */ -#define FPU_accum_1 -8(%ebp) - -#else -/* Local storage in a static area: */ -.data - .align 4,0 -FPU_accum_0: - .long 0 -FPU_accum_1: - .long 0 -#endif NON_REENTRANT_FPU - - -.text - .align 2,144 - -.globl _reg_u_mul -_reg_u_mul: - pushl %ebp - movl %esp,%ebp -#ifndef NON_REENTRANT_FPU - subl $8,%esp -#endif NON_REENTRANT_FPU - - pushl %esi - pushl %edi - pushl %ebx - - movl PARAM1,%esi - movl PARAM2,%edi - -#ifdef PARANOID - testl $0x80000000,SIGH(%esi) - jz L_bugged - testl $0x80000000,SIGH(%edi) - jz L_bugged -#endif PARANOID - -#ifdef DENORM_OPERAND - movl EXP(%esi),%eax - cmpl EXP_UNDER,%eax - jg xOp1_not_denorm - - call _denormal_operand - orl %eax,%eax - jnz fpu_Arith_exit - -xOp1_not_denorm: - movl EXP(%edi),%eax - cmpl EXP_UNDER,%eax - jg xOp2_not_denorm - - call _denormal_operand - orl %eax,%eax - jnz fpu_Arith_exit - -xOp2_not_denorm: -#endif DENORM_OPERAND - - xorl %ecx,%ecx - xorl %ebx,%ebx - - movl SIGL(%esi),%eax - mull SIGL(%edi) - movl %eax,FPU_accum_0 - movl %edx,FPU_accum_1 - - movl SIGL(%esi),%eax - mull SIGH(%edi) - addl %eax,FPU_accum_1 - adcl %edx,%ebx -/* adcl $0,%ecx // overflow here is not possible */ - - movl SIGH(%esi),%eax - mull SIGL(%edi) - addl %eax,FPU_accum_1 - adcl %edx,%ebx - adcl $0,%ecx - - movl SIGH(%esi),%eax - mull SIGH(%edi) - addl %eax,%ebx - adcl %edx,%ecx - - movl EXP(%esi),%eax /* Compute the exponent */ - addl EXP(%edi),%eax - subl EXP_BIAS-1,%eax - -/* Have now finished with the sources */ - movl PARAM3,%edi /* Point to the destination */ - movl %eax,EXP(%edi) - -/* Now make sure that the result is normalized */ - testl $0x80000000,%ecx - jnz LResult_Normalised - - /* Normalize by shifting left one bit */ - shll $1,FPU_accum_0 - rcll $1,FPU_accum_1 - rcll $1,%ebx - rcll $1,%ecx - decl EXP(%edi) - -LResult_Normalised: - movl FPU_accum_0,%eax - movl FPU_accum_1,%edx - orl %eax,%eax - jz L_extent_zero - - orl $1,%edx - -L_extent_zero: - movl %ecx,%eax - jmp fpu_reg_round - - -#ifdef PARANOID -L_bugged: - pushl EX_INTERNAL|0x205 - call EXCEPTION - pop %ebx - jmp L_exit - -L_exit: - popl %ebx - popl %edi - popl %esi - leave - ret -#endif PARANOID - diff --git a/drivers/FPU-emu/reg_u_sub.S b/drivers/FPU-emu/reg_u_sub.S deleted file mode 100644 index fbec17dfb..000000000 --- a/drivers/FPU-emu/reg_u_sub.S +++ /dev/null @@ -1,292 +0,0 @@ - .file "reg_u_sub.S" -/*---------------------------------------------------------------------------+ - | reg_u_sub.S | - | | - | Core floating point subtraction routine. | - | | - | Copyright (C) 1992,1993 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | Call from C as: | - | void reg_u_sub(FPU_REG *arg1, FPU_REG *arg2, FPU_REG *answ, | - | int control_w) | - | | - +---------------------------------------------------------------------------*/ - -/* - | Kernel subtraction routine reg_u_sub(reg *arg1, reg *arg2, reg *answ). - | Takes two valid reg f.p. numbers (TW_Valid), which are - | treated as unsigned numbers, - | and returns their difference as a TW_Valid or TW_Zero f.p. - | number. - | The first number (arg1) must be the larger. - | The returned number is normalized. - | Basic checks are performed if PARANOID is defined. - */ - -#include "exception.h" -#include "fpu_asm.h" -#include "control_w.h" - -.text - .align 2,144 -.globl _reg_u_sub -_reg_u_sub: - pushl %ebp - movl %esp,%ebp - pushl %esi - pushl %edi - pushl %ebx - - movl PARAM1,%esi /* source 1 */ - movl PARAM2,%edi /* source 2 */ - -#ifdef DENORM_OPERAND - cmpl EXP_UNDER,EXP(%esi) - jg xOp1_not_denorm - - call _denormal_operand - orl %eax,%eax - jnz fpu_Arith_exit - -xOp1_not_denorm: - cmpl EXP_UNDER,EXP(%edi) - jg xOp2_not_denorm - - call _denormal_operand - orl %eax,%eax - jnz fpu_Arith_exit - -xOp2_not_denorm: -#endif DENORM_OPERAND - - movl EXP(%esi),%ecx - subl EXP(%edi),%ecx /* exp1 - exp2 */ - -#ifdef PARANOID - /* source 2 is always smaller than source 1 */ - js L_bugged_1 - - testl $0x80000000,SIGH(%edi) /* The args are assumed to be be normalized */ - je L_bugged_2 - - testl $0x80000000,SIGH(%esi) - je L_bugged_2 -#endif PARANOID - -/*--------------------------------------+ - | Form a register holding the | - | smaller number | - +--------------------------------------*/ - movl SIGH(%edi),%eax /* register ms word */ - movl SIGL(%edi),%ebx /* register ls word */ - - movl PARAM3,%edi /* destination */ - movl EXP(%esi),%edx - movl %edx,EXP(%edi) /* Copy exponent to destination */ -/* movb SIGN(%esi),%dl - movb %dl,SIGN(%edi) */ /* Copy the sign from the first arg */ - - xorl %edx,%edx /* register extension */ - -/*--------------------------------------+ - | Shift the temporary register | - | right the required number of | - | places. | - +--------------------------------------*/ -L_shift_r: - cmpl $32,%ecx /* shrd only works for 0..31 bits */ - jnc L_more_than_31 - -/* less than 32 bits */ - shrd %cl,%ebx,%edx - shrd %cl,%eax,%ebx - shr %cl,%eax - jmp L_shift_done - -L_more_than_31: - cmpl $64,%ecx - jnc L_more_than_63 - - subb $32,%cl - jz L_exactly_32 - - shrd %cl,%eax,%edx - shr %cl,%eax - orl %ebx,%ebx - jz L_more_31_no_low /* none of the lowest bits is set */ - - orl $1,%edx /* record the fact in the extension */ - -L_more_31_no_low: - movl %eax,%ebx - xorl %eax,%eax - jmp L_shift_done - -L_exactly_32: - movl %ebx,%edx - movl %eax,%ebx - xorl %eax,%eax - jmp L_shift_done - -L_more_than_63: - cmpw $65,%cx - jnc L_more_than_64 - - /* Shift right by 64 bits */ - movl %eax,%edx - orl %ebx,%ebx - jz L_more_63_no_low - - orl $1,%edx - jmp L_more_63_no_low - -L_more_than_64: - jne L_more_than_65 - - /* Shift right by 65 bits */ - /* Carry is clear if we get here */ - movl %eax,%edx - rcrl %edx - jnc L_shift_65_nc - - orl $1,%edx - jmp L_more_63_no_low - -L_shift_65_nc: - orl %ebx,%ebx - jz L_more_63_no_low - - orl $1,%edx - jmp L_more_63_no_low - -L_more_than_65: - movl $1,%edx /* The shifted nr always at least one '1' */ - -L_more_63_no_low: - xorl %ebx,%ebx - xorl %eax,%eax - -L_shift_done: -L_subtr: -/*------------------------------+ - | Do the subtraction | - +------------------------------*/ - xorl %ecx,%ecx - subl %edx,%ecx - movl %ecx,%edx - movl SIGL(%esi),%ecx - sbbl %ebx,%ecx - movl %ecx,%ebx - movl SIGH(%esi),%ecx - sbbl %eax,%ecx - movl %ecx,%eax - -#ifdef PARANOID - /* We can never get a borrow */ - jc L_bugged -#endif PARANOID - -/*--------------------------------------+ - | Normalize the result | - +--------------------------------------*/ - testl $0x80000000,%eax - jnz L_round /* no shifting needed */ - - orl %eax,%eax - jnz L_shift_1 /* shift left 1 - 31 bits */ - - orl %ebx,%ebx - jnz L_shift_32 /* shift left 32 - 63 bits */ - -/* - * A rare case, the only one which is non-zero if we got here - * is: 1000000 .... 0000 - * -0111111 .... 1111 1 - * -------------------- - * 0000000 .... 0000 1 - */ - - cmpl $0x80000000,%edx - jnz L_must_be_zero - - /* Shift left 64 bits */ - subl $64,EXP(%edi) - xchg %edx,%eax - jmp fpu_reg_round - -L_must_be_zero: -#ifdef PARANOID - orl %edx,%edx - jnz L_bugged_3 -#endif PARANOID - - /* The result is zero */ - movb TW_Zero,TAG(%edi) - movl $0,EXP(%edi) /* exponent */ - movl $0,SIGL(%edi) - movl $0,SIGH(%edi) - jmp L_exit /* %eax contains zero */ - -L_shift_32: - movl %ebx,%eax - movl %edx,%ebx - movl $0,%edx - subl $32,EXP(%edi) /* Can get underflow here */ - -/* We need to shift left by 1 - 31 bits */ -L_shift_1: - bsrl %eax,%ecx /* get the required shift in %ecx */ - subl $31,%ecx - negl %ecx - shld %cl,%ebx,%eax - shld %cl,%edx,%ebx - shl %cl,%edx - subl %ecx,EXP(%edi) /* Can get underflow here */ - -L_round: - jmp fpu_reg_round /* Round the result */ - - -#ifdef PARANOID -L_bugged_1: - pushl EX_INTERNAL|0x206 - call EXCEPTION - pop %ebx - jmp L_error_exit - -L_bugged_2: - pushl EX_INTERNAL|0x209 - call EXCEPTION - pop %ebx - jmp L_error_exit - -L_bugged_3: - pushl EX_INTERNAL|0x210 - call EXCEPTION - pop %ebx - jmp L_error_exit - -L_bugged_4: - pushl EX_INTERNAL|0x211 - call EXCEPTION - pop %ebx - jmp L_error_exit - -L_bugged: - pushl EX_INTERNAL|0x212 - call EXCEPTION - pop %ebx - jmp L_error_exit -#endif PARANOID - - -L_error_exit: - movl $1,%eax -L_exit: - popl %ebx - popl %edi - popl %esi - leave - ret diff --git a/drivers/FPU-emu/round_Xsig.S b/drivers/FPU-emu/round_Xsig.S deleted file mode 100644 index 163755878..000000000 --- a/drivers/FPU-emu/round_Xsig.S +++ /dev/null @@ -1,148 +0,0 @@ -/*---------------------------------------------------------------------------+ - | round_Xsig.S | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | Normalize and round a 12 byte quantity. | - | Call from C as: | - | int round_Xsig(Xsig *n) | - | | - | Normalize a 12 byte quantity. | - | Call from C as: | - | int norm_Xsig(Xsig *n) | - | | - | Each function returns the size of the shift (nr of bits). | - | | - +---------------------------------------------------------------------------*/ - .file "round_Xsig.S" - -#include "fpu_asm.h" - - -.text - - .align 2,144 -.globl _round_Xsig - -_round_Xsig: - pushl %ebp - movl %esp,%ebp - pushl %ebx /* Reserve some space */ - pushl %ebx - pushl %esi - - movl PARAM1,%esi - - movl 8(%esi),%edx - movl 4(%esi),%ebx - movl (%esi),%eax - - movl $0,-4(%ebp) - - orl %edx,%edx /* ms bits */ - js L_round /* Already normalized */ - jnz L_shift_1 /* Shift left 1 - 31 bits */ - - movl %ebx,%edx - movl %eax,%ebx - xorl %eax,%eax - movl $-32,-4(%ebp) - -/* We need to shift left by 1 - 31 bits */ -L_shift_1: - bsrl %edx,%ecx /* get the required shift in %ecx */ - subl $31,%ecx - negl %ecx - subl %ecx,-4(%ebp) - shld %cl,%ebx,%edx - shld %cl,%eax,%ebx - shl %cl,%eax - -L_round: - testl $0x80000000,%eax - jz L_exit - - addl $1,%ebx - adcl $0,%edx - jnz L_exit - - movl $0x80000000,%edx - incl -4(%ebp) - -L_exit: - movl %edx,8(%esi) - movl %ebx,4(%esi) - movl %eax,(%esi) - - movl -4(%ebp),%eax - - popl %esi - popl %ebx - leave - ret - - - - - .align 2,144 -.globl _norm_Xsig - -_norm_Xsig: - pushl %ebp - movl %esp,%ebp - pushl %ebx /* Reserve some space */ - pushl %ebx - pushl %esi - - movl PARAM1,%esi - - movl 8(%esi),%edx - movl 4(%esi),%ebx - movl (%esi),%eax - - movl $0,-4(%ebp) - - orl %edx,%edx /* ms bits */ - js L_n_exit /* Already normalized */ - jnz L_n_shift_1 /* Shift left 1 - 31 bits */ - - movl %ebx,%edx - movl %eax,%ebx - xorl %eax,%eax - movl $-32,-4(%ebp) - - orl %edx,%edx /* ms bits */ - js L_n_exit /* Normalized now */ - jnz L_n_shift_1 /* Shift left 1 - 31 bits */ - - movl %ebx,%edx - movl %eax,%ebx - xorl %eax,%eax - addl $-32,-4(%ebp) - jmp L_n_exit /* Might not be normalized, - but shift no more. */ - -/* We need to shift left by 1 - 31 bits */ -L_n_shift_1: - bsrl %edx,%ecx /* get the required shift in %ecx */ - subl $31,%ecx - negl %ecx - subl %ecx,-4(%ebp) - shld %cl,%ebx,%edx - shld %cl,%eax,%ebx - shl %cl,%eax - -L_n_exit: - movl %edx,8(%esi) - movl %ebx,4(%esi) - movl %eax,(%esi) - - movl -4(%ebp),%eax - - popl %esi - popl %ebx - leave - ret - diff --git a/drivers/FPU-emu/shr_Xsig.S b/drivers/FPU-emu/shr_Xsig.S deleted file mode 100644 index d6724a204..000000000 --- a/drivers/FPU-emu/shr_Xsig.S +++ /dev/null @@ -1,90 +0,0 @@ - .file "shr_Xsig.S" -/*---------------------------------------------------------------------------+ - | shr_Xsig.S | - | | - | 12 byte right shift function | - | | - | Copyright (C) 1992,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | Call from C as: | - | void shr_Xsig(Xsig *arg, unsigned nr) | - | | - | Extended shift right function. | - | Fastest for small shifts. | - | Shifts the 12 byte quantity pointed to by the first arg (arg) | - | right by the number of bits specified by the second arg (nr). | - | | - +---------------------------------------------------------------------------*/ - -#include "fpu_asm.h" - -.text - .align 2,144 - - .globl _shr_Xsig -_shr_Xsig: - push %ebp - movl %esp,%ebp - pushl %esi - movl PARAM2,%ecx - movl PARAM1,%esi - cmpl $32,%ecx /* shrd only works for 0..31 bits */ - jnc L_more_than_31 - -/* less than 32 bits */ - pushl %ebx - movl (%esi),%eax /* lsl */ - movl 4(%esi),%ebx /* midl */ - movl 8(%esi),%edx /* msl */ - shrd %cl,%ebx,%eax - shrd %cl,%edx,%ebx - shr %cl,%edx - movl %eax,(%esi) - movl %ebx,4(%esi) - movl %edx,8(%esi) - popl %ebx - popl %esi - leave - ret - -L_more_than_31: - cmpl $64,%ecx - jnc L_more_than_63 - - subb $32,%cl - movl 4(%esi),%eax /* midl */ - movl 8(%esi),%edx /* msl */ - shrd %cl,%edx,%eax - shr %cl,%edx - movl %eax,(%esi) - movl %edx,4(%esi) - movl $0,8(%esi) - popl %esi - leave - ret - -L_more_than_63: - cmpl $96,%ecx - jnc L_more_than_95 - - subb $64,%cl - movl 8(%esi),%eax /* msl */ - shr %cl,%eax - xorl %edx,%edx - movl %eax,(%esi) - movl %edx,4(%esi) - movl %edx,8(%esi) - popl %esi - leave - ret - -L_more_than_95: - xorl %eax,%eax - movl %eax,(%esi) - movl %eax,4(%esi) - movl %eax,8(%esi) - popl %esi - leave - ret diff --git a/drivers/FPU-emu/status_w.h b/drivers/FPU-emu/status_w.h deleted file mode 100644 index 96607d0e1..000000000 --- a/drivers/FPU-emu/status_w.h +++ /dev/null @@ -1,65 +0,0 @@ -/*---------------------------------------------------------------------------+ - | status_w.h | - | | - | Copyright (C) 1992,1993 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - +---------------------------------------------------------------------------*/ - -#ifndef _STATUS_H_ -#define _STATUS_H_ - -#include "fpu_emu.h" /* for definition of PECULIAR_486 */ - -#ifdef __ASSEMBLER__ -#define Const__(x) $##x -#else -#define Const__(x) x -#endif - -#define SW_Backward Const__(0x8000) /* backward compatibility */ -#define SW_C3 Const__(0x4000) /* condition bit 3 */ -#define SW_Top Const__(0x3800) /* top of stack */ -#define SW_Top_Shift Const__(11) /* shift for top of stack bits */ -#define SW_C2 Const__(0x0400) /* condition bit 2 */ -#define SW_C1 Const__(0x0200) /* condition bit 1 */ -#define SW_C0 Const__(0x0100) /* condition bit 0 */ -#define SW_Summary Const__(0x0080) /* exception summary */ -#define SW_Stack_Fault Const__(0x0040) /* stack fault */ -#define SW_Precision Const__(0x0020) /* loss of precision */ -#define SW_Underflow Const__(0x0010) /* underflow */ -#define SW_Overflow Const__(0x0008) /* overflow */ -#define SW_Zero_Div Const__(0x0004) /* divide by zero */ -#define SW_Denorm_Op Const__(0x0002) /* denormalized operand */ -#define SW_Invalid Const__(0x0001) /* invalid operation */ - -#define SW_Exc_Mask Const__(0x27f) /* Status word exception bit mask */ - -#ifndef __ASSEMBLER__ - -#define COMP_A_gt_B 1 -#define COMP_A_eq_B 2 -#define COMP_A_lt_B 3 -#define COMP_No_Comp 4 -#define COMP_Denormal 0x20 -#define COMP_NaN 0x40 -#define COMP_SNaN 0x80 - -#define status_word() \ - ((partial_status & ~SW_Top & 0xffff) | ((top << SW_Top_Shift) & SW_Top)) -#define setcc(cc) ({ \ - partial_status &= ~(SW_C0|SW_C1|SW_C2|SW_C3); \ - partial_status |= (cc) & (SW_C0|SW_C1|SW_C2|SW_C3); }) - -#ifdef PECULIAR_486 - /* Default, this conveys no information, but an 80486 does it. */ - /* Clear the SW_C1 bit, "other bits undefined". */ -# define clear_C1() { partial_status &= ~SW_C1; } -# else -# define clear_C1() -#endif PECULIAR_486 - -#endif __ASSEMBLER__ - -#endif _STATUS_H_ diff --git a/drivers/FPU-emu/version.h b/drivers/FPU-emu/version.h deleted file mode 100644 index 4c75a4792..000000000 --- a/drivers/FPU-emu/version.h +++ /dev/null @@ -1,12 +0,0 @@ -/*---------------------------------------------------------------------------+ - | version.h | - | | - | | - | Copyright (C) 1992,1993,1994 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | | - +---------------------------------------------------------------------------*/ - -#define FPU_VERSION "wm-FPU-emu version 1.20" diff --git a/drivers/FPU-emu/wm_shrx.S b/drivers/FPU-emu/wm_shrx.S deleted file mode 100644 index bef0e1963..000000000 --- a/drivers/FPU-emu/wm_shrx.S +++ /dev/null @@ -1,208 +0,0 @@ - .file "wm_shrx.S" -/*---------------------------------------------------------------------------+ - | wm_shrx.S | - | | - | 64 bit right shift functions | - | | - | Copyright (C) 1992 W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | Call from C as: | - | unsigned shrx(void *arg1, unsigned arg2) | - | and | - | unsigned shrxs(void *arg1, unsigned arg2) | - | | - +---------------------------------------------------------------------------*/ - -#include "fpu_asm.h" - -.text - .align 2,144 - -/*---------------------------------------------------------------------------+ - | unsigned shrx(void *arg1, unsigned arg2) | - | | - | Extended shift right function. | - | Fastest for small shifts. | - | Shifts the 64 bit quantity pointed to by the first arg (arg1) | - | right by the number of bits specified by the second arg (arg2). | - | Forms a 96 bit quantity from the 64 bit arg and eax: | - | [ 64 bit arg ][ eax ] | - | shift right ---------> | - | The eax register is initialized to 0 before the shifting. | - | Results returned in the 64 bit arg and eax. | - +---------------------------------------------------------------------------*/ - - .globl _shrx - -_shrx: - push %ebp - movl %esp,%ebp - pushl %esi - movl PARAM2,%ecx - movl PARAM1,%esi - cmpl $32,%ecx /* shrd only works for 0..31 bits */ - jnc L_more_than_31 - -/* less than 32 bits */ - pushl %ebx - movl (%esi),%ebx /* lsl */ - movl 4(%esi),%edx /* msl */ - xorl %eax,%eax /* extension */ - shrd %cl,%ebx,%eax - shrd %cl,%edx,%ebx - shr %cl,%edx - movl %ebx,(%esi) - movl %edx,4(%esi) - popl %ebx - popl %esi - leave - ret - -L_more_than_31: - cmpl $64,%ecx - jnc L_more_than_63 - - subb $32,%cl - movl (%esi),%eax /* lsl */ - movl 4(%esi),%edx /* msl */ - shrd %cl,%edx,%eax - shr %cl,%edx - movl %edx,(%esi) - movl $0,4(%esi) - popl %esi - leave - ret - -L_more_than_63: - cmpl $96,%ecx - jnc L_more_than_95 - - subb $64,%cl - movl 4(%esi),%eax /* msl */ - shr %cl,%eax - xorl %edx,%edx - movl %edx,(%esi) - movl %edx,4(%esi) - popl %esi - leave - ret - -L_more_than_95: - xorl %eax,%eax - movl %eax,(%esi) - movl %eax,4(%esi) - popl %esi - leave - ret - - -/*---------------------------------------------------------------------------+ - | unsigned shrxs(void *arg1, unsigned arg2) | - | | - | Extended shift right function (optimized for small floating point | - | integers). | - | Shifts the 64 bit quantity pointed to by the first arg (arg1) | - | right by the number of bits specified by the second arg (arg2). | - | Forms a 96 bit quantity from the 64 bit arg and eax: | - | [ 64 bit arg ][ eax ] | - | shift right ---------> | - | The eax register is initialized to 0 before the shifting. | - | The lower 8 bits of eax are lost and replaced by a flag which is | - | set (to 0x01) if any bit, apart from the first one, is set in the | - | part which has been shifted out of the arg. | - | Results returned in the 64 bit arg and eax. | - +---------------------------------------------------------------------------*/ - .globl _shrxs -_shrxs: - push %ebp - movl %esp,%ebp - pushl %esi - pushl %ebx - movl PARAM2,%ecx - movl PARAM1,%esi - cmpl $64,%ecx /* shrd only works for 0..31 bits */ - jnc Ls_more_than_63 - - cmpl $32,%ecx /* shrd only works for 0..31 bits */ - jc Ls_less_than_32 - -/* We got here without jumps by assuming that the most common requirement - is for small integers */ -/* Shift by [32..63] bits */ - subb $32,%cl - movl (%esi),%eax /* lsl */ - movl 4(%esi),%edx /* msl */ - xorl %ebx,%ebx - shrd %cl,%eax,%ebx - shrd %cl,%edx,%eax - shr %cl,%edx - orl %ebx,%ebx /* test these 32 bits */ - setne %bl - test $0x7fffffff,%eax /* and 31 bits here */ - setne %bh - orw %bx,%bx /* Any of the 63 bit set ? */ - setne %al - movl %edx,(%esi) - movl $0,4(%esi) - popl %ebx - popl %esi - leave - ret - -/* Shift by [0..31] bits */ -Ls_less_than_32: - movl (%esi),%ebx /* lsl */ - movl 4(%esi),%edx /* msl */ - xorl %eax,%eax /* extension */ - shrd %cl,%ebx,%eax - shrd %cl,%edx,%ebx - shr %cl,%edx - test $0x7fffffff,%eax /* only need to look at eax here */ - setne %al - movl %ebx,(%esi) - movl %edx,4(%esi) - popl %ebx - popl %esi - leave - ret - -/* Shift by [64..95] bits */ -Ls_more_than_63: - cmpl $96,%ecx - jnc Ls_more_than_95 - - subb $64,%cl - movl (%esi),%ebx /* lsl */ - movl 4(%esi),%eax /* msl */ - xorl %edx,%edx /* extension */ - shrd %cl,%ebx,%edx - shrd %cl,%eax,%ebx - shr %cl,%eax - orl %ebx,%edx - setne %bl - test $0x7fffffff,%eax /* only need to look at eax here */ - setne %bh - orw %bx,%bx - setne %al - xorl %edx,%edx - movl %edx,(%esi) /* set to zero */ - movl %edx,4(%esi) /* set to zero */ - popl %ebx - popl %esi - leave - ret - -Ls_more_than_95: -/* Shift by [96..inf) bits */ - xorl %eax,%eax - movl (%esi),%ebx - orl 4(%esi),%ebx - setne %al - xorl %ebx,%ebx - movl %ebx,(%esi) - movl %ebx,4(%esi) - popl %ebx - popl %esi - leave - ret diff --git a/drivers/FPU-emu/wm_sqrt.S b/drivers/FPU-emu/wm_sqrt.S deleted file mode 100644 index 4e028cb80..000000000 --- a/drivers/FPU-emu/wm_sqrt.S +++ /dev/null @@ -1,474 +0,0 @@ - .file "wm_sqrt.S" -/*---------------------------------------------------------------------------+ - | wm_sqrt.S | - | | - | Fixed point arithmetic square root evaluation. | - | | - | Copyright (C) 1992,1993 | - | W. Metzenthen, 22 Parker St, Ormond, Vic 3163, | - | Australia. E-mail billm@vaxc.cc.monash.edu.au | - | | - | Call from C as: | - | void wm_sqrt(FPU_REG *n, unsigned int control_word) | - | | - +---------------------------------------------------------------------------*/ - -/*---------------------------------------------------------------------------+ - | wm_sqrt(FPU_REG *n, unsigned int control_word) | - | returns the square root of n in n. | - | | - | Use Newton's method to compute the square root of a number, which must | - | be in the range [1.0 .. 4.0), to 64 bits accuracy. | - | Does not check the sign or tag of the argument. | - | Sets the exponent, but not the sign or tag of the result. | - | | - | The guess is kept in %esi:%edi | - +---------------------------------------------------------------------------*/ - -#include "exception.h" -#include "fpu_asm.h" - - -#ifndef NON_REENTRANT_FPU -/* Local storage on the stack: */ -#define FPU_accum_3 -4(%ebp) /* ms word */ -#define FPU_accum_2 -8(%ebp) -#define FPU_accum_1 -12(%ebp) -#define FPU_accum_0 -16(%ebp) - -/* - * The de-normalised argument: - * sq_2 sq_1 sq_0 - * b b b b b b b ... b b b b b b .... b b b b 0 0 0 ... 0 - * ^ binary point here - */ -#define FPU_fsqrt_arg_2 -20(%ebp) /* ms word */ -#define FPU_fsqrt_arg_1 -24(%ebp) -#define FPU_fsqrt_arg_0 -28(%ebp) /* ls word, at most the ms bit is set */ - -#else -/* Local storage in a static area: */ -.data - .align 4,0 -FPU_accum_3: - .long 0 /* ms word */ -FPU_accum_2: - .long 0 -FPU_accum_1: - .long 0 -FPU_accum_0: - .long 0 - -/* The de-normalised argument: - sq_2 sq_1 sq_0 - b b b b b b b ... b b b b b b .... b b b b 0 0 0 ... 0 - ^ binary point here - */ -FPU_fsqrt_arg_2: - .long 0 /* ms word */ -FPU_fsqrt_arg_1: - .long 0 -FPU_fsqrt_arg_0: - .long 0 /* ls word, at most the ms bit is set */ -#endif NON_REENTRANT_FPU - - -.text - .align 2,144 - -.globl _wm_sqrt -_wm_sqrt: - pushl %ebp - movl %esp,%ebp -#ifndef NON_REENTRANT_FPU - subl $28,%esp -#endif NON_REENTRANT_FPU - pushl %esi - pushl %edi - pushl %ebx - - movl PARAM1,%esi - - movl SIGH(%esi),%eax - movl SIGL(%esi),%ecx - xorl %edx,%edx - -/* We use a rough linear estimate for the first guess.. */ - - cmpl EXP_BIAS,EXP(%esi) - jnz sqrt_arg_ge_2 - - shrl $1,%eax /* arg is in the range [1.0 .. 2.0) */ - rcrl $1,%ecx - rcrl $1,%edx - -sqrt_arg_ge_2: -/* From here on, n is never accessed directly again until it is - replaced by the answer. */ - - movl %eax,FPU_fsqrt_arg_2 /* ms word of n */ - movl %ecx,FPU_fsqrt_arg_1 - movl %edx,FPU_fsqrt_arg_0 - -/* Make a linear first estimate */ - shrl $1,%eax - addl $0x40000000,%eax - movl $0xaaaaaaaa,%ecx - mull %ecx - shll %edx /* max result was 7fff... */ - testl $0x80000000,%edx /* but min was 3fff... */ - jnz sqrt_prelim_no_adjust - - movl $0x80000000,%edx /* round up */ - -sqrt_prelim_no_adjust: - movl %edx,%esi /* Our first guess */ - -/* We have now computed (approx) (2 + x) / 3, which forms the basis - for a few iterations of Newton's method */ - - movl FPU_fsqrt_arg_2,%ecx /* ms word */ - -/* - * From our initial estimate, three iterations are enough to get us - * to 30 bits or so. This will then allow two iterations at better - * precision to complete the process. - */ - -/* Compute (g + n/g)/2 at each iteration (g is the guess). */ - shrl %ecx /* Doing this first will prevent a divide */ - /* overflow later. */ - - movl %ecx,%edx /* msw of the arg / 2 */ - divl %esi /* current estimate */ - shrl %esi /* divide by 2 */ - addl %eax,%esi /* the new estimate */ - - movl %ecx,%edx - divl %esi - shrl %esi - addl %eax,%esi - - movl %ecx,%edx - divl %esi - shrl %esi - addl %eax,%esi - -/* - * Now that an estimate accurate to about 30 bits has been obtained (in %esi), - * we improve it to 60 bits or so. - * - * The strategy from now on is to compute new estimates from - * guess := guess + (n - guess^2) / (2 * guess) - */ - -/* First, find the square of the guess */ - movl %esi,%eax - mull %esi -/* guess^2 now in %edx:%eax */ - - movl FPU_fsqrt_arg_1,%ecx - subl %ecx,%eax - movl FPU_fsqrt_arg_2,%ecx /* ms word of normalized n */ - sbbl %ecx,%edx - jnc sqrt_stage_2_positive - -/* Subtraction gives a negative result, - negate the result before division. */ - notl %edx - notl %eax - addl $1,%eax - adcl $0,%edx - - divl %esi - movl %eax,%ecx - - movl %edx,%eax - divl %esi - jmp sqrt_stage_2_finish - -sqrt_stage_2_positive: - divl %esi - movl %eax,%ecx - - movl %edx,%eax - divl %esi - - notl %ecx - notl %eax - addl $1,%eax - adcl $0,%ecx - -sqrt_stage_2_finish: - sarl $1,%ecx /* divide by 2 */ - rcrl $1,%eax - - /* Form the new estimate in %esi:%edi */ - movl %eax,%edi - addl %ecx,%esi - - jnz sqrt_stage_2_done /* result should be [1..2) */ - -#ifdef PARANOID -/* It should be possible to get here only if the arg is ffff....ffff */ - cmp $0xffffffff,FPU_fsqrt_arg_1 - jnz sqrt_stage_2_error -#endif PARANOID - -/* The best rounded result. */ - xorl %eax,%eax - decl %eax - movl %eax,%edi - movl %eax,%esi - movl $0x7fffffff,%eax - jmp sqrt_round_result - -#ifdef PARANOID -sqrt_stage_2_error: - pushl EX_INTERNAL|0x213 - call EXCEPTION -#endif PARANOID - -sqrt_stage_2_done: - -/* Now the square root has been computed to better than 60 bits. */ - -/* Find the square of the guess. */ - movl %edi,%eax /* ls word of guess */ - mull %edi - movl %edx,FPU_accum_1 - - movl %esi,%eax - mull %esi - movl %edx,FPU_accum_3 - movl %eax,FPU_accum_2 - - movl %edi,%eax - mull %esi - addl %eax,FPU_accum_1 - adcl %edx,FPU_accum_2 - adcl $0,FPU_accum_3 - -/* movl %esi,%eax */ -/* mull %edi */ - addl %eax,FPU_accum_1 - adcl %edx,FPU_accum_2 - adcl $0,FPU_accum_3 - -/* guess^2 now in FPU_accum_3:FPU_accum_2:FPU_accum_1 */ - - movl FPU_fsqrt_arg_0,%eax /* get normalized n */ - subl %eax,FPU_accum_1 - movl FPU_fsqrt_arg_1,%eax - sbbl %eax,FPU_accum_2 - movl FPU_fsqrt_arg_2,%eax /* ms word of normalized n */ - sbbl %eax,FPU_accum_3 - jnc sqrt_stage_3_positive - -/* Subtraction gives a negative result, - negate the result before division */ - notl FPU_accum_1 - notl FPU_accum_2 - notl FPU_accum_3 - addl $1,FPU_accum_1 - adcl $0,FPU_accum_2 - -#ifdef PARANOID - adcl $0,FPU_accum_3 /* This must be zero */ - jz sqrt_stage_3_no_error - -sqrt_stage_3_error: - pushl EX_INTERNAL|0x207 - call EXCEPTION - -sqrt_stage_3_no_error: -#endif PARANOID - - movl FPU_accum_2,%edx - movl FPU_accum_1,%eax - divl %esi - movl %eax,%ecx - - movl %edx,%eax - divl %esi - - sarl $1,%ecx /* divide by 2 */ - rcrl $1,%eax - - /* prepare to round the result */ - - addl %ecx,%edi - adcl $0,%esi - - jmp sqrt_stage_3_finished - -sqrt_stage_3_positive: - movl FPU_accum_2,%edx - movl FPU_accum_1,%eax - divl %esi - movl %eax,%ecx - - movl %edx,%eax - divl %esi - - sarl $1,%ecx /* divide by 2 */ - rcrl $1,%eax - - /* prepare to round the result */ - - notl %eax /* Negate the correction term */ - notl %ecx - addl $1,%eax - adcl $0,%ecx /* carry here ==> correction == 0 */ - adcl $0xffffffff,%esi - - addl %ecx,%edi - adcl $0,%esi - -sqrt_stage_3_finished: - -/* - * The result in %esi:%edi:%esi should be good to about 90 bits here, - * and the rounding information here does not have sufficient accuracy - * in a few rare cases. - */ - cmpl $0xffffffe0,%eax - ja sqrt_near_exact_x - - cmpl $0x00000020,%eax - jb sqrt_near_exact - - cmpl $0x7fffffe0,%eax - jb sqrt_round_result - - cmpl $0x80000020,%eax - jb sqrt_get_more_precision - -sqrt_round_result: -/* Set up for rounding operations */ - movl %eax,%edx - movl %esi,%eax - movl %edi,%ebx - movl PARAM1,%edi - movl EXP_BIAS,EXP(%edi) /* Result is in [1.0 .. 2.0) */ - movl PARAM2,%ecx - jmp fpu_reg_round_sqrt - - -sqrt_near_exact_x: -/* First, the estimate must be rounded up. */ - addl $1,%edi - adcl $0,%esi - -sqrt_near_exact: -/* - * This is an easy case because x^1/2 is monotonic. - * We need just find the square of our estimate, compare it - * with the argument, and deduce whether our estimate is - * above, below, or exact. We use the fact that the estimate - * is known to be accurate to about 90 bits. - */ - movl %edi,%eax /* ls word of guess */ - mull %edi - movl %edx,%ebx /* 2nd ls word of square */ - movl %eax,%ecx /* ls word of square */ - - movl %edi,%eax - mull %esi - addl %eax,%ebx - addl %eax,%ebx - -#ifdef PARANOID - cmp $0xffffffb0,%ebx - jb sqrt_near_exact_ok - - cmp $0x00000050,%ebx - ja sqrt_near_exact_ok - - pushl EX_INTERNAL|0x214 - call EXCEPTION - -sqrt_near_exact_ok: -#endif PARANOID - - or %ebx,%ebx - js sqrt_near_exact_small - - jnz sqrt_near_exact_large - - or %ebx,%edx - jnz sqrt_near_exact_large - -/* Our estimate is exactly the right answer */ - xorl %eax,%eax - jmp sqrt_round_result - -sqrt_near_exact_small: -/* Our estimate is too small */ - movl $0x000000ff,%eax - jmp sqrt_round_result - -sqrt_near_exact_large: -/* Our estimate is too large, we need to decrement it */ - subl $1,%edi - sbbl $0,%esi - movl $0xffffff00,%eax - jmp sqrt_round_result - - -sqrt_get_more_precision: -/* This case is almost the same as the above, except we start - with an extra bit of precision in the estimate. */ - stc /* The extra bit. */ - rcll $1,%edi /* Shift the estimate left one bit */ - rcll $1,%esi - - movl %edi,%eax /* ls word of guess */ - mull %edi - movl %edx,%ebx /* 2nd ls word of square */ - movl %eax,%ecx /* ls word of square */ - - movl %edi,%eax - mull %esi - addl %eax,%ebx - addl %eax,%ebx - -/* Put our estimate back to its original value */ - stc /* The ms bit. */ - rcrl $1,%esi /* Shift the estimate left one bit */ - rcrl $1,%edi - -#ifdef PARANOID - cmp $0xffffff60,%ebx - jb sqrt_more_prec_ok - - cmp $0x000000a0,%ebx - ja sqrt_more_prec_ok - - pushl EX_INTERNAL|0x215 - call EXCEPTION - -sqrt_more_prec_ok: -#endif PARANOID - - or %ebx,%ebx - js sqrt_more_prec_small - - jnz sqrt_more_prec_large - - or %ebx,%ecx - jnz sqrt_more_prec_large - -/* Our estimate is exactly the right answer */ - movl $0x80000000,%eax - jmp sqrt_round_result - -sqrt_more_prec_small: -/* Our estimate is too small */ - movl $0x800000ff,%eax - jmp sqrt_round_result - -sqrt_more_prec_large: -/* Our estimate is too large */ - movl $0x7fffff00,%eax - jmp sqrt_round_result diff --git a/drivers/Makefile b/drivers/Makefile index 80d4488e4..3781c0d5d 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -16,10 +16,10 @@ .c.o: $(CC) $(CFLAGS) -c $< -SUBDIRS = block char net +SUBDIRS = block char net #streams -ifdef CONFIG_MATH_EMULATION -SUBDIRS := $(SUBDIRS) FPU-emu +ifdef CONFIG_PCI +SUBDIRS := $(SUBDIRS) pci endif ifdef CONFIG_SCSI @@ -35,6 +35,9 @@ all: driversubdirs driversubdirs: dummy set -e; for i in $(SUBDIRS); do $(MAKE) -C $$i; done +modules: dummy + set -e; for i in $(SUBDIRS); do $(MAKE) -C $$i modules; done + dep: set -e; for i in $(SUBDIRS); do $(MAKE) -C $$i dep; done diff --git a/drivers/block/MAKEDEV.ide1 b/drivers/block/MAKEDEV.ide1 new file mode 100644 index 000000000..506115ca5 --- /dev/null +++ b/drivers/block/MAKEDEV.ide1 @@ -0,0 +1,24 @@ +#!/bin/sh +makedev () { + rm -f /dev/$1 + echo mknod /dev/$1 $2 $3 $4 && + mknod /dev/$1 $2 $3 $4 && + chown $5 /dev/$1 && + chmod $6 /dev/$1 +} + +# Create /dev/hdc* +makedev hdc b 22 0 root:$disk 660 +for part in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 +do + makedev hdc$part b 22 `expr 0 + $part` root:$disk 660 +done +echo " " + +# Create /dev/hdd* +makedev hdd b 22 64 root:$disk 660 +for part in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 +do + makedev hdd$part b 22 `expr 64 + $part` root:$disk 660 +done + diff --git a/drivers/block/Makefile b/drivers/block/Makefile index ea77020f6..ebf02d8a5 100644 --- a/drivers/block/Makefile +++ b/drivers/block/Makefile @@ -21,8 +21,14 @@ # In the future, some of these should be built conditionally. # -OBJS := ll_rw_blk.o floppy.o ramdisk.o genhd.o -SRCS := ll_rw_blk.c floppy.c ramdisk.c genhd.c +OBJS := ll_rw_blk.o ramdisk.o genhd.o +SRCS := ll_rw_blk.c ramdisk.c genhd.c +BLOCK_MODULE_OBJS = + +ifdef CONFIG_BLK_DEV_FD +OBJS := $(OBJS) floppy.o +SRCS := $(SRCS) floppy.c +endif ifdef CONFIG_CDU31A OBJS := $(OBJS) cdu31a.o @@ -34,9 +40,18 @@ OBJS := $(OBJS) mcd.o SRCS := $(SRCS) mcd.c endif +ifdef CONFIG_AZTCD +OBJS := $(OBJS) aztcd.o +SRCS := $(SRCS) aztcd.c +else +BLOCK_MODULE_OBJS := $(BLOCK_MODULE_OBJS) aztcd.o +endif + ifdef CONFIG_SBPCD OBJS := $(OBJS) sbpcd.o SRCS := $(SRCS) sbpcd.c +else +BLOCK_MODULE_OBJS := $(BLOCK_MODULE_OBJS) sbpcd.o endif #CONFIG_SBPCD ifdef CONFIG_SBPCD2 @@ -59,11 +74,23 @@ OBJS := $(OBJS) hd.o SRCS := $(SRCS) hd.c endif +ifdef CONFIG_BLK_DEV_IDE +OBJS := ide.o $(OBJS) +SRCS := ide.c $(SRCS) +endif + ifdef CONFIG_BLK_DEV_XD OBJS := $(OBJS) xd.o SRCS := $(SRCS) xd.c endif +ifdef CONFIG_CDU535 +OBJS := $(OBJS) sonycd535.o +SRCS := $(SRCS) sonycd535.c +else +BLOCK_MODULE_OBJS := $(BLOCK_MODULE_OBJS) sonycd535.o +endif + all: block.a block.a: $(OBJS) @@ -73,6 +100,13 @@ block.a: $(OBJS) dep: $(CPP) -M $(SRCS) > .depend +ifdef BLOCK_MODULE_OBJS + $(CPP) -M -DMODULE $(BLOCK_MODULE_OBJS:.o=.c) >> .depend +endif + +modules: $(BLOCK_MODULE_OBJS) + echo $(BLOCK_MODULE_OBJS) > ../../modules/BLOCK_MODULES + (cd ../../modules;for i in $(BLOCK_MODULE_OBJS); do ln -sf ../drivers/block/$$i .; done) dummy: diff --git a/drivers/block/README.aztcd b/drivers/block/README.aztcd new file mode 100644 index 000000000..797c11222 --- /dev/null +++ b/drivers/block/README.aztcd @@ -0,0 +1,730 @@ + Readme-File README.aztcd + for Aztech CD-ROM CDA268-01A, ORCHID CD-3110, OKANO/WEARNES CDD110 + CD-ROM Driver + Version 1.0 and newer + (for other drives see 6.-8.) + +NOTE: THIS DRIVER WILL WORK WITH THE CD-ROM DRIVES LISTED, WHICH HAVE + A PROPRIETARY INTERFACE (implemented on a sound card or on a + ISA-AT-bus card). + IT WILL DEFINITELY NOT WORK WITH CD-ROM DRIVES WITH *IDE*-INTERFACE, + such as the Aztech CDA269-031SE !!! IF YOU'RE USING A CD-ROM DRIVE + WITH IDE-INTERFACE, SOMETIMES ALSO CALLED ATAPI-COMPATIBLE, PLEASE + USE THE ide-cd.c DRIVER, WRITTEN BY MARK LORD AND SCOTT SNYDER !!! +---------------------------------------------------------------------------- +Contents of this file: + 1. NOTE + 2. INSTALLATION + 3. CONFIGURING YOUR KERNEL + 4. RECOMPILING YOUR KERNEL + 5. KNOWN PROBLEMS, FUTURE DEVELOPMENTS + 6. BUG REPORTS + 7. OTHER DRIVES + 8. IF YOU DON'T SUCCEED ... DEBUGGING + 9. TECHNICAL HISTORY OF THE DRIVER + 10. ACKNOWLEDGMENTS + 11. PROGRAMMING ADD ONS: CDPLAY.C + APPENDIX: Source code of cdplay.c +---------------------------------------------------------------------------- + +1. NOTE +This software has been successfully in alpha and beta test for quite a long +time with AZTECH CDA268-01A, ORCHID CDS-3110 and ORCHID/WEARNES CDD110 +and has proven to be stable with kernel versions 1.0.9 to 1.2.0. But with +any software there still may be bugs in it. So if you encounter problems, +you are invited to help us improve this software. Please send me a detailed +bug report (see chapter BUG REPORTS). You are also invited in helping us to +increase the number of drives, which are supported. + +Please read the README-files carefully and always keep a backup copy of your +old kernel, in order to reboot if something goes wrong! + + +2. INSTALLATION +If you received this software as a standalone package AZTECH.CDROM.Vxx.tgz +(xx=version number) and not included in the standard Linux kernel, read the +file AZTECH.CDROM.README included in that package to install it. The +standalone package's home is 'ftp.gwdg.de : pub/linux/cdrom/drivers/aztech'. +The driver consists of a header file 'aztcd.h', which normally should reside +in /usr/include/linux and the source code 'aztcd.c', which normally resides in +/usr/src/linux/drivers/block. It uses /dev/aztcd0, which must be a valid block +device with major number 29 and reside in directory /dev. To mount a CD-ROM, +your kernel needs to have the ISO9660-filesystem support included. + + +3. CONFIGURING YOUR KERNEL +If your kernel is already configured for using the AZTECH driver you will +see the following message while Linux boots: + Aztech CD-ROM Init: DriverVersion= BaseAddress= + Aztech CD-ROM Init: FirmwareVersion=>> + Aztech CD-ROM Init: detected + Aztech CD-ROM Init: End +If the message looks different and you are sure to have a supported drive, +it may have a different base address. The Aztech driver does look for the +CD-ROM drive at the base address specified in aztcd.h at compile time. This +address can be overwritten by boot parameter aztcd=....You should reboot and +start Linux with boot parameter aztcd=, e.g. aztcd=0x320. If +you do not know the base address, start your PC with DOS and look at the boot +message of your CD-ROM's DOS driver. + +If the message looks correct, as user 'root' you should be able to mount the +drive by + mount -t iso9660 -r /dev/aztcd0 /mnt +and use it as any other filesystem. (If this does not work, check if + /dev/aztcd0 and /mnt do exist and create them, if necessary by doing + mknod /dev/aztcd0 b 29 0 + mkdir /mnt + +If you still get a different message while Linux boots or when you get the +message, that the ISO9660-filesystem is not supported by your kernel, when +you try to mount the CD-ROM drive, you have to recompile your kernel. + +If you do *not* have an Aztech/Orchid/Okano/Wearnes drive and want to bypass +drive detection during Linux boot up, start with boot parameter aztcd=0. + +Joe Nardone has compiled a boot disk with the Aztech driver for installing +Slackware from CDROM. You can find the disk images at 'sunsite.unc.edu'; +see file 'aztech.gz.README' for instructions on how to use it. + + +4. RECOMPILING YOUR KERNEL +If your kernel is not yet configured for the AZTECH driver and the ISO9660- +filesystem, you have to recompile your kernel: + +- Edit aztcd.h to set the I/O-address to your I/O-Base address (AZT_BASE_ADDR), + the driver does not use interrupts or DMA, so if you are using an AZTECH + CD268, an ORCHID CD-3110 or ORCHID/WEARNES CDD110 that's the only item you + have to set up. + Users of other drives should read chapter OTHER DRIVES of this file. + You also can configure that address by LILO boot parameter aztcd=... +- Build a new kernel, configure it for 'Aztech/Orchid/Okano/Wearnes support' + (if you want aztcd to be part of the kernel). Do not configure it for + 'Aztech... support', if you want to use aztcd as a run time loadable module. + But in any case you must have the ISO9660-filesystem included in your + kernel. +- Activate the new kernel, normally this is done by running lilo (don't for- + get to configure it before and to keep a copy of your old kernel in case + something goes wrong!). +- Reboot +- If you've included aztcd in your kernel, you now should see during boot + some messages like + Aztech CD-ROM Init: DriverVersion= BaseAddress= + Aztech CD-ROM Init: FirmwareVersion= + Aztech CD-ROM Init: detected + Aztech CD-ROM Init: End +- If you have not included aztcd in your kernel, but want to load aztcd as a + run time loadable module see 4.1. +- If the message looks correct, as user 'root' you should be able to mount + the drive by + mount -t iso9660 -r /dev/aztcd0 /mnt + and use it as any other filesystem. (If this does not work, check if + /dev/aztcd0 and /mnt do exist and create them, if necessary by doing + mknod /dev/aztcd0 b 29 0 + mkdir /mnt +- If this still does not help, see chapters OTHER DRIVES and DEBUGGING. + +4.1 AZTCD AS A RUN-TIME LOADABLE MODULE +If you do not need aztcd permanently, you can also load and remove the driver +during runtime via insmod and rmmod. To build aztcd as a loadable module you +must *not* configure your kernel for AZTECH support. But you need to have +the ISO9660-filesystem included! So rebuild your kernel, if necessary. + +Now edit the base address of your AZTECH interface card in +/usr/src/linux/include/linux/aztcd.h to the appropriate value. Then change +to /usr/src/linux and do a + make modules + make modules_install +After that you can run-time load the driver via + insmod /lib/modules/X.X.X/misc/aztcd.o +and remove it via rmmod aztcd. +If you have not configured the correct base address, you can also supply the +base address when loading the driver via + insmod /lib/modules/X.X.X/misc/aztcd.o aztcd= +In all commands 'X.X.X' is the current linux kernel version number. For details +see file README.modules in /usr/src/linux. + + +5. KNOWN PROBLEMS, FUTURE DEVELOPMENTS +The driver does not support applications such as photo CD, multi session CD +etc.. I do not plan to include the support for that in the driver, because I +do not use such applications. If you are interested in that stuff and would +like to extend the drivers capability on your own, please contact me, I'll +support you as far as I can. + +The drive status recognition does not work correctly in all cases. Changing +a disk or having the door open, when a drive is already mounted, is detected +by the Aztech driver itself, but nevertheless causes multiple read attempts +by the different layers of the ISO9660-filesystem driver, which finally timeout, +so you have to wait quite a little... But isn't it bad style to change a disk +in a mounted drive, anyhow ?! + +The driver uses busy wait in most cases for the drive handshake (macros +STEN_LOW and DTEN_LOW). I tested with a 486/DX2 at 66MHz and a Pentium at +60MHz. Whenever you use a much faster machine you are likely to get timeout +messages. In that case edit aztcd.h and increase the timeout value AZT_TIMEOUT. + +For some 'slow' drive commands I implemented waiting with a timer waitqueue +(macro STEN_LOW_WAIT). If you get this timeout message, you may also edit +aztcd.h and increase the timeout value AZT_STATUS_DELAY. The waitqueue has +shown to be a little critical. If you get kernel panic messages, edit aztcd.c +and substitute STEN_LOW_WAIT by STEN_LOW. Busy waiting with STEN_LOW is more +stable, but also causes CPU overhead. + + +6. BUG REPORTS +Please send detailed bug reports and bug fixes via EMail to + + zimmerma@rz.fht-esslingen.de + +Please include a description of your CD-ROM drive type and interface card, +the exact firmware message during Linux bootup, the version number of the +AZTECH-CDROM-driver and the Linux kernel version. Also a description of your +system's other hardware could be of interest, especially microprocessor type, +clock frequency, other interface cards such as soundcards, ethernet adapter, +game cards etc.. + +I will try to collect the reports and make the necessary modifications from +time to time. I may also come back to you directly with some bug fixes and +ask you to do further testing and debugging. + +Editors of CD-ROMs are invited to send a 'cooperation' copy of their +CD-ROMs to the volunteers, who provided the CD-ROM support for Linux. My +snail mail address for such 'stuff' is + Prof. Dr. W. Zimmermann + Fachhochschule fuer Technik Esslingen + Fachbereich NT + Flandernstrasse 101 + D-73732 Esslingen + Germany + + +7. OTHER DRIVES +The following drives ORCHID CDS3110, OKANO CDD110 and WEARNES nearly look +the same as AZTECH CDA268-01A, especially they seem to use the same command +codes. So it was quite simple to make the AZTECH driver work with these drives. + +Unfortunately I do not have any of these drives available, so I couldn't test +it myself. But I've got reports, that it works with ORCHID CDS3110 and Game- +Wave32 sound cards and also with WEARNES CDD110 in some different combinations. +In some installations, it seems necessary to initialize the drive with the DOS +driver before (especially if combined with a sound card) and then do a warm +boot (CTRL-ALT-RESET) or start Linux from DOS, e.g. with 'loadlin'. + +If you do not succeed, read chapter DEBUGGING. Thanks in advance! + +Sorry for the inconvenience, but it is difficult to develop for hardware, +which you don't have available for testing. So if you like, please help us. + + +8. DEBUGGING : IF YOU DON'T SUCCEED, TRY THE FOLLOWING +-reread the complete README file +-make sure, that your drive is hardware configured for + transfer mode: polled + IRQ: not used + DMA: not used + Base Address: something like 300, 320 ... + You can check this, when you start the DOS driver, which came with your + drive. By appropriately configuring the drive and the DOS driver you can + check, whether your drive does operate in this mode correctly under DOS. If + it does not operate under DOS, it won't under Linux. + Make sure the Base Address is configured correctly in aztcd.h, also make + sure, that /dev/aztcd0 exists with the correct major number (compare it with + the entry in file /usr/include/linux/major.h for the Aztech drive). +-insert a CD-ROM and close the tray +-cold boot your PC (i.e. via the power on switch or the reset button) +-if you start Linux via DOS, e.g. using loadlin, make sure, that the DOS + driver for the CD-ROM drive is not loaded (comment out the calling lines + in DOS' config.sys!) +-look for the aztcd: init message during Linux init and note them exactly +-log in as root and do a mount -t iso9660 /dev/aztcd0 /mnt +-if you don't succeed in the first time, try several times. Try also to open + and close the tray, then mount again. Please note carefully all commands + you typed in and the aztcd-messages, which you get. +-if you get an 'Aztech CD-ROM init: aborted' message, read the remarks about + the version string below. + +If this does not help, do the same with the following differences +-start DOS before; make now sure, that the DOS driver for the CD-ROM is + loaded under DOS (i.e. uncomment it again in config.sys) +-warm boot your PC (i.e. via CTRL-ALT-DEL) + if you have it, you can also start via loadlin (try both). + ... + Again note all commands and the aztcd-messages. + +If you see STEN_LOW or STEN_LOW_WAIT error messages, increase the timeout +values. + +If this still does not help, +-look in aztcd.c for the lines #if 0 + #define AZT_TEST1 + ... + #endif + and substitute '#if 0' by '#if 1'. +-recompile your kernel and repeat the above two procedures. You will now get + a bundle of debugging messages from the driver. Again note your commands + and the appropriate messages. If you have syslogd running, these messages + may also be found in syslogd's kernel log file. Nevertheless in some + installations syslogd does not yet run, when init() is called, thus look for + the aztcd-messages during init, before the login-prompt appears. + Then look in aztcd.c, to find out, what happened. The normal calling sequence + is: aztcd_init() during Linux bootup procedure init() + after doing a 'mount -t iso9660 /dev/aztcd0 /mnt' the normal calling sequence is + aztcd_open() -> Status 2c after cold reboot with CDROM or audio CD inserted + -> Status 8 after warm reboot with CDROM inserted + -> Status 2e after cold reboot with no disk, closed tray + -> Status 6e after cold reboot, mount with door open + aztUpdateToc() + aztGetDiskInfo() + aztGetQChannelInfo() repeated several times + aztGetToc() + aztGetQChannelInfo() repeated several times + a list of track informations + do_aztcd_request() } + azt_transfer() } repeated several times + azt_poll } + Check, if there is a difference in the calling sequence or the status flags! + + There are a lot of other messages, eg. the ACMD-command code (defined in + aztcd.h), status info from the getAztStatus-command and the state sequence of + the finite state machine in azt_poll(). The most important are the status + messages, look how they are defined and try to understand, if they make + sense in the context where they appear. With a CD-ROM inserted the status + should always be 8, except in aztcd_open(). Try to open the tray, insert a + audio disk, insert no disk or reinsert the CD-ROM and check, if the status + bits change accordingly. The status bits are the most likely point, where + the drive manufacturers may implement changes. + +If you still don't succeed, a good point to start is to look in aztcd.c in +function aztcd_init, where the drive should be detected during init. Do the +following: +-reboot the system with boot parameter 'aztcd=,0x79'. With + parameter 0x79 most of the drive version detection is bypassed. After that + you should see the complete version string including leading and trailing + blanks during init. + Now adapt the statement + if ((result[1]=='A')&&(result[2]=='Z' ...) + in aztcd_init() to exactly match the first 3 or 4 letters you have seen. +-Another point is the 'smart' card detection feature in aztcd_init(). Normally + the CD-ROM drive is ready, when aztcd_init is trying to read the version + string and a time consuming ACMD_SOFT_RESET command can be avoided. This is + detected by looking, if AFL_OP_OK can be read correctly. If the CD-ROM drive + hangs in some unknown state, e.g. because of an error before a warm start or + because you first operated under DOS, even the version string may be correct, + but the following commands will not. Then change the code in such a way, + that the ACMD_SOFT_RESET is issued in any case, by substituting the + if-statement 'if ( ...=AFL_OP_OK)' by 'if (1)'. + +If you succeed, please mail may the exact version string of your drive and +the code modifications, you have made together with a short explanation. +If you don't succeed, you may mail me the output of the debugging messages. +But remember, they are only useful, if they are exact and complete and you +describe in detail your hardware setup and what you did (cold/warm reboot, +with/without DOS, DOS-driver started/not started, which Linux-commands etc.) + + +9. TECHNICAL HISTORY OF THE DRIVER +The AZTECH-Driver is a rework of the Mitsumi-Driver. Four major items had to +be reworked: + +a) The Mitsumi drive does issue complete status information acknowledging +each command, the Aztech drive does only signal that the command was +processed. So whenever the complete status information is needed, an extra +ACMD_GET_STATUS command is issued. The handshake procedure for the drive +can be found in the functions aztSendCmd(), sendAztCmd() and getAztStatus(). + +b) The Aztech Drive does not have a ACMD_GET_DISK_INFO command, so the +necessary info about the number of tracks (firstTrack, lastTrack), disk +length etc. has to be read from the TOC in the lead in track (see function +aztGetDiskInfo()). + +c) Whenever data is read from the drive, the Mitsumi drive is started with a +command to read an indefinite (0xffffff) number of sectors. When the appropriate +number of sectors is read, the drive is stopped by a ACDM_STOP command. This +does not work with the Aztech drive. I did not find a way to stop it. The +stop and pause commands do only work in AUDIO mode but not in DATA mode. +Therefore I had to modify the 'finite state machine' in function azt_poll to +only read a certain number of sectors and then start a new read on demand. As I +have not completely understood, how the buffer/caching scheme of the Mitsumi +driver was implemented, I am not sure, if I have covered all cases correctly, +whenever you get timeout messages, the bug is most likely to be in that +function azt_poll() around switch(cmd) .... case ACD_S_DATA. + +d) I did not get information about changing drive mode. So I doubt, that the +code around function azt_poll() case AZT_S_MODE does work. In my test I have +not been able to switch to reading in raw mode. For reading raw mode, Aztech +uses a different command than for cooked mode, which I only have implemen- +ted in the ioctl-section but not in the section which is used by the ISO9660- + +The driver was developed on an AST PC with Intel 486/DX2, 8MB RAM, 340MB IDE +hard disk and on an AST PC with Intel Pentium 60MHz, 16MB RAM, 520MB IDE +running Linux kernel version 1.0.9 from the LST 1.8 Distribution. The kernel +was compiled with gcc.2.5.8. My CD-ROM drive is an Aztech CDA268-01A. My +drive says, that it has Firmware Version AZT26801A1.3. It came with a ISA-bus +interface card and works with polled I/O without DMA and without interrupts. +The code for all other drives was 'remote' tested and debugged by a number of +volunteers on the Internet. + +Points, where I feel that possible problems might be and all points where I +did not completely understand the drive's behaviour or trust my own code are +marked with /*???*/ in the source code. There are also some parts in the +Mitsumi driver, where I did not completely understand their code. + + +10. ACKNOWLEDGMENTS +Without the help of P.Bush, Aztech, who delivered technical information +about the Aztech Drive and without the help of E.Moenkeberg, GWDG, who did a +great job in analyzing the command structure of various CD-ROM drives, this +work would not have been possible. E.Moenkeberg was also a great help in +making the software 'kernel ready' and in answering many of the CDROM-related +questions in the newsgroups. He really is *the* Linux CD-ROM guru. Thanks +also to all the guys on the Internet, who collected valuable technical +information about CDROMs. + +Joe Nardone (nardone@clark.net) was a patient tester even for my first +trial, which was more than slow, and made suggestions for code improvement. +Especially the 'finite state machine' azt_poll() was rewritten by Joe to get +clean C code and avoid the ugly 'gotos', which I copied from mcd.c. + +Robby Schirmer (schirmer@fmi.uni-passau.de) tested the audio stuff (ioctls) +and suggested a lot of patches for them. + +Joseph Piskor and Peter Nugent were the first users with the ORCHID CD3110 +and also were very patient with the problems which occurred. + +Anybody, who is interested in these items should have a look at 'ftp.gwdg.de', +directory 'pub/linux/cdrom' and at 'ftp.cdrom.com', directory 'pub/cdrom'. + +11. PROGRAMMING ADD ONs: cdplay.c +You can use the ioctl-functions included in aztcd.c in your own programs. As +an example on how to do this, you will find a tiny CD Player for audio CDs +named 'cdplay.c'. It allows you to play audio CDs. You can play a specified +track, pause and resume or skip tracks forward and backwards. If you quit the +program without stopping the drive, playing is continued. You can also +(mis)use cdplay to read and hexdump data disks. +'cdplay.c' can be found as a separate file in package AZTECH.CDROM.Vxx.tgz. +If you don't have it, you can find the code in the APPENDIX of this file, +which you should cut out with an editor and store in a separate file +'cdplay.c'. To compile it and make it executable, do + gcc -s -Wall -O2 -L/usr/lib cdplay.c -o /usr/local/bin/cdplay # compiles it + chmod +755 /usr/local/bin/cdplay # makes it executable + ln -s /dev/aztcd0 /dev/cdrom # creates a link + (for /usr/lib substitute the top level directory, where your include files + reside, and for /usr/local/bin the directory, where you want the executable + binary to reside ) + +You have to set the correct permissions for cdplay *and* for /dev/mcd0 or +/dev/aztcd0 in order to use it. Remember, that you should not have /dev/cdrom +mounted, when you're playing audio CDs. + +This program is just a hack for testing the ioctl-functions in aztcd.c, I will +not maintain it, so if you run into problems, discard it or have a look into +the source code 'cdplay.c'. The program does only contain a minimum of user +protection and input error detection. If you use the commands in the wrong +order or if you try to read a CD at wrong addresses, you may get error messages +or even hang your machine. If you get STEN_LOW, STEN_LOW_WAIT or segment violation +error messages when using cdplay, after that, the system might not be stable +any more, so you'd better reboot. As the ioctl-functions run in kernel mode, +most normal Linux-multitasking protection features do not work. By using +uninitialized 'wild' pointers etc., it is easy to write to other users data and +program areas, destroy kernel tables etc.. So if you experiment with ioctls +as always when you are doing systems programming and kernel hacking, you +should have a backup copy of your system in a safe place (and you also +should try before, how to restore from a backup copy)! + + +Werner Zimmermann +Fachhochschule fuer Technik Esslingen +(EMail: zimmerma@rz.fht-esslingen.de) +Mar 24, 1995 + +--------------------------------------------------------------------------- +APPENDIX: Source code of cdplay.c + +/* Tiny Audio CD Player + + Copyright 1994, 1995 Werner Zimmermann (zimmerma@rz.fht-esslingen.de) + +This program originally was written to test the audio functions of the +AZTECH.CDROM-driver, but it should work with every CD-ROM drive. Before +using it, you should set a symlink from /dev/cdrom to your real CDROM +device. + +The GNU General Public License applies to this program. + +History: V0.1 W.Zimmermann: First release. Nov. 8, 1994 + V0.2 W.Zimmermann: Enhanced functionality. Nov. 9, 1994 + V0.3 W.Zimmermann: Additional functions. Nov. 28, 1994 + V0.4 W.Zimmermann: fixed some bugs. Dec. 17, 1994 + V0.5 W.Zimmermann: clean 'scanf' commands without compiler warnings + Jan. 6, 1995 + V0.6 W.Zimmermann: volume control (still experimental). Jan. 24, 1995 +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +void help(void) +{ printf("Available Commands: STOP s EJECT e QUIT q\n"); + printf(" PLAY TRACK t PAUSE p RESUME r\n"); + printf(" NEXT TRACK n REPEAT LAST l HELP h\n"); + printf(" SUB CHANNEL c TRACK INFO i PLAY AT a\n"); + printf(" READ d READ RAW w VOLUME v\n"); +} + +int main(void) +{ int handle; + unsigned char command=' ', ini=0, first=1, last=1; + unsigned int cmd, i,j,k, arg1,arg2,arg3; + struct cdrom_ti ti; + struct cdrom_tochdr tocHdr; + struct cdrom_subchnl subchnl; + struct cdrom_tocentry entry; + struct cdrom_msf msf; + union { struct cdrom_msf msf; + unsigned char buf[2336]; + } azt; + struct cdrom_volctrl volctrl; + + printf("\nMini-Audio CD-Player V0.6 (C) 1994,1995 W.Zimmermann\n"); + handle=open("/dev/cdrom",O_RDWR); + ioctl(handle,CDROMRESUME); + + if (handle<=0) + { printf("Drive Error: already playing, no audio disk, door open\n"); + printf(" or no permission (you must be ROOT in order to use this program)\n"); + } + else + { help(); + while (1) + { printf("Type command (h = help): "); + scanf("%s",&command); + switch (command) + { case 'e': cmd=CDROMEJECT; + ioctl(handle,cmd); + break; + case 'p': if (!ini) + { printf("Command not allowed - play track first\n"); + } + else + { cmd=CDROMPAUSE; + if (ioctl(handle,cmd)) printf("Drive Error\n"); + } + break; + case 'r': if (!ini) + { printf("Command not allowed - play track first\n"); + } + else + { cmd=CDROMRESUME; + if (ioctl(handle,cmd)) printf("Drive Error\n"); + } + break; + case 's': cmd=CDROMPAUSE; + if (ioctl(handle,cmd)) printf("Drive error or already stopped\n"); + ini=0; + cmd=CDROMSTOP; + if (ioctl(handle,cmd)) printf("Drive error\n"); + ini=0; + break; + case 't': cmd=CDROMREADTOCHDR; + if (ioctl(handle,cmd,&tocHdr)) printf("Drive Error\n"); + first=tocHdr.cdth_trk0; + last= tocHdr.cdth_trk1; + if ((first==0)||(first>last)) + { printf ("--could not read TOC\n"); + } + else + { printf("--first track: %d --last track: %d --enter track number: ",first,last); + cmd=CDROMPLAYTRKIND; + scanf("%i",&arg1); + ti.cdti_trk0=arg1; + if (ti.cdti_trk0last) ti.cdti_trk0=last; + ti.cdti_ind0=0; + ti.cdti_trk1=last; + ti.cdti_ind1=0; + if (ioctl(handle,cmd,&ti)) printf("Drive Error\n"); + ini=1; + } + break; + case 'n': if (!ini++) + { if (ioctl(handle,CDROMREADTOCHDR,&tocHdr)) printf("Drive Error\n"); + first=tocHdr.cdth_trk0; + last= tocHdr.cdth_trk1; + ti.cdti_trk0=first-1; + } + if ((first==0)||(first>last)) + { printf ("--could not read TOC\n"); + } + else + { cmd=CDROMPLAYTRKIND; + if (++ti.cdti_trk0 > last) ti.cdti_trk0=last; + ti.cdti_ind0=0; + ti.cdti_trk1=last; + ti.cdti_ind1=0; + if (ioctl(handle,cmd,&ti)) printf("Drive Error\n"); + ini=1; + } + break; + case 'l': if (!ini++) + { if (ioctl(handle,CDROMREADTOCHDR,&tocHdr)) printf("Drive Error\n"); + first=tocHdr.cdth_trk0; + last= tocHdr.cdth_trk1; + ti.cdti_trk0=first+1; + } + if ((first==0)||(first>last)) + { printf ("--could not read TOC\n"); + } + else + { cmd=CDROMPLAYTRKIND; + if (--ti.cdti_trk0 < first) ti.cdti_trk0=first; + ti.cdti_ind0=0; + ti.cdti_trk1=last; + ti.cdti_ind1=0; + if (ioctl(handle,cmd,&ti)) printf("Drive Error\n"); + ini=1; + } + break; + case 'c': subchnl.cdsc_format=CDROM_MSF; + if (ioctl(handle,CDROMSUBCHNL,&subchnl)) + printf("Drive Error\n"); + else + { printf("AudioStatus:%s Track:%d Mode:%d MSF=%d:%d:%d\n", \ + subchnl.cdsc_audiostatus==CDROM_AUDIO_PLAY ? "PLAYING":"NOT PLAYING",\ + subchnl.cdsc_trk,subchnl.cdsc_adr, \ + subchnl.cdsc_absaddr.msf.minute, subchnl.cdsc_absaddr.msf.second, \ + subchnl.cdsc_absaddr.msf.frame); + } + break; + case 'i': if (!ini) + { printf("Command not allowed - play track first\n"); + } + else + { cmd=CDROMREADTOCENTRY; + printf("Track No.: "); + scanf("%d",&arg1); + entry.cdte_track=arg1; + if (entry.cdte_tracklast) entry.cdte_track=last; + entry.cdte_format=CDROM_MSF; + if (ioctl(handle,cmd,&entry)) + { printf("Drive error or invalid track no.\n"); + } + else + { printf("Mode %d Track, starts at %d:%d:%d\n", \ + entry.cdte_adr,entry.cdte_addr.msf.minute, \ + entry.cdte_addr.msf.second,entry.cdte_addr.msf.frame); + } + } + break; + case 'a': cmd=CDROMPLAYMSF; + printf("Address (min:sec:frame) "); + scanf("%d:%d:%d",&arg1,&arg2,&arg3); + msf.cdmsf_min0 =arg1; + msf.cdmsf_sec0 =arg2; + msf.cdmsf_frame0=arg3; + if (msf.cdmsf_sec0 > 59) msf.cdmsf_sec0 =59; + if (msf.cdmsf_frame0> 74) msf.cdmsf_frame0=74; + msf.cdmsf_min1=60; + msf.cdmsf_sec1=00; + msf.cdmsf_frame1=00; + if (ioctl(handle,cmd,&msf)) + { printf("Drive error or invalid address\n"); + } + break; +#ifdef AZT_PRIVATE_IOCTLS /*not supported by every CDROM driver*/ + case 'd': cmd=CDROMREADMODE1; + printf("Address (min:sec:frame) "); + scanf("%d:%d:%d",&arg1,&arg2,&arg3); + azt.msf.cdmsf_min0 =arg1; + azt.msf.cdmsf_sec0 =arg2; + azt.msf.cdmsf_frame0=arg3; + if (azt.msf.cdmsf_sec0 > 59) azt.msf.cdmsf_sec0 =59; + if (azt.msf.cdmsf_frame0> 74) azt.msf.cdmsf_frame0=74; + if (ioctl(handle,cmd,&azt.msf)) + { printf("Drive error, invalid address or unsupported command\n"); + } + k=0; + getchar(); + for (i=0;i<128;i++) + { printf("%4d:",i*16); + for (j=0;j<16;j++) + { printf("%2x ",azt.buf[i*16+j]); + } + for (j=0;j<16;j++) + { if (isalnum(azt.buf[i*16+j])) + printf("%c",azt.buf[i*16+j]); + else + printf("."); + } + printf("\n"); + k++; + if (k>=20) + { printf("press ENTER to continue\n"); + getchar(); + k=0; + } + } + break; + case 'w': cmd=CDROMREADMODE2; + printf("Address (min:sec:frame) "); + scanf("%d:%d:%d",&arg1,&arg2,&arg3); + azt.msf.cdmsf_min0 =arg1; + azt.msf.cdmsf_sec0 =arg2; + azt.msf.cdmsf_frame0=arg3; + if (azt.msf.cdmsf_sec0 > 59) azt.msf.cdmsf_sec0 =59; + if (azt.msf.cdmsf_frame0> 74) azt.msf.cdmsf_frame0=74; + if (ioctl(handle,cmd,&azt)) + { printf("Drive error, invalid address or unsupported command\n"); + } + k=0; + for (i=0;i<146;i++) + { printf("%4d:",i*16); + for (j=0;j<16;j++) + { printf("%2x ",azt.buf[i*16+j]); + } + for (j=0;j<16;j++) + { if (isalnum(azt.buf[i*16+j])) + printf("%c",azt.buf[i*16+j]); + else + printf("."); + } + printf("\n"); + k++; + if (k>=20) + { getchar(); + k=0; + } + } + break; +#endif + case 'v': cmd=CDROMVOLCTRL; + printf("--Channel 0 Left (0-255): "); + scanf("%d",&arg1); + printf("--Channel 1 Right (0-255): "); + scanf("%d",&arg2); + volctrl.channel0=arg1; + volctrl.channel1=arg2; + volctrl.channel2=0; + volctrl.channel3=0; + if (ioctl(handle,cmd,&volctrl)) + { printf("Drive error or unsupported command\n"); + } + break; + case 'q': if (close(handle)) printf("Drive Error: CLOSE\n"); + exit(0); + case 'h': help(); + break; + default: printf("unknown command\n"); + break; + } + } + } + return 0; +} + diff --git a/drivers/block/README.fd b/drivers/block/README.fd new file mode 100644 index 000000000..2bd07338c --- /dev/null +++ b/drivers/block/README.fd @@ -0,0 +1,168 @@ +This Readme file describes the floppy driver. + +FAQ list: +========= + + A FAQ list may be found in the fdutils package (see below), and also +at ftp.imag.fr:pub/Linux/ZLIBC/floppy/FAQ + + +Lilo config options (Thinkpad users, read this) +=============================================== + + The floppy driver is configured using the 'floppy=' option in +lilo. This option can be typed at the boot prompt, or entered in the +lilo configuration file. + Example: If your kernel is called linux-72, type the following line +at the lilo boot prompt (if you have a thinkpad): + linux-72 floppy=thinkpad +You may also enter the following line in /etc/lilo.conf, in the description +of linux-72: + append = "floppy=thinkpad" + + Several floppy related options may be given, example: + linux-72 floppy=daring floppy=two_fdc + append = "floppy=daring floppy=two_fdc" + + If you give options both in the lilo config file and on the boot +prompt, the option strings of both places are concatenated, the boot +prompt options coming last. That's why there are also options to +restore the default behaviour. + + The floppy related options include: + + floppy=,allowed_drive_mask + Sets the bitmask of allowed drives to . By default, only units + 0 and 1 of each floppy controller are allowed. This is done because + certain non-standard hardware (ASUS PCI motherboards) mess up the + keyboard when accessing units 2 or 3. This option is somewhat + obsoleted by the cmos option. + + floppy=all_drives + Sets the bitmask of allowed drives to all drives. Use this if you have + more than two drives connected to a floppy controller. + + floppy=asus_pci + Sets the bitmask to allow only units 0 and 1. (The default) + + floppy=daring + Tells the floppy driver that you have a well behaved floppy controller. + This allows more efficient and smoother operation, but may fail on + certain controllers. This may speed up certain operations. + + floppy=0,daring + Tells the floppy driver that your floppy controller should be used + with caution. + + floppy=one_fdc + Tells the floppy driver that you have only floppy controller (default) + + floppy=two_fdc + floppy=
,two_fdc + Tells the floppy driver that you have two floppy controllers. The + second floppy controller is assumed to be at
. If
+ is not given, 0x370 is assumed. + + floppy=thinkpad + Tells the floppy driver that you have a Thinkpad. Thinkpads use an + inverted convention for the disk change line. + + floppy=0,thinkpad + Tells the floppy driver that you don't have a Thinkpad. + + floppy=,,cmos + Sets the cmos type of to . Additionally, this drive is + allowed in the bitmask. This is useful if you have more than two + floppy drives (only two can be described in the physical cmos), or if + your BIOS uses non-standard CMOS types. The CMOS types are: + 0 - unknown or not installed + 1 - 5 1/4 DD + 2 - 5 1/4 HD + 3 - 3 1/2 DD + 4 - 3 1/2 HD + 5 - 3 1/2 ED + 6 - 3 1/2 ED + (Note: there are two valid types for ED drives. This is because 5 was + initially chosen to represent floppy *tapes*, and 6 for ED drives. + AMI ignored this, and used 5 for ED drives. That's why the floppy + driver handles both) + Setting the CMOS to 0 for the first two drives (default) makes the + floppy driver read the physical cmos for those drives. + + floppy=unexpected_interrupts + Print a warning message when an unexpected interrupt is received + (default behaviour) + + floppy=no_unexpected_interrupts + floppy=L40SX + Don't print a message when an unexpected interrupt is received. This + is needed on IBM L40SX laptops in certain video modes. (There seems + to be an interaction between video and floppy. The unexpected interrupts + only affect performance, and can safely be ignored.) + + +Supporting utilities and additional documentation: +================================================== + + Additional parameters of the floppy driver can be configured at run +time. Utilities which do this can be found in the fdutils +package. This package also contains a new version of mtools which +allows to access high capacity disks (up to 1992K on a high density 3 +1/2 disk!). It also contains additional documentation about the floppy +driver. It can be found at: + ftp.imag.fr:pub/Linux/ZLIBC/fdutils/fdutils-4.0.src.tar.gz + sunsite.unc.edu:/pub/Linux/system/Misc/fdutils-4.0.src.tar.gz + tsx-11.mit.edu:/pub/linux/sources/sbin/fdutils-4.0.src.tar.gz + + Alpha patches to these utilities are at: + ftp.imag.fr:pub/Linux/ZLIBC/fdutils/ALPHA + All patches contained in this directory are directly against the base +version, i.e. DON'T APPLY THEM ON TOP OF EACH OTHER. Only apply the +most recent one. + + +Alpha patches for the floppy driver: +==================================== + + You may find ALPHA patches of the driver itself in +ftp.imag.fr:pub/Linux/ZLIBC/floppy/ALPHA. These patches are named +fdp-.diff.gz + WARNING: These _are_ ALPHA, and may introduce new problems! Some +problems may only show up on certain hardware, or when trying weirdo +things. So don't be misled by people claiming they are stable and +should really be BETA. What works for one person, may not work for +somebody else at all. This directory contains a RELEASES file +describing the features of some of these patches. + + If after some testing these patches prove to be sufficiently stable, +they'll move into ftp.imag.fr:pub/Linux/ZLIBC/floppy/BETA. + + You may find quick&dirty fixes to the driver in +ftp.imag.fr:pub/Linux/ZLIBC/QDF. These patches are named +fdp-.diff + These patches fix only the most obvious problems, or provide trivial +enhancements. The main objective is to keep these patches small and +local, in order to keep the probability of introducing new problems as +small as possible. However, they may not attack the root of the +problem but only cure the symptoms. This directory contains a RELEASES +file describing the features of these patches. + + The ALPHA, BETA and QDF directories are removed, and replaced by a +README file when they get empty due to integration of the patches into +the stock kernel. You may still find patches to old kernels in +ftp.imag.fr:pub/Linux/ZLIBC/obsolete + + +Reporting problems about the floppy driver +========================================== + + If you have a question or a bug report about the floppy driver, mail +me at Alain.Knaff@imag.fr. If you post to the news, use preferably one +of the groups comp.os.linux.help (for questions) or +comp.os.linux.hardware (for bug reports). As the volume in these +groups is rather high, be sure to include the word "floppy" (or +"FLOPPY") in the subject line. + + Be sure to read the FAQ before mailing/posting any bug reports! + + Alain diff --git a/drivers/block/README.ide b/drivers/block/README.ide new file mode 100644 index 000000000..39ae4fa79 --- /dev/null +++ b/drivers/block/README.ide @@ -0,0 +1,278 @@ +README.ide -- Information regarding ide.c and ide-cd.c (IDE driver in 1.2.x) +================================================================================ +Supported by: mlord@bnr.ca -- disks, interfaces, probing + snyder@fnald0.fnal.gov -- cdroms, ATAPI, audio + +(see description later on below for handling BIG IDE drives with >1024 cyls). + +Major features of ide.c & ide-cd.c: + + - support for up to two IDE interfaces on one or two IRQs + - support for any mix of up to four disk and/or cdrom drives + - support for reading IDE ATAPI cdrom drives (NEC,MITSUMI,VERTOS,SONY) + - support for audio functions + - auto-detection of interfaces, drives, IRQs, and disk geometries + -- "single" drives should be jumpered as "master", not "slave" + - support for BIOSs which report "more than 16 heads" on disk drives + - uses LBA (slightly faster) on disk drives which support it + - support for lots of fancy (E)IDE drive functions with hdparm utility + - optional (compile time) support for 32-bit VLB data transfers + - support for IDE multiple (block) mode (same as hd.c) + - support for interrupt unmasking during I/O (better than hd.c) + - improved handshaking and error detection/recovery + - can co-exist with hd.c to control only the secondary interface +NEW! - support for reliable operation of buggy CMD-640 interfaces + - use kernel command line option: hda=serialize +NEW! - experimental support for DTC-2278D interfaces + - use kernel command line option: hda=dtc2278 +NEW! - run-time selectable 32bit interface support (using hdparm-2.3) + +Under construction: + + - improved CMD support: tech info is supposedly "in the mail" + - support for interface speed selection on jumperless interfaces + - improved detection of non-standard IDE ATAPI cdrom drives + - support for non-standard 3rd/4th drive interface on Promise cards + +*** + +IMPORTANT NOTICE: "CMD" EIDE Interfaces will not (by default) work *reliably* +when drives are attached to the second interface. To "fix" this, supply the +special kernel "command line" parameter to LILO: hda=serialize +Failure to do so can cause severe data corruption! + +*** + +To access devices on the second interface, device entries must first be +created in /dev for them. To create such entries, simply run the included +shell script: MAKEDEV.ide1 + +Apparently the early releases of Slackware 2.2 have incorrect entries +in /dev for hdc* and hdd* -- this can also be corrected by running MAKEDEV.ide1 + +ide.c automatically probes for the primary and secondary interfaces, +for the drives/geometries attached to those interfaces, and for the +IRQ numbers being used by the interfaces (normally IRQ14 & IRQ15). + +The primary and secondary interfaces may share a single IRQ if necessary, +at a slight performance penalty, whether on separate cards or a single VLB card. + +Drives are normally found by auto-probing and/or examining the CMOS/BIOS data. +For really weird situations, the apparent (fdisk) geometry can also be specified +on the kernel "command line" using LILO. The format of such lines is: + + hdx=cyls,heads,sects,wpcom,irq +or hdx=cdrom + +where hdx can be any of {hda,hdb,hdc,hdd}, or simply hd, for the "next" drive +in sequence. Only the first three parameters are required (cyls,heads,sects), +and wpcom is ignored for IDE drives. For example: + + hdc=1050,32,64 hdd=cdrom + +If an irq number is given, it will apply to both drives on the same interface, +either {hda,hdb} or {hdc,hdd}. The results of successful auto-probing may +override the physical geometry/irq specified, though the "original" geometry +is retained as the "logical" geometry for partitioning purposes (fdisk). + +If the auto-probing during boot time confuses a drive (ie. the drive works +with hd.c but not with ide.c), then an command line option may be specified +for each drive for which you'd like the drive to skip the hardware +probe/identification sequence. For example: + + hdb=noprobe +or + hdc=768,16,32 + hdc=noprobe + +Note that when only one IDE device is attached to an interface, +it must be jumpered as "single" or "master", *not* "slave". +Many folks have had "trouble" with cdroms because of this requirement +of the ATA (IDE) standard. + +Courtesy of Scott Snyder, the driver now supports ATAPI cdrom drives +such as the NEC-260 and the new MITSUMI triple/quad speed drives. +Such drives will be identified at boot time, as hda,hdb,hdc or hdd, +just like a harddisk. + +If for some reason your cdrom drive is *not* found at boot time, you can force +the probe to look harder by supplying a kernel command line parameter +via LILO, such as: hdc=cdrom + +For example, a GW2000 system might have a harddrive on the primary +interface (/dev/hda) and an IDE cdrom drive on the secondary interface +(/dev/hdc). To mount a CD in the cdrom drive, one would use something like: + + ln -sf /dev/hdc /dev/cdrom + mkdir /cd + mount /dev/cdrom /cd -t iso9660 -o ro + +Please pass on any feedback on the cdrom stuff to the author & maintainer, +Scott Snyder (snyder@fnald0.fnal.gov). + +The kernel is now be able to execute binaries directly off of the cdrom, +provided it is mounted with the default block size of 1024. + +The hdparm.c program for controlling various IDE features is now packaged +separately. Look for it on popular linux FTP sites. + +mlord@bnr.ca +snyder@fnald0.fnal.gov +================================================================================ + +Some Terminology +---------------- +IDE = Integrated Drive Electronics, meaning that each drive has a built-in +controller, which is why an "IDE interface card" is not a "controller card". + +IDE drives are designed to attach almost directly to the ISA bus of an AT-style +computer. The typical IDE interface card merely provides I/O port address +decoding and tri-state buffers, although several newer localbus cards go much +beyond the basics. When purchasing a localbus IDE interface, avoid cards with +an onboard BIOS and those which require special drivers. Instead, look for a +card which uses hardware switches/jumpers to select the interface timing speed, +to allow much faster data transfers than the original 8Mhz ISA bus allows. + +ATA = AT (the old IBM 286 computer) Attachment Interface, a draft American +National Standard for connecting hard drives to PCs. This is the official +name for "IDE". + +The latest standards define some enhancements, known as the ATA-2 spec, +which grew out of vendor-specific "Enhanced IDE" (EIDE) implementations. + +ATAPI = ATA Packet Interface, a new protocol for controlling the drives, +similar to SCSI protocols, created at the same time as the ATA2 standard. +ATAPI is currently used for controlling CDROM and TAPE devices, and will +likely also soon be used for Floppy drives, removable R/W cartridges, +and for high capacity hard disk drives. + +How To Use *Big* ATA/IDE drives with Linux +------------------------------------------ +The ATA Interface spec for IDE disk drives allows a total of 28 bits +(8 bits for sector, 16 bits for cylinder, and 4 bits for head) for addressing +individual disk sectors of 512 bytes each (in "Linear Block Address" (LBA) +mode, there is still only a total of 28 bits available in the hardware). +This "limits" the capacity of an IDE drive to no more than 128GB (Giga-bytes). +All current day IDE drives are somewhat smaller than this upper limit, and +within a few years, ATAPI disk drives will raise the limit considerably. + +All IDE disk drives "suffer" from a "16-heads" limitation: the hardware has +only a four bit field for head selection, restricting the number of "physical" +heads to 16 or less. Since the BIOS usually has a 63 sectors/track limit, +this means that all IDE drivers larger than 504MB (528Meg) must use a "physical" +geometry with more than 1024 cylinders. + + (1024cyls * 16heads * 63sects * 512bytes/sector) / (1024 * 1024) == 504MB + +(Some BIOSs (and controllers with onboard BIOS) pretend to allow "32" or "64" + heads per drive (discussed below), but can only do so by playing games with + the real (hidden) geometry, which is always limited to 16 or fewer heads). + +This presents two problems to most systems: + + 1. The INT13 interface to the BIOS only allows 10-bits for cylinder + addresses, giving a limit of 1024cyls for programs which use it. + + 2. The physical geometry fields of the disk partition table only + allow 10-bits for cylinder addresses, giving a similar limit of 1024 + cyls for operating systems that do not use the "sector count" fields + instead of the physical Cyl/Head/Sect (CHS) geometry fields. + +Neither of these limitations affects Linux itself, as it (1) does not use the +BIOS for disk access, and it (2) is clever enough to use the "sector count" +fields of the partition table instead of the physical CHS geometry fields. + + a) Most folks use LILO to load linux. LILO uses the INT13 interface + to the BIOS to load the kernel at boot time. Therefore, LILO can only + load linux if the files it needs (usually just the kernel images) are + located below the magic 1024 cylinder "boundary" (more on this later). + + b) Many folks also like to have bootable DOS partitions on their + drive(s). DOS also uses the INT13 interface to the BIOS, not only + for booting, but also for operation after booting. Therefore, DOS + can normally only access partitions which are contained entirely below + the magic 1024 cylinder "boundary". + +There are at least seven commonly used schemes for kludging DOS to work +around this "limitation". In the long term, the problem is being solved +by introduction of an alternative BIOS interface that does not have the +same limitations as the INT13 interface. New versions of DOS are expected +to detect and use this interface in systems whose BIOS provides it. + +But in the present day, alternative solutions are necessary. + +The most popular solution in newer systems is to have the BIOS shift bits +between the cylinder and head number fields. This is activated by entering +a translated logical geometry into the BIOS/CMOS setup for the drive. +Thus, if the drive has a geometry of 2100/16/63 (CHS), then the BIOS could +present a "logical" geometry of 525/64/63 by "shifting" two bits from the +cylinder number into the head number field for purposes of the partition table, +CMOS setup, and INT13 interfaces. Linux kernels 1.1.39 and higher detect and +"handle" this translation automatically, making this a rather painless solution +for the 1024 cyls problem. If for some reason Linux gets confused (unlikely), +then use the kernel command line parameters to pass the *logical* geometry, +as in: hda=525,64,63 + +If the BIOS does not support this form of drive translation, then several +options remain, listed below in inverse order of popularity: + + - boot from a floppy disk instead of the hard drive (takes 10 seconds). + - use a partition below the 1024 cyl boundary to hold the linux + boot files (kernel images and /boot directory), and place the rest + of linux anywhere else on the drive. These files can reside in a DOS + partition, or in a tailor-made linux boot partition. + +If you cannot use drive translation, *and* your BIOS also restricts you to +entering no more than 1024 cylinders in the geometry field in the CMOS setup, +then just set it to 1024. As of v3.5 of this driver, Linux automatically +determines the *real* number of cylinders for fdisk to use, allowing easy +access to the full disk capacity without having to fiddle around. + +Regardless of what you do, all DOS partitions *must* be contained entirely +within the first 1024 logical cylinders. For a 1Gig WD disk drive, here's +a good "half and half" partitioning scheme to start with: + + geometry = 2100/16/63 + /dev/hda1 from cyl 1 to 992 dos + /dev/hda2 from cyl 993 to 1023 swap + /dev/hda3 from cyl 1024 to 2100 linux + +To ensure that LILO can boot linux, the boot files (kernel and /boot/*) +must reside within the first 1024 cylinders of the drive. If your linux +root partition is *not* completely within the first 1024 cyls (quite common), +then you can use LILO to boot linux from files on your DOS partition +by doing the following after installing slackware (or whatever): + + 0. Boot from the "boot floppy" created during the installation + 1. Mount your DOS partition as /dos (and stick it in /etc/fstab) + 2. Move your kernel (/vmlinuz) to /dos/vmlinuz with: mv /vmlinuz /dos + 3. Edit /etc/lilo.conf to change /vmlinuz to /dos/vmlinuz + 4. Move /boot to /dos/boot with: cp -a /boot /dos ; rm -r /boot + 5. Create a symlink for LILO to use with: ln -s /dos/boot /boot + 6. Re-run LILO with: lilo + + A danger with this approach is that whenever an MS-DOS "defragmentation" + program is run (like Norton "speeddisk"), it may move the Linux boot + files around, confusing LILO and making the (Linux) system unbootable. + Be sure to keep a kernel "boot floppy" at hand for such circumstances. + +If you "don't do DOS", then partition as you please, but remember to create +a small partition to hold the /boot directory (and vmlinuz) as described above +such that they stay within the first 1024 cylinders. + +Note that when creating partitions that span beyond cylinder 1024, +Linux fdisk will complain about "Partition X has different physical/logical +endings" and emit messages such as "This is larger than 1024, and may cause +problems with some software". Ignore them for linux partitions. The "some +software" refers to DOS, the BIOS, and LILO, as described previously. + +Western Digital now ships a "DiskManager 6.03" diskette with all of their big +hard drives. Burn it! That idiotic piece of garbage isn't even universally +compatible with DOS, let alone other operating systems like Linux. Eventually +some kind person will kludge Linux to work with it, but at present the two +are completely incompatible. If you have this version of DiskManager on your +hard disk already, it can be exterminated at the expense of all data on the +drive (back it up elsewhere), by using the "DM" command from the diskette +as follows: DM /Y- + +mlord@bnr.ca diff --git a/drivers/block/README.sbpcd b/drivers/block/README.sbpcd index 74f188c26..6a40abfb0 100644 --- a/drivers/block/README.sbpcd +++ b/drivers/block/README.sbpcd @@ -1,44 +1,60 @@ -This README belongs to release 2.8 or newer of the SoundBlaster Pro -(Matsushita, Kotobuki, Panasonic, CreativeLabs) CD-ROM driver for Linux. +This README belongs to release 3.7 or newer of the SoundBlaster Pro +(Matsushita, Kotobuki, Panasonic, CreativeLabs, Longshine and TEAC) +CD-ROM driver for Linux. The driver is able to drive the whole family of "traditional" IDE-style (that -has nothing to do with the new "Enhanced IDE" drive standard) Matsushita, +is NOT the new "Enhanced IDE" or "ATAPI" drive standard) Matsushita, Kotobuki, Panasonic drives, sometimes labelled as "CreativeLabs". The well-known drives are CR-521, CR-522, CR-523, CR-562, CR-563. The Longshine LCS-7260 is a double-speed drive which uses the "old" -Matsushita command set. It should be fully supported soon, too. +Matsushita command set. It is supported now - with help by Serge Robyns. -There exists an "IBM External ISA CD-ROM Drive" which in fact is a CR-562 +There exists an "IBM External ISA CD-ROM Drive" which in fact is a CR-563 with a special controller board. This drive is supported (the interface is of the "LaserMate" type), and it is possibly the best buy today (cheaper than an internal drive, and you can use it as an internal, too - f.e. plug it into -a soundcard). If you own such a drive, please mail me the DOS driver (gzipped -and uuencoded) and the specifications (exact name, address range etc.) I still -have to confirm my opinion. ;-) +a soundcard). -This driver is NOT for Mitsumi or Sony or Aztech or Philips or XXX drives. -The matter that some other brand's drives work with newer sound card -interfaces does NOT make the drives compatible. :-) +CreativeLabs has a new drive "CD-200". Support is under construction. +Drive detection and playing audio should already work. I need qualified +feedback about the bugs within the data functions or a drive (I never saw +a CD200). -It will work with the soundcard interfaces (SB Pro, SB 16, Galaxy, SoundFX, -...) and/or with the "no-sound" cards (Panasonic CI-101P, LaserMate, +The quad-speed TEAC CD-55A drive is supported. The routines may still be +a little bit buggy, but the data rate already reaches 500 kB/sec if you +set SBP_BUFFER_FRAMES to 64. The drive is able to deliver 600 kB/sec, so +this has to get a point of work. + +This driver is NOT for Mitsumi or Sony or Aztech or Philips or XXX drives, +and this driver is in no way usable for any new IDE ATAPI drive. + +This driver will work with the soundcard interfaces (SB Pro, SB 16, Galaxy, +SoundFX, ...) and/or with the "no-sound" cards (Panasonic CI-101P, LaserMate, WDH-7001C, Longshine LCS-6853, older Aztech cards, ...). + It should work too now with the "configurable" interface "Sequoia S-1000", which is found on the Spea Media FX sound card. I still need feedback about -this. +this, or such a card. Anyways, the procedure "boot DOS and wait until +CONFIG.SYS is done, then use CTL-ALT-DEL to boot Linux" should make it +work. + The interface type has to get configured in /usr/include/linux/sbpcd.h, because the behavior of some sound card interfaces is different. -The driver respects different drive firmware releases - my old drive is a 2.11, -but it should work with "old" drives <2.01 ... >3.00 and with "new" drives -(which count the releases around 0.75 or 1.00). +With some TEAC drives I have seen interface cards which seem to lack the +"drive select" lines; always drive 0 gets addressed. To avoid "mirror drives" +with such interface cards, set MAX_DRIVES to 1 and jumper your drive to ID 0. + +The driver respects all known drive firmware releases - my old drive is a 2.11, +but it should work with CR-52x drives <2.01 ... >3.00 and with CR-56x drives +<0.75 .. 5.00. Up to 4 drives per interface card, and up to 4 interface cards are supported. -CR-52x ("old"), CR-56x ("new") and LCS drives can be mixed, but the CR-521 -ones are hard-wired to drive ID 0. The drives have to use different drive IDs, -and each drive has to get a unique minor number (0...3), corresponding to it's -drive ID. +All supported drive families can be mixed, but the CR-521 drives are +hard-wired to drive ID 0. The drives have to use different drive IDs, and each +drive has to get a unique minor number (0...3), corresponding indirectly to +its drive ID. The drive IDs may be selected freely from 0 to 3 - they do not have to be in consecutive order. @@ -59,40 +75,38 @@ to change old drives to any ID, too. He writes in this sense: did not work with other values. If the values are not good, ID 3 behaves like ID 0." -To use more than 4 drives (now that the single-speed CR-521's are as cheap as -50$), you need a second interface card and you have to "duplicate" the driver. -Just copy sbpcd.c into sbpcd2.c and so forth and change SBPCD_ISSUE (at top -of sbpcd2.c) accordingly. +To use more than 4 drives, you simply need a second controller card at a +different address and a second cable. The driver supports reading of data from the CD and playing of audio tracks. The audio part should run with WorkMan, xcdplayer, with the "non-X11" products CDplayer and WorkBone - tell me if it is not compatible with other software. -With the "new" drive family CR-562 and CR-563, the reading of audio frames is -possible. This is currently implemented by an IOCTL function which reads only -up to 4 frames of 2352 bytes at once. Reading more than 1 frame at once misses -some chunks at each frame boundary. Reading the same frame a second time gives -different data; the frame data start at a different position. But all read -bytes are valid, and we always read 98 consecutive chunks (of 24 Bytes) as a -frame. This lack has to get corrected by higher level software which reads the -same frame again and tries to find and eliminate overlapping chunks -(24-byte-pieces). - -The transfer rate with reading audio (1-frame-pieces) is as slow as 32 kB/sec. -This could be better reading bigger chunks, but the "missing" chunks occur at -the beginning of each single frame. +With the CR-562 and CR-563 drives, the reading of audio frames is possible. +This is implemented by an IOCTL function which reads READ_AUDIO frames of +2352 bytes at once (configurable with the "READ_AUDIO" define, default is 0). +Reading the same frame a second time gives different data; the frame data +start at a different position, but all read bytes are valid, and we always +read 98 consecutive chunks (of 24 Bytes) as a frame. Reading more than 1 frame +at once possibly misses some chunks at each frame boundary. This lack has to +get corrected by external, "higher level" software which reads the same frame +again and tries to find and eliminate overlapping chunks (24-byte-pieces). + +The transfer rate with reading audio (1-frame-pieces) currently is very slow. +This can be better reading bigger chunks, but the "missing" chunks possibly +occur at the beginning of each single frame. The software interface possibly may change a bit the day the SCSI driver supports it too. -With the CR-562 and CR-563 drives, MultiSession is supported, "ManySession" -(not recommended, see below) alternatively. -Photo CDs work, too (the "old" drives like CR-521 can access only the first +With all but the CR-52x drives, MultiSession is supported. +Photo CDs work (the "old" drives like CR-521 can access only the first session of a photoCD). -At ftp.gwdg.de:/pub/linux/hpcdtoppm/ is Hadmut Danisch's package to convert -photo CD image files. +At ftp.gwdg.de:/pub/linux/hpcdtoppm/ you will find Hadmut Danisch's package to +convert photo CD image files and Gerd Knorr's viewing utility. -The transfer rate will reach 150 kB/sec with "old" drives and 300 kB/sec with -double-speed drives. XA (PhotoCD) disks with "old" drives give only 50 kB/sec. +The transfer rate will reach 150 kB/sec with "old" drives, 300 kB/sec with +double-speed drives, and about 500 kB/sec with quad speed drives. +XA (PhotoCD) disks with "old" drives give only 50 kB/sec. This release is part of the standard kernel and consists of - this README file @@ -113,11 +127,13 @@ To install: Most "compatible" sound cards (for example "Highscreen", "SoundFX" and "Galaxy") need "SBPRO 0". The "no-sound" board from OmniCd needs the "SBPRO 1" setup. + All other "no-sound" boards need the "SBPRO 0" setup. The Spea Media FX sound card needs "SBPRO 2". - sbpcd.c holds some examples in it's auto-probe list. + sbpcd.c holds some examples in its auto-probe list. If you configure "SBPRO" wrong, the playing of audio CDs will work, but you will not be able to mount a data CD. a2. Tell the address of your CDROM_PORT (not of the sound port). + a3. Set DISTRIBUTION to 0. b. Additionally for 2.a1 and 2.a2, the setup may be done during boot time (via the "kernel command line" or "LILO option"): sbpcd=0x230,SoundBlaster @@ -125,31 +141,33 @@ To install: sbpcd=0x320,LaserMate or sbpcd=0x330,SPEA - (these strings are case sensitive!). This is especially useful if you install a fresh distribution. 2. "cd /usr/src/linux" and do a "make config" and select "y" for Matsushita CD-ROM support and for ISO9660 FileSystem support. If you do not have a - second, third, or fourth controller and/or did not prepare sbpcd2 etc., - do not say "y" to the secondary Matsushita CD-ROM questions. + second, third, or fourth controller installed, do not say "y" to the + secondary Matsushita CD-ROM questions. SCSI and/or SCSI CD-ROM support is not needed. 3. Then do a "make dep", then make the kernel image ("make zlilo" or else). 4. Make the device file(s). The driver uses definitely and exclusive the MAJOR 25, so do - mknod /dev/sbpcd b 25 0 (if you have only drive #0) + mknod /dev/sbpcd b 25 0 (if you have only one drive) and/or mknod /dev/sbpcd0 b 25 0 mknod /dev/sbpcd1 b 25 1 mknod /dev/sbpcd2 b 25 2 mknod /dev/sbpcd3 b 25 3 to make the node(s). - Take care that you create a node with the same MINOR as your drive ID is. - So, if the DOS driver tells you have drive id #3, you have to - mknod /dev/ b 25 3 + + The driver no longer uses the "AT bus style" device numbering; the SCSI + scheme is used now; that means, the "first found" drive gets MINOR 0 + (regardless to its jumpered ID), the "next found" (at the same cable) + gets MINOR 1, ... For a second interface board, you have to make nodes like mknod /dev/sbpcd4 b 26 0 + mknod /dev/sbpcd5 b 26 1 and so on. Use the MAJORs 26, 27, 28. If you further make a link like @@ -168,29 +186,34 @@ and see the contents of your CD in the /CD directory, and/or hear music with "workman -c /dev/sbpcd &". +Using sbpcd as a "loadable module": +----------------------------------- + +If you do NOT select "Matsushita/Panasonic CDROM driver support" during the +"make config" of your kernel, you can build the "loadable module" sbpcd.o. +Read /usr/src/linux/README.modules on this. + +If sbpcd gets used as a module, the support of more than one interface +card (i.e. drives 4...15) is disabled. + +You can specify interface address and type with the "insmod" command like: + # insmod /usr/src/linux/modules/sbpcd.o sbpcd=0x340,0 +or + # insmod /usr/src/linux/modules/sbpcd.o sbpcd=0x230,1 +where the last number represents the SBPRO setting (no strings allowed here). + + Things of interest: ------------------- -The driver is configured to try the SoundBlaster Pro type of interface at -I/O port 0x0230 first. If this is not appropriate, sbpcd.h should get changed +The driver is configured to try the LaserMate type of interface at I/O port +0x0340 first. If this is not appropriate, sbpcd.h should get changed (you will find the right place - just at the beginning). -No DMA and no IRQ is used, so the IRQ line stays free for the SB Pro sound -drivers. - -To reduce or increase the amount of kernel messages, edit sbpcd.c and change -the initialization of the variable "sbpcd_debug". This is the way to get rid -of the initial warning message block, too. +No DMA and no IRQ is used. -With "#define MANY_SESSION 1" (sbpcd.c), the driver can use "many-session" CDs. -This will work only with "new" drives like CR-562 or CR-563. That is NOT -multisession - it is a CD with multiple independent sessions, each containing -block addresses as if it were the only session. With this feature enabled, the -driver will read the LAST session. Without it, the FIRST session gets read. -If you would like the support of reading "in-between" sessions, drop me a mail -and some food for the soul. :-) -Those "many-session" CDs can get made by CDROM writers like Philips CDD 521. -If you enable this feature, it is impossible to read true multisession CDs. +To reduce or increase the amount of kernel messages, edit sbpcd.c and play +with the "DBG_xxx" switches (initialization of the variable "sbpcd_debug"). The driver uses the "variable BLOCK_SIZE" feature. To use it, you have to specify "block=2048" as a mount option. Doing this will disable the direct @@ -204,65 +227,28 @@ do not use block=2048 with those. At the beginning of sbpcd.c, you will find some "#define"s (f.e. EJECT and JUKEBOX). With that, you can configure the driver for some special things. -The following program disables the auto-eject feature during runtime: - -/*=================== begin program ========================================*/ -/* - * set the "eject" switch (enable/disable auto-ejecting) - * - * (c) 1994 Eberhard Moenkeberg - * may be used & enhanced freely - * - * Disables or enables the auto-eject feature at run time. - * Works only if a CD is in the drive (just like the feature itself ;-) - * Useful for a "quiet" shutdown or for weird audio player programs. - */ -#define EJECT 0 /* 0: disable, 1: enable auto-ejecting */ - -#include -#include -#include - -static char arg=EJECT; -static int drive; -static int err; - -main(int argc, char *argv[]) -{ -/* - * open /dev/cdrom - */ - drive=open("/dev/cdrom", 0); - if (drive<0) - { - fprintf(stderr, "can't open drive /dev/cdrom.\n"); - exit (-1); - } -/* - * set EJECT_SW - */ - err=ioctl(drive, CDROMEJECT_SW, arg); - if (err!=0) - { - fprintf(stderr, "can't set EJECT_SW (error %d).\n", err); - exit (-1); - } - else - fprintf(stdout, "EJECT_SW set to %d\n", arg); -} -/*===================== end program ========================================*/ +You can use the appended program "cdtester" to set the auto-eject feature +during runtime. Jeff Tranter's "eject" utility can do this, too (and more) +for you. +There is a new ioctl CDROMMULTISESSION to obtain with a user program if +the CD is an XA disk and - if it is - where the last session starts. The +"cdtester" program illustrates how to call it. Auto-probing at boot time: -------------------------- -The driver does auto-probing at many well-known interface card addresses. The -idea to do that came from Adam J. Richter (YGGDRASIL). Some well-known -addresses are excluded from auto-probing because they can cause a hang if an -ethernet card gets touched. +The driver does auto-probing at many well-known interface card addresses, +but not all: +Some probings can cause a hang if an NE2000 ethernet card gets touched, because +SBPCD's auto-probing happens before the initialization of the net drivers. +Those "hazardous" addresses are excluded from auto-probing; the "kernel +command line" feature has to be used during installation if you have your +drive at those addresses. The "module" version is allowed to probe at those +addresses, too. -This auto-probing looks first at the configured address resp. the address +The auto-probing looks first at the configured address resp. the address submitted by the kernel command line. With this, it is possible to use this driver within installation boot floppies, and for any non-standard address, too. @@ -273,20 +259,30 @@ o.k., but you will get I/O errors during mount). In that case, use the "kernel command line" feature and specify address & type at boot time to find out the right setup. -SBPCD's auto-probing happens before the initialization of the net drivers. That -makes a hang possible if an ethernet card gets touched. - For every-day use, address and type should get configured within sbpcd.h. That will stop the auto-probing due to success with the first try. +The kernel command "sbpcd=0" suppresses each auto-probing and causes +the driver not to find any drive; it is meant for people who love sbpcd +so much that they do not want to miss it, even if they miss the drives. ;-) + +If you configure "#define CDROM_PORT 0" in sbpcd.h, the auto-probing is +initially disabled and needs an explicit kernel command to get activated. +Once activated, it does not stop before success or end-of-list. This may be +useful within "universal" CDROM installation boot floppies (but using the +loadable module would be better because it allows an "extended" auto-probing +without fearing NE2000 cards). + +To shorten the auto-probing list to a single entry, set DISTRIBUTION 0 within +sbpcd.h. + Setting up address and interface type: -------------------------------------- -If your I/O port address is not 0x0230 or if you use a "no-sound" interface -other than OmniCD, you have to look for the #defines near the beginning of -sbpcd.h and configure them: set SBPRO to 0 or 1 or 2, and change CDROM_PORT to -the address of your CDROM I/O port. +If your I/O port address is not 0x340, you have to look for the #defines near +the beginning of sbpcd.h and configure them: set SBPRO to 0 or 1 or 2, and +change CDROM_PORT to the address of your CDROM I/O port. Most of the "SoundBlaster compatible" cards behave like the no-sound interfaces! @@ -300,19 +296,19 @@ you can set SOUND_BASE (in sbpcd.h) to get it done with your card, too... Using audio CDs: ---------------- -Workman, WorkBone, xcdplayer and cdplayer should work good now, even with the -double-speed drives. +Workman, WorkBone, xcdplayer, cdplayer and the nice little tool "cdplay" (see +README.aztcd from the Aztech driver package) should work. The program CDplayer likes to talk to "/dev/mcd" only, xcdplayer wants -"/dev/rsr0", workman loves "/dev/sr0" - so, do the appropriate links for using -them without the need of supplying parameters. +"/dev/rsr0", workman loves "/dev/sr0" or "/dev/cdrom" - so, do the appropriate +links for using them without the need of supplying parameters. Copying audio tracks: --------------------- -The following little program will copy track 2 of an audio CD into the file -"track02": +The following program will copy track 1 (or a piece of it) from an audio CD +into the file "track01": /*=================== begin program ========================================*/ /* @@ -335,13 +331,14 @@ The following little program will copy track 2 of an audio CD into the file * This is only an example of the low-level access routine. The read data are * pure 16-bit CDDA values; they have to get converted to make sound out of * them. + * It is no fun to listen to it without prior overlap/underlap correction! */ #include #include #include static struct cdrom_tochdr hdr; -static struct cdrom_tocentry entry[100]; +static struct cdrom_tocentry entry[101]; static struct cdrom_read_audio arg; static u_char buffer[CD_FRAMESIZE_RAW]; static int datafile, drive; @@ -400,10 +397,9 @@ main(int argc, char *argv[]) /* * ask for track number (not implemented here) */ -track=2; -#if 0 /* just read a little piece */ -entry[track].cdte_addr.lba=170; -entry[track+1].cdte_addr.lba=190; +track=1; +#if 0 /* just read a little piece (4 seconds) */ +entry[track+1].cdte_addr.lba=entry[track].cdte_addr.lba+300; #endif /* * read track into file @@ -420,20 +416,19 @@ entry[track+1].cdte_addr.lba=190; arg.nframes=1; arg.buf=&buffer[0]; limit=entry[track+1].cdte_addr.lba; - for (i=arg.addr.lba;i + * published under the GPL + * + * made under heavy use of the "Tiny Audio CD Player" + * from Werner Zimmermann + * (see linux/drivers/block/README.aztcd) + */ +#undef AZT_PRIVATE_IOCTLS /* not supported by every CDROM driver */ +#define SBP_PRIVATE_IOCTLS /* not supported by every CDROM driver */ + +#include +#include +#include +#include +#include + +#ifdef AZT_PRIVATE_IOCTLS +#include +#endif AZT_PRIVATE_IOCTLS +#ifdef SBP_PRIVATE_IOCTLS +#include +#include +#endif SBP_PRIVATE_IOCTLS + +struct cdrom_tochdr hdr; +struct cdrom_tochdr tocHdr; +struct cdrom_tocentry TocEntry[101]; +struct cdrom_tocentry entry; +struct cdrom_multisession ms_info; +struct cdrom_read_audio read_audio; +struct cdrom_ti ti; +struct cdrom_subchnl subchnl; +struct cdrom_msf msf; +struct cdrom_volctrl volctrl; +#ifdef AZT_PRIVATE_IOCTLS +union +{ + struct cdrom_msf msf; + unsigned char buf[CD_FRAMESIZE_RAW]; +} azt; +#endif AZT_PRIVATE_IOCTLS +int i, i1, i2, i3, j, k; +unsigned char sequence=0; +unsigned char command[80]; +unsigned char first=1, last=1; +char *default_device="/dev/cdrom"; +char dev[20]; +char filename[20]; +int drive; +int datafile; +int rc; + +void help(void) +{ + printf("Available Commands:\n"); + printf("STOP s EJECT e QUIT q\n"); + printf("PLAY TRACK t PAUSE p RESUME r\n"); + printf("NEXT TRACK n REPEAT LAST l HELP h\n"); + printf("SUBCHANNEL_Q c TRACK INFO i PLAY AT a\n"); + printf("READ d READ RAW w READ AUDIO A\n"); + printf("MS-INFO M TOC T START S\n"); + printf("SET EJECTSW X DEVICE D DEBUG Y\n"); + printf("AUDIO_BUFSIZ Z RESET R BLKRASET B\n"); + printf("SET VOLUME v GET VOLUME V\n"); +} + +/* + * convert MSF number (3 bytes only) to Logical_Block_Address + */ +int msf2lba(u_char *msf) +{ + int i; + + i=(msf[0] * CD_SECS + msf[1]) * CD_FRAMES + msf[2] - CD_BLOCK_OFFSET; + if (i<0) return (0); + return (i); +} +/* + * convert logical_block_address to m-s-f_number (3 bytes only) + */ +void lba2msf(int lba, unsigned char *msf) +{ + lba += CD_BLOCK_OFFSET; + msf[0] = lba / (CD_SECS*CD_FRAMES); + lba %= CD_SECS*CD_FRAMES; + msf[1] = lba / CD_FRAMES; + msf[2] = lba % CD_FRAMES; +} + +int init_drive(char *dev) +{ + unsigned char msf_ent[3]; + + /* + * open the device + */ + drive=open(dev,0); + if (drive<0) return (-1); + /* + * get TocHeader + */ + printf("getting TocHeader...\n"); + rc=ioctl(drive,CDROMREADTOCHDR,&hdr); + if (rc!=0) + { + printf("can't get TocHeader (error %d).\n",rc); + return (-2); + } + else + first=hdr.cdth_trk0; + last=hdr.cdth_trk1; + printf("TocHeader: %d %d\n",hdr.cdth_trk0,hdr.cdth_trk1); + /* + * get and display all TocEntries + */ + printf("getting TocEntries...\n"); + for (i=1;i<=hdr.cdth_trk1+1;i++) + { + if (i!=hdr.cdth_trk1+1) TocEntry[i].cdte_track = i; + else TocEntry[i].cdte_track = CDROM_LEADOUT; + TocEntry[i].cdte_format = CDROM_LBA; + rc=ioctl(drive,CDROMREADTOCENTRY,&TocEntry[i]); + if (rc!=0) + { + printf("can't get TocEntry #%d (error %d).\n",i,rc); + } + else + { + lba2msf(TocEntry[i].cdte_addr.lba,&msf_ent[0]); + if (TocEntry[i].cdte_track==CDROM_LEADOUT) + { + printf("TocEntry #%02X: %1X %1X %02d:%02d:%02d (lba: 0x%06X) %02X\n", + TocEntry[i].cdte_track, + TocEntry[i].cdte_adr, + TocEntry[i].cdte_ctrl, + msf_ent[0], + msf_ent[1], + msf_ent[2], + TocEntry[i].cdte_addr.lba, + TocEntry[i].cdte_datamode); + } + else + { + printf("TocEntry #%02d: %1X %1X %02d:%02d:%02d (lba: 0x%06X) %02X\n", + TocEntry[i].cdte_track, + TocEntry[i].cdte_adr, + TocEntry[i].cdte_ctrl, + msf_ent[0], + msf_ent[1], + msf_ent[2], + TocEntry[i].cdte_addr.lba, + TocEntry[i].cdte_datamode); + } + } + } + return (hdr.cdth_trk1); /* number of tracks */ +} + +void display(int size,unsigned char *buffer) +{ + k=0; + getchar(); + for (i=0;i<(size+1)/16;i++) + { + printf("%4d:",i*16); + for (j=0;j<16;j++) + { + printf(" %02X",buffer[i*16+j]); + } + printf(" "); + for (j=0;j<16;j++) + { + if (isalnum(buffer[i*16+j])) + printf("%c",buffer[i*16+j]); + else + printf("."); + } + printf("\n"); + k++; + if (k>=20) + { + printf("press ENTER to continue\n"); + getchar(); + k=0; + } + } +} + +main(int argc, char *argv[]) +{ + printf("\nTesting tool for a CDROM driver's audio functions V0.1\n"); + printf("(C) 1995 Eberhard Moenkeberg \n"); + printf("initializing...\n"); + + rc=init_drive(default_device); + if (rc<0) printf("could not open %s (rc=%d).\n",default_device,rc); + help(); + while (1) + { + printf("Give a one-letter command (h = help): "); + scanf("%s",command); + command[1]=0; + switch (command[0]) + { + case 'D': + printf("device name (f.e. /dev/sbpcd3): ? "); + scanf("%s",&dev); + close(drive); + rc=init_drive(dev); + if (rc<0) printf("could not open %s (rc %d).\n",dev,rc); + break; + case 'e': + rc=ioctl(drive,CDROMEJECT); + if (rc<0) printf("CDROMEJECT: rc=%d.\n",rc); + break; + case 'p': + rc=ioctl(drive,CDROMPAUSE); + if (rc<0) printf("CDROMPAUSE: rc=%d.\n",rc); + break; + case 'r': + rc=ioctl(drive,CDROMRESUME); + if (rc<0) printf("CDROMRESUME: rc=%d.\n",rc); + break; + case 's': + rc=ioctl(drive,CDROMSTOP); + if (rc<0) printf("CDROMSTOP: rc=%d.\n",rc); + break; + case 'S': + rc=ioctl(drive,CDROMSTART); + if (rc<0) printf("CDROMSTART: rc=%d.\n",rc); + break; + case 't': + rc=ioctl(drive,CDROMREADTOCHDR,&tocHdr); + if (rc<0) + { + printf("CDROMREADTOCHDR: rc=%d.\n",rc); + break; + } + first=tocHdr.cdth_trk0; + last= tocHdr.cdth_trk1; + if ((first==0)||(first>last)) + { + printf ("--got invalid TOC data.\n"); + } + else + { + printf("--enter track number(first=%d, last=%d): ",first,last); + scanf("%d",&i1); + ti.cdti_trk0=i1; + if (ti.cdti_trk0last) ti.cdti_trk0=last; + ti.cdti_ind0=0; + ti.cdti_trk1=last; + ti.cdti_ind1=0; + rc=ioctl(drive,CDROMSTOP); + rc=ioctl(drive,CDROMPLAYTRKIND,&ti); + if (rc<0) printf("CDROMPLAYTRKIND: rc=%d.\n",rc); + } + break; + case 'n': + rc=ioctl(drive,CDROMSTOP); + if (++ti.cdti_trk0>last) ti.cdti_trk0=last; + ti.cdti_ind0=0; + ti.cdti_trk1=last; + ti.cdti_ind1=0; + rc=ioctl(drive,CDROMPLAYTRKIND,&ti); + if (rc<0) printf("CDROMPLAYTRKIND: rc=%d.\n",rc); + break; + case 'l': + rc=ioctl(drive,CDROMSTOP); + if (--ti.cdti_trk0last) entry.cdte_track=last; + entry.cdte_format=CDROM_MSF; + rc=ioctl(drive,CDROMREADTOCENTRY,&entry); + if (rc<0) printf("CDROMREADTOCENTRY: rc=%d.\n",rc); + else + { + printf("Mode %d Track, starts at %02d:%02d:%02d\n", + entry.cdte_adr, + entry.cdte_addr.msf.minute, + entry.cdte_addr.msf.second, + entry.cdte_addr.msf.frame); + } + break; + case 'a': + printf("Address (min:sec:frm) "); + scanf("%d:%d:%d",&i1,&i2,&i3); + msf.cdmsf_min0=i1; + msf.cdmsf_sec0=i2; + msf.cdmsf_frame0=i3; + if (msf.cdmsf_sec0>59) msf.cdmsf_sec0=59; + if (msf.cdmsf_frame0>74) msf.cdmsf_frame0=74; + lba2msf(TocEntry[last+1].cdte_addr.lba-1,&msf.cdmsf_min1); + rc=ioctl(drive,CDROMSTOP); + rc=ioctl(drive,CDROMPLAYMSF,&msf); + if (rc<0) printf("CDROMPLAYMSF: rc=%d.\n",rc); + break; +#ifdef AZT_PRIVATE_IOCTLS /*not supported by every CDROM driver*/ + case 'd': + printf("Address (min:sec:frm) "); + scanf("%d:%d:%d",&i1,&i2,&i3); + azt.msf.cdmsf_min0=i1; + azt.msf.cdmsf_sec0=i2; + azt.msf.cdmsf_frame0=i3; + if (azt.msf.cdmsf_sec0>59) azt.msf.cdmsf_sec0=59; + if (azt.msf.cdmsf_frame0>74) azt.msf.cdmsf_frame0=74; + rc=ioctl(drive,CDROMREADMODE1,&azt.msf); + if (rc<0) printf("CDROMREADMODE1: rc=%d.\n",rc); + else display(CD_FRAMESIZE,azt.buf); + break; + case 'w': + printf("Address (min:sec:frame) "); + scanf("%d:%d:%d",&i1,&i2,&i3); + azt.msf.cdmsf_min0=i1; + azt.msf.cdmsf_sec0=i2; + azt.msf.cdmsf_frame0=i3; + if (azt.msf.cdmsf_sec0>59) azt.msf.cdmsf_sec0=59; + if (azt.msf.cdmsf_frame0>74) azt.msf.cdmsf_frame0=74; + rc=ioctl(drive,CDROMREADMODE2,&azt.msf); + if (rc<0) printf("CDROMREADMODE2: rc=%d.\n",rc); + else display(CD_FRAMESIZE_RAW,azt.buf); /* currently only 2336 */ + break; +#endif + case 'v': + printf("--Channel 0 (Left) (0-255): "); + scanf("%d",&i1); + volctrl.channel0=i1; + printf("--Channel 1 (Right) (0-255): "); + scanf("%d",&i1); + volctrl.channel1=i1; + volctrl.channel2=0; + volctrl.channel3=0; + rc=ioctl(drive,CDROMVOLCTRL,&volctrl); + if (rc<0) printf("CDROMVOLCTRL: rc=%d.\n",rc); + break; + case 'q': + close(drive); + exit(0); + case 'h': + help(); + break; + case 'T': /* display TOC entry - without involving the driver */ + scanf("%d",&i); + if ((ihdr.cdth_trk1)) + printf("invalid track number.\n"); + else + printf("TocEntry %02d: adr=%01X ctrl=%01X msf=%02d:%02d:%02d mode=%02X\n", + TocEntry[i].cdte_track, + TocEntry[i].cdte_adr, + TocEntry[i].cdte_ctrl, + TocEntry[i].cdte_addr.msf.minute, + TocEntry[i].cdte_addr.msf.second, + TocEntry[i].cdte_addr.msf.frame, + TocEntry[i].cdte_datamode); + break; + case 'A': /* read audio data into file */ + printf("Address (min:sec:frm) ? "); + scanf("%d:%d:%d",&i1,&i2,&i3); + read_audio.addr.msf.minute=i1; + read_audio.addr.msf.second=i2; + read_audio.addr.msf.frame=i3; + read_audio.addr_format=CDROM_MSF; + printf("# of frames ? "); + scanf("%d",&i1); + read_audio.nframes=i1; + k=read_audio.nframes*CD_FRAMESIZE_RAW; + read_audio.buf=malloc(k); + if (read_audio.buf==NULL) + { + printf("can't malloc %d bytes.\n",k); + break; + } + sprintf(filename,"audio_%02d%02d%02d_%02d.%02d\0", + read_audio.addr.msf.minute, + read_audio.addr.msf.second, + read_audio.addr.msf.frame, + read_audio.nframes, + ++sequence); + datafile=creat(filename, 0755); + if (datafile<0) + { + printf("can't open datafile %s.\n",filename); + break; + } + rc=ioctl(drive,CDROMREADAUDIO,&read_audio); + if (rc!=0) + { + printf("CDROMREADAUDIO: rc=%d.\n",rc); + } + else + { + rc=write(datafile,&read_audio.buf,k); + if (rc!=k) printf("datafile I/O error (%d).\n",rc); + } + close(datafile); + break; + case 'X': /* set EJECT_SW (0: disable, 1: enable auto-ejecting) */ + scanf("%d",&i); + rc=ioctl(drive,CDROMEJECT_SW,i); + if (rc!=0) + printf("CDROMEJECT_SW: rc=%d.\n",rc); + else + printf("EJECT_SW set to %d\n",i); + break; + case 'M': /* get the multisession redirection info */ + ms_info.addr_format=CDROM_LBA; + rc=ioctl(drive,CDROMMULTISESSION,&ms_info); + if (rc!=0) + { + printf("CDROMMULTISESSION(lba): rc=%d.\n",rc); + } + else + { + if (ms_info.xa_flag) printf("MultiSession offset (lba): %d (0x%06X)\n",ms_info.addr.lba,ms_info.addr.lba); + else + { + printf("this CD is not an XA disk.\n"); + break; + } + } + ms_info.addr_format=CDROM_MSF; + rc=ioctl(drive,CDROMMULTISESSION,&ms_info); + if (rc!=0) + { + printf("CDROMMULTISESSION(msf): rc=%d.\n",rc); + } + else + { + if (ms_info.xa_flag) + printf("MultiSession offset (msf): %02d:%02d:%02d (0x%02X%02X%02X)\n", + ms_info.addr.msf.minute, + ms_info.addr.msf.second, + ms_info.addr.msf.frame, + ms_info.addr.msf.minute, + ms_info.addr.msf.second, + ms_info.addr.msf.frame); + else printf("this CD is not an XA disk.\n"); + } + break; +#ifdef SBP_PRIVATE_IOCTLS + case 'Y': /* set the driver's message level */ +#if 0 /* not implemented yet */ + printf("enter switch name (f.e. DBG_CMD): "); + scanf("%s",&dbg_switch); + j=get_dbg_num(dbg_switch); +#else + printf("enter DDIOCSDBG switch number: "); + scanf("%d",&j); +#endif + printf("enter 0 for \"off\", 1 for \"on\": "); + scanf("%d",&i); + if (i==0) j|=0x80; + printf("calling \"ioctl(drive,DDIOCSDBG,%d)\"\n",j); + rc=ioctl(drive,DDIOCSDBG,j); + printf("DDIOCSDBG: rc=%d.\n",rc); + break; + case 'Z': /* set the audio buffer size */ + printf("# frames wanted: ? "); + scanf("%d",&j); + rc=ioctl(drive,CDROMAUDIOBUFSIZ,j); + printf("%d frames granted.\n",rc); + break; + case 'V': + rc=ioctl(drive,CDROMVOLREAD,&volctrl); + if (rc<0) printf("CDROMVOLCTRL: rc=%d.\n",rc); + printf("Volume: channel 0 (left) %d, channel 1 (right) %d\n",volctrl.channel0,volctrl.channel1); + break; + case 'R': + rc=ioctl(drive,CDROMRESET); + if (rc<0) printf("CDROMRESET: rc=%d.\n",rc); + break; + case 'B': /* set the driver's (?) read ahead value */ + printf("enter read-ahead size: ? "); + scanf("%d",&i); + rc=ioctl(drive,BLKRASET,i); + if (rc<0) printf("BLKRASET: rc=%d.\n",rc); + break; +#endif SBP_PRIVATE_IOCTLS + default: + printf("unknown command: \"%s\".\n",command); + break; + } + } +} +/*==========================================================================*/ +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 8 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -8 + * c-argdecl-indent: 8 + * c-label-offset: -8 + * c-continued-statement-offset: 8 + * c-continued-brace-offset: 0 + * End: + */ + diff --git a/drivers/block/README.sonycd535 b/drivers/block/README.sonycd535 new file mode 100644 index 000000000..ca5c371fd --- /dev/null +++ b/drivers/block/README.sonycd535 @@ -0,0 +1,119 @@ + README FOR LINUX SONY CDU-535/531 DRIVER + ======================================== + +This is the the Sony CDU-535 (and 531) driver version 0.7 for Linux. +I do not think I have the documentation to add features like DMA support +so if anyone else wants to pursue it or help me with it, please do. +(I need to see what was done for the CDU-31A driver -- perhaps I can +steal some of that code.) + +This is a Linux device driver for the Sony CDU-535 CDROM drive. This is +one of the older Sony drives with its own interface card (Sony bus). +The DOS driver for this drive is named SONY_CDU.SYS - when you boot DOS +your drive should be identified as a SONY CDU-535. The driver works +with a CDU-531 also. One user reported that the driver worked on drives +OEM'ed by Procomm, drive and interface board were labelled Procomm. + +The Linux driver is based on Corey Minyard's sonycd 0.3 driver for +the CDU-31A. Ron Jeppesen just changed the commands that were sent +to the drive to correspond to the CDU-535 commands and registers. +There were enough changes to let bugs creep in but it seems to be stable. +Ron was able to tar an entire CDROM (should read all blocks) and built +ghostview and xfig off Walnut Creek's X11R5/GNU CDROM. xcdplayer and +workman work with the driver. Others have used the driver without +problems except those dealing with wait loops (fixed in third release). +Like Minyard's original driver this one uses a polled interface (this +is also the default setup for the DOS driver). It has not been tried +with interrupts or DMA enabled on the board. + +REQUIREMENTS +============ + + - Sony CDU-535 drive, preferably without interrupts and DMA + enabled on the card. + + - Drive must be set up as unit 1. Only the first unit will be + recognized + + - you must enter your interface address into + /usr/src/linux/include/linux/sonycd535.h and build the + appropriate kernel or use the "kernel command line" parameter + sonycd535=0x320 + with the correct interface address. + +NOTES: +====== + +1) The drive MUST be turned on when booting or it will not be recognized! + (but see comments on modularized version below) + +2) when the cdrom device is opened the eject button is disabled to keep the + user from ejecting a mounted disk and replacing it with another. + Unfortunately xcdplayer and workman also open the cdrom device so you + have to use the eject button in the software. Keep this in mind if your + cdrom player refuses to give up its disk -- exit workman or xcdplayer, or + umount the drive if it has been mounted. + +THANKS +====== + +Many thanks to Ron Jeppesen (ronj.an@site007.saic.com) for getting +this project off the ground. He wrote the initial release +and the first two patches to this driver (0.1, 0.2, and 0.3). +Thanks also to Eberhard Moenkeberg (emoenke@gwdg.de) for prodding +me to place this code into the mainstream Linux source tree +(as of Linux version 1.1.91), as well as some patches to make +it a better device citizen. Further thanks to "S. Joel Katz" + for his MODULE patches (see details below), +Porfiri Claudio for patches +to make the driver work with the older CDU-510/515 series, and +Heiko Eissfeldt for pointing out that +the verify_area() checks were ignoring the results of said checks. + +(Acknowledgments from Ron Jeppesen in the 0.3 release:) +Thanks to Corey Minyard who wrote the original CDU-31A driver on which +this driver is based. Thanks to Ken Pizzini and Bob Blair who provided +patches and feedback on the first release of this driver. + +Ken Pizzini +ken@halcyon.com + +------------------------------------------------------------------------------ +(The following is from Joel Katz .) + + To build a version of sony535.o that can be installed as a module, +use the following command: + +gcc -c -D__KERNEL__ -DMODULE -O2 sonycd535.c -o sonycd535.o + + To install the module, simply type: + +insmod sony535.o + + And to remove it: + +rmmod sony535 + + The code checks to see if MODULE is defined and behaves as it used +to if MODULE is not defined. That means your patched file should behave +exactly as it used to if compiled into the kernel. + + I have an external drive, and I usually leave it powered off. I used +to have to reboot if I needed to use the CDROM drive. Now I don't. + + Even if you have an internal drive, why waste the 268K of memory +(unswappable) that the driver uses if you use your CD-ROM drive infrequently? + + This driver will not install (whether compiled in or loaded as a +module) if the CDROM drive is not available during its initialization. This +means that you can have the driver compiled into the kernel and still load +the module later (assuming the driver doesn't install itself during +power-on). This only wastes 12K when you boot with the CDROM drive off. + + This is what I usually do; I leave the driver compiled into the +kernel, but load it as a module if I powered the system up with the drive +off and then later decided to use the CDROM drive. + + Since the driver only uses a single page to point to the chunks, +attempting to set the buffer cache to more than 2 Megabytes would be very +bad; don't do that. diff --git a/drivers/block/aztcd.c b/drivers/block/aztcd.c new file mode 100644 index 000000000..66e289452 --- /dev/null +++ b/drivers/block/aztcd.c @@ -0,0 +1,1713 @@ +#define AZT_VERSION "V1.0" +/* $Id: aztcd.c,v 1.0 1995/03/25 08:27:11 root Exp $ + linux/drivers/block/aztcd.c - AztechCD268 CDROM driver + + Copyright (C) 1994,1995 Werner Zimmermann (zimmerma@rz.fht-esslingen.de) + + based on Mitsumi CDROM driver by Martin Hariss and preworks by + Eberhard Moenkeberg; contains contributions by Joe Nardone and Robby + Schirmer. + + 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, or (at your option) + 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. + + HISTORY + V0.0 Adaption to Adaptec CD268-01A Version 1.3 + Version is PRE_ALPHA, unresolved points: + 1. I use busy wait instead of timer wait in STEN_LOW,DTEN_LOW + thus driver causes CPU overhead and is very slow + 2. could not find a way to stop the drive, when it is + in data read mode, therefore I had to set + msf.end.min/sec/frame to 0:0:1 (in azt_poll); so only one + frame can be read in sequence, this is also the reason for + 3. getting 'timeout in state 4' messages, but nevertheless + it works + W.Zimmermann, Oct. 31, 1994 + V0.1 Version is ALPHA, problems #2 and #3 resolved. + W.Zimmermann, Nov. 3, 1994 + V0.2 Modification to some comments, debugging aids for partial test + with Borland C under DOS eliminated. Timer interrupt wait + STEN_LOW_WAIT additionally to busy wait for STEN_LOW implemented; + use it only for the 'slow' commands (ACMD_GET_Q_CHANNEL, ACMD_ + SEEK_TO_LEAD_IN), all other commands are so 'fast', that busy + waiting seems better to me than interrupt rescheduling. + Besides that, when used in the wrong place, STEN_LOW_WAIT causes + kernel panic. + In function aztPlay command ACMD_PLAY_AUDIO added, should make + audio functions work. The Aztech drive needs different commands + to read data tracks and play audio tracks. + W.Zimmermann, Nov. 8, 1994 + V0.3 Recognition of missing drive during boot up improved (speeded up). + W.Zimmermann, Nov. 13, 1994 + V0.35 Rewrote the control mechanism in azt_poll (formerly mcd_poll) + including removal of all 'goto' commands. :-); + J. Nardone, Nov. 14, 1994 + V0.4 Renamed variables and constants to 'azt' instead of 'mcd'; had + to make some "compatibility" defines in azt.h; please note, + that the source file was renamed to azt.c, the include file to + azt.h + Speeded up drive recognition during init (will be a little bit + slower than before if no drive is installed!); suggested by + Robby Schirmer. + read_count declared volatile and set to AZT_BUF_SIZ to make + drive faster (now 300kB/sec, was 60kB/sec before, measured + by 'time dd if=/dev/cdrom of=/dev/null bs=2048 count=4096'; + different AZT_BUF_SIZes were test, above 16 no further im- + provement seems to be possible; suggested by E.Moenkeberg. + W.Zimmermann, Nov. 18, 1994 + V0.42 Included getAztStatus command in GetQChannelInfo() to allow + reading Q-channel info on audio disks, if drive is stopped, + and some other bug fixes in the audio stuff, suggested by + Robby Schirmer. + Added more ioctls (reading data in mode 1 and mode 2). + Completely removed the old azt_poll() routine. + Detection of ORCHID CDS-3110 in aztcd_init implemented. + Additional debugging aids (see the readme file). + W.Zimmermann, Dec. 9, 1994 + V0.50 Autodetection of drives implemented. + W.Zimmermann, Dec. 12, 1994 + V0.52 Prepared for including in the standard kernel, renamed most + variables to contain 'azt', included autoconf.h + W.Zimmermann, Dec. 16, 1994 + V0.6 Version for being included in the standard Linux kernel. + Renamed source and header file to aztcd.c and aztcd.h + W.Zimmermann, Dec. 24, 1994 + V0.7 Changed VERIFY_READ to VERIFY_WRITE in aztcd_ioctl, case + CDROMREADMODE1 and CDROMREADMODE2; bug fix in the ioctl, + which causes kernel crashes when playing audio, changed + include-files (config.h instead of autoconf.h, removed + delay.h) + W.Zimmermann, Jan. 8, 1995 + V0.72 Some more modifications for adaption to the standard kernel. + W.Zimmermann, Jan. 16, 1995 + V0.80 aztcd is now part of the standard kernel since version 1.1.83. + Modified the SET_TIMER and CLEAR_TIMER macros to comply with + the new timer scheme. + W.Zimmermann, Jan. 21, 1995 + V0.90 Included CDROMVOLCTRL, but with my Aztech drive I can only turn + the channels on and off. If it works better with your drive, + please mail me. Also implemented ACMD_CLOSE for CDROMSTART. + W.Zimmermann, Jan. 24, 1995 + V1.00 Implemented close and lock tray commands. Patches supplied by + Frank Racis + Added support for loadable MODULEs, so aztcd can now also be + loaded by insmod and removed by rmmod during run time + Werner Zimmermann, Mar. 24, 95 + NOTE: + Points marked with ??? are questionable ! +*/ +#include +#include + +#ifdef MODULE +# include +# include +# ifndef CONFIG_MODVERSIONS + char kernel_version[]= UTS_RELEASE; +# endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define MAJOR_NR AZTECH_CDROM_MAJOR + +#ifdef MODULE +# include "/usr/src/linux/drivers/block/blk.h" +#else +# include "blk.h" +# define MOD_INC_USE_COUNT +# define MOD_DEC_USE_COUNT +#endif + +#include + +static int aztPresent = 0; + +#if 0 +#define AZT_TEST1 /* */ +#define AZT_TEST2 /* do_aztcd_request */ +#define AZT_TEST3 /* AZT_S_state */ +#define AZT_TEST4 /* QUICK_LOOP-counter */ +#define AZT_TEST5 /* port(1) state */ +#define AZT_DEBUG +#endif + +#define CURRENT_VALID \ + (CURRENT && MAJOR(CURRENT -> dev) == MAJOR_NR && CURRENT -> cmd == READ \ + && CURRENT -> sector != -1) + +#define AFL_STATUSorDATA (AFL_STATUS | AFL_DATA) +#define AZT_BUF_SIZ 16 + +static volatile int azt_transfer_is_active=0; + +static char azt_buf[2048*AZT_BUF_SIZ]; /*buffer for block size conversion*/ +#ifdef AZT_PRIVATE_IOCTLS +static char buf[2336]; /*separate buffer for the ioctls*/ +#endif + +static volatile int azt_buf_bn[AZT_BUF_SIZ], azt_next_bn; +static volatile int azt_buf_in, azt_buf_out = -1; +static volatile int azt_error=0; +static int azt_open_count=0; +enum azt_state_e { + AZT_S_IDLE, /* 0 */ + AZT_S_START, /* 1 */ + AZT_S_MODE, /* 2 */ + AZT_S_READ, /* 3 */ + AZT_S_DATA, /* 4 */ + AZT_S_STOP, /* 5 */ + AZT_S_STOPPING /* 6 */ +}; +static volatile enum azt_state_e azt_state = AZT_S_IDLE; +#ifdef AZT_TEST3 +static volatile enum azt_state_e azt_state_old = AZT_S_STOP; +static volatile int azt_st_old = 0; +#endif + +static int azt_mode = -1; +static int ACMD_DATA_READ= ACMD_PLAY_READ; +static volatile int azt_read_count = 1; + +#define READ_TIMEOUT 3000 + +#define azt_port aztcd /*needed for the modutils*/ +static short azt_port = AZT_BASE_ADDR; + +static char azt_cont = 0; +static char azt_init_end = 0; + +static int AztTimeout, AztTries; +static struct wait_queue *azt_waitq = NULL; +static struct timer_list delay_timer = { NULL, NULL, 0, 0, NULL }; + +static struct azt_DiskInfo DiskInfo; +static struct azt_Toc Toc[MAX_TRACKS]; +static struct azt_Play_msf azt_Play; + +static int aztAudioStatus = CDROM_AUDIO_NO_STATUS; +static char aztDiskChanged = 1; +static char aztTocUpToDate = 0; + + +static void azt_transfer(void); +static void azt_poll(void); +static void azt_invalidate_buffers(void); +static void do_aztcd_request(void); +static void azt_hsg2msf(long hsg, struct msf *msf); +static void azt_bin2bcd(unsigned char *p); +static int azt_bcd2bin(unsigned char bcd); +static int aztStatus(void); +static int getAztStatus(void); +static int aztSendCmd(int cmd); +static int sendAztCmd(int cmd, struct azt_Play_msf *params); +static int aztGetQChannelInfo(struct azt_Toc *qp); +static int aztUpdateToc(void); +static int aztGetDiskInfo(void); +static int aztGetToc(void); +static int aztGetValue(unsigned char *result); +static void aztStatTimer(void); +static void aztCloseDoor(void); +static void aztLockDoor(void); +static void aztUnlockDoor(void); + +static unsigned char aztIndatum; +static unsigned long aztTimeOutCount; + +/* Macros for the drive hardware interface handshake, these macros use + busy waiting */ +/* Wait for OP_OK = drive answers with AFL_OP_OK after receiving a command*/ +# define OP_OK op_ok() +void op_ok(void) +{ aztTimeOutCount=0; + do { aztIndatum=inb(DATA_PORT); + aztTimeOutCount++; + if (aztTimeOutCount>=AZT_TIMEOUT) + { printk("aztcd: Error Wait OP_OK\n"); + break; + } + } while (aztIndatum!=AFL_OP_OK); +} + +/* Wait for PA_OK = drive answers with AFL_PA_OK after receiving parameters*/ +# define PA_OK pa_ok() +void pa_ok(void) +{ aztTimeOutCount=0; + do { aztIndatum=inb(DATA_PORT); + aztTimeOutCount++; + if (aztTimeOutCount>=AZT_TIMEOUT) + { printk("aztcd: Error Wait PA_OK\n"); + break; + } + } while (aztIndatum!=AFL_PA_OK); +} + +/* Wait for STEN=Low = handshake signal 'AFL_.._OK available or command executed*/ +# define STEN_LOW sten_low() +void sten_low(void) +{ aztTimeOutCount=0; + do { aztIndatum=inb(STATUS_PORT); + aztTimeOutCount++; + if (aztTimeOutCount>=AZT_TIMEOUT) + { if (azt_init_end) printk("aztcd: Error Wait STEN_LOW\n"); + break; + } + } while (aztIndatum&AFL_STATUS); +} + +/* Wait for DTEN=Low = handshake signal 'Data available'*/ +# define DTEN_LOW dten_low() +void dten_low(void) +{ aztTimeOutCount=0; + do { aztIndatum=inb(STATUS_PORT); + aztTimeOutCount++; + if (aztTimeOutCount>=AZT_TIMEOUT) + { printk("aztcd: Error Wait DTEN_OK\n"); + break; + } + } while (aztIndatum&AFL_DATA); +} + +/* + * Macro for timer wait on STEN=Low, should only be used for 'slow' commands; + * may cause kernel panic when used in the wrong place +*/ +#define STEN_LOW_WAIT statusAzt() +void statusAzt(void) +{ AztTimeout = AZT_STATUS_DELAY; + SET_TIMER(aztStatTimer, 1); + sleep_on(&azt_waitq); + if (AztTimeout <= 0) printk("aztcd: Error Wait STEN_LOW_WAIT\n"); + return; +} + +static void aztStatTimer(void) +{ if (!(inb(STATUS_PORT) & AFL_STATUS)) + { wake_up(&azt_waitq); + return; + } + AztTimeout--; + if (AztTimeout <= 0) + { wake_up(&azt_waitq); + return; + } + SET_TIMER(aztStatTimer, 1); +} + + +void aztcd_setup(char *str, int *ints) +{ if (ints[0] > 0) + azt_port = ints[1]; + if (ints[0] > 1) + azt_cont = ints[2]; +} + +/* + * Subroutines to automatically close the door (tray) and + * lock it closed when the cd is mounted. Leave the tray + * locking as an option + */ +static void aztCloseDoor(void) +{ + aztSendCmd(ACMD_CLOSE); + STEN_LOW; + return; +} + +static void aztLockDoor(void) +{ +#ifdef AZT_ALLOW_TRAY_LOCK + aztSendCmd(ACMD_LOCK); + STEN_LOW; +#endif + return; +} + +static void aztUnlockDoor(void) +{ +#ifdef AZT_ALLOW_TRAY_LOCK + aztSendCmd(ACMD_UNLOCK); + STEN_LOW; +#endif + return; +} + +/* + * Send a single command, return -1 on error, else 0 +*/ +static int aztSendCmd(int cmd) +{ unsigned char data; + int retry; + +#ifdef AZT_DEBUG + printk("aztcd: Executing command %x\n",cmd); +#endif + outb(POLLED,MODE_PORT); + do { if (inb(STATUS_PORT)&AFL_STATUS) break; + inb(DATA_PORT); /* if status left from last command, read and */ + } while (1); /* discard it */ + do { if (inb(STATUS_PORT)&AFL_DATA) break; + inb(DATA_PORT); /* if data left from last command, read and */ + } while (1); /* discard it */ + for (retry=0;retry=AZT_RETRY_ATTEMPTS) + { printk("### Error 2 aztcd: aztSendCmd %x \n",cmd); + azt_error=0xA5; + } + return -1; +} + +/* + * Send a play or read command to the drive, return -1 on error, else 0 +*/ +static int sendAztCmd(int cmd, struct azt_Play_msf *params) +{ unsigned char data; + int retry; + +#ifdef AZT_DEBUG + printk("start=%02x:%02x:%02x end=%02x:%02x:%02x\n", \ + params->start.min, params->start.sec, params->start.frame, \ + params->end.min, params->end.sec, params->end.frame); +#endif + for (retry=0;retry start.min,CMD_PORT); + outb(params -> start.sec,CMD_PORT); + outb(params -> start.frame,CMD_PORT); + outb(params -> end.min,CMD_PORT); + outb(params -> end.sec,CMD_PORT); + outb(params -> end.frame,CMD_PORT); + STEN_LOW; + data=inb(DATA_PORT); + if (data==AFL_PA_OK) + { return 0;} /*PA_OK ?*/ + if (data==AFL_PA_ERR) + { STEN_LOW; + data=inb(DATA_PORT); + printk("### Error 1 aztcd: sendAztCmd %x Error Code %x\n",cmd,data); + } + } + if (retry>=AZT_RETRY_ATTEMPTS) + { printk("### Error 2 aztcd: sendAztCmd %x\n ",cmd); + azt_error=0xA5; + } + return -1; +} + + +/* + * Checking if the media has been changed not yet implemented +*/ +static int check_aztcd_media_change(dev_t full_dev) +{ return 0; +} + + +/* used in azt_poll to poll the status, expects another program to issue a + * ACMD_GET_STATUS directly before + */ +static int aztStatus(void) +{ int st; + int i; + + i = inb(STATUS_PORT) & AFL_STATUS; /* is STEN=0? ???*/ + if (!i) + { + st = inb(DATA_PORT) & 0xFF; + return st; + } + else + return -1; +} + +/* + * Get the drive status + */ +static int getAztStatus(void) +{ int st; + + if (aztSendCmd(ACMD_GET_STATUS)) return -1; + STEN_LOW; + st = inb(DATA_PORT) & 0xFF; +#ifdef AZT_DEBUG + printk("aztcd: Status = %x\n",st); +#endif + if ((st == 0xFF)||(st&AST_CMD_CHECK)) + { printk("aztcd: AST_CMD_CHECK error or no status available\n"); + return -1; + } + + if (((st&AST_MODE_BITS)!=AST_BUSY) && (aztAudioStatus == CDROM_AUDIO_PLAY)) + /* XXX might be an error? look at q-channel? */ + aztAudioStatus = CDROM_AUDIO_COMPLETED; + + if (st & AST_DSK_CHG) + { + aztDiskChanged = 1; + aztTocUpToDate = 0; + aztAudioStatus = CDROM_AUDIO_NO_STATUS; + } + return st; +} + + +/* + * Send a 'Play' command and get the status. Use only from the top half. + */ +static int aztPlay(struct azt_Play_msf *arg) +{ if (sendAztCmd(ACMD_PLAY_AUDIO, arg) < 0) return -1; + return 0; +} + + +long azt_msf2hsg(struct msf *mp) +{ +#ifdef AZT_DEBUG + if (mp->min >=70) printk("aztcd: Error msf2hsg address Minutes\n"); + if (mp->sec >=60) printk("aztcd: Error msf2hsg address Seconds\n"); + if (mp->frame>=75) printk("aztcd: Error msf2hsg address Frames\n"); +#endif + return azt_bcd2bin(mp -> frame) + + azt_bcd2bin(mp -> sec) * 75 + + azt_bcd2bin(mp -> min) * 4500 + - 150; +} + +static int aztcd_ioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg) +{ int i, st; + struct azt_Toc qInfo; + struct cdrom_ti ti; + struct cdrom_tochdr tocHdr; + struct cdrom_msf msf; + struct cdrom_tocentry entry; + struct azt_Toc *tocPtr; + struct cdrom_subchnl subchnl; + struct cdrom_volctrl volctrl; + +#ifdef AZT_DEBUG + printk("aztcd: starting aztcd_ioctl - Command:%x\n",cmd); +#endif + if (!ip) return -EINVAL; + if (getAztStatus()<0) return -EIO; + if (!aztTocUpToDate) + { if ((i=aztUpdateToc())<0) return i; /* error reading TOC */ + } + + switch (cmd) + { + case CDROMSTART: /* Spin up the drive. Don't know, what to do, + at least close the tray */ +#ifdef AZT_PRIVATE_IOCTLS + if (aztSendCmd(ACMD_CLOSE)) return -1; + STEN_LOW_WAIT; +#endif + break; + case CDROMSTOP: /* Spin down the drive */ + if (aztSendCmd(ACMD_STOP)) return -1; + STEN_LOW_WAIT; + /* should we do anything if it fails? */ + aztAudioStatus = CDROM_AUDIO_NO_STATUS; + break; + case CDROMPAUSE: /* Pause the drive */ + if (aztAudioStatus != CDROM_AUDIO_PLAY) return -EINVAL; + + if (aztGetQChannelInfo(&qInfo) < 0) + { /* didn't get q channel info */ + aztAudioStatus = CDROM_AUDIO_NO_STATUS; + return 0; + } + azt_Play.start = qInfo.diskTime; /* remember restart point */ + if (aztSendCmd(ACMD_PAUSE)) return -1; + STEN_LOW_WAIT; + aztAudioStatus = CDROM_AUDIO_PAUSED; + break; + case CDROMRESUME: /* Play it again, Sam */ + if (aztAudioStatus != CDROM_AUDIO_PAUSED) return -EINVAL; + /* restart the drive at the saved position. */ + i = aztPlay(&azt_Play); + if (i < 0) + { aztAudioStatus = CDROM_AUDIO_ERROR; + return -EIO; + } + aztAudioStatus = CDROM_AUDIO_PLAY; + break; + case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */ + st = verify_area(VERIFY_READ, (void *) arg, sizeof ti); + if (st) return st; + memcpy_fromfs(&ti, (void *) arg, sizeof ti); + if (ti.cdti_trk0 < DiskInfo.first + || ti.cdti_trk0 > DiskInfo.last + || ti.cdti_trk1 < ti.cdti_trk0) + { return -EINVAL; + } + if (ti.cdti_trk1 > DiskInfo.last) + ti. cdti_trk1 = DiskInfo.last; + azt_Play.start = Toc[ti.cdti_trk0].diskTime; + azt_Play.end = Toc[ti.cdti_trk1 + 1].diskTime; +#ifdef AZT_DEBUG +printk("aztcd play: %02x:%02x.%02x to %02x:%02x.%02x\n", + azt_Play.start.min, azt_Play.start.sec, azt_Play.start.frame, + azt_Play.end.min, azt_Play.end.sec, azt_Play.end.frame); +#endif + i = aztPlay(&azt_Play); + if (i < 0) + { aztAudioStatus = CDROM_AUDIO_ERROR; + return -EIO; + } + aztAudioStatus = CDROM_AUDIO_PLAY; + break; + case CDROMPLAYMSF: /* Play starting at the given MSF address. */ +/* if (aztAudioStatus == CDROM_AUDIO_PLAY) + { if (aztSendCmd(ACMD_STOP)) return -1; + STEN_LOW; + aztAudioStatus = CDROM_AUDIO_NO_STATUS; + } +*/ + st = verify_area(VERIFY_READ, (void *) arg, sizeof msf); + if (st) return st; + memcpy_fromfs(&msf, (void *) arg, sizeof msf); + /* convert to bcd */ + azt_bin2bcd(&msf.cdmsf_min0); + azt_bin2bcd(&msf.cdmsf_sec0); + azt_bin2bcd(&msf.cdmsf_frame0); + azt_bin2bcd(&msf.cdmsf_min1); + azt_bin2bcd(&msf.cdmsf_sec1); + azt_bin2bcd(&msf.cdmsf_frame1); + azt_Play.start.min = msf.cdmsf_min0; + azt_Play.start.sec = msf.cdmsf_sec0; + azt_Play.start.frame = msf.cdmsf_frame0; + azt_Play.end.min = msf.cdmsf_min1; + azt_Play.end.sec = msf.cdmsf_sec1; + azt_Play.end.frame = msf.cdmsf_frame1; +#ifdef AZT_DEBUG +printk("aztcd play: %02x:%02x.%02x to %02x:%02x.%02x\n", +azt_Play.start.min, azt_Play.start.sec, azt_Play.start.frame, +azt_Play.end.min, azt_Play.end.sec, azt_Play.end.frame); +#endif + i = aztPlay(&azt_Play); + if (i < 0) + { aztAudioStatus = CDROM_AUDIO_ERROR; + return -EIO; + } + aztAudioStatus = CDROM_AUDIO_PLAY; + break; + + case CDROMREADTOCHDR: /* Read the table of contents header */ + st = verify_area(VERIFY_WRITE, (void *) arg, sizeof tocHdr); + if (st) return st; + tocHdr.cdth_trk0 = DiskInfo.first; + tocHdr.cdth_trk1 = DiskInfo.last; + memcpy_tofs((void *) arg, &tocHdr, sizeof tocHdr); + break; + case CDROMREADTOCENTRY: /* Read an entry in the table of contents */ + st = verify_area(VERIFY_READ, (void *) arg, sizeof entry); + if (st) return st; + st = verify_area(VERIFY_WRITE, (void *) arg, sizeof entry); + if (st) return st; + memcpy_fromfs(&entry, (void *) arg, sizeof entry); + if (!aztTocUpToDate) aztGetDiskInfo(); + if (entry.cdte_track == CDROM_LEADOUT) + tocPtr = &Toc[DiskInfo.last + 1]; /* ??? */ + else if (entry.cdte_track > DiskInfo.last + || entry.cdte_track < DiskInfo.first) + { return -EINVAL; + } + else + tocPtr = &Toc[entry.cdte_track]; + entry.cdte_adr = tocPtr -> ctrl_addr; + entry.cdte_ctrl = tocPtr -> ctrl_addr >> 4; + if (entry.cdte_format == CDROM_LBA) + entry.cdte_addr.lba = azt_msf2hsg(&tocPtr -> diskTime); + else if (entry.cdte_format == CDROM_MSF) + { entry.cdte_addr.msf.minute = azt_bcd2bin(tocPtr -> diskTime.min); + entry.cdte_addr.msf.second = azt_bcd2bin(tocPtr -> diskTime.sec); + entry.cdte_addr.msf.frame = azt_bcd2bin(tocPtr -> diskTime.frame); + } + else + { return -EINVAL; + } + memcpy_tofs((void *) arg, &entry, sizeof entry); + break; + case CDROMSUBCHNL: /* Get subchannel info */ + st = verify_area(VERIFY_READ, (void *) arg, sizeof subchnl); + if (st) return st; + st = verify_area(VERIFY_WRITE, (void *) arg, sizeof subchnl); + if (st) return st; + memcpy_fromfs(&subchnl, (void *) arg, sizeof subchnl); + if (aztGetQChannelInfo(&qInfo) < 0) + return -EIO; + subchnl.cdsc_audiostatus = aztAudioStatus; + subchnl.cdsc_adr = qInfo.ctrl_addr; + subchnl.cdsc_ctrl = qInfo.ctrl_addr >> 4; + subchnl.cdsc_trk = azt_bcd2bin(qInfo.track); + subchnl.cdsc_ind = azt_bcd2bin(qInfo.pointIndex); + if (subchnl.cdsc_format == CDROM_LBA) + { subchnl.cdsc_absaddr.lba = azt_msf2hsg(&qInfo.diskTime); + subchnl.cdsc_reladdr.lba = azt_msf2hsg(&qInfo.trackTime); + } + else if (subchnl.cdsc_format == CDROM_MSF) + { subchnl.cdsc_absaddr.msf.minute = azt_bcd2bin(qInfo.diskTime.min); + subchnl.cdsc_absaddr.msf.second = azt_bcd2bin(qInfo.diskTime.sec); + subchnl.cdsc_absaddr.msf.frame = azt_bcd2bin(qInfo.diskTime.frame); + subchnl.cdsc_reladdr.msf.minute = azt_bcd2bin(qInfo.trackTime.min); + subchnl.cdsc_reladdr.msf.second = azt_bcd2bin(qInfo.trackTime.sec); + subchnl.cdsc_reladdr.msf.frame = azt_bcd2bin(qInfo.trackTime.frame); + } + else + return -EINVAL; + memcpy_tofs((void *) arg, &subchnl, sizeof subchnl); + break; + case CDROMVOLCTRL: /* Volume control + * With my Aztech CD268-01A volume control does not work, I can only + turn the channels on (any value !=0) or off (value==0). Maybe it + works better with your drive */ + st=verify_area(VERIFY_READ,(void *) arg, sizeof(volctrl)); + if (st) return (st); + memcpy_fromfs(&volctrl,(char *) arg,sizeof(volctrl)); + azt_Play.start.min = 0x21; + azt_Play.start.sec = 0x84; + azt_Play.start.frame = volctrl.channel0; + azt_Play.end.min = volctrl.channel1; + azt_Play.end.sec = volctrl.channel2; + azt_Play.end.frame = volctrl.channel3; + sendAztCmd(ACMD_SET_VOLUME, &azt_Play); + STEN_LOW_WAIT; + break; + case CDROMEJECT: + aztUnlockDoor(); /* Assume user knows what they're doing */ + /* all drives can at least stop! */ + if (aztAudioStatus == CDROM_AUDIO_PLAY) + { if (aztSendCmd(ACMD_STOP)) return -1; + STEN_LOW_WAIT; + } + if (aztSendCmd(ACMD_EJECT)) return -1; + aztAudioStatus = CDROM_AUDIO_NO_STATUS; + break; + case CDROMREADMODE1: /*read data in mode 1 (2048 Bytes)*/ + case CDROMREADMODE2: /*read data in mode 2 (2336 Bytes)*/ +/*Take care, the following code is not compatible with other CD-ROM drivers, + use it at your own risk with cdplay.c. Normally it is not activated, as + AZT_PRIVATE_IOCTLS is not defined +*/ +#ifdef AZT_PRIVATE_IOCTLS + { st = verify_area(VERIFY_READ, (void *) arg, sizeof msf); + if (st) return st; + st = verify_area(VERIFY_WRITE, (void *) arg, sizeof buf); + if (st) return st; + memcpy_fromfs(&msf, (void *) arg, sizeof msf); + /* convert to bcd */ + azt_bin2bcd(&msf.cdmsf_min0); + azt_bin2bcd(&msf.cdmsf_sec0); + azt_bin2bcd(&msf.cdmsf_frame0); + msf.cdmsf_min1=0; + msf.cdmsf_sec1=0; + msf.cdmsf_frame1=1; /*read only one frame*/ + azt_Play.start.min = msf.cdmsf_min0; + azt_Play.start.sec = msf.cdmsf_sec0; + azt_Play.start.frame = msf.cdmsf_frame0; + azt_Play.end.min = msf.cdmsf_min1; + azt_Play.end.sec = msf.cdmsf_sec1; + azt_Play.end.frame = msf.cdmsf_frame1; + if (cmd==CDROMREADMODE1) + { sendAztCmd(ACMD_DATA_READ, &azt_Play); + DTEN_LOW; + insb(DATA_PORT,buf,2048); + memcpy_tofs((void *) arg, &buf, 2048); + } + else /*CDROMREADMODE2*/ + { sendAztCmd(ACMD_DATA_READ_RAW, &azt_Play); + DTEN_LOW; + insb(DATA_PORT,buf,2336); + memcpy_tofs((void *) arg, &buf, 2336); + } + } +#endif /*end of incompatible code*/ + break; + default: + return -EINVAL; + } +#ifdef AZT_DEBUG + printk("aztcd: exiting aztcd_ioctl\n"); +#endif + return 0; +} + + +/* + * Take care of the different block sizes between cdrom and Linux. + * When Linux gets variable block sizes this will probably go away. + */ +static void azt_transfer(void) +{ +#ifdef AZT_TEST + printk("aztcd: executing azt_transfer\n"); +#endif + if (CURRENT_VALID) { + while (CURRENT -> nr_sectors) { + int bn = CURRENT -> sector / 4; + int i; + for (i = 0; i < AZT_BUF_SIZ && azt_buf_bn[i] != bn; ++i) + ; + if (i < AZT_BUF_SIZ) { + int offs = (i * 4 + (CURRENT -> sector & 3)) * 512; + int nr_sectors = 4 - (CURRENT -> sector & 3); + if (azt_buf_out != i) { + azt_buf_out = i; + if (azt_buf_bn[i] != bn) { + azt_buf_out = -1; + continue; + } + } + if (nr_sectors > CURRENT -> nr_sectors) + nr_sectors = CURRENT -> nr_sectors; + memcpy(CURRENT -> buffer, azt_buf + offs, nr_sectors * 512); + CURRENT -> nr_sectors -= nr_sectors; + CURRENT -> sector += nr_sectors; + CURRENT -> buffer += nr_sectors * 512; + } else { + azt_buf_out = -1; + break; + } + } + } +} + + +static void do_aztcd_request(void) +{ +#ifdef AZT_TEST + printk(" do_aztcd_request(%ld+%ld)\n", CURRENT -> sector, CURRENT -> nr_sectors); +#endif + azt_transfer_is_active = 1; + while (CURRENT_VALID) { + if (CURRENT->bh) { + if (!CURRENT->bh->b_lock) + panic(DEVICE_NAME ": block not locked"); + } + azt_transfer(); + if (CURRENT -> nr_sectors == 0) { + end_request(1); + } else { + azt_buf_out = -1; /* Want to read a block not in buffer */ + if (azt_state == AZT_S_IDLE) { + if (!aztTocUpToDate) { + if (aztUpdateToc() < 0) { + while (CURRENT_VALID) + end_request(0); + break; + } + } + azt_state = AZT_S_START; + AztTries = 5; + SET_TIMER(azt_poll, 1); + } + break; + } + } + azt_transfer_is_active = 0; +#ifdef AZT_TEST2 + printk("azt_next_bn:%x azt_buf_in:%x azt_buf_out:%x azt_buf_bn:%x\n", \ + azt_next_bn, azt_buf_in, azt_buf_out, azt_buf_bn[azt_buf_in]); + printk(" do_aztcd_request ends\n"); +#endif + +} + +static void azt_poll(void) +{ + int st = 0; + int loop_ctl = 1; + int skip = 0; + + if (azt_error) { /* ???*/ + if (aztSendCmd(ACMD_GET_ERROR)) return; + STEN_LOW; + azt_error=inb(DATA_PORT)&0xFF; + printk("aztcd: I/O error 0x%02x\n", azt_error); + azt_invalidate_buffers(); +#ifdef WARN_IF_READ_FAILURE + if (AztTries == 5) + printk("aztcd: read of block %d failed - maybe audio disk?\n", azt_next_bn); +#endif + if (!AztTries--) { + printk("aztcd: read of block %d failed, maybe audio disk? Giving up\n", azt_next_bn); + if (azt_transfer_is_active) { + AztTries = 0; + loop_ctl = 0; + } + if (CURRENT_VALID) + end_request(0); + AztTries = 5; + } + azt_error = 0; + azt_state = AZT_S_STOP; + } + + while (loop_ctl) + { + loop_ctl = 0; /* each case must flip this back to 1 if we want + to come back up here */ + switch (azt_state) { + + case AZT_S_IDLE: +#ifdef AZT_TEST3 + if (azt_state!=azt_state_old) { + azt_state_old=azt_state; + printk("AZT_S_IDLE\n"); + } +#endif + return; + + case AZT_S_START: +#ifdef AZT_TEST3 + if (azt_state!=azt_state_old) { + azt_state_old=azt_state; + printk("AZT_S_START\n"); + } +#endif + + if(aztSendCmd(ACMD_GET_STATUS)) return; /*result will be checked by aztStatus() */ + azt_state = azt_mode == 1 ? AZT_S_READ : AZT_S_MODE; + AztTimeout = 3000; + break; + + case AZT_S_MODE: +#ifdef AZT_TEST3 + if (azt_state!=azt_state_old) { + azt_state_old=azt_state; + printk("AZT_S_MODE\n"); + } +#endif + if (!skip) { + if ((st = aztStatus()) != -1) { + if (st & AST_DSK_CHG) { + aztDiskChanged = 1; + aztTocUpToDate = 0; + azt_invalidate_buffers(); + } + } else break; + } + skip = 0; + + if ((st & AST_DOOR_OPEN)||(st & AST_NOT_READY)) { + aztDiskChanged = 1; + aztTocUpToDate = 0; + printk((st & AST_DOOR_OPEN) ? "aztcd: door open\n" : "aztcd: disk removed\n"); + if (azt_transfer_is_active) { + azt_state = AZT_S_START; + loop_ctl = 1; /* goto immediately */ + break; + } + azt_state = AZT_S_IDLE; + while (CURRENT_VALID) + end_request(0); + return; + } + /*???*/ + if (aztSendCmd(ACMD_SET_MODE)) return; + outb(0x01, DATA_PORT); /*Mode 1*/ + PA_OK; + STEN_LOW; + if (aztSendCmd(ACMD_GET_STATUS)) return; + azt_mode = 1; + azt_state = AZT_S_READ; + AztTimeout = 3000; + + break; + + + case AZT_S_READ: +#ifdef AZT_TEST3 + if (azt_state!=azt_state_old) { + azt_state_old=azt_state; + printk("AZT_S_READ\n"); + } +#endif + if (!skip) { + if ((st = aztStatus()) != -1) { + if (st & AST_DSK_CHG) { + aztDiskChanged = 1; + aztTocUpToDate = 0; + azt_invalidate_buffers(); + } + } else break; + } + + skip = 0; + if ((st & AST_DOOR_OPEN)||(st & AST_NOT_READY)) { + aztDiskChanged = 1; + aztTocUpToDate = 0; + printk((st & AST_DOOR_OPEN) ? "aztcd: door open\n" : "aztcd: disk removed\n"); + if (azt_transfer_is_active) { + azt_state = AZT_S_START; + loop_ctl = 1; + break; + } + azt_state = AZT_S_IDLE; + while (CURRENT_VALID) + end_request(0); + return; + } + + if (CURRENT_VALID) { + struct azt_Play_msf msf; + azt_next_bn = CURRENT -> sector / 4; + azt_hsg2msf(azt_next_bn, &msf.start); + azt_read_count=AZT_BUF_SIZ; /*??? fast, because we read ahead*/ +/* azt_read_count= CURRENT->nr_sectors; slow +*/ + msf.end.min = 0; + msf.end.sec = 0; + msf.end.frame = azt_read_count ;/*Mitsumi here reads 0xffffff sectors*/ +#ifdef AZT_TEST3 + printk("---reading msf-address %x:%x:%x %x:%x:%x\n",msf.start.min,msf.start.sec,msf.start.frame,msf.end.min,msf.end.sec,msf.end.frame); + printk("azt_next_bn:%x azt_buf_in:%x azt_buf_out:%x azt_buf_bn:%x\n", \ + azt_next_bn, azt_buf_in, azt_buf_out, azt_buf_bn[azt_buf_in]); +#endif + sendAztCmd(ACMD_DATA_READ, &msf); + azt_state = AZT_S_DATA; + AztTimeout = READ_TIMEOUT; + } else { + azt_state = AZT_S_STOP; + loop_ctl = 1; + break; + } + + break; + + + case AZT_S_DATA: +#ifdef AZT_TEST3 + if (azt_state!=azt_state_old) { + azt_state_old=azt_state; + printk("AZT_S_DATA\n"); + } +#endif + + st = inb(STATUS_PORT) & AFL_STATUSorDATA; /*???*/ + + switch (st) { + + case AFL_DATA: +#ifdef AZT_TEST3 + if (st!=azt_st_old) { + azt_st_old=st; + printk("---AFL_DATA st:%x\n",st); + } +#endif +#ifdef WARN_IF_READ_FAILURE + if (AztTries == 5) + printk("aztcd: read of block %d failed - maybe audio disk?\n", azt_next_bn); +#endif + if (!AztTries--) { + printk("aztcd: read of block %d failed, maybe audio disk ? Giving up\n", azt_next_bn); + if (azt_transfer_is_active) { + AztTries = 0; + break; + } + if (CURRENT_VALID) + end_request(0); + AztTries = 5; + } + azt_state = AZT_S_START; + AztTimeout = READ_TIMEOUT; + loop_ctl = 1; + break; + + case AFL_STATUSorDATA: +#ifdef AZT_TEST3 + if (st!=azt_st_old) { + azt_st_old=st; + printk("---AFL_STATUSorDATA st:%x\n",st); + } +#endif + break; + + default: +#ifdef AZT_TEST3 + if (st!=azt_st_old) { + azt_st_old=st; + printk("---default: st:%x\n",st); + } +#endif + AztTries = 5; + if (!CURRENT_VALID && azt_buf_in == azt_buf_out) { + azt_state = AZT_S_STOP; + loop_ctl = 1; + break; + } + if (azt_read_count<=0) + printk("aztcd: warning - try to read 0 frames\n"); + while (azt_read_count) /*??? fast read ahead loop*/ + { azt_buf_bn[azt_buf_in] = -1; + DTEN_LOW; /*??? unsolved problem, very + seldom we get timeouts + here, don't now the real + reason. With my drive this + sometimes also happens with + Aztech's original driver under + DOS. Is it a hardware bug? + I tried to recover from such + situations here. Zimmermann*/ + if (aztTimeOutCount>=AZT_TIMEOUT) + { printk("read_count:%d CURRENT->nr_sectors:%ld azt_buf_in:%d\n", azt_read_count,CURRENT->nr_sectors,azt_buf_in); + printk("azt_transfer_is_active:%x\n",azt_transfer_is_active); + azt_read_count=0; + azt_state = AZT_S_STOP; + loop_ctl = 1; + end_request(1); /*should we have here (1) or (0)? */ + } + else + { insb(DATA_PORT, azt_buf + 2048 * azt_buf_in, 2048); + azt_read_count--; +#ifdef AZT_TEST3 + printk("AZT_S_DATA; ---I've read data- read_count: %d\n",azt_read_count); + printk("azt_next_bn:%d azt_buf_in:%d azt_buf_out:%d azt_buf_bn:%d\n", \ + azt_next_bn, azt_buf_in, azt_buf_out, azt_buf_bn[azt_buf_in]); +#endif + azt_buf_bn[azt_buf_in] = azt_next_bn++; + if (azt_buf_out == -1) + azt_buf_out = azt_buf_in; + azt_buf_in = azt_buf_in + 1 == AZT_BUF_SIZ ? 0 : azt_buf_in + 1; + } + } + if (!azt_transfer_is_active) { + while (CURRENT_VALID) { + azt_transfer(); + if (CURRENT -> nr_sectors == 0) + end_request(1); + else + break; + } + } + + if (CURRENT_VALID + && (CURRENT -> sector / 4 < azt_next_bn || + CURRENT -> sector / 4 > azt_next_bn + AZT_BUF_SIZ)) { + azt_state = AZT_S_STOP; + loop_ctl = 1; + break; + } + AztTimeout = READ_TIMEOUT; + if (azt_read_count==0) { + azt_state = AZT_S_STOP; /*???*/ + loop_ctl = 1; + break; + } + break; + } + break; + + + case AZT_S_STOP: +#ifdef AZT_TEST3 + if (azt_state!=azt_state_old) { + azt_state_old=azt_state; + printk("AZT_S_STOP\n"); + } +#endif + if (azt_read_count!=0) printk("aztcd: discard data=%x frames\n",azt_read_count); /*???*/ + while (azt_read_count!=0) { + int i; + if ( !(inb(STATUS_PORT) & AFL_DATA) ) { + for (i=0; i<2048; i++) { + inb(DATA_PORT); + } + } + azt_read_count--; + } + if (aztSendCmd(ACMD_GET_STATUS)) return; + azt_state = AZT_S_STOPPING; + AztTimeout = 1000; + break; + + case AZT_S_STOPPING: +#ifdef AZT_TEST3 + if (azt_state!=azt_state_old) { + azt_state_old=azt_state; + printk("AZT_S_STOPPING\n"); + } +#endif + + if ((st = aztStatus()) == -1 && AztTimeout) + break; + + if ((st != -1) && (st & AST_DSK_CHG)) { + aztDiskChanged = 1; + aztTocUpToDate = 0; + azt_invalidate_buffers(); + } + + +#ifdef AZT_TEST3 + printk("CURRENT_VALID %d azt_mode %d\n", + CURRENT_VALID, azt_mode); +#endif + + if (CURRENT_VALID) { + if (st != -1) { + if (azt_mode == 1) { + azt_state = AZT_S_READ; + loop_ctl = 1; + skip = 1; + break; + } else { + azt_state = AZT_S_MODE; + loop_ctl = 1; + skip = 1; + break; + } + } else { + azt_state = AZT_S_START; + AztTimeout = 1; + } + } else { + azt_state = AZT_S_IDLE; + return; + } + break; + + default: + printk("aztcd: invalid state %d\n", azt_state); + return; + } /* case */ + } /* while */ + + + if (!AztTimeout--) + { printk("aztcd: timeout in state %d\n", azt_state); + azt_state = AZT_S_STOP; + if (aztSendCmd(ACMD_STOP)) return; + STEN_LOW_WAIT; + }; + + SET_TIMER(azt_poll, 1); +} + +static void azt_invalidate_buffers(void) +{ int i; + +#ifdef AZT_DEBUG + printk("aztcd: executing azt_invalidate_buffers\n"); +#endif + for (i = 0; i < AZT_BUF_SIZ; ++i) + azt_buf_bn[i] = -1; + azt_buf_out = -1; +} + +/* + * Open the device special file. Check that a disk is in. + */ +int aztcd_open(struct inode *ip, struct file *fp) +{ int st; + +#ifdef AZT_DEBUG + printk("aztcd: starting aztcd_open\n"); +#endif + if (aztPresent == 0) + return -ENXIO; /* no hardware */ + + if (!azt_open_count && azt_state == AZT_S_IDLE) { + + azt_invalidate_buffers(); + + st = getAztStatus(); /* check drive status */ + if (st == -1) + return -EIO; /* drive doesn't respond */ + + if (st&AST_DOOR_OPEN) + { + /* close door, then get the status again. */ + aztCloseDoor(); + st = getAztStatus(); + } + + if ((st&AST_DOOR_OPEN)||(st&AST_NOT_READY)) /* no disk in drive or door open*/ + { /* Door should be closed, probably no disk in drive */ + printk("aztcd: no disk in drive or door open\n"); + return -EIO; + } + + if (aztUpdateToc() < 0) + return -EIO; + + } + ++azt_open_count; + MOD_INC_USE_COUNT; + aztLockDoor(); +#ifdef AZT_DEBUG + printk("aztcd: exiting aztcd_open\n"); +#endif + return 0; +} + + +/* + * On close, we flush all azt blocks from the buffer cache. + */ +static void aztcd_release(struct inode * inode, struct file * file) +{ +#ifdef AZT_DEBUG + printk("aztcd: executing aztcd_release\n"); + printk("inode: %p, inode->i_rdev: %x file: %p\n",inode,inode->i_rdev,file); +#endif + MOD_DEC_USE_COUNT; + if (!--azt_open_count) { + azt_invalidate_buffers(); + sync_dev(inode->i_rdev); /*??? isn't it a read only dev?*/ + invalidate_buffers(inode -> i_rdev); + aztUnlockDoor(); + CLEAR_TIMER; + } + return; +} + + +static struct file_operations azt_fops = { + NULL, /* lseek - default */ + block_read, /* read - general block-dev read */ + block_write, /* write - general block-dev write */ + NULL, /* readdir - bad */ + NULL, /* select */ + aztcd_ioctl, /* ioctl */ + NULL, /* mmap */ + aztcd_open, /* open */ + aztcd_release, /* release */ + NULL, /* fsync */ + NULL, /* fasync*/ + check_aztcd_media_change, /*media change*/ + NULL /* revalidate*/ +}; + +/* + * Test for presence of drive and initialize it. Called at boot time. + */ +#ifndef MODULE +unsigned long aztcd_init(unsigned long mem_start, unsigned long mem_end) +#else +int init_module(void) +#endif +{ long int count, max_count; + unsigned char result[50]; + int st; + + if (azt_port <= 0) { + printk("aztcd: no Aztech CD-ROM Initialization"); +#ifndef MODULE + return (mem_start); +#else + return -EIO; +#endif + } + printk("Aztech CD-ROM Init: Aztech, Orchid, Okano, Wearnes CD-ROM Driver\n"); + printk("Aztech CD-ROM Init: (C) 1994,1995 Werner Zimmermann\n"); + printk("Aztech CD-ROM Init: DriverVersion=%s BaseAddress=0x%x \n",AZT_VERSION,azt_port); + + if (check_region(azt_port, 4)) { + printk("aztcd: conflict, I/O port (%X) already used\n", + azt_port); +#ifndef MODULE + return (mem_start); +#else + return -EIO; +#endif + } + + /* check for card */ + outb(POLLED,MODE_PORT); /*???*/ + inb(CMD_PORT); + inb(CMD_PORT); + outb(ACMD_GET_VERSION,CMD_PORT); /*Try to get version info*/ + STEN_LOW; + if (inb(DATA_PORT)!=AFL_OP_OK) /*OP_OK? If not, reset and try again*/ + { printk("aztcd: drive reset - please wait\n"); + for (count=0;count<50;count++) + { inb(STATUS_PORT); /*removing all data from earlier tries*/ + inb(DATA_PORT); + } + outb(POLLED,MODE_PORT); /*???*/ + inb(CMD_PORT); + inb(CMD_PORT); + outb(ACMD_SOFT_RESET,CMD_PORT); /*send reset*/ + STEN_LOW; + if (inb(DATA_PORT)!=AFL_OP_OK) /*OP_OK?*/ + { printk("aztcd: no AZTECH CD-ROM drive found\n"); +#ifndef MODULE + return (mem_start); +#else + return -EIO; +#endif + } + for (count = 0; count < AZT_TIMEOUT; count++); /* delay a bit */ + if ((st=getAztStatus())==-1) + { printk("aztcd: Drive Status Error Status=%x\n",st); +#ifndef MODULE + return (mem_start); +#else + return -EIO; +#endif + } +#ifdef AZT_DEBUG + printk("aztcd: Status = %x\n",st); +#endif + outb(POLLED,MODE_PORT); /*???*/ + inb(CMD_PORT); + inb(CMD_PORT); + outb(ACMD_GET_VERSION,CMD_PORT); /*GetVersion*/ + STEN_LOW; + OP_OK; + } + azt_init_end=1; + STEN_LOW; + result[0]=inb(DATA_PORT); /*reading in a null byte???*/ + for (count=1;count<50;count++) /*Reading version string*/ + { aztTimeOutCount=0; /*here we must implement STEN_LOW differently*/ + do { aztIndatum=inb(STATUS_PORT);/*because we want to exit by timeout*/ + aztTimeOutCount++; + if (aztTimeOutCount>=AZT_FAST_TIMEOUT) break; + } while (aztIndatum&AFL_STATUS); + if (aztTimeOutCount>=AZT_FAST_TIMEOUT) break; /*all chars read?*/ + result[count]=inb(DATA_PORT); + } + if (count>30) max_count=30; /*print max.30 chars of the version string*/ + else max_count=count; + printk("Aztech CD-ROM Init: FirmwareVersion="); + for (count=1;count min = hsg / 4500; + hsg %= 4500; + msf -> sec = hsg / 75; + msf -> frame = hsg % 75; +#ifdef AZT_DEBUG + if (msf->min >=70) printk("aztcd: Error hsg2msf address Minutes\n"); + if (msf->sec >=60) printk("aztcd: Error hsg2msf address Seconds\n"); + if (msf->frame>=75) printk("aztcd: Error hsg2msf address Frames\n"); +#endif + azt_bin2bcd(&msf -> min); /* convert to BCD */ + azt_bin2bcd(&msf -> sec); + azt_bin2bcd(&msf -> frame); +} + + +static void azt_bin2bcd(unsigned char *p) +{ int u, t; + + u = *p % 10; + t = *p / 10; + *p = u | (t << 4); +} + +static int azt_bcd2bin(unsigned char bcd) +{ return (bcd >> 4) * 10 + (bcd & 0xF); +} + + + +/* + * Read a value from the drive. Should return quickly, so a busy wait + * is used to avoid excessive rescheduling. The read command itself must + * be issued with aztSendCmd() directly before + */ +static int aztGetValue(unsigned char *result) +{ int s; + + STEN_LOW; + if (aztTimeOutCount>=AZT_TIMEOUT) + { printk("aztcd: aztGetValue timeout\n"); + return -1; + } + s = inb(DATA_PORT) & 0xFF; + *result = (unsigned char) s; + return 0; +} + + +/* + * Read the current Q-channel info. Also used for reading the + * table of contents. + */ +int aztGetQChannelInfo(struct azt_Toc *qp) +{ unsigned char notUsed; + int st; + +#ifdef AZT_DEBUG + printk("aztcd: starting aztGetQChannelInfo\n"); +#endif + if ((st=getAztStatus())==-1) return -1; + if (aztSendCmd(ACMD_GET_Q_CHANNEL)) return -1; + STEN_LOW_WAIT; + if (aztGetValue(¬Used) <0) return -1; /*Nullbyte ein-*/ + /*lesen ???*/ + if ((st&AST_MODE_BITS)==AST_INITIAL) + { qp->ctrl_addr=0; /* when audio stop ACMD_GET_Q_CHANNEL returns */ + qp->track=0; /* only one byte with Aztech drives */ + qp->pointIndex=0; + qp->trackTime.min=0; + qp->trackTime.sec=0; + qp->trackTime.frame=0; + qp->diskTime.min=0; + qp->diskTime.sec=0; + qp->diskTime.frame=0; + return 0; + } + else + { if (aztGetValue(&qp -> ctrl_addr) < 0) return -1; + if (aztGetValue(&qp -> track) < 0) return -1; + if (aztGetValue(&qp -> pointIndex) < 0) return -1; + if (aztGetValue(&qp -> trackTime.min) < 0) return -1; + if (aztGetValue(&qp -> trackTime.sec) < 0) return -1; + if (aztGetValue(&qp -> trackTime.frame) < 0) return -1; + if (aztGetValue(¬Used) < 0) return -1; + if (aztGetValue(&qp -> diskTime.min) < 0) return -1; + if (aztGetValue(&qp -> diskTime.sec) < 0) return -1; + if (aztGetValue(&qp -> diskTime.frame) < 0) return -1; + } +#ifdef AZT_DEBUG + printk("aztcd: exiting aztGetQChannelInfo\n"); +#endif + return 0; +} + +/* + * Read the table of contents (TOC) and TOC header if necessary + */ +static int aztUpdateToc() +{ +#ifdef AZT_DEBUG + printk("aztcd: starting aztUpdateToc\n"); +#endif + if (aztTocUpToDate) + return 0; + + if (aztGetDiskInfo() < 0) + return -EIO; + + if (aztGetToc() < 0) + return -EIO; + + aztTocUpToDate = 1; +#ifdef AZT_DEBUG + printk("aztcd: exiting aztUpdateToc\n"); +#endif + return 0; +} + + +/* + * Read the table of contents header + */ +static int aztGetDiskInfo() +{ int limit; + unsigned char test; + struct azt_Toc qInfo; + +#ifdef AZT_DEBUG + printk("aztcd: starting aztGetDiskInfo\n"); +#endif + if (aztSendCmd(ACMD_SEEK_TO_LEADIN)) return -1; + STEN_LOW_WAIT; + test=0; + for (limit=300;limit>0;limit--) + { if (aztGetQChannelInfo(&qInfo)<0) return -1; + if (qInfo.pointIndex==0xA0) /*Number of FirstTrack*/ + { DiskInfo.first=qInfo.diskTime.min; + DiskInfo.first = azt_bcd2bin(DiskInfo.first); + test=test|0x01; + } + if (qInfo.pointIndex==0xA1) /*Number of LastTrack*/ + { DiskInfo.last=qInfo.diskTime.min; + DiskInfo.last = azt_bcd2bin(DiskInfo.last); + test=test|0x02; + } + if (qInfo.pointIndex==0xA2) /*DiskLength*/ + { DiskInfo.diskLength.min=qInfo.diskTime.min; + DiskInfo.diskLength.sec=qInfo.diskTime.sec-2; + DiskInfo.diskLength.frame=qInfo.diskTime.frame; + test=test|0x04; + } + if ((qInfo.pointIndex==DiskInfo.first)&&(test&0x01)) /*StartTime of First Track*/ + { DiskInfo.firstTrack.min=qInfo.diskTime.min; + DiskInfo.firstTrack.sec=qInfo.diskTime.sec; + DiskInfo.firstTrack.frame=qInfo.diskTime.frame; + test=test|0x08; + } + if (test==0x0F) break; + } +#ifdef AZT_DEBUG +printk ("aztcd: exiting aztGetDiskInfo\n"); +printk("Disk Info: first %d last %d length %02x:%02x.%02x first %02x:%02x.%02x\n", + DiskInfo.first, + DiskInfo.last, + DiskInfo.diskLength.min, + DiskInfo.diskLength.sec, + DiskInfo.diskLength.frame, + DiskInfo.firstTrack.min, + DiskInfo.firstTrack.sec, + DiskInfo.firstTrack.frame); +#endif + if (test!=0x0F) return -1; + return 0; +} + + +/* + * Read the table of contents (TOC) + */ +static int aztGetToc() +{ int i, px; + int limit; + struct azt_Toc qInfo; + +#ifdef AZT_DEBUG + printk("aztcd: starting aztGetToc\n"); +#endif + for (i = 0; i < MAX_TRACKS; i++) + Toc[i].pointIndex = 0; + + i = DiskInfo.last + 3; + +/* Is there a good reason to stop motor before TOC read? + if (aztSendCmd(ACMD_STOP)) return -1; + STEN_LOW_WAIT; +*/ + + azt_mode = 0x05; + if (aztSendCmd(ACMD_SEEK_TO_LEADIN)) return -1; /*???*/ + STEN_LOW_WAIT; + + for (limit = 300; limit > 0; limit--) + { + if (aztGetQChannelInfo(&qInfo) < 0) + break; + + px = azt_bcd2bin(qInfo.pointIndex); + if (px > 0 && px < MAX_TRACKS && qInfo.track == 0) + if (Toc[px].pointIndex == 0) + { + Toc[px] = qInfo; + i--; + } + + if (i <= 0) + break; + } + + Toc[DiskInfo.last + 1].diskTime = DiskInfo.diskLength; + +#ifdef AZT_DEBUG +printk("aztcd: exiting aztGetToc\n"); +for (i = 1; i <= DiskInfo.last+1; i++) +printk("i = %2d ctl-adr = %02X track %2d px %02X %02X:%02X.%02X %02X:%02X.%02X\n", +i, Toc[i].ctrl_addr, Toc[i].track, Toc[i].pointIndex, +Toc[i].trackTime.min, Toc[i].trackTime.sec, Toc[i].trackTime.frame, +Toc[i].diskTime.min, Toc[i].diskTime.sec, Toc[i].diskTime.frame); +for (i = 100; i < 103; i++) +printk("i = %2d ctl-adr = %02X track %2d px %02X %02X:%02X.%02X %02X:%02X.%02X\n", +i, Toc[i].ctrl_addr, Toc[i].track, Toc[i].pointIndex, +Toc[i].trackTime.min, Toc[i].trackTime.sec, Toc[i].trackTime.frame, +Toc[i].diskTime.min, Toc[i].diskTime.sec, Toc[i].diskTime.frame); +#endif + + return limit > 0 ? 0 : -1; +} + +#ifdef MODULE +void cleanup_module(void) +{ if (MOD_IN_USE) + { printk("aztcd module in use - can't remove it.\n"); + return; + } + if ((unregister_blkdev(MAJOR_NR, "aztcd") == -EINVAL)) + { printk("What's that: can't unregister aztcd\n"); + return; + } + release_region(azt_port,4); + printk("aztcd module released.\n"); +} +#endif MODULE diff --git a/drivers/block/blk.h b/drivers/block/blk.h index 53d55b17d..797c6a95e 100644 --- a/drivers/block/blk.h +++ b/drivers/block/blk.h @@ -3,16 +3,12 @@ #include #include +#include /* * NR_REQUEST is the number of entries in the request-queue. * NOTE that writes may use only the low 2/3 of these: reads * take precedence. - * - * 32 seems to be a reasonable number: enough to get some benefit - * from the elevator-mechanism, but not so much as to lock a lot of - * buffers when they are in the queue. 64 seems to be too many (easily - * long pauses in reading when heavy writing/syncing is going on) */ #define NR_REQUEST 64 @@ -30,16 +26,31 @@ * These will have to be changed to be aware of different buffer * sizes etc.. It actually needs a major cleanup. */ +#ifdef IDE_DRIVER +#define SECTOR_MASK ((BLOCK_SIZE >> 9) - 1) +#else #define SECTOR_MASK (blksize_size[MAJOR_NR] && \ blksize_size[MAJOR_NR][MINOR(CURRENT->dev)] ? \ ((blksize_size[MAJOR_NR][MINOR(CURRENT->dev)] >> 9) - 1) : \ ((BLOCK_SIZE >> 9) - 1)) +#endif /* IDE_DRIVER */ #define SUBSECTOR(block) (CURRENT->current_nr_sectors > 0) -extern unsigned long hd_init(unsigned long mem_start, unsigned long mem_end); extern unsigned long cdu31a_init(unsigned long mem_start, unsigned long mem_end); extern unsigned long mcd_init(unsigned long mem_start, unsigned long mem_end); +#ifdef CONFIG_AZTCD +extern unsigned long aztcd_init(unsigned long mem_start, unsigned long mem_end); +#endif +#ifdef CONFIG_CDU535 +extern unsigned long sony535_init(unsigned long mem_start, unsigned long mem_end); +#endif +#ifdef CONFIG_BLK_DEV_HD +extern unsigned long hd_init(unsigned long mem_start, unsigned long mem_end); +#endif +#ifdef CONFIG_BLK_DEV_IDE +extern unsigned long ide_init(unsigned long mem_start, unsigned long mem_end); +#endif #ifdef CONFIG_SBPCD extern unsigned long sbpcd_init(unsigned long, unsigned long); #endif CONFIG_SBPCD @@ -64,14 +75,19 @@ extern unsigned long xd_init(unsigned long mem_start, unsigned long mem_end); case BLKROGET: { int __err = verify_area(VERIFY_WRITE, (void *) (where), sizeof(long)); \ if (!__err) put_fs_long(0!=is_read_only(dev),(long *) (where)); return __err; } -#ifdef MAJOR_NR +#if defined(MAJOR_NR) || defined(IDE_DRIVER) /* - * Add entries as needed. Currently the only block devices - * supported are hard-disks and floppies. + * Add entries as needed. */ -#if (MAJOR_NR == MEM_MAJOR) +#ifdef IDE_DRIVER + +#define DEVICE_NR(device) (MINOR(device) >> PARTN_BITS) +#define DEVICE_ON(device) /* nothing */ +#define DEVICE_OFF(device) /* nothing */ + +#elif (MAJOR_NR == MEM_MAJOR) /* ram disk */ #define DEVICE_NAME "ramdisk" @@ -117,7 +133,7 @@ static void floppy_off(unsigned int nr); #define DEVICE_NAME "scsitape" #define DEVICE_INTR do_st -#define DEVICE_NR(device) (MINOR(device)) +#define DEVICE_NR(device) (MINOR(device) & 0x7f) #define DEVICE_ON(device) #define DEVICE_OFF(device) @@ -155,6 +171,23 @@ static void floppy_off(unsigned int nr); #define DEVICE_ON(device) #define DEVICE_OFF(device) +#elif (MAJOR_NR == AZTECH_CDROM_MAJOR) + +#define DEVICE_NAME "Aztech CD-ROM" +#define DEVICE_REQUEST do_aztcd_request +#define DEVICE_NR(device) (MINOR(device)) +#define DEVICE_ON(device) +#define DEVICE_OFF(device) + +#elif (MAJOR_NR == CDU535_CDROM_MAJOR) + +#define DEVICE_NAME "SONY-CDU535" +#define DEVICE_INTR do_cdu535 +#define DEVICE_REQUEST do_cdu535_request +#define DEVICE_NR(device) (MINOR(device)) +#define DEVICE_ON(device) +#define DEVICE_OFF(device) + #elif (MAJOR_NR == MATSUSHITA_CDROM_MAJOR) #define DEVICE_NAME "Matsushita CD-ROM controller #1" @@ -187,9 +220,9 @@ static void floppy_off(unsigned int nr); #define DEVICE_ON(device) #define DEVICE_OFF(device) -#endif +#endif /* MAJOR_NR == whatever */ -#if (MAJOR_NR != SCSI_TAPE_MAJOR) +#if (MAJOR_NR != SCSI_TAPE_MAJOR) && !defined(IDE_DRIVER) #ifndef CURRENT #define CURRENT (blk_dev[MAJOR_NR].current_request) @@ -215,27 +248,50 @@ if ((DEVICE_INTR = (x)) != NULL) \ else \ CLEAR_TIMER; -#else +#else /* !DEVICE_TIMEOUT */ #define SET_INTR(x) (DEVICE_INTR = (x)) -#endif +#endif /* DEVICE_TIMEOUT */ + static void (DEVICE_REQUEST)(void); +#ifdef DEVICE_INTR +#define CLEAR_INTR SET_INTR(NULL) +#else +#define CLEAR_INTR +#endif + +#define INIT_REQUEST \ + if (!CURRENT) {\ + CLEAR_INTR; \ + return; \ + } \ + if (MAJOR(CURRENT->dev) != MAJOR_NR) \ + panic(DEVICE_NAME ": request list destroyed"); \ + if (CURRENT->bh) { \ + if (!CURRENT->bh->b_lock) \ + panic(DEVICE_NAME ": block not locked"); \ + } + +#endif /* (MAJOR_NR != SCSI_TAPE_MAJOR) && !defined(IDE_DRIVER) */ + /* end_request() - SCSI devices have their own version */ #if ! SCSI_MAJOR(MAJOR_NR) -static void end_request(int uptodate) -{ - struct request * req; +#ifdef IDE_DRIVER +static void end_request(byte uptodate, byte hwif) { + struct request *req = ide_cur_rq[HWIF]; +#else +static void end_request(int uptodate) { + struct request *req = CURRENT; +#endif /* IDE_DRIVER */ struct buffer_head * bh; - req = CURRENT; req->errors = 0; if (!uptodate) { - printk(DEVICE_NAME " I/O error\n"); - printk("dev %04lX, sector %lu\n", + printk("end_request: I/O error, dev %04lX, sector %lu\n", (unsigned long)req->dev, req->sector); req->nr_sectors--; req->nr_sectors &= ~SECTOR_MASK; @@ -259,34 +315,19 @@ static void end_request(int uptodate) return; } } +#ifdef IDE_DRIVER + ide_cur_rq[HWIF] = NULL; +#else DEVICE_OFF(req->dev); CURRENT = req->next; +#endif /* IDE_DRIVER */ if (req->sem != NULL) up(req->sem); req->dev = -1; wake_up(&wait_for_request); } -#endif +#endif /* ! SCSI_MAJOR(MAJOR_NR) */ -#ifdef DEVICE_INTR -#define CLEAR_INTR SET_INTR(NULL) -#else -#define CLEAR_INTR -#endif +#endif /* defined(MAJOR_NR) || defined(IDE_DRIVER) */ -#define INIT_REQUEST \ - if (!CURRENT) {\ - CLEAR_INTR; \ - return; \ - } \ - if (MAJOR(CURRENT->dev) != MAJOR_NR) \ - panic(DEVICE_NAME ": request list destroyed"); \ - if (CURRENT->bh) { \ - if (!CURRENT->bh->b_lock) \ - panic(DEVICE_NAME ": block not locked"); \ - } - -#endif - -#endif -#endif +#endif /* _BLK_H */ diff --git a/drivers/block/cdu31a.c b/drivers/block/cdu31a.c index e4d789e4d..06847baf1 100644 --- a/drivers/block/cdu31a.c +++ b/drivers/block/cdu31a.c @@ -78,16 +78,9 @@ * * Multi-Session * - * A multi-session disk is treated like a partitioned disk, each session - * has it's own minor device number, starting with 0. The support is - * pretty transparent, music, TOC operations, and read operations should - * all work transparently on any session. Note that since the driver - * writer doesn't have a multi-session disk, this is all theoretical. - * Also, music operation will obviously only work on one session at a - * time. - * - * NOTE: At the current time, multi-session still doesn't work. Maybe - * I'll get a multi-session disk soon so I can play with it. + * A multi-session disk looks just like a normal disk to the user. + * Just mount one normally, and all the data should be there. + * A special thanks to Koen for help with this! * * Raw sector I/O * @@ -226,7 +219,7 @@ static struct static int handle_sony_cd_attention(void); static int read_subcode(void); -static void sony_get_toc(int dev); +static void sony_get_toc(void); static int scd_open(struct inode *inode, struct file *filp); static void do_sony_cd_cmd(unsigned char cmd, unsigned char *params, @@ -274,9 +267,12 @@ static unsigned int sony_usage = 0; /* How many processes have the static int sony_pas_init = 0; /* Initialize the Pro-Audio Spectrum card? */ -static struct s_sony_session_toc *(ses_tocs[MAX_TRACKS]); /* Points to the - table of - contents. */ +static struct s_sony_session_toc *sony_toc; /* Points to the + table of + contents. */ + +static int sony_toc_read = 0; /* Has the TOC been read for + the drive? */ static struct s_sony_subcode * volatile last_sony_subcode; /* Points to the last subcode address read */ @@ -317,11 +313,10 @@ static struct wait_queue *cdu31a_irq_wait = NULL; static int curr_control_reg = 0; /* Current value of the control register */ -/* A disk changed variable for every possible session. When a disk change - is detected, these will all be set to TRUE. As the upper layers ask - for disk_changed status for individual minor numbers, they will be - cleared. */ -static char disk_changed[MAX_TRACKS]; +/* A disk changed variable. When a disk change is detected, it will + all be set to TRUE. As the upper layers ask for disk_changed status + it will be cleared. */ +static char disk_changed; /* Variable for using the readahead buffer. The readahead buffer is used for raw sector reads and for blocksizes that are smaller @@ -338,18 +333,10 @@ static int readahead_bad = 0; static int scd_disk_change(dev_t full_dev) { - int retval, target; - - - target = MINOR(full_dev); - - if (target >= MAX_TRACKS) { - printk("Sony CD-ROM request error: invalid device.\n"); - return 0; - } + int retval; - retval = disk_changed[target]; - disk_changed[target] = 0; + retval = disk_changed; + disk_changed = 0; return retval; } @@ -373,7 +360,7 @@ disable_interrupts(void) } static void -cdu31a_interrupt(int unused) +cdu31a_interrupt(int irq, struct pt_regs *regs) { disable_interrupts(); if (cdu31a_irq_wait != NULL) @@ -859,7 +846,6 @@ handle_sony_cd_attention(void) { unsigned char atten_code; static int num_consecutive_attentions = 0; - int i; if (is_attention()) @@ -879,15 +865,8 @@ handle_sony_cd_attention(void) { /* Someone changed the CD. Mark it as changed */ case SONY_MECH_LOADED_ATTN: - for (i=0; i= ses_tocs[dev]->lead_out_start_lba) + else if ((sector + nsect) >= sony_toc->lead_out_start_lba) { - read_size = ses_tocs[dev]->lead_out_start_lba - sector; + read_size = sony_toc->lead_out_start_lba - sector; } /* Read the full readahead amount. */ else @@ -1374,7 +1352,6 @@ static void do_cdu31a_request(void) { int block; - unsigned int dev; int nblock; unsigned char res_reg[12]; unsigned int res_size; @@ -1407,7 +1384,7 @@ do_cdu31a_request(void) cdu31a_request_startover: /* * The beginning here is stolen from the hard disk driver. I hope - * its right. + * it's right. */ if (!(CURRENT) || CURRENT->dev < 0) { @@ -1440,36 +1417,24 @@ cdu31a_request_startover: } } - dev = MINOR(CURRENT->dev); block = CURRENT->sector; nblock = CURRENT->nr_sectors; - /* Check for multi-session disks. */ - if (dev > MAX_TRACKS) - { - printk("CDU31A: Invalid device request: %d\n", dev); - end_request(0); - goto cdu31a_request_startover; - } - else if (ses_tocs[dev] == NULL) + if (!sony_toc_read) { - printk("CDU31A: session TOC not read: %d\n", dev); + printk("CDU31A: TOC not read\n"); end_request(0); goto cdu31a_request_startover; } - /* Check for base read of multi-session disk. */ - if ((dev != 0) && (block == 64)) + /* Check for base read of multi-session disk. This will still work + for single session disks, so just do it. Blocks less than 80 + are for the volume info, so offset them by the start track (which + should be zero for a single-session disk). */ + if (block < 80) { - if (ses_tocs[dev]->first_track_num == ses_tocs[dev]->last_track_num) - { - printk("CDU31A: Not a multi-session disk: %d\n", dev); - end_request(0); - goto cdu31a_request_startover; - } - /* Offset the request into the session. */ - block += (ses_tocs[dev]->start_track_lba * 4); + block += (sony_toc->start_track_lba * 4); } switch(CURRENT->cmd) @@ -1479,19 +1444,21 @@ cdu31a_request_startover: * If the block address is invalid or the request goes beyond the end of * the media, return an error. */ - if ((block / 4) < ses_tocs[dev]->start_track_lba) +#if 0 + if ((block / 4) < sony_toc->start_track_lba) { printk("CDU31A: Request before beginning of media\n"); end_request(0); goto cdu31a_request_startover; } - if ((block / 4) >= ses_tocs[dev]->lead_out_start_lba) +#endif + if ((block / 4) >= sony_toc->lead_out_start_lba) { printk("CDU31A: Request past end of media\n"); end_request(0); goto cdu31a_request_startover; } - if (((block + nblock) / 4) >= ses_tocs[dev]->lead_out_start_lba) + if (((block + nblock) / 4) >= sony_toc->lead_out_start_lba) { printk("CDU31A: Request past end of media\n"); end_request(0); @@ -1504,9 +1471,9 @@ try_read_again: while (handle_sony_cd_attention()) ; - if (ses_tocs[dev] == NULL) + if (!sony_toc_read) { - printk("CDU31A: session TOC not read: %d\n", dev); + printk("CDU31A: TOC not read\n"); end_request(0); goto cdu31a_request_startover; } @@ -1515,7 +1482,7 @@ try_read_again: next request. */ if (sony_blocks_left == 0) { - if (start_request(block / 4, CDU31A_READAHEAD / 4, 0, dev)) + if (start_request(block / 4, CDU31A_READAHEAD / 4, 0)) { end_request(0); goto cdu31a_request_startover; @@ -1532,13 +1499,13 @@ try_read_again: sony_next_block); #endif abort_read(); - if (ses_tocs[dev] == NULL) + if (!sony_toc_read) { - printk("CDU31A: session TOC not read: %d\n", dev); + printk("CDU31A: TOC not read\n"); end_request(0); goto cdu31a_request_startover; } - else if (start_request(block / 4, CDU31A_READAHEAD / 4, 0, dev)) + if (start_request(block / 4, CDU31A_READAHEAD / 4, 0)) { end_request(0); goto cdu31a_request_startover; @@ -1581,7 +1548,7 @@ try_read_again: } end_do_cdu31a_request: -#if 0 +#if 1 /* After finished, cancel any pending operations. */ abort_read(); #endif @@ -1614,93 +1581,137 @@ mcovlp(char *dst, * successful. */ static void -sony_get_toc(int dev) +sony_get_toc(void) { + unsigned char res_reg[2]; unsigned int res_size; unsigned char parms[1]; + int session; - if (dev >= MAX_TRACKS) - { - printk("CDU31A: Request for invalid TOC track: %d\n", dev); - } - else if (ses_tocs[dev] == NULL) +#if DEBUG + printk("Entering sony_get_toc\n"); +#endif + + if (!sony_toc_read) { - ses_tocs[dev] = kmalloc(sizeof(struct s_sony_session_toc), 0); - if (ses_tocs[dev] == NULL) - { - printk("CDU31A: Unable to alloc mem for TOC\n"); - } - else + /* The idea here is we keep asking for sessions until the command + fails. Then we know what the last valid session on the disk is. + No need to check session 0, since session 0 is the same as session + 1; the command returns different information if you give it 0. + Don't check session 1 because that is the first session, it must + be there. */ + session = 2; + while (1) { - parms[0] = dev+1; - do_sony_cd_cmd(SONY_REQ_TOC_DATA_SPEC_CMD, +#if DEBUG + printk("Trying session %d\n", session); +#endif + parms[0] = session; + do_sony_cd_cmd(SONY_READ_TOC_SPEC_CMD, parms, 1, - (unsigned char *) ses_tocs[dev], + res_reg, &res_size); - if ((res_size < 2) || ((ses_tocs[dev]->exec_status[0] & 0xf0) == 0x20)) - { - kfree_s(ses_tocs[dev], sizeof(struct s_sony_session_toc)); - ses_tocs[dev] = NULL; - return; - } - /* For points that do not exist, move the data over them - to the right location. */ - if (ses_tocs[dev]->pointb0 != 0xb0) - { - mcovlp(((char *) ses_tocs[dev]) + 27, - ((char *) ses_tocs[dev]) + 18, - res_size - 18); - res_size += 9; - } - if (ses_tocs[dev]->pointb1 != 0xb1) - { - mcovlp(((char *) ses_tocs[dev]) + 36, - ((char *) ses_tocs[dev]) + 27, - res_size - 27); - res_size += 9; - } - if (ses_tocs[dev]->pointb2 != 0xb2) - { - mcovlp(((char *) ses_tocs[dev]) + 45, - ((char *) ses_tocs[dev]) + 36, - res_size - 36); - res_size += 9; - } - if (ses_tocs[dev]->pointb3 != 0xb3) - { - mcovlp(((char *) ses_tocs[dev]) + 54, - ((char *) ses_tocs[dev]) + 45, - res_size - 45); - res_size += 9; - } - if (ses_tocs[dev]->pointb4 != 0xb4) - { - mcovlp(((char *) ses_tocs[dev]) + 63, - ((char *) ses_tocs[dev]) + 54, - res_size - 54); - res_size += 9; +#if DEBUG + printk("%2.2x %2.2x\n", res_reg[0], res_reg[1]); +#endif + + if ((res_size < 2) || ((res_reg[0] & 0xf0) == 0x20)) + { + /* An error reading the TOC, this must be past the last session. */ + break; } - if (ses_tocs[dev]->pointc0 != 0xc0) + + session++; + + /* Let's not get carried away... */ + if (session > 20) { - mcovlp(((char *) ses_tocs[dev]) + 72, - ((char *) ses_tocs[dev]) + 63, - res_size - 63); - res_size += 9; + return; } + } + + session--; - ses_tocs[dev]->start_track_lba = msf_to_log(ses_tocs[dev]->tracks[0].track_start_msf); - ses_tocs[dev]->lead_out_start_lba = msf_to_log(ses_tocs[dev]->lead_out_start_msf); #if DEBUG - printk("Disk session %d, start track: %d, stop track: %d\n", - dev, - ses_tocs[dev]->start_track_lba, - ses_tocs[dev]->lead_out_start_lba); + printk("Reading session %d\n", session); #endif + + parms[0] = session; + do_sony_cd_cmd(SONY_REQ_TOC_DATA_SPEC_CMD, + parms, + 1, + (unsigned char *) sony_toc, + &res_size); + if ((res_size < 2) || ((sony_toc->exec_status[0] & 0xf0) == 0x20)) + { + /* An error reading the TOC. Return without sony_toc_read + set. */ + return; + } + + sony_toc_read = 1; + + /* For points that do not exist, move the data over them + to the right location. */ + if (sony_toc->pointb0 != 0xb0) + { + mcovlp(((char *) sony_toc) + 27, + ((char *) sony_toc) + 18, + res_size - 18); + res_size += 9; + } + if (sony_toc->pointb1 != 0xb1) + { + mcovlp(((char *) sony_toc) + 36, + ((char *) sony_toc) + 27, + res_size - 27); + res_size += 9; + } + if (sony_toc->pointb2 != 0xb2) + { + mcovlp(((char *) sony_toc) + 45, + ((char *) sony_toc) + 36, + res_size - 36); + res_size += 9; + } + if (sony_toc->pointb3 != 0xb3) + { + mcovlp(((char *) sony_toc) + 54, + ((char *) sony_toc) + 45, + res_size - 45); + res_size += 9; } + if (sony_toc->pointb4 != 0xb4) + { + mcovlp(((char *) sony_toc) + 63, + ((char *) sony_toc) + 54, + res_size - 54); + res_size += 9; + } + if (sony_toc->pointc0 != 0xc0) + { + mcovlp(((char *) sony_toc) + 72, + ((char *) sony_toc) + 63, + res_size - 63); + res_size += 9; + } + + sony_toc->start_track_lba = msf_to_log(sony_toc->tracks[0].track_start_msf); + sony_toc->lead_out_start_lba = msf_to_log(sony_toc->lead_out_start_msf); + +#if DEBUG + printk("Disk session %d, start track: %d, stop track: %d\n", + session, + sony_toc->start_track_lba, + sony_toc->lead_out_start_lba); +#endif } +#if DEBUG + printk("Leaving sony_get_toc\n"); +#endif } @@ -1708,17 +1719,16 @@ sony_get_toc(int dev) * Search for a specific track in the table of contents. */ static int -find_track(int track, - int dev) +find_track(int track) { int i; int num_tracks; - num_tracks = ses_tocs[dev]->last_track_num - ses_tocs[dev]->first_track_num + 1; + num_tracks = sony_toc->last_track_num - sony_toc->first_track_num + 1; for (i = 0; i < num_tracks; i++) { - if (ses_tocs[dev]->tracks[i].track == track) + if (sony_toc->tracks[i].track == track) { return i; } @@ -1761,8 +1771,7 @@ read_subcode(void) * (not BCD), so all the conversions are done. */ static int -sony_get_subchnl_info(long arg, - int dev) +sony_get_subchnl_info(long arg) { struct cdrom_subchnl schi; @@ -1771,8 +1780,8 @@ sony_get_subchnl_info(long arg, while (handle_sony_cd_attention()) ; - sony_get_toc(dev); - if (ses_tocs[dev] == NULL) + sony_get_toc(); + if (!sony_toc_read) { return -EIO; } @@ -2025,7 +2034,7 @@ read_audio(struct cdrom_read_audio *ra, retval = 0; /* start_request clears out any readahead data, so it should be safe. */ - if (start_request(ra->addr.lba, ra->nframes, 1, 0)) + if (start_request(ra->addr.lba, ra->nframes, 1)) { retval = -EIO; goto exit_read_audio; @@ -2064,7 +2073,7 @@ read_audio(struct cdrom_read_audio *ra, } /* Restart the request on the current frame. */ - if (start_request(ra->addr.lba + cframe, ra->nframes - cframe, 1, 0)) + if (start_request(ra->addr.lba + cframe, ra->nframes - cframe, 1)) { retval = -EIO; goto exit_read_audio; @@ -2164,7 +2173,6 @@ scd_ioctl(struct inode *inode, unsigned int cmd, unsigned long arg) { - unsigned int dev; unsigned char res_reg[12]; unsigned int res_size; unsigned char params[7]; @@ -2175,11 +2183,6 @@ scd_ioctl(struct inode *inode, { return -EINVAL; } - dev = MINOR(inode->i_rdev); - if (dev > MAX_TRACKS) - { - return -EINVAL; - } switch (cmd) { @@ -2289,16 +2292,16 @@ scd_ioctl(struct inode *inode, struct cdrom_tochdr *hdr; struct cdrom_tochdr loc_hdr; - sony_get_toc(dev); - if (ses_tocs[dev] == NULL) + sony_get_toc(); + if (!sony_toc_read) { return -EIO; } hdr = (struct cdrom_tochdr *) arg; verify_area(VERIFY_WRITE, hdr, sizeof(*hdr)); - loc_hdr.cdth_trk0 = bcd_to_int(ses_tocs[dev]->first_track_num); - loc_hdr.cdth_trk1 = bcd_to_int(ses_tocs[dev]->last_track_num); + loc_hdr.cdth_trk0 = bcd_to_int(sony_toc->first_track_num); + loc_hdr.cdth_trk1 = bcd_to_int(sony_toc->last_track_num); memcpy_tofs(hdr, &loc_hdr, sizeof(*hdr)); } return 0; @@ -2311,8 +2314,8 @@ scd_ioctl(struct inode *inode, int track_idx; unsigned char *msf_val = NULL; - sony_get_toc(dev); - if (ses_tocs[dev] == NULL) + sony_get_toc(); + if (!sony_toc_read) { return -EIO; } @@ -2326,21 +2329,21 @@ scd_ioctl(struct inode *inode, /* Lead out is handled separately since it is special. */ if (loc_entry.cdte_track == CDROM_LEADOUT) { - loc_entry.cdte_adr = ses_tocs[dev]->address2; - loc_entry.cdte_ctrl = ses_tocs[dev]->control2; - msf_val = ses_tocs[dev]->lead_out_start_msf; + loc_entry.cdte_adr = sony_toc->address2; + loc_entry.cdte_ctrl = sony_toc->control2; + msf_val = sony_toc->lead_out_start_msf; } else { - track_idx = find_track(int_to_bcd(loc_entry.cdte_track), dev); + track_idx = find_track(int_to_bcd(loc_entry.cdte_track)); if (track_idx < 0) { return -EINVAL; } - loc_entry.cdte_adr = ses_tocs[dev]->tracks[track_idx].address; - loc_entry.cdte_ctrl = ses_tocs[dev]->tracks[track_idx].control; - msf_val = ses_tocs[dev]->tracks[track_idx].track_start_msf; + loc_entry.cdte_adr = sony_toc->tracks[track_idx].address; + loc_entry.cdte_ctrl = sony_toc->tracks[track_idx].control; + msf_val = sony_toc->tracks[track_idx].track_start_msf; } /* Logical buffer address or MSF format requested? */ @@ -2364,8 +2367,8 @@ scd_ioctl(struct inode *inode, struct cdrom_ti ti; int track_idx; - sony_get_toc(dev); - if (ses_tocs[dev] == NULL) + sony_get_toc(); + if (!sony_toc_read) { return -EIO; } @@ -2373,39 +2376,39 @@ scd_ioctl(struct inode *inode, verify_area(VERIFY_READ, (char *) arg, sizeof(ti)); memcpy_fromfs(&ti, (char *) arg, sizeof(ti)); - if ( (ti.cdti_trk0 < ses_tocs[dev]->first_track_num) - || (ti.cdti_trk0 > ses_tocs[dev]->last_track_num) + if ( (ti.cdti_trk0 < sony_toc->first_track_num) + || (ti.cdti_trk0 > sony_toc->last_track_num) || (ti.cdti_trk1 < ti.cdti_trk0)) { return -EINVAL; } - track_idx = find_track(int_to_bcd(ti.cdti_trk0), dev); + track_idx = find_track(int_to_bcd(ti.cdti_trk0)); if (track_idx < 0) { return -EINVAL; } - params[1] = ses_tocs[dev]->tracks[track_idx].track_start_msf[0]; - params[2] = ses_tocs[dev]->tracks[track_idx].track_start_msf[1]; - params[3] = ses_tocs[dev]->tracks[track_idx].track_start_msf[2]; + params[1] = sony_toc->tracks[track_idx].track_start_msf[0]; + params[2] = sony_toc->tracks[track_idx].track_start_msf[1]; + params[3] = sony_toc->tracks[track_idx].track_start_msf[2]; /* * If we want to stop after the last track, use the lead-out * MSF to do that. */ - if (ti.cdti_trk1 >= bcd_to_int(ses_tocs[dev]->last_track_num)) + if (ti.cdti_trk1 >= bcd_to_int(sony_toc->last_track_num)) { - log_to_msf(msf_to_log(ses_tocs[dev]->lead_out_start_msf)-1, + log_to_msf(msf_to_log(sony_toc->lead_out_start_msf)-1, &(params[4])); } else { - track_idx = find_track(int_to_bcd(ti.cdti_trk1+1), dev); + track_idx = find_track(int_to_bcd(ti.cdti_trk1+1)); if (track_idx < 0) { return -EINVAL; } - log_to_msf(msf_to_log(ses_tocs[dev]->tracks[track_idx].track_start_msf)-1, + log_to_msf(msf_to_log(sony_toc->tracks[track_idx].track_start_msf)-1, &(params[4])); } params[0] = 0x03; @@ -2430,7 +2433,7 @@ scd_ioctl(struct inode *inode, } case CDROMSUBCHNL: /* Get subchannel info */ - return sony_get_subchnl_info(arg, dev); + return sony_get_subchnl_info(arg); case CDROMVOLCTRL: /* Volume control. What volume does this change, anyway? */ { @@ -2471,8 +2474,8 @@ scd_ioctl(struct inode *inode, struct cdrom_read_audio ra; - sony_get_toc(dev); - if (ses_tocs[dev] == NULL) + sony_get_toc(); + if (!sony_toc_read) { return -EIO; } @@ -2484,8 +2487,8 @@ scd_ioctl(struct inode *inode, if (ra.addr_format == CDROM_LBA) { - if ( (ra.addr.lba >= ses_tocs[dev]->lead_out_start_lba) - || (ra.addr.lba + ra.nframes >= ses_tocs[dev]->lead_out_start_lba)) + if ( (ra.addr.lba >= sony_toc->lead_out_start_lba) + || (ra.addr.lba + ra.nframes >= sony_toc->lead_out_start_lba)) { return -EINVAL; } @@ -2502,8 +2505,8 @@ scd_ioctl(struct inode *inode, ra.addr.lba = ( (ra.addr.msf.minute * 4500) + (ra.addr.msf.second * 75) + ra.addr.msf.frame); - if ( (ra.addr.lba >= ses_tocs[dev]->lead_out_start_lba) - || (ra.addr.lba + ra.nframes >= ses_tocs[dev]->lead_out_start_lba)) + if ( (ra.addr.lba >= sony_toc->lead_out_start_lba) + || (ra.addr.lba + ra.nframes >= sony_toc->lead_out_start_lba)) { return -EINVAL; } @@ -2541,7 +2544,6 @@ scd_open(struct inode *inode, unsigned int res_size; int num_spin_ups; unsigned char params[2]; - int dev; if ((filp) && filp->f_mode & 2) @@ -2568,7 +2570,7 @@ respinup_on_open: it. It seems to mean the drive has already done the operation. */ if ((res_size < 2) || ((res_reg[0] != 0) && (res_reg[1] != 0))) { - /* If the drive is already playing, its ok. */ + /* If the drive is already playing, it's ok. */ if ((res_reg[1] == SONY_AUDIO_PLAYING_ERR) || (res_reg[1] == 0)) { goto drive_spinning; @@ -2589,9 +2591,8 @@ respinup_on_open: return -EIO; } - dev = MINOR(inode->i_rdev); - sony_get_toc(dev); - if (ses_tocs[dev] == NULL) + sony_get_toc(); + if (!sony_toc_read) { do_sony_cd_cmd(SONY_SPIN_DOWN_CMD, NULL, 0, res_reg, &res_size); return -EIO; @@ -2599,7 +2600,7 @@ respinup_on_open: /* For XA on the CDU31A only, we have to do special reads. The CDU33A handles XA automagically. */ - if ( (ses_tocs[dev]->disk_type == SONY_XA_DISK_TYPE) + if ( (sony_toc->disk_type == SONY_XA_DISK_TYPE) && (!is_double_speed)) { params[0] = SONY_SD_DECODE_PARAM; @@ -2724,7 +2725,7 @@ get_drive_configuration(unsigned short base_io, if (read_status_register() != 0xff) { /* - * Reset the drive and wait for attention from it (to say its reset). + * Reset the drive and wait for attention from it (to say it's reset). * If you don't wait, the next operation will probably fail. */ reset_drive(); @@ -2863,7 +2864,7 @@ cdu31a_init(unsigned long mem_start, unsigned long mem_end) if (drive_found) { - snarf_region(sony_cd_base_io, 4); + request_region(sony_cd_base_io, 4,"cdu31a"); if (register_blkdev(MAJOR_NR,"cdu31a",&scd_fops)) { @@ -2954,13 +2955,12 @@ cdu31a_init(unsigned long mem_start, unsigned long mem_end) mem_start += sizeof(*last_sony_subcode); readahead_buffer = (unsigned char *) mem_start; mem_start += CD_FRAMESIZE_RAW; + sony_toc = (struct s_sony_session_toc *) mem_start; + mem_start += sizeof(struct s_sony_session_toc); } - for (i=0; i +/* do print messages for unexpected interrupts */ +static int print_unex=1; + #ifndef FD_MODULE /* the following is the mask of allowed drives. By default units 2 and * 3 of both floppy controllers are disabled, because switching on the * motor of these drives causes system hangs on some PCI computers. drive * 0 is the low bit (0x1), and drive 7 is the high bit (0x80). Bits are on if * a drive is allowed. */ -#ifdef CONFIG_FLOPPY_23 -#define ALLOWED_DRIVE_MASK 0xff -#else -#define ALLOWED_DRIVE_MASK 0x33 -#endif +static int ALLOWED_DRIVE_MASK=0x33; #define FLOPPY_IRQ 6 #define FLOPPY_DMA 2 #define FDC1 0x3f0 -#define FDC2 0x370 +static int FDC2=-1; #endif #define MODULE_AWARE_DRIVER -#ifdef CONFIG_BLK_DEV_FD #include #include #include @@ -127,6 +127,7 @@ #include #include #include +#include /* CMOS defines */ #include #include @@ -140,15 +141,44 @@ static unsigned int fake_change = 0; static int initialising=1; +#define FLOPPY0_TYPE ((CMOS_READ(0x10) >> 4) & 15) +#define FLOPPY1_TYPE (CMOS_READ(0x10) & 15) -#ifdef CONFIG_FLOPPY_2_FDC #define N_FDC 2 #define N_DRIVE 8 -#else -#define N_FDC 1 -#define N_DRIVE 4 + +/* + * Again, the CMOS information doesn't work on the alpha.. + */ +#ifdef __alpha__ +#undef FLOPPY0_TYPE +#undef FLOPPY1_TYPE +#define FLOPPY0_TYPE 6 +#define FLOPPY1_TYPE 0 #endif +/* + * And on Mips's it doesn't work too. + */ +#ifdef __mips__ +#include +#include +#include +#include +#include +#undef FLOPPY0_TYPE +#undef FLOPPY1_TYPE +#undef FDC1 +#define FLOPPY0_TYPE 4 /* this is wrong for the Olli M700, but who cares... */ +#define FLOPPY1_TYPE 0 +#define FDC1 ((boot_info.machtype == MACH_ACER_PICA_61 || \ + boot_info.machtype == MACH_MIPS_MAGNUM_4000 || \ + boot_info.machtype == MACH_OLIVETTI_M700) ? \ + 0xe0003000 : PORT_BASE + 0x3f0) +#undef N_FDC +#define N_FDC 1 /* do you *really* want a second controller ? */ +#endif /* __mips__ */ + #define TYPE(x) ( ((x)>>2) & 0x1f ) #define DRIVE(x) ( ((x)&0x03) | (((x)&0x80 ) >> 5)) #define UNIT(x) ( (x) & 0x03 ) /* drive on fdc */ @@ -171,16 +201,16 @@ static int initialising=1; #define USETF(x) (set_bit(x##_BIT, &UDRS->flags)) #define UTESTF(x) (test_bit(x##_BIT, &UDRS->flags)) -#define DPRINT(x) printk(DEVICE_NAME "%d: " x,current_drive); +#define DPRINT(x) printk(DEVICE_NAME "%d: " x,current_drive) #define DPRINT1(x,x1) \ -printk(DEVICE_NAME "%d: " x,current_drive,(x1)); +printk(DEVICE_NAME "%d: " x,current_drive,(x1)) #define DPRINT2(x,x1,x2) \ -printk(DEVICE_NAME "%d: " x,current_drive,(x1),(x2)); +printk(DEVICE_NAME "%d: " x,current_drive,(x1),(x2)) #define DPRINT3(x,x1,x2,x3) \ -printk(DEVICE_NAME "%d: " x,current_drive,(x1),(x2),(x3)); +printk(DEVICE_NAME "%d: " x,current_drive,(x1),(x2),(x3)) /* read/write */ #define COMMAND raw_cmd.cmd[0] @@ -217,6 +247,8 @@ printk(DEVICE_NAME "%d: " x,current_drive,(x1),(x2),(x3)); * Went back to the 1MB limit, as some people had problems with the floppy * driver otherwise. It doesn't matter much for performance anyway, as most * floppy accesses go through the track buffer. + * On MIPSes, this actually means that *all* transfers go thru the + * track buffer since 0x1000000 is always smaller than KSEG0/1. */ #define LAST_DMA_ADDR (0x1000000) #define K_64 (0x10000) /* 64 k */ @@ -236,6 +268,9 @@ static int inr; /* size of reply buffer, when called from interrupt */ #define R_SECTOR (reply_buffer[5]) #define R_SIZECODE (reply_buffer[6]) +#define SEL_DLY (2*HZ/100) + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof( (x)[0] )) /* * this struct defines the different floppy drive types. */ @@ -249,38 +284,38 @@ static struct { | | Head load time, msec | | | Head unload time, msec (not used) | | | | Step rate interval, usec - | | | | | Time needed for spinup time (jiffies) - | | | | | | Timeout for spinning down (jiffies) - | | | | | | | Spindown offset (where disk stops) - | | | | | | | | Select delay - | | | | | | | | | RPS - | | | | | | | | | | Max number of tracks - | | | | | | | | | | | Interrupt timeout - | | | | | | | | | | | | Max nonintlv. sectors - | | | | | | | | | | | | | -Max Errors- flags */ -{{0, 500, 16, 16, 8000, 100, 300, 0, 2, 5, 80, 3*HZ, 20, {3,1,2,0,2}, 0, - 0, { 7, 4, 8, 2, 1, 5, 3,10}, 150, 0 }, "unknown" }, - -{{1, 300, 16, 16, 8000, 100, 300, 0, 2, 5, 40, 3*HZ, 17, {3,1,2,0,2}, 0, - 0, { 1, 0, 0, 0, 0, 0, 0, 0}, 150, 1 }, "360K PC" }, /*5 1/4 360 KB PC*/ - -{{2, 500, 16, 16, 6000, 40, 300, 14, 2, 6, 83, 3*HZ, 17, {3,1,2,0,2}, 0, - 0, { 2, 5, 6,23,10,20,11, 0}, 150, 2 }, "1.2M" }, /*5 1/4 HD AT*/ - -{{3, 250, 16, 16, 3000, 100, 300, 0, 2, 5, 83, 3*HZ, 20, {3,1,2,0,2}, 0, - 0, { 4,22,21,30, 3, 0, 0, 0}, 150, 4 }, "720k" }, /*3 1/2 DD*/ - -{{4, 500, 16, 16, 4000, 40, 300, 10, 2, 5, 83, 3*HZ, 20, {3,1,2,0,2}, 0, - 0, { 7, 4,25,22,31,21,29,11}, 150, 7 }, "1.44M" }, /*3 1/2 HD*/ - -{{5, 1000, 15, 8, 3000, 40, 300, 10, 2, 5, 83, 3*HZ, 40, {3,1,2,0,2}, 0, - 0, { 7, 8, 4,25,28,22,31,21}, 150, 8 }, "2.88M AMI BIOS" }, /*3 1/2 ED*/ - -{{6, 1000, 15, 8, 3000, 40, 300, 10, 2, 5, 83, 3*HZ, 40, {3,1,2,0,2}, 0, - 0, { 7, 8, 4,25,28,22,31,21}, 150, 8 }, "2.88M" } /*3 1/2 ED*/ -/* | ---autodetected formats-- | | | - read_track | | Name printed when booting - | Native format + | | | | | Time needed for spinup time (jiffies) + | | | | | | Timeout for spinning down (jiffies) + | | | | | | | Spindown offset (where disk stops) + | | | | | | | | Select delay + | | | | | | | | | RPS + | | | | | | | | | | Max number of tracks + | | | | | | | | | | | Interrupt timeout + | | | | | | | | | | | | Max nonintlv. sectors + | | | | | | | | | | | | | -Max Errors- flags */ +{{0, 500, 16, 16, 8000, 1*HZ, 3*HZ, 0, SEL_DLY, 5, 80, 3*HZ, 20, {3,1,2,0,2}, 0, + 0, { 7, 4, 8, 2, 1, 5, 3,10}, 3*HZ/2, 0 }, "unknown" }, + +{{1, 300, 16, 16, 8000, 1*HZ, 3*HZ, 0, SEL_DLY, 5, 40, 3*HZ, 17, {3,1,2,0,2}, 0, + 0, { 1, 0, 0, 0, 0, 0, 0, 0}, 3*HZ/2, 1 }, "360K PC" }, /*5 1/4 360 KB PC*/ + +{{2, 500, 16, 16, 6000, 4*HZ/10, 3*HZ, 14, SEL_DLY, 6, 83, 3*HZ, 17, {3,1,2,0,2}, 0, + 0, { 2, 5, 6,23,10,20,11, 0}, 3*HZ/2, 2 }, "1.2M" }, /*5 1/4 HD AT*/ + +{{3, 250, 16, 16, 3000, 1*HZ, 3*HZ, 0, SEL_DLY, 5, 83, 3*HZ, 20, {3,1,2,0,2}, 0, + 0, { 4,22,21,30, 3, 0, 0, 0}, 3*HZ/2, 4 }, "720k" }, /*3 1/2 DD*/ + +{{4, 500, 16, 16, 4000, 4*HZ/10, 3*HZ, 10, SEL_DLY, 5, 83, 3*HZ, 20, {3,1,2,0,2}, 0, + 0, { 7, 4,25,22,31,21,29,11}, 3*HZ/2, 7 }, "1.44M" }, /*3 1/2 HD*/ + +{{5, 1000, 15, 8, 3000, 4*HZ/10, 3*HZ, 10, SEL_DLY, 5, 83, 3*HZ, 40, {3,1,2,0,2}, 0, + 0, { 7, 8, 4,25,28,22,31,21}, 3*HZ/2, 8 }, "2.88M AMI BIOS" }, /*3 1/2 ED*/ + +{{6, 1000, 15, 8, 3000, 4*HZ/10, 3*HZ, 10, SEL_DLY, 5, 83, 3*HZ, 40, {3,1,2,0,2}, 0, + 0, { 7, 8, 4,25,28,22,31,21}, 3*HZ/2, 8 }, "2.88M" } /*3 1/2 ED*/ +/* | ---autodetected formats-- | | | + read_track | | Name printed when booting + | Native format Frequency of disk change checks */ }; @@ -339,11 +374,8 @@ static struct floppy_struct floppy_type[32] = { /* Auto-detection: Disk type used until the next media change occurs. */ struct floppy_struct *current_type[N_DRIVE] = { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL -#ifdef CONFIG_FLOPPY_2_FDC - , - NULL, NULL, NULL, NULL -#endif }; /* @@ -479,6 +511,107 @@ static inline void debugt(char *message) #endif } +/* + * Port access functions + */ + +#ifdef CONFIG_MIPS_JAZZ +static inline unsigned int fd_in(unsigned int port) +{ + if (port >= JAZZ_LOCAL_IO_SPACE) { + return (*(volatile unsigned char *)port); + udelay(1); + } + else + return inb_p(port); +} + +static inline void fd_out(unsigned char value, unsigned int port) +{ + if (port >= JAZZ_LOCAL_IO_SPACE) { + *(volatile unsigned char *)port = value; + udelay(1); + } + else + outb_p(value, port); +} +#else /* !CONFIG_MIPS_JAZZ */ +#define fd_in(port) inb_p(port) +#define fd_out(port,value) outb_p(port,value) +#endif + +/* + * DMA control stuff. If MIPS_JAZZ support is compiled in, we have to + * check if we're running on such a board. If not, we just call the + * standard PC macros and functions. + */ + +#ifdef CONFIG_MIPS_JAZZ +#define fd_enable_dma() ((boot_info.machtype == MACH_ACER_PICA_61 || \ + boot_info.machtype == MACH_MIPS_MAGNUM_4000) ? \ + vdma_enable(JAZZ_FLOPPY_DMA) : \ + enable_dma(FLOPPY_DMA)) +#define fd_disable_dma() ((boot_info.machtype == MACH_ACER_PICA_61 || \ + boot_info.machtype == MACH_MIPS_MAGNUM_4000) ? \ + vdma_disable(JAZZ_FLOPPY_DMA) : \ + disable_dma(FLOPPY_DMA)) +#define fd_request_dma() ((boot_info.machtype == MACH_ACER_PICA_61 || \ + boot_info.machtype == MACH_MIPS_MAGNUM_4000) ? \ + 0 : request_dma(FLOPPY_DMA,"floppy")) +#define fd_free_dma() ((boot_info.machtype == MACH_ACER_PICA_61 || \ + boot_info.machtype == MACH_MIPS_MAGNUM_4000) ? \ + : free_dma(FLOPPY_DMA)) +#define fd_clear_dma_ff() ((boot_info.machtype == MACH_ACER_PICA_61 || \ + boot_info.machtype == MACH_MIPS_MAGNUM_4000) ? \ + : clear_dma_ff(FLOPPY_DMA)) +#define fd_set_dma_mode(mode) ((boot_info.machtype == MACH_ACER_PICA_61 || \ + boot_info.machtype == MACH_MIPS_MAGNUM_4000) ? \ + vdma_set_mode(JAZZ_FLOPPY_DMA,mode) : \ + set_dma_mode(FLOPPY_DMA,mode)) +#define fd_set_dma_addr(addr) ((boot_info.machtype == MACH_ACER_PICA_61 || \ + boot_info.machtype == MACH_MIPS_MAGNUM_4000) ? \ + vdma_set_addr(JAZZ_FLOPPY_DMA, \ + vdma_phys2log(PHYSADDR(addr))) : \ + set_dma_addr(FLOPPY_DMA,addr)) +#define fd_set_dma_count(count) ((boot_info.machtype == MACH_ACER_PICA_61 || \ + boot_info.machtype == MACH_MIPS_MAGNUM_4000) ? \ + vdma_set_count(JAZZ_FLOPPY_DMA,count) : \ + set_dma_count(FLOPPY_DMA,count)) +#define fd_get_dma_residue() ((boot_info.machtype == MACH_ACER_PICA_61 || \ + boot_info.machtype == MACH_MIPS_MAGNUM_4000) ? \ + vdma_get_residue(JAZZ_FLOPPY_DMA) : \ + get_dma_residue(FLOPPY_DMA)) +#define fd_enable_irq() ((boot_info.machtype == MACH_ACER_PICA_61 || \ + boot_info.machtype == MACH_MIPS_MAGNUM_4000) ? \ + : enable_irq(FLOPPY_IRQ)) +#define fd_disable_irq() ((boot_info.machtype == MACH_ACER_PICA_61 || \ + boot_info.machtype == MACH_MIPS_MAGNUM_4000) ? \ + : disable_irq(FLOPPY_IRQ)) +#define fd_cacheflush(addr,size) \ + ((boot_info.machtype == MACH_ACER_PICA_61 || \ + boot_info.machtype == MACH_MIPS_MAGNUM_4000) ? \ + sys_cacheflush((char *)addr, size, DCACHE) : empty() ) +#else +#define fd_enable_dma() enable_dma(FLOPPY_DMA) +#define fd_disable_dma() disable_dma(FLOPPY_DMA) +#define fd_request_dma() request_dma(FLOPPY_DMA,"floppy") +#define fd_free_dma() free_dma(FLOPPY_DMA) +#define fd_clear_dma_ff() clear_dma_ff(FLOPPY_DMA) +#define fd_set_dma_mode(mode) set_dma_mode(FLOPPY_DMA,mode) +#define fd_set_dma_addr(addr) set_dma_addr(FLOPPY_DMA,addr) +#define fd_set_dma_count(count) set_dma_count(FLOPPY_DMA,count) +#define fd_enable_irq() enable_irq(FLOPPY_IRQ) +#define fd_disable_irq() disable_irq(FLOPPY_IRQ) +#define fd_cacheflush(addr,size) /* nothing */ +#endif + +/* Just for coherency */ + +#define fd_request_irq() request_irq(FLOPPY_IRQ, floppy_interrupt, \ + SA_INTERRUPT, "floppy") +#define fd_free_irq() free_irq(FLOPPY_IRQ); + + /* * Bottom half floppy driver. * ========================== @@ -531,13 +664,13 @@ static int disk_change(int drive) if (UDP->flags & FD_DEBUG){ DPRINT1("checking disk change line for drive %d\n",drive); DPRINT1("jiffies=%ld\n", jiffies); - DPRINT1("disk change line=%x\n",inb_p(FD_DIR)&0x80); + DPRINT1("disk change line=%x\n",fd_in(FD_DIR)&0x80); DPRINT1("flags=%x\n",UDRS->flags); } #endif if (UDP->flags & FD_BROKEN_DCL) return UTESTF(FD_DISK_CHANGED); - if( (inb_p(FD_DIR) ^ UDP->flags) & 0x80){ + if( (fd_in(FD_DIR) ^ UDP->flags) & 0x80){ USETF(FD_VERIFY); /* verify write protection */ if(UDRS->maxblock){ /* mark it changed */ @@ -555,7 +688,7 @@ static int disk_change(int drive) } /*USETF(FD_DISK_NEWCHANGE);*/ return 1; - } else if(jiffies >= DRS->select_date+DP->select_delay){ + } else { UDRS->last_checked=jiffies; UCLEARF(FD_DISK_NEWCHANGE); } @@ -588,7 +721,8 @@ static int set_dor(int fdc, char mask, char data) disk_change(drive); } FDCS->dor = newdor; - outb_p(newdor, FD_DOR); +/* printk("setting DOR to %02x\n",(unsigned)newdor); */ + fd_out(newdor, FD_DOR); unit = newdor & 0x3; if(!is_selected(olddor, unit) && is_selected(newdor,unit)){ @@ -607,8 +741,8 @@ static void twaddle(void) { if (DP->select_delay) return; - outb_p(FDCS->dor & ~(0x10<dor, FD_DOR); + fd_out(FDCS->dor & ~(0x10<dor, FD_DOR); DRS->select_date = jiffies; } @@ -636,12 +770,10 @@ static void set_fdc(int drive) current_drive = drive; } set_dor(fdc,~0,8); -#ifdef CONFIG_FLOPPY_2_FDC set_dor(1-fdc, ~8, 0); -#endif if ( FDCS->rawcmd == 2 ) reset_fdc_info(1); - if( inb_p(FD_STATUS) != STATUS_READY ) + if( fd_in(FD_STATUS) != STATUS_READY ) FDCS->reset = 1; } @@ -704,14 +836,11 @@ static struct timer_list motor_off_timer[N_DRIVE] = { { NULL, NULL, 0, 0, motor_off_callback }, { NULL, NULL, 0, 1, motor_off_callback }, { NULL, NULL, 0, 2, motor_off_callback }, - { NULL, NULL, 0, 3, motor_off_callback } -#ifdef CONFIG_FLOPPY_2_FDC - , + { NULL, NULL, 0, 3, motor_off_callback }, { NULL, NULL, 0, 4, motor_off_callback }, { NULL, NULL, 0, 5, motor_off_callback }, { NULL, NULL, 0, 6, motor_off_callback }, { NULL, NULL, 0, 7, motor_off_callback } -#endif }; /* schedules motor off */ @@ -812,9 +941,50 @@ static int wait_for_completion(int delay, timeout_fn function) return 0; } +static int hlt_disabled=0; +static void floppy_disable_hlt(void) +{ + unsigned long flags; + save_flags(flags); + cli(); + if(!hlt_disabled){ + hlt_disabled=1; +#ifdef HAVE_DISABLE_HLT + disable_hlt(); +#endif + } + restore_flags(flags); +} + +static void floppy_enable_hlt(void) +{ + unsigned long flags; + save_flags(flags); + cli(); + if(hlt_disabled){ + hlt_disabled=0; +#ifdef HAVE_DISABLE_HLT + enable_hlt(); +#endif + } + restore_flags(flags); +} + + static void setup_DMA(void) { #ifdef CONFIG_FLOPPY_SANITY + if (raw_cmd.length == 0){ + int i; + + printk("zero dma transfer size:"); + for(i=0; i< raw_cmd.cmd_count; i++) + printk("%x,", raw_cmd.cmd[i]); + printk("\n"); + cont->done(0); + FDCS->reset = 1; + return; + } if ((!CURRENT || CURRENT->buffer != current_addr || raw_cmd.length > 512 * CURRENT->nr_sectors) && @@ -832,14 +1002,14 @@ static void setup_DMA(void) FDCS->reset=1; return; } - if ((long) current_addr % 512 ){ + if ((unsigned long) current_addr % 512 ){ printk("non aligned address: %p\n", current_addr ); cont->done(0); FDCS->reset=1; return; } - if ( ( (long)current_addr & ~(64*1024-1) ) != - ((long)(current_addr + raw_cmd.length-1) & ~(64*1024-1))){ + if ( ( (unsigned long)current_addr & ~(64*1024-1) ) != + ((unsigned long)(current_addr + raw_cmd.length-1) & ~(64*1024-1))){ printk("DMA crossing 64-K boundary %p-%p\n", current_addr, current_addr + raw_cmd.length); cont->done(0); @@ -849,15 +1019,15 @@ static void setup_DMA(void) #endif cli(); - disable_dma(FLOPPY_DMA); - clear_dma_ff(FLOPPY_DMA); - set_dma_mode(FLOPPY_DMA, - (raw_cmd.flags & FD_RAW_READ)? - DMA_MODE_READ : DMA_MODE_WRITE); - set_dma_addr(FLOPPY_DMA, (long) current_addr); - set_dma_count(FLOPPY_DMA, raw_cmd.length); - enable_dma(FLOPPY_DMA); + fd_disable_dma(); + fd_clear_dma_ff(); + fd_set_dma_mode((raw_cmd.flags & FD_RAW_READ)? + DMA_MODE_READ : DMA_MODE_WRITE); + fd_set_dma_addr((long) current_addr); + fd_set_dma_count(raw_cmd.length); + fd_enable_dma(); sti(); + floppy_disable_hlt(); } /* sends a command byte to the fdc */ @@ -868,12 +1038,13 @@ static int output_byte(char byte) if (FDCS->reset) return -1; - for(counter = 0 ; counter < 10000 && !FDCS->reset ; counter++) { - status = inb_p(FD_STATUS) &(STATUS_READY|STATUS_DIR|STATUS_DMA); +/* for(counter = 0 ; counter < 10000 && !FDCS->reset ; counter++) { */ + for(counter = 0 ; counter < 100000 && !FDCS->reset ; counter++) { + status = fd_in(FD_STATUS) &(STATUS_READY|STATUS_DIR|STATUS_DMA); if (!(status & STATUS_READY)) continue; if (status == STATUS_READY){ - outb_p(byte,FD_DATA); + fd_out(byte,FD_DATA); return 0; } else break; @@ -894,7 +1065,7 @@ static int result(void) if (FDCS->reset) return -1; for (counter = 0 ; counter < 10000 && !FDCS->reset ; counter++) { - status = inb_p(FD_STATUS)& + status = fd_in(FD_STATUS)& (STATUS_DIR|STATUS_READY|STATUS_BUSY|STATUS_DMA); if (!(status & STATUS_READY)) continue; @@ -907,7 +1078,7 @@ static int result(void) DPRINT("floppy_stat reply overrun\n"); break; } - reply_buffer[i++] = inb_p(FD_DATA); + reply_buffer[i++] = fd_in(FD_DATA); } } FDCS->reset = 1; @@ -993,7 +1164,7 @@ static void fdc_specify(void) /* TODO: lock this in via LOCK during initialization */ output_byte(FD_CONFIGURE); output_byte(0); - output_byte(0x1A); /* FIFO on, polling off, 10 byte threshold */ + output_byte(0x2A); /* FIFO on, polling off, 10 byte threshold */ output_byte(0); /* precompensation from track 0 upwards */ if ( FDCS->reset ){ FDCS->has_fifo=0; @@ -1063,7 +1234,7 @@ static int fdc_dtr(void) return 0; /* Set dtr */ - outb_p(raw_cmd.rate, FD_DCR); + fd_out(raw_cmd.rate, FD_DCR); /* TODO: some FDC/drive combinations (C&T 82C711 with TEAC 1.2MB) * need a stabilization period of several milliseconds to be @@ -1398,18 +1569,22 @@ static void unexpected_floppy_interrupt(void) int i; if ( initialising ) return; - DPRINT("unexpected interrupt\n"); - if ( inr >= 0 ) - for(i=0; i= 0 ) + for(i=0; ireset = 1; } @@ -1418,10 +1593,11 @@ struct tq_struct floppy_tq = { 0, 0, (void *) (void *) unexpected_floppy_interrupt, 0 }; /* interrupt handler */ -static void floppy_interrupt(int unused) +static void floppy_interrupt(int irq, struct pt_regs * regs) { void (*handler)(void) = DEVICE_INTR; + floppy_enable_hlt(); CLEAR_INTR; if ( fdc >= N_FDC || FDCS->address == -1){ /* we don't even know which FDC is the culprit */ @@ -1476,15 +1652,16 @@ static void reset_interrupt(void) */ static void reset_fdc(void) { +/* printk("in reset_fdc()\n"); */ SET_INTR(reset_interrupt); FDCS->reset = 0; reset_fdc_info(0); if ( FDCS->version >= FDC_82077 ) - outb_p(0x80 | ( FDCS->dtr &3), FD_STATUS); + fd_out(0x80 | ( FDCS->dtr &3), FD_STATUS); else { - outb_p(FDCS->dor & ~0x04, FD_DOR); + fd_out(FDCS->dor & ~0x04, FD_DOR); udelay(FD_RESET_DELAY); - outb(FDCS->dor, FD_DOR); + fd_out(FDCS->dor, FD_DOR); } } @@ -1500,11 +1677,13 @@ void show_floppy(void) printk("floppy driver state\n"); printk("-------------------\n"); for(i=0; iaddress != -1){ + printk("dor %d = %x\n", i, fdc_state[i].dor ); + fd_out(fdc_state[i].address+2, fdc_state[i].dor); + udelay(1000); /* maybe we'll catch an interrupt... */ + } } - printk("status=%x\n", inb_p(FD_STATUS)); + printk("status=%x\n", fd_in(FD_STATUS)); printk("fdc_busy=%d\n", fdc_busy); if( DEVICE_INTR) printk("DEVICE_INTR=%p\n", DEVICE_INTR); @@ -1529,7 +1708,8 @@ static void floppy_shutdown(void) floppy_tq.routine = (void *)(void *) empty; del_timer( &fd_timer); - disable_dma(FLOPPY_DMA); + floppy_enable_hlt(); + fd_disable_dma(); /* avoid dma going to a random drive after shutdown */ if(!initialising) @@ -1710,9 +1890,10 @@ static void failure_and_wakeup(void) static int next_valid_format(void) { int probed_format; + + probed_format = DRS->probed_format; while(1){ - probed_format = DRS->probed_format; - if ( probed_format > N_DRIVE || + if ( probed_format >= 8 || ! DP->autodetect[probed_format] ){ DRS->probed_format = 0; return 1; @@ -2098,7 +2279,7 @@ static void copy_buffer(int ssize, int max_sector, int max_sector_2) floppy_track_buffer + (max_buffer_sectors << 10) || dma_buffer < floppy_track_buffer ){ DPRINT1("buffer overrun in copy buffer %d\n", - (floppy_track_buffer - dma_buffer) >>9); + (int) ((floppy_track_buffer - dma_buffer) >>9)); printk("sector_t=%d buffer_min=%d\n", sector_t, buffer_min); printk("current_count_sectors=%ld\n", @@ -2109,13 +2290,17 @@ static void copy_buffer(int ssize, int max_sector, int max_sector_2) printk("write\n"); break; } - if ( ((int)buffer) % 512 ) + if ( ((unsigned long)buffer) % 512 ) DPRINT1("%p buffer not aligned\n", buffer); #endif - if ( CT(COMMAND) == FD_READ ) + if ( CT(COMMAND) == FD_READ ) { + fd_cacheflush(dma_buffer, size); memcpy( buffer, dma_buffer, size); - else + } + else { memcpy( dma_buffer, buffer, size); + fd_cacheflush(dma_buffer, size); + } remaining -= size; if ( !remaining) break; @@ -2246,19 +2431,19 @@ static int make_raw_rw_request(void) raw_cmd.flags &= ~FD_RAW_WRITE; raw_cmd.flags |= FD_RAW_READ; COMMAND = FM_MODE(floppy,FD_READ); - } else if ((long)CURRENT->buffer <= LAST_DMA_ADDR ) { + } else if ((unsigned long)CURRENT->buffer <= LAST_DMA_ADDR ) { int direct, indirect; indirect= transfer_size(ssize,max_sector,max_buffer_sectors*2) - sector_t; max_size = buffer_chain_size(); - if ( max_size > ( LAST_DMA_ADDR - ((long) CURRENT->buffer))>>9) - max_size=(LAST_DMA_ADDR - ((long)CURRENT->buffer))>>9; + if ( max_size > ( LAST_DMA_ADDR - ((unsigned long) CURRENT->buffer))>>9) + max_size=(LAST_DMA_ADDR - ((unsigned long)CURRENT->buffer))>>9; /* 64 kb boundaries */ - if ( ((max_size << 9) + ((long) CURRENT->buffer)) / K_64 != - ((long) CURRENT->buffer ) / K_64 ) - max_size = ( K_64 - ((long) CURRENT->buffer) % K_64)>>9; + if ( ((max_size << 9) + ((unsigned long) CURRENT->buffer)) / K_64 != + ((unsigned long) CURRENT->buffer ) / K_64 ) + max_size = ( K_64 - ((unsigned long) CURRENT->buffer) % K_64)>>9; direct = transfer_size(ssize,max_sector,max_size) - sector_t; /* * We try to read tracks, but if we get too many errors, we @@ -2267,15 +2452,21 @@ static int make_raw_rw_request(void) * This means we should be able to read a sector even if there * are other bad sectors on this track. */ - if ((indirect - sector_t) * 2 > (direct - sector_t) * 3 && - *errors < DP->max_errors.read_track && - /*!TESTF( FD_NEED_TWADDLE) &&*/ - ( ( !probing || (DP->read_track & - (1 <probed_format))))){ + if (!direct || + (indirect * 2 > direct * 3 && + *errors < DP->max_errors.read_track && + /*!TESTF( FD_NEED_TWADDLE) &&*/ + ((!probing || (DP->read_track&(1<probed_format)))))){ max_size = CURRENT->nr_sectors; } else { current_addr = CURRENT->buffer; raw_cmd.length = current_count_sectors << 9; + if (raw_cmd.length == 0){ + DPRINT("zero dma transfer attempted from make_raw_request\n"); + DPRINT3("indirect=%d direct=%d sector_t=%d", + indirect, direct, sector_t); + return 0; + } return 2; } } @@ -2286,6 +2477,7 @@ static int make_raw_rw_request(void) /* claim buffer track if needed */ if (buffer_track != raw_cmd.track || /* bad track */ buffer_drive !=current_drive || /* bad drive */ + sector_t > buffer_max || sector_t < buffer_min || ((CT(COMMAND) == FD_READ || (aligned_sector_t == sector_t && CURRENT->nr_sectors >= ssize ))&& @@ -2330,7 +2522,7 @@ static int make_raw_rw_request(void) raw_cmd.length, current_count_sectors); if ( current_addr != CURRENT->buffer ) printk("addr=%d, length=%ld\n", - (current_addr - floppy_track_buffer ) >> 9, + (int) ((current_addr - floppy_track_buffer ) >> 9), current_count_sectors); printk("st=%d ast=%d mse=%d msi=%d\n", sector_t, aligned_sector_t, max_sector, max_size); @@ -2371,6 +2563,10 @@ static int make_raw_rw_request(void) printk("bytes=%ld\n", raw_cmd.length >> 9 ); printk("sectors=%ld\n", current_count_sectors); } + if (raw_cmd.length == 0){ + DPRINT("zero dma transfer attempted from make_raw_request\n"); + return 0; + } #endif return 2; } @@ -2551,6 +2747,9 @@ static int fd_copyout(void *param, volatile void *address, int size) i = verify_area(VERIFY_WRITE,param,size); if (i) return i; + fd_cacheflush(address, size); /* is this necessary ??? */ + /* Ralf: Yes; only the l2 cache is completly chipset + controlled */ memcpy_tofs(param,(void *) address, size); return 0; } @@ -2612,6 +2811,10 @@ static int raw_cmd_ioctl(void *param) if (raw_cmd.flags & (FD_RAW_WRITE | FD_RAW_READ)){ if(count > max_buffer_sectors * 1024 ) return -ENOMEM; + if(count == 0){ + printk("attempt to do a 0 byte dma transfer\n"); + return -EINVAL; + } buffer_track = -1; } if ( raw_cmd.flags & FD_RAW_WRITE ){ @@ -2619,6 +2822,7 @@ static int raw_cmd_ioctl(void *param) if (i) return i; memcpy_fromfs(floppy_track_buffer, raw_cmd.data, count); + fd_cacheflush(floppy_track_buffer, count); } current_addr = floppy_track_buffer; @@ -2649,6 +2853,7 @@ static int raw_cmd_ioctl(void *param) return ret; if ( raw_cmd.flags & FD_RAW_READ ){ + fd_cacheflush(floppy_track_buffer, count); i=fd_copyout( raw_cmd.data, floppy_track_buffer, count); if (i) return i; @@ -2784,9 +2989,7 @@ static int fd_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, cnt < (type << 2 ) + 4 ; cnt++) floppy_sizes[cnt]= -#ifdef CONFIG_FLOPPY_2_FDC floppy_sizes[cnt+0x80]= -#endif floppy_type[type].size>>1; process_fd_request(); for ( cnt = 0; cnt < N_DRIVE; cnt++){ @@ -2854,41 +3057,44 @@ static int fd_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, #undef IOCTL_ALLOWED } -#define CMOS_READ(addr) ({ \ -outb_p(addr,0x70); \ -inb_p(0x71); \ -}) - -static void set_base_type(int drive,int code) -{ - if (code > 0 && code <= NUMBER(default_drive_params)) { - memcpy((char *) UDP, - (char *) (&default_drive_params[code].params), - sizeof( struct floppy_drive_params )); - printk("fd%d is %s", drive, default_drive_params[code].name); - return; - } else if (!code) - printk("fd%d is not installed", drive); - else - printk("fd%d is unknown type %d",drive,code); -} - static void config_types(void) { + int first=1; int drive; - for (drive=0; driveparams), - sizeof( struct floppy_drive_params )); - } - printk("Floppy drive(s): "); - set_base_type(0, (CMOS_READ(0x10) >> 4) & 15); - if (CMOS_READ(0x10) & 15) { - printk(", "); - set_base_type(1, CMOS_READ(0x10) & 15); + /* read drive info out of physical cmos */ + drive=0; + if (!UDP->cmos ) + UDP->cmos= FLOPPY0_TYPE; + drive=1; + if (!UDP->cmos && FLOPPY1_TYPE) + UDP->cmos = FLOPPY1_TYPE; + + /* XXX */ + /* additional physical CMOS drive detection should go here */ + + for (drive=0; drive < N_DRIVE; drive++){ + if (UDP->cmos >= 0 && UDP->cmos <= NUMBER(default_drive_params)) + memcpy((char *) UDP, + (char *) (&default_drive_params[(int)UDP->cmos].params), + sizeof(struct floppy_drive_params)); + if (UDP->cmos){ + if (first) + printk("Floppy drive(s): "); + else + printk(", "); + first=0; + if (UDP->cmos > 0 ){ + ALLOWED_DRIVE_MASK |= 1 << drive; + printk("fd%d is %s", drive, + default_drive_params[(int)UDP->cmos].name); + } else + printk("fd%d is unknown type %d",drive, + UDP->cmos); + } } - printk("\n"); + if(!first) + printk("\n"); } static int floppy_read(struct inode * inode, struct file * filp, @@ -3000,7 +3206,7 @@ static int floppy_open(struct inode * inode, struct file * filp) } /* Allow ioctls if we have write-permissions even if read-only open */ - if ((filp->f_mode & 2) || permission(inode,2)) + if ((filp->f_mode & 2) || (permission(inode,2) == 0)) filp->f_mode |= IOCTL_MODE_BIT; if (filp->f_mode & 2) filp->f_mode |= OPEN_WRITE_BIT; @@ -3163,6 +3369,130 @@ static char get_fdc_version(void) return FDC_82077; /* Revised 82077AA passes all the tests */ } /* get_fdc_version */ +/* lilo configuration */ + +/* we make the invert_dcl function global. One day, somebody might +want to centralize all thinkpad related options into one lilo option, +there are just so many thinkpad related quirks! */ +void floppy_invert_dcl(int *ints,int param) +{ + int i; + + for (i=0; i < ARRAY_SIZE(default_drive_params); i++){ + if (param) + default_drive_params[i].params.flags |= 0x80; + else + default_drive_params[i].params.flags &= ~0x80; + } + DPRINT("Configuring drives for inverted dcl\n"); +} + +static void daring(int *ints,int param) +{ + int i; + + for (i=0; i < ARRAY_SIZE(default_drive_params); i++){ + if (param){ + default_drive_params[i].params.select_delay = 0; + default_drive_params[i].params.flags |= FD_SILENT_DCL_CLEAR; + } else { + default_drive_params[i].params.select_delay = 2*HZ/100; + default_drive_params[i].params.flags &= ~FD_SILENT_DCL_CLEAR; + } + } + DPRINT1("Assuming %s floppy hardware\n", param ? "standard" : "broken"); +} + +static void allow_drives(int *ints, int param) +{ + ALLOWED_DRIVE_MASK=param; + DPRINT1("setting allowed_drive_mask to 0x%x\n", param); +} + +static void fdc2_adr(int *ints, int param) +{ + FDC2 = param; + if(param) + DPRINT1("enabling second fdc at address 0x%3x\n", FDC2); + else + DPRINT("disabling second fdc\n"); +} + +static void unex(int *ints,int param) +{ + print_unex = param; + DPRINT1("%sprinting messages for unexpected interrupts\n", + param ? "" : "not "); +} + +static void set_cmos(int *ints, int dummy) +{ + int current_drive=0; + + if ( ints[0] != 2 ){ + DPRINT("wrong number of parameter for cmos\n"); + return; + } + current_drive = ints[1]; + if (current_drive < 0 || current_drive >= 8 ){ + DPRINT("bad drive for set_cmos\n"); + return; + } + if(ints[2] <= 0 || ints[2] >= NUMBER(default_drive_params)){ + DPRINT1("bad cmos code %d\n", ints[2]); + return; + } + DP->cmos = ints[2]; + DPRINT1("setting cmos code to %d\n", ints[2]); +} + +static struct param_table { + char *name; + void (*fn)(int *ints, int param); + int def_param; +} config_params[]={ +{ "allowed_drive_mask", allow_drives, 0xff }, +{ "all_drives", allow_drives, 0xff }, +{ "asus_pci", allow_drives, 0x33 }, + +{ "daring", daring, 1}, + +{ "two_fdc", fdc2_adr, 0x370 }, +{ "one_fdc", fdc2_adr, 0 }, + +{ "thinkpad", floppy_invert_dcl, 1 }, + +{ "cmos", set_cmos, 0 }, + +{ "unexpected_interrupts", unex, 1 }, +{ "no_unexpected_interrupts", unex, 0 }, +{ "L40SX", unex, 0 } }; + +#define FLOPPY_SETUP +void floppy_setup(char *str, int *ints) +{ + int i; + int param; + if(!str) + return; + for(i=0; i< ARRAY_SIZE(config_params); i++){ + if (strcmp(str,config_params[i].name) == 0 ){ + if (ints[0] ) + param = ints[1]; + else + param = config_params[i].def_param; + config_params[i].fn(ints,param); + return; + } + } + DPRINT1("unknown floppy option %s\n", str); + DPRINT("allowed options are:"); + for(i=0; i< ARRAY_SIZE(config_params); i++) + printk(" %s",config_params[i].name); + printk("\n"); + DPRINT("Read linux/drivers/block/README.fd\n"); +} + #ifdef FD_MODULE static #endif @@ -3230,12 +3560,16 @@ int new_floppy_init(void) if (FDCS->address == -1 ) continue; FDCS->rawcmd = 2; - if(user_reset_fdc(-1,FD_RESET_IF_NEEDED,0)) + if(user_reset_fdc(-1,FD_RESET_IF_NEEDED,0)){ + FDCS->address = -1; continue; + } /* Try to determine the floppy controller type */ FDCS->version = get_fdc_version(); - if (FDCS->version == FDC_NONE) + if (FDCS->version == FDC_NONE){ + FDCS->address = -1; continue; + } have_no_fdc = 0; /* Not all FDCs seem to be able to handle the version command @@ -3251,6 +3585,14 @@ int new_floppy_init(void) initialising=0; if(have_no_fdc) unregister_blkdev(MAJOR_NR,"fd"); +#ifdef CONFIG_MIPS_JAZZ + else { + if (boot_info.machtype == MACH_ACER_PICA_61 || + boot_info.machtype == MACH_MIPS_MAGNUM_4000) + vdma_alloc(PHYSADDR(floppy_track_buffer), + 512*2*MAX_BUFFER_SECTORS); + } +#endif return have_no_fdc; } @@ -3272,29 +3614,32 @@ static int floppy_grab_irq_and_dma(void) #ifdef FD_MODULE MOD_INC_USE_COUNT; #endif - for(i=0; i< N_FDC; i++){ - fdc = i; - reset_fdc_info(1); - outb_p(FDCS->dor, FD_DOR); + for(i=0; i< N_FDC; i++){ + if(FDCS->address != -1){ + fdc = i; + reset_fdc_info(1); + fd_out(FDCS->dor, FD_DOR); + } } set_dor(0, ~0, 8); /* avoid immediate interrupt */ - if (request_irq(FLOPPY_IRQ, floppy_interrupt, SA_INTERRUPT, "floppy")) { + if (fd_request_irq()) { DPRINT1("Unable to grab IRQ%d for the floppy driver\n", FLOPPY_IRQ); return -1; } - if (request_dma(FLOPPY_DMA,"floppy")) { + if (fd_request_dma()) { DPRINT1("Unable to grab DMA%d for the floppy driver\n", FLOPPY_DMA); - free_irq(FLOPPY_IRQ); + fd_free_irq(); return -1; } for(fdc = 0; fdc < N_FDC ; fdc++) if(FDCS->address != -1) - outb_p(FDCS->dor, FD_DOR); + fd_out(FDCS->dor, FD_DOR); fdc = 0; - enable_irq(FLOPPY_IRQ); +/* printk("enable irq %d\n",FLOPPY_IRQ); */ + fd_enable_irq(); return 0; } @@ -3312,16 +3657,16 @@ static void floppy_release_irq_and_dma(void) #ifdef FD_MODULE MOD_DEC_USE_COUNT; #endif - disable_dma(FLOPPY_DMA); - free_dma(FLOPPY_DMA); - disable_irq(FLOPPY_IRQ); - free_irq(FLOPPY_IRQ); + fd_disable_dma(); + fd_free_dma(); + fd_disable_irq(); + fd_free_irq(); set_dor(0, ~0, 8); #if N_FDC > 1 set_dor(1, ~8, 0); #endif - + floppy_enable_hlt(); #ifdef CONFIG_FLOPPY_SANITY for(drive=0; drive < N_FDC * 4; drive++) if( motor_off_timer[drive].next ) @@ -3336,4 +3681,3 @@ static void floppy_release_irq_and_dma(void) #endif } -#endif diff --git a/drivers/block/genhd.c b/drivers/block/genhd.c index f7e98e1ed..1108288fe 100644 --- a/drivers/block/genhd.c +++ b/drivers/block/genhd.c @@ -10,10 +10,10 @@ * in the early extended-partition checks and added DM partitions */ -#include #include #include #include +#include struct gendisk *gendisk_head = NULL; @@ -22,6 +22,19 @@ extern int *blk_size[]; extern void rd_load(void); extern int ramdisk_size; +static char minor_name (struct gendisk *hd, int minor) +{ + char base_name = (hd->major == IDE1_MAJOR) ? 'c' : 'a'; + return base_name + (minor >> hd->minor_shift); +} + +static void add_partition (struct gendisk *hd, int minor, int start, int size) +{ + hd->part[minor].start_sect = start; + hd->part[minor].nr_sects = size; + printk(" %s%c%d", hd->major_name, minor_name(hd, minor), + minor & ((1 << hd->minor_shift) - 1)); +} /* * Create devices for each logical partition in an extended partition. * The logical partitions form a linked list, with each entry being @@ -61,13 +74,9 @@ static void extended_partition(struct gendisk *hd, int dev) * Process the first entry, which should be the real * data partition. */ - if (p->sys_ind == EXTENDED_PARTITION || - !(hd->part[current_minor].nr_sects = p->nr_sects)) + if (p->sys_ind == EXTENDED_PARTITION || !p->nr_sects) goto done; /* shouldn't happen */ - hd->part[current_minor].start_sect = this_sector + p->start_sect; - printk(" %s%c%d", hd->major_name, - 'a'+(current_minor >> hd->minor_shift), - mask & current_minor); + add_partition(hd, current_minor, this_sector+p->start_sect, p->nr_sects); current_minor++; p++; /* @@ -81,6 +90,7 @@ static void extended_partition(struct gendisk *hd, int dev) !(hd->part[current_minor].nr_sects = p->nr_sects)) goto done; /* no more logicals in this partition */ hd->part[current_minor].start_sect = first_sector + p->start_sect; + hd->sizes[current_minor] = p->nr_sects >> (BLOCK_SIZE_BITS - 9); this_sector = first_sector + p->start_sect; dev = ((hd->major) << 8) | current_minor; brelse(bh); @@ -104,19 +114,28 @@ static void check_partition(struct gendisk *hd, unsigned int dev) printk("Partition check:\n"); first_time = 0; first_sector = hd->part[MINOR(dev)].start_sect; + + /* + * This is a kludge to allow the partition check to be + * skipped for specific drives (ie. IDE cd-rom drives) + */ + if ((int)first_sector == -1) { + hd->part[MINOR(dev)].start_sect = 0; + return; + } + if (!(bh = bread(dev,0,1024))) { printk(" unable to read partition table of device %04x\n",dev); return; } - printk(" %s%c:", hd->major_name, 'a'+(minor >> hd->minor_shift)); - current_minor += 4; /* first "extra" minor */ + printk(" %s%c:", hd->major_name, minor_name(hd, minor)); + current_minor += 4; /* first "extra" minor (for extended partitions) */ if (*(unsigned short *) (bh->b_data+510) == 0xAA55) { p = (struct partition *) (0x1BE + bh->b_data); for (i=1 ; i<=4 ; minor++,i++,p++) { - if (!(hd->part[minor].nr_sects = p->nr_sects)) + if (!p->nr_sects) continue; - hd->part[minor].start_sect = first_sector + p->start_sect; - printk(" %s%c%d", hd->major_name,'a'+(minor >> hd->minor_shift), i); + add_partition(hd, minor, first_sector+p->start_sect, p->nr_sects); if ((current_minor & 0x3f) >= 60) continue; if (p->sys_ind == EXTENDED_PARTITION) { @@ -136,11 +155,7 @@ static void check_partition(struct gendisk *hd, unsigned int dev) break; if (!(p->start_sect && p->nr_sects)) continue; - hd->part[current_minor].start_sect = p->start_sect; - hd->part[current_minor].nr_sects = p->nr_sects; - printk(" %s%c%d", hd->major_name, - 'a'+(current_minor >> hd->minor_shift), - current_minor & mask); + add_partition(hd, current_minor, p->start_sect, p->nr_sects); } } } else diff --git a/drivers/block/hd.c b/drivers/block/hd.c index f6af9eaa4..f17aa86d9 100644 --- a/drivers/block/hd.c +++ b/drivers/block/hd.c @@ -1,5 +1,5 @@ /* - * linux/kernel/hd.c + * linux/drivers/block/hd.c * * Copyright (C) 1991, 1992 Linus Torvalds */ @@ -31,9 +31,10 @@ #include #include #include -#include #include #include +#include +#include /* CMOS defines */ #define REALLY_SLOW_IO #include @@ -47,12 +48,6 @@ static int revalidate_hddisk(int, int); -static inline unsigned char CMOS_READ(unsigned char addr) -{ - outb_p(addr,0x70); - return inb_p(0x71); -} - #define HD_DELAY 0 #define MAX_ERRORS 16 /* Max read/write errors/sector */ @@ -836,14 +831,6 @@ static int hd_ioctl(struct inode * inode, struct file * file, case BLKRRPART: /* Re-read partition tables */ return revalidate_hddisk(inode->i_rdev, 1); - case HDIO_SETUNMASKINTR: /* obsolete */ - printk("hd: obsolete syscall: HDIO_SETUNMASKINTR\n"); - if (!arg) return -EINVAL; - err = verify_area(VERIFY_READ, (long *) arg, sizeof(long)); - if (err) - return err; - arg = get_fs_long((long *) arg); - /* drop into HDIO_SET_UNMASKINTR */ case HDIO_SET_UNMASKINTR: if (!suser()) return -EACCES; if ((arg > 1) || (MINOR(inode->i_rdev) & 0x3F)) @@ -867,14 +854,6 @@ static int hd_ioctl(struct inode * inode, struct file * file, put_fs_long(mult_count[dev], (long *) arg); return 0; - case HDIO_SETMULTCOUNT: /* obsolete */ - printk("hd: obsolete syscall: HDIO_SETMULTCOUNT\n"); - if (!arg) return -EINVAL; - err = verify_area(VERIFY_READ, (long *) arg, sizeof(long)); - if (err) - return err; - arg = get_fs_long((long *) arg); - /* drop into HDIO_SET_MULTCOUNT */ case HDIO_SET_MULTCOUNT: if (!suser()) return -EACCES; if (MINOR(inode->i_rdev) & 0x3F) return -EINVAL; @@ -914,6 +893,8 @@ static int hd_open(struct inode * inode, struct file * filp) int target; target = DEVICE_NR(inode->i_rdev); + if (target >= NR_HD) + return -ENODEV; while (busy[target]) sleep_on(&busy_wait); access_count[target]++; @@ -950,7 +931,7 @@ static struct gendisk hd_gendisk = { NULL /* next */ }; -static void hd_interrupt(int unused) +static void hd_interrupt(int irq, struct pt_regs *regs) { void (*handler)(void) = DEVICE_INTR; @@ -1040,6 +1021,9 @@ static void hd_geninit(void) if (request_irq(HD_IRQ, hd_interrupt, SA_INTERRUPT, "hd")) { printk("hd: unable to get IRQ%d for the harddisk driver\n",HD_IRQ); NR_HD = 0; + } else { + request_region(HD_DATA, 8, "hd"); + request_region(HD_CMD, 1, "hd(cmd)"); } } hd_gendisk.nr_real = NR_HD; diff --git a/drivers/block/ide-cd.c b/drivers/block/ide-cd.c new file mode 100644 index 000000000..1aef40dcf --- /dev/null +++ b/drivers/block/ide-cd.c @@ -0,0 +1,2126 @@ +/* + * linux/drivers/block/ide-cd.c + * + * 1.00 Oct 31, 1994 -- Initial version. + * 1.01 Nov 2, 1994 -- Fixed problem with starting request in + * cdrom_check_status. + * 1.03 Nov 25, 1994 -- leaving unmask_intr[] as a user-setting (as for disks) + * (from mlord) -- minor changes to cdrom_setup() + * -- renamed ide_dev_s to ide_dev_t, enable irq on command + * 2.00 Nov 27, 1994 -- Generalize packet command interface; + * add audio ioctls. + * 2.01 Dec 3, 1994 -- Rework packet command interface to handle devices + * which send an interrupt when ready for a command. + * 2.02 Dec 11, 1994 -- Cache the TOC in the driver. + * Don't use SCMD_PLAYAUDIO_TI; it's not included + * in the current version of ATAPI. + * Try to use LBA instead of track or MSF addressing + * when possible. + * Don't wait for READY_STAT. + * 2.03 Jan 10, 1995 -- Rewrite block read routines to handle block sizes + * other than 2k and to move multiple sectors in a + * single transaction. + * 2.04 Apr 21, 1995 -- Add work-around for Creative Labs CD220E drives. + * Thanks to Nick Saw for + * help in figuring this out. Ditto for Acer and + * Aztech drives, which seem to have the same problem. + * 2.04b May 30, 1995 -- Fix to match changes in ide.c version 3.16 -ml + * 2.05 Jun 8, 1995 -- Don't attempt to retry after an illegal request + * or data protect error. + * Use HWIF and DEV_HWIF macros as in ide.c. + * Always try to do a request_sense after + * a failed command. + * Include an option to give textual descriptions + * of ATAPI errors. + * Fix a bug in handling the sector cache which + * showed up if the drive returned data in 512 byte + * blocks (like Pioneer drives). Thanks to + * Richard Hirst for diagnosing this. + * Properly supply the page number field in the + * MODE_SELECT command. + * PLAYAUDIO12 is broken on the Aztech; work around it. + * + * + * ATAPI cd-rom driver. To be used with ide.c. + * + * Copyright (C) 1994, 1995 scott snyder + * May be copied or modified under the terms of the GNU General Public License + * (../../COPYING). + */ + + +/* Turn this on to have the driver print out the meanings of the + ATAPI error codes. This will use up additional kernel-space + memory, though. */ + +#ifndef VERBOSE_IDE_CD_ERRORS +#define VERBOSE_IDE_CD_ERRORS 0 +#endif + +/***************************************************************************/ + +#include + +#define SECTOR_SIZE 512 +#define SECTOR_BITS 9 +#define SECTORS_PER_FRAME (CD_FRAMESIZE / SECTOR_SIZE) + +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +#if 1 /* "old" method */ +#define OUT_WORDS(b,n) outsw (IDE_PORT (HD_DATA, DEV_HWIF), (b), (n)) +#define IN_WORDS(b,n) insw (IDE_PORT (HD_DATA, DEV_HWIF), (b), (n)) +#else /* "new" method -- should really fix each instance instead of this */ +#define OUT_WORDS(b,n) output_ide_data(dev,b,(n)/2) +#define IN_WORDS(b,n) input_ide_data(dev,b,(n)/2) +#endif + +/* special command codes for strategy routine. */ +#define PACKET_COMMAND 4315 +#define REQUEST_SENSE_COMMAND 4316 + +#define WIN_PACKETCMD 0xa0 /* Send a packet command. */ + +/* Some ATAPI command opcodes (just like SCSI). + (Some other cdrom-specific codes are in cdrom.h.) */ +#define TEST_UNIT_READY 0x00 +#define REQUEST_SENSE 0x03 +#define START_STOP 0x1b +#define ALLOW_MEDIUM_REMOVAL 0x1e +#define READ_10 0x28 +#define MODE_SENSE_10 0x5a +#define MODE_SELECT_10 0x55 + + +/* ATAPI sense keys (mostly copied from scsi.h). */ + +#define NO_SENSE 0x00 +#define RECOVERED_ERROR 0x01 +#define NOT_READY 0x02 +#define MEDIUM_ERROR 0x03 +#define HARDWARE_ERROR 0x04 +#define ILLEGAL_REQUEST 0x05 +#define UNIT_ATTENTION 0x06 +#define DATA_PROTECT 0x07 +#define ABORTED_COMMAND 0x0b +#define MISCOMPARE 0x0e + + +struct packet_command { + char *buffer; + int buflen; + int stat; + unsigned char c[12]; +}; + + +struct atapi_request_sense { + unsigned char error_code : 7; + unsigned char valid : 1; + byte reserved1; + unsigned char sense_key : 4; + unsigned char reserved2 : 1; + unsigned char ili : 1; + unsigned char reserved3 : 2; + byte info[4]; + byte sense_len; + byte command_info[4]; + byte asc; + byte ascq; + byte fru; + byte sense_key_specific[3]; +}; + +/* We want some additional flags for cd-rom drives. + To save space in the ide_dev_t struct, use one of the fields which + doesn't make sense for cd-roms -- `bios_sect'. */ + +struct ide_cd_flags { + unsigned drq_interrupt : 1; /* Device sends an interrupt when ready + for a packet command. */ + unsigned no_playaudio12: 1; /* The PLAYAUDIO12 command is not supported. */ + + unsigned media_changed : 1; /* Driver has noticed a media change. */ + unsigned toc_valid : 1; /* Saved TOC information is current. */ + unsigned no_lba_toc : 1; /* Drive cannot return TOC info in LBA format. */ + unsigned msf_as_bcd : 1; /* Drive uses BCD in PLAYAUDIO_MSF. */ + unsigned reserved : 2; +}; + +#define CDROM_FLAGS(dev) ((struct ide_cd_flags *)&((dev)->bios_sect)) + + +/* Space to hold the disk TOC. */ + +#define MAX_TRACKS 99 +struct atapi_toc_header { + unsigned short toc_length; + byte first_track; + byte last_track; +}; + +struct atapi_toc_entry { + byte reserved1; + unsigned control : 4; + unsigned adr : 4; + byte track; + byte reserved2; + unsigned lba; +}; + +struct atapi_toc { + struct atapi_toc_header hdr; + struct atapi_toc_entry ent[MAX_TRACKS+1]; /* One extra for the leadout. */ +}; + + +#define SECTOR_BUFFER_SIZE CD_FRAMESIZE + +/* Extra per-device info for cdrom drives. */ +struct cdrom_info { + + /* Buffer for table of contents. NULL if we haven't allocated + a TOC buffer for this device yet. */ + + struct atapi_toc *toc; + + /* Sector buffer. If a read request wants only the first part of a cdrom + block, we cache the rest of the block here, in the expectation that that + data is going to be wanted soon. SECTOR_BUFFERED is the number of the + first buffered sector, and NSECTORS_BUFFERED is the number of sectors + in the buffer. Before the buffer is allocated, we should have + SECTOR_BUFFER == NULL and NSECTORS_BUFFERED == 0. */ + + unsigned long sector_buffered; + unsigned long nsectors_buffered; + char *sector_buffer; + + /* The result of the last successful request sense command + on this device. */ + struct atapi_request_sense sense_data; +}; + + +static struct cdrom_info cdrom_info[2][MAX_DRIVES]; + +/* Statically allocate one request packet and one packet command struct + for each interface for retrieving sense data during error recovery. */ + +static struct request request_sense_request[2]; +static struct packet_command request_sense_pc[2]; + + + +/**************************************************************************** + * Descriptions of ATAPI error codes. + */ + +#define ARY_LEN(a) ((sizeof(a) / sizeof(a[0]))) + +#if VERBOSE_IDE_CD_ERRORS + +/* From Table 124 of the ATAPI 1.2 spec. */ + +char *sense_key_texts[16] = { + "No sense data", + "Recovered error", + "Not ready", + "Medium error", + "Hardware error", + "Illegal request", + "Unit attention", + "Data protect", + "(reserved)", + "(reserved)", + "(reserved)", + "Aborted command", + "(reserved)", + "(reserved)", + "Miscompare", + "(reserved)", +}; + + +/* From Table 125 of the ATAPI 1.2 spec. */ + +struct { + short asc_ascq; + char *text; +} sense_data_texts[] = { + { 0x0000, "No additional sense information" }, + { 0x0011, "Audio play operation in progress" }, + { 0x0012, "Audio play operation paused" }, + { 0x0013, "Audio play operation successfully completed" }, + { 0x0014, "Audio play operation stopped due to error" }, + { 0x0015, "No current audio status to return" }, + + { 0x0200, "No seek complete" }, + + { 0x0400, "Logical unit not ready - cause not reportable" }, + { 0x0401, "Logical unit not ready - in progress (sic) of becoming ready" }, + { 0x0402, "Logical unit not ready - initializing command required" }, + { 0x0403, "Logical unit not ready - manual intervention required" }, + + { 0x0600, "No reference position found" }, + + { 0x0900, "Track following error" }, + { 0x0901, "Tracking servo failure" }, + { 0x0902, "Focus servo failure" }, + { 0x0903, "Spindle servo failure" }, + + { 0x1100, "Unrecovered read error" }, + { 0x1106, "CIRC unrecovered error" }, + + { 0x1500, "Random positioning error" }, + { 0x1501, "Mechanical positioning error" }, + { 0x1502, "Positioning error detected by read of medium" }, + + { 0x1700, "Recovered data with no error correction applied" }, + { 0x1701, "Recovered data with retries" }, + { 0x1702, "Recovered data with positive head offset" }, + { 0x1703, "Recovered data with negative head offset" }, + { 0x1704, "Recovered data with retries and/or CIRC applied" }, + { 0x1705, "Recovered data using previous sector ID" }, + + { 0x1800, "Recovered data with error correction applied" }, + { 0x1801, "Recovered data with error correction and retries applied" }, + { 0x1802, "Recovered data - the data was auto-reallocated" }, + { 0x1803, "Recovered data with CIRC" }, + { 0x1804, "Recovered data with L-EC" }, + { 0x1805, "Recovered data - recommend reassignment" }, + { 0x1806, "Recovered data - recommend rewrite" }, + + { 0x1a00, "Parameter list length error" }, + + { 0x2000, "Invalid command operation code" }, + + { 0x2100, "Logical block address out of range" }, + + { 0x2400, "Invalid field in command packet" }, + + { 0x2600, "Invalid field in parameter list" }, + { 0x2601, "Parameter not supported" }, + { 0x2602, "Parameter value invalid" }, + { 0x2603, "Threshold parameters not supported" }, + + { 0x2800, "Not ready to ready transition, medium may have changed" }, + + { 0x2900, "Power on, reset or bus device reset occurred" }, + + { 0x2a00, "Parameters changed" }, + { 0x2a01, "Mode parameters changed" }, + + { 0x3000, "Incompatible medium installed" }, + { 0x3001, "Cannot read medium - unknown format" }, + { 0x3002, "Cannot read medium - incompatible format" }, + + { 0x3700, "Rounded parameter" }, + + { 0x3900, "Saving parameters not supported" }, + + { 0x3a00, "Medium not present" }, + + { 0x3f00, "ATAPI CD-ROM drive operating conditions have changed" }, + { 0x3f01, "Microcode has been changed" }, + { 0x3f02, "Changed operating definition" }, + { 0x3f03, "Inquiry data has changed" }, + + { 0x4000, "Diagnostic failure on component (ASCQ)" }, + + { 0x4400, "Internal ATAPI CD-ROM drive failure" }, + + { 0x4e00, "Overlapped commands attempted" }, + + { 0x5300, "Media load or eject failed" }, + { 0x5302, "Medium removal prevented" }, + + { 0x5700, "Unable to recover table of contents" }, + + { 0x5a00, "Operator request or state change input (unspecified)" }, + { 0x5a01, "Operator medium removal request" }, + + { 0x5b00, "Threshold condition met" }, + + { 0x5c00, "Status change" }, + + { 0x6300, "End of user area encountered on this track" }, + + { 0x6400, "Illegal mode for this track" }, + + { 0xbf00, "Loss of streaming" }, +}; +#endif + + + +/**************************************************************************** + * Generic packet command support routines. + */ + + +static +void cdrom_analyze_sense_data (ide_dev_t *dev, + struct atapi_request_sense *reqbuf, + struct packet_command *failed_command) +{ + /* Don't print not ready or unit attention errors for READ_SUBCHANNEL. + Workman (and probably other programs) uses this command to poll + the drive, and we don't want to fill the syslog with useless errors. */ + if (failed_command && + failed_command->c[0] == SCMD_READ_SUBCHANNEL && + (reqbuf->sense_key == 2 || reqbuf->sense_key == 6)) + return; + +#if VERBOSE_IDE_CD_ERRORS + { + int i; + char *s; + char buf[80]; + + printk ("ATAPI device %s:\n", dev->name); + + printk (" Error code: %x\n", reqbuf->error_code); + + if (reqbuf->sense_key >= 0 && + reqbuf->sense_key < ARY_LEN (sense_key_texts)) + s = sense_key_texts[reqbuf->sense_key]; + else + s = "(bad sense key)"; + + printk (" Sense key: %x - %s\n", reqbuf->sense_key, s); + + if (reqbuf->asc == 0x40) { + sprintf (buf, "Diagnostic failure on component %x", reqbuf->ascq); + s = buf; + } + + else { + int lo, hi; + int key = (reqbuf->asc << 8); + if ( ! (reqbuf->ascq >= 0x80 && reqbuf->ascq <= 0xdd) ) + key |= reqbuf->ascq; + + lo = 0; + hi = ARY_LEN (sense_data_texts); + s = NULL; + + while (hi > lo) { + int mid = (lo + hi) / 2; + if (sense_data_texts[mid].asc_ascq == key) { + s = sense_data_texts[mid].text; + break; + } + else if (sense_data_texts[mid].asc_ascq > key) + hi = mid; + else + lo = mid+1; + } + } + + if (s == NULL) { + if (reqbuf->asc > 0x80) + s = "(vendor-specific error)"; + else + s = "(reserved error code)"; + } + + printk (" Additional sense data: %x, %x - %s\n", + reqbuf->asc, reqbuf->ascq, s); + + if (failed_command != NULL) { + printk (" Failed packet command: "); + for (i=0; ic); i++) + printk ("%02x ", failed_command->c[i]); + printk ("\n"); + } + } + +#else + printk ("%s: code: %x key: %x asc: %x ascq: %x\n", + dev->name, + reqbuf->error_code, reqbuf->sense_key, reqbuf->asc, reqbuf->ascq); +#endif +} + + +/* Fix up a possibly partially-processed request so that we can + start it over entirely, or even put it back on the request queue. */ +static void restore_request (struct request *rq) +{ + if (rq->buffer != rq->bh->b_data) + { + int n = (rq->buffer - rq->bh->b_data) / SECTOR_SIZE; + rq->buffer = rq->bh->b_data; + rq->nr_sectors += n; + rq->sector -= n; + } + rq->current_nr_sectors = rq->bh->b_size >> SECTOR_BITS; +} + + +static void cdrom_queue_request_sense (ide_dev_t *dev) +{ + struct request *rq; + struct packet_command *pc; + struct atapi_request_sense *reqbuf; + unsigned long flags; + + int major = ide_major[DEV_HWIF]; + + save_flags (flags); + cli (); /* safety */ + + rq = ide_cur_rq[DEV_HWIF]; + + /* If we're processing a request, put it back on the request queue. */ + if (rq != NULL) + { + restore_request (rq); + rq->next = blk_dev[major].current_request; + blk_dev[major].current_request = rq; + ide_cur_rq[DEV_HWIF] = NULL; + } + + restore_flags (flags); + + /* Make up a new request to retrieve sense information. */ + reqbuf = &cdrom_info[DEV_HWIF][dev->select.b.drive].sense_data; + + pc = &request_sense_pc[DEV_HWIF]; + memset (pc, 0, sizeof (*pc)); + + pc->c[0] = REQUEST_SENSE; + pc->c[4] = sizeof (*reqbuf); + pc->buffer = (char *)reqbuf; + pc->buflen = sizeof (*reqbuf); + + rq = &request_sense_request[DEV_HWIF]; + rq->dev = MKDEV (major, (dev->select.b.drive) << PARTN_BITS); + rq->cmd = REQUEST_SENSE_COMMAND; + rq->errors = 0; + rq->sector = 0; + rq->nr_sectors = 0; + rq->current_nr_sectors = 0; + rq->buffer = (char *)pc; + rq->sem = NULL; + rq->bh = NULL; + rq->bhtail = NULL; + rq->next = NULL; + + save_flags (flags); + cli (); /* safety */ + + /* Stick it onto the front of the queue. */ + rq->next = blk_dev[major].current_request; + blk_dev[major].current_request = rq; + + restore_flags (flags); +} + + +static void cdrom_end_request (int uptodate, ide_dev_t *dev) +{ + struct request *rq = ide_cur_rq[DEV_HWIF]; + + /* The code in blk.h can screw us up on error recovery if the block + size is larger than 1k. Fix that up here. */ + if (!uptodate && rq->bh != 0) + { + int adj = rq->current_nr_sectors - 1; + rq->current_nr_sectors -= adj; + rq->sector += adj; + } + + if (rq->cmd == REQUEST_SENSE_COMMAND && uptodate) + { + struct atapi_request_sense *reqbuf; + reqbuf = &cdrom_info[DEV_HWIF][dev->select.b.drive].sense_data; + cdrom_analyze_sense_data (dev, reqbuf, NULL); + } + + end_request (uptodate, DEV_HWIF); +} + + +/* Mark that we've seen a media change, and invalidate our internal + buffers. */ +static void cdrom_saw_media_change (ide_dev_t *dev) +{ + CDROM_FLAGS (dev)->media_changed = 1; + CDROM_FLAGS (dev)->toc_valid = 0; + cdrom_info[DEV_HWIF][dev->select.b.drive].nsectors_buffered = 0; +} + + +/* Returns 0 if the request should be continued. + Returns 1 if the request was ended. */ +static int cdrom_decode_status (ide_dev_t *dev, int good_stat, int *stat_ret) +{ + struct request *rq = ide_cur_rq[DEV_HWIF]; + int stat, err, sense_key, cmd; + + /* Check for errors. */ + stat = GET_STAT (DEV_HWIF); + *stat_ret = stat; + + if (OK_STAT (stat, good_stat, BAD_R_STAT)) + return 0; + + /* Got an error. */ + err = IN_BYTE (HD_ERROR, DEV_HWIF); + sense_key = err >> 4; + + if (rq == NULL) + printk ("%s : missing request in cdrom_decode_status\n", dev->name); + else + { + cmd = rq->cmd; + + /* Check for tray open */ + if (sense_key == NOT_READY) + { + struct packet_command *pc; + cdrom_saw_media_change (dev); + + /* Fail the request if this is a read command. */ + if (cmd == READ) + { + printk ("%s : tray open\n", dev->name); + cdrom_end_request (0, dev); + } + + else + { + /* Otherwise, it's some other packet command. + Print an error message to the syslog. + Exception: don't print anything if this is a read subchannel + command. This is because workman constantly polls the drive + with this command, and we don't want to uselessly fill up + the syslog. */ + pc = (struct packet_command *)rq->buffer; + if (pc->c[0] != SCMD_READ_SUBCHANNEL) + printk ("%s : tray open\n", dev->name); + + /* Set the error flag and complete the request. */ + pc->stat = 1; + cdrom_end_request (1, dev); + } + } + + /* Check for media change. */ + else if (sense_key == UNIT_ATTENTION) + { + cdrom_saw_media_change (dev); + printk ("%s: media changed\n", dev->name); + + /* Return failure for a packet command, so that + cdrom_queue_packet_command can do a request sense before + the command gets retried. */ + + if (cmd == PACKET_COMMAND) + { + struct packet_command *pc = (struct packet_command *)rq->buffer; + pc->stat = 1; + cdrom_end_request (1, dev); + } + + /* Otherwise, it's a block read. Arrange to retry it. + But be sure to give up if we've retried too many times. */ + else if ((++rq->errors > ERROR_MAX)) + { + cdrom_end_request (0, dev); + } + } + + /* Don't attempt to retry if this was a packet command. */ + else if (cmd == PACKET_COMMAND) + { + struct packet_command *pc = (struct packet_command *)rq->buffer; + dump_status (DEV_HWIF, "packet command error", stat); + pc->stat = 1; /* signal error */ + cdrom_end_request (1, dev); + } + + /* No point in retrying after an illegal request or data protect error.*/ + else if (sense_key == ILLEGAL_REQUEST || sense_key == DATA_PROTECT) + { + dump_status (DEV_HWIF, "command error", stat); + cdrom_end_request (0, dev); + } + + /* If there were other errors, go to the default handler. */ + else if ((err & ~ABRT_ERR) != 0) + { + ide_error (dev, "cdrom_decode_status", stat); + } + + /* Else, abort if we've racked up too many retries. */ + else if ((++rq->errors > ERROR_MAX)) + { + cdrom_end_request (0, dev); + } + + /* If we got a CHECK_STATUS condition, and this was a READ request, + queue a request sense command to try to find out more about + what went wrong (and clear a unit attention)? For packet commands, + this is done separately in cdrom_queue_packet_command. */ + if ((stat & ERR_STAT) != 0 && cmd == READ) + cdrom_queue_request_sense (dev); + } + + /* Retry, or handle the next request. */ + DO_REQUEST; + return 1; +} + + +/* Set up the device registers for transferring a packet command on DEV, + expecting to later transfer XFERLEN bytes. This should be followed + by a call to cdrom_transfer_packet_command; however, if this is a + drq_interrupt device, one must wait for an interrupt first. */ +static int cdrom_start_packet_command (ide_dev_t *dev, int xferlen) +{ + /* Wait for the controller to be idle. */ + if (wait_stat (dev, 0, BUSY_STAT, WAIT_READY)) return 1; + + /* Set up the controller registers. */ + OUT_BYTE (0, HD_FEATURE); + OUT_BYTE (0, HD_NSECTOR); + OUT_BYTE (0, HD_SECTOR); + + OUT_BYTE (xferlen & 0xff, HD_LCYL); + OUT_BYTE (xferlen >> 8 , HD_HCYL); + OUT_BYTE (dev->ctl, HD_CMD); + OUT_BYTE (WIN_PACKETCMD, HD_COMMAND); /* packet command */ + + return 0; +} + + +/* Send a packet command to DEV described by CMD_BUF and CMD_LEN. + The device registers must have already been prepared + by cdrom_start_packet_command. */ +static int cdrom_transfer_packet_command (ide_dev_t *dev, + char *cmd_buf, int cmd_len) +{ + if (CDROM_FLAGS (dev)->drq_interrupt) + { + /* Here we should have been called after receiving an interrupt + from the device. DRQ should how be set. */ + int stat_dum; + + /* Check for errors. */ + if (cdrom_decode_status (dev, DRQ_STAT, &stat_dum)) return 1; + } + else + { + /* Otherwise, we must wait for DRQ to get set. */ + if (wait_stat (dev, DRQ_STAT, BUSY_STAT, WAIT_READY)) return 1; + } + + /* Send the command to the device. */ + OUT_WORDS (cmd_buf, cmd_len/2); + + return 0; +} + + + +/**************************************************************************** + * Block read functions. + */ + +/* + * Buffer up to SECTORS_TO_TRANSFER sectors from the drive in our sector + * buffer. Once the first sector is added, any subsequent sectors are + * assumed to be continuous (until the buffer is cleared). For the first + * sector added, SECTOR is its sector number. (SECTOR is then ignored until + * the buffer is cleared.) + */ +static void cdrom_buffer_sectors (ide_dev_t *dev, unsigned long sector, + int sectors_to_transfer) +{ + struct cdrom_info *info = &cdrom_info[DEV_HWIF][dev->select.b.drive]; + + /* Number of sectors to read into the buffer. */ + int sectors_to_buffer = MIN (sectors_to_transfer, + (SECTOR_BUFFER_SIZE >> SECTOR_BITS) - + info->nsectors_buffered); + + char *dest; + + /* If we don't yet have a sector buffer, try to allocate one. + If we can't get one atomically, it's not fatal -- we'll just throw + the data away rather than caching it. */ + if (info->sector_buffer == NULL) + { + info->sector_buffer = (char *) kmalloc (SECTOR_BUFFER_SIZE, GFP_ATOMIC); + + /* If we couldn't get a buffer, don't try to buffer anything... */ + if (info->sector_buffer == NULL) + sectors_to_buffer = 0; + } + + /* If this is the first sector in the buffer, remember its number. */ + if (info->nsectors_buffered == 0) + info->sector_buffered = sector; + + /* Read the data into the buffer. */ + dest = info->sector_buffer + info->nsectors_buffered * SECTOR_SIZE; + while (sectors_to_buffer > 0) + { + IN_WORDS (dest, SECTOR_SIZE / 2); + --sectors_to_buffer; + --sectors_to_transfer; + ++info->nsectors_buffered; + dest += SECTOR_SIZE; + } + + /* Throw away any remaining data. */ + while (sectors_to_transfer > 0) + { + char dum[SECTOR_SIZE]; + IN_WORDS (dum, sizeof (dum) / 2); + --sectors_to_transfer; + } +} + + +/* + * Check the contents of the interrupt reason register from the cdrom + * and attempt to recover if there are problems. Returns 0 if everything's + * ok; nonzero if the request has been terminated. + */ +static inline +int cdrom_read_check_ireason (ide_dev_t *dev, int len, int ireason) +{ + ireason &= 3; + if (ireason == 2) return 0; + + if (ireason == 0) + { + /* Whoops... The drive is expecting to receive data from us! */ + printk ("%s: cdrom_read_intr: " + "Drive wants to transfer data the wrong way!\n", + dev->name); + + /* Throw some data at the drive so it doesn't hang + and quit this request. */ + while (len > 0) + { + short dum = 0; + OUT_WORDS (&dum, 1); + len -= 2; + } + } + + else + { + /* Drive wants a command packet, or invalid ireason... */ + printk ("%s: cdrom_read_intr: bad interrupt reason %d\n", + dev->name, ireason); + } + + cdrom_end_request (0, dev); + DO_REQUEST; + return -1; +} + + +/* + * Interrupt routine. Called when a read request has completed. + */ +static void cdrom_read_intr (ide_dev_t *dev) +{ + int stat; + int ireason, len, sectors_to_transfer, nskip; + + struct request *rq = ide_cur_rq[DEV_HWIF]; + + /* Check for errors. */ + if (cdrom_decode_status (dev, 0, &stat)) return; + + /* Read the interrupt reason and the transfer length. */ + ireason = IN_BYTE (HD_NSECTOR, DEV_HWIF); + len = IN_BYTE (HD_LCYL, DEV_HWIF) + 256 * IN_BYTE (HD_HCYL, DEV_HWIF); + + /* If DRQ is clear, the command has completed. */ + if ((stat & DRQ_STAT) == 0) + { + /* If we're not done filling the current buffer, complain. + Otherwise, complete the command normally. */ + if (rq->current_nr_sectors > 0) + { + printk ("%s: cdrom_read_intr: data underrun (%ld blocks)\n", + dev->name, rq->current_nr_sectors); + cdrom_end_request (0, dev); + } + else + cdrom_end_request (1, dev); + + DO_REQUEST; + return; + } + + /* Check that the drive is expecting to do the same thing that we are. */ + if (cdrom_read_check_ireason (dev, len, ireason)) return; + + /* Assume that the drive will always provide data in multiples of at least + SECTOR_SIZE, as it gets hairy to keep track of the transfers otherwise. */ + if ((len % SECTOR_SIZE) != 0) + { + printk ("%s: cdrom_read_intr: Bad transfer size %d\n", + dev->name, len); + printk (" This drive is not supported by this version of the driver\n"); + cdrom_end_request (0, dev); + DO_REQUEST; + return; + } + + /* The number of sectors we need to read from the drive. */ + sectors_to_transfer = len / SECTOR_SIZE; + + /* First, figure out if we need to bit-bucket any of the leading sectors. */ + nskip = MIN ((int)(rq->current_nr_sectors - (rq->bh->b_size >> SECTOR_BITS)), + sectors_to_transfer); + + while (nskip > 0) + { + /* We need to throw away a sector. */ + char dum[SECTOR_SIZE]; + IN_WORDS (dum, sizeof (dum) / 2); + + --rq->current_nr_sectors; + --nskip; + --sectors_to_transfer; + } + + /* Now loop while we still have data to read from the drive. */ + while (sectors_to_transfer > 0) + { + int this_transfer; + + /* If we've filled the present buffer but there's another chained + buffer after it, move on. */ + if (rq->current_nr_sectors == 0 && + rq->nr_sectors > 0) + cdrom_end_request (1, dev); + + /* If the buffers are full, cache the rest of the data in our + internal buffer. */ + if (rq->current_nr_sectors == 0) + { + cdrom_buffer_sectors (dev, rq->sector, sectors_to_transfer); + sectors_to_transfer = 0; + } + else + { + /* Transfer data to the buffers. + Figure out how many sectors we can transfer + to the current buffer. */ + this_transfer = MIN (sectors_to_transfer, + rq->current_nr_sectors); + + /* Read this_transfer sectors into the current buffer. */ + while (this_transfer > 0) + { + IN_WORDS (rq->buffer, SECTOR_SIZE / 2); + rq->buffer += SECTOR_SIZE; + --rq->nr_sectors; + --rq->current_nr_sectors; + ++rq->sector; + --this_transfer; + --sectors_to_transfer; + } + } + } + + /* Done moving data! + Wait for another interrupt. */ + ide_handler[DEV_HWIF] = cdrom_read_intr; +} + + +/* + * Try to satisfy some of the current read request from our cached data. + * Returns nonzero if the request has been completed, zero otherwise. + */ +static int cdrom_read_from_buffer (ide_dev_t *dev) +{ + struct cdrom_info *info = &cdrom_info[DEV_HWIF][dev->select.b.drive]; + struct request *rq = ide_cur_rq[DEV_HWIF]; + + /* Can't do anything if there's no buffer. */ + if (info->sector_buffer == NULL) return 0; + + /* Loop while this request needs data and the next block is present + in our cache. */ + while (rq->nr_sectors > 0 && + rq->sector >= info->sector_buffered && + rq->sector < info->sector_buffered + info->nsectors_buffered) + { + if (rq->current_nr_sectors == 0) + cdrom_end_request (1, dev); + + memcpy (rq->buffer, + info->sector_buffer + + (rq->sector - info->sector_buffered) * SECTOR_SIZE, + SECTOR_SIZE); + rq->buffer += SECTOR_SIZE; + --rq->current_nr_sectors; + --rq->nr_sectors; + ++rq->sector; + } + + /* If we've satisfied the current request, terminate it successfully. */ + if (rq->nr_sectors == 0) + { + cdrom_end_request (1, dev); + return -1; + } + + /* Move on to the next buffer if needed. */ + if (rq->current_nr_sectors == 0) + cdrom_end_request (1, dev); + + /* If this condition does not hold, then the kluge i use to + represent the number of sectors to skip at the start of a transfer + will fail. I think that this will never happen, but let's be + paranoid and check. */ + if (rq->current_nr_sectors < (rq->bh->b_size >> SECTOR_BITS) && + (rq->sector % SECTORS_PER_FRAME) != 0) + { + printk ("%s: cdrom_read_from_buffer: buffer botch (%ld)\n", + dev->name, rq->sector); + cdrom_end_request (0, dev); + return -1; + } + + return 0; +} + + + +/* + * Routine to send a read packet command to the drive. + * This is usually called directly from cdrom_start_read. + * However, for drq_interrupt devices, it is called from an interrupt + * when the drive is ready to accept the command. + */ +static int cdrom_start_read_continuation (ide_dev_t *dev) +{ + struct packet_command pc; + struct request *rq = ide_cur_rq[DEV_HWIF]; + + int nsect, sector, nframes, frame, nskip; + + /* Number of sectors to transfer. */ + nsect = rq->nr_sectors; + + /* Starting sector. */ + sector = rq->sector; + + /* If the requested sector doesn't start on a cdrom block boundary, + we must adjust the start of the transfer so that it does, + and remember to skip the first few sectors. If the CURRENT_NR_SECTORS + field is larger than the size of the buffer, it will mean that + we're to skip a number of sectors equal to the amount by which + CURRENT_NR_SECTORS is larger than the buffer size. */ + nskip = (sector % SECTORS_PER_FRAME); + if (nskip > 0) + { + /* Sanity check... */ + if (rq->current_nr_sectors != (rq->bh->b_size >> SECTOR_BITS)) + { + printk ("%s: cdrom_start_read_continuation: buffer botch (%ld)\n", + dev->name, rq->current_nr_sectors); + cdrom_end_request (0, dev); + DO_REQUEST; + return 1; + } + + sector -= nskip; + nsect += nskip; + rq->current_nr_sectors += nskip; + } + + /* Convert from sectors to cdrom blocks, rounding up the transfer + length if needed. */ + nframes = (nsect + SECTORS_PER_FRAME-1) / SECTORS_PER_FRAME; + frame = sector / SECTORS_PER_FRAME; + + /* Largest number of frames was can transfer at once is 64k-1. */ + nframes = MIN (nframes, 65535); + + /* Set up the command */ + memset (&pc.c, 0, sizeof (pc.c)); + pc.c[0] = READ_10; + pc.c[7] = (nframes >> 8); + pc.c[8] = (nframes & 0xff); + + /* Write the sector address into the command image. */ + { + union { + struct {unsigned char b0, b1, b2, b3;} b; + struct {unsigned long l0;} l; + } conv; + conv.l.l0 = frame; + pc.c[2] = conv.b.b3; + pc.c[3] = conv.b.b2; + pc.c[4] = conv.b.b1; + pc.c[5] = conv.b.b0; + } + + if (cdrom_transfer_packet_command (dev, pc.c, sizeof (pc.c))) + return 1; + + /* Set up our interrupt handler and return. */ + ide_handler[DEV_HWIF] = cdrom_read_intr; + + return 0; +} + + +/* + * Start a read request from the CD-ROM. + * Returns 0 if the request was started successfully, + * 1 if there was an error and we should either retry or move on to the + * next request. + */ +static int cdrom_start_read (ide_dev_t *dev, unsigned int block) +{ + struct request *rq = ide_cur_rq[DEV_HWIF]; + + /* We may be retrying this request after an error. + Fix up any weirdness which might be present in the request packet. */ + restore_request (rq); + + /* Satisfy whatever we can of this request from our cached sector. */ + if (cdrom_read_from_buffer (dev)) + return 1; + + /* Clear the local sector buffer. */ + cdrom_info[DEV_HWIF][dev->select.b.drive].nsectors_buffered = 0; + + if (cdrom_start_packet_command (dev, 32768)) + return 1; + + if (CDROM_FLAGS (dev)->drq_interrupt) + ide_handler[DEV_HWIF] = (void (*)(ide_dev_t *))cdrom_start_read_continuation; + else + { + if (cdrom_start_read_continuation (dev)) + return 1; + } + + return 0; +} + + + + +/**************************************************************************** + * Execute all other packet commands. + */ + +/* Forward declaration */ +static int +cdrom_request_sense (ide_dev_t *dev, struct atapi_request_sense *reqbuf); + + +/* Interrupt routine for packet command completion. */ +static void cdrom_pc_intr (ide_dev_t *dev) +{ + int ireason, len, stat, thislen; + struct request *rq = ide_cur_rq[DEV_HWIF]; + struct packet_command *pc = (struct packet_command *)rq->buffer; + + /* Check for errors. */ + if (cdrom_decode_status (dev, 0, &stat)) return; + + /* Read the interrupt reason and the transfer length. */ + ireason = IN_BYTE (HD_NSECTOR, DEV_HWIF); + len = IN_BYTE (HD_LCYL, DEV_HWIF) + 256 * IN_BYTE (HD_HCYL, DEV_HWIF); + + /* If DRQ is clear, the command has completed. + Complain if we still have data left to transfer. */ + if ((stat & DRQ_STAT) == 0) + { + /* Some of the trailing request sense fields are optional, and + some drives don't send them. Sigh. */ + if (pc->c[0] == REQUEST_SENSE && pc->buflen > 0 && pc->buflen <= 5) { + while (pc->buflen > 0) { + *pc->buffer++ = 0; + --pc->buflen; + } + } + + if (pc->buflen == 0) + cdrom_end_request (1, dev); + else + { + printk ("%s: cdrom_pc_intr: data underrun %d\n", + dev->name, pc->buflen); + pc->stat = 1; + cdrom_end_request (1, dev); + } + DO_REQUEST; + return; + } + + /* Figure out how much data to transfer. */ + thislen = pc->buflen; + if (thislen < 0) thislen = -thislen; + if (thislen > len) thislen = len; + + /* The drive wants to be written to. */ + if ((ireason & 3) == 0) + { + /* Check that we want to write. */ + if (pc->buflen > 0) + { + printk ("%s: cdrom_pc_intr: Drive wants to transfer data the wrong way!\n", + dev->name); + pc->stat = 1; + thislen = 0; + } + + /* Transfer the data. */ + OUT_WORDS (pc->buffer, thislen / 2); + + /* If we haven't moved enough data to satisfy the drive, + add some padding. */ + while (len > thislen) + { + short dum = 0; + OUT_WORDS (&dum, 1); + len -= 2; + } + + /* Keep count of how much data we've moved. */ + pc->buffer += thislen; + pc->buflen += thislen; + } + + /* Same drill for reading. */ + else if ((ireason & 3) == 2) + { + /* Check that we want to read. */ + if (pc->buflen < 0) + { + printk ("%s: cdrom_pc_intr: Drive wants to transfer data the wrong way!\n", + dev->name); + pc->stat = 1; + thislen = 0; + } + + /* Transfer the data. */ + IN_WORDS (pc->buffer, thislen / 2); + + /* If we haven't moved enough data to satisfy the drive, + add some padding. */ + while (len > thislen) + { + short dum = 0; + IN_WORDS (&dum, 1); + len -= 2; + } + + /* Keep count of how much data we've moved. */ + pc->buffer += thislen; + pc->buflen -= thislen; + } + + else + { + printk ("%s: cdrom_pc_intr: The drive appears confused (ireason = 0x%2x)\n", + dev->name, ireason); + pc->stat = 1; + } + + /* Now we wait for another interrupt. */ + ide_handler[DEV_HWIF] = cdrom_pc_intr; +} + + +static int cdrom_do_pc_continuation (ide_dev_t *dev) +{ + struct request *rq = ide_cur_rq[DEV_HWIF]; + struct packet_command *pc = (struct packet_command *)rq->buffer; + + if (cdrom_transfer_packet_command (dev, pc->c, sizeof (pc->c))) + return 1; + + /* Set up our interrupt handler and return. */ + ide_handler[DEV_HWIF] = cdrom_pc_intr; + + return 0; +} + + +static int cdrom_do_packet_command (ide_dev_t *dev) +{ + int len; + struct request *rq = ide_cur_rq[DEV_HWIF]; + struct packet_command *pc = (struct packet_command *)rq->buffer; + + len = pc->buflen; + if (len < 0) len = -len; + + pc->stat = 0; + + if (cdrom_start_packet_command (dev, len)) + return 1; + + if (CDROM_FLAGS (dev)->drq_interrupt) + ide_handler[DEV_HWIF] = (void (*)(ide_dev_t *))cdrom_do_pc_continuation; + else + { + if (cdrom_do_pc_continuation (dev)) + return 1; + } + + return 0; +} + + +static +int cdrom_queue_packet_command (ide_dev_t *dev, struct packet_command *pc) +{ + int retries = 3; + unsigned long flags; + struct request req, **p, **pfirst; + struct semaphore sem = MUTEX_LOCKED; + int major = ide_major[DEV_HWIF]; + + retry: + req.dev = MKDEV (major, (dev->select.b.drive) << PARTN_BITS); + req.cmd = PACKET_COMMAND; + req.errors = 0; + req.sector = 0; + req.nr_sectors = 0; + req.current_nr_sectors = 0; + req.buffer = (char *)pc; + req.sem = &sem; + req.bh = NULL; + req.bhtail = NULL; + req.next = NULL; + + save_flags (flags); + cli (); + + p = &blk_dev[major].current_request; + pfirst = p; + while ((*p) != NULL) + { + p = &((*p)->next); + } + *p = &req; + if (p == pfirst) + blk_dev[major].request_fn (); + + restore_flags (flags); + + down (&sem); + + if (pc->stat != 0) + { + /* The request failed. Try to do a request sense to get more information + about the error; store the result in the cdrom_info struct + for this drive. Check to be sure that it wasn't a request sense + request that failed, though, to prevent infinite loops. */ + + struct atapi_request_sense *reqbuf = + &cdrom_info[DEV_HWIF][dev->select.b.drive].sense_data; + + if (pc->c[0] == REQUEST_SENSE || cdrom_request_sense (dev, reqbuf)) + { + memset (reqbuf, 0, sizeof (*reqbuf)); + reqbuf->asc = 0xff; + } + cdrom_analyze_sense_data (dev, reqbuf, pc); + + /* If the error was a unit attention (usually means media was changed), + retry the command. */ + if (reqbuf->sense_key == UNIT_ATTENTION && retries > 0) + { + --retries; + goto retry; + } + + return -EIO; + } + else + return 0; +} + + + +/**************************************************************************** + * cdrom driver request routine. + */ + +static int do_rw_cdrom (ide_dev_t *dev, unsigned long block) +{ + struct request *rq = ide_cur_rq[DEV_HWIF]; + + if (rq -> cmd == PACKET_COMMAND || rq -> cmd == REQUEST_SENSE_COMMAND) + return cdrom_do_packet_command (dev); + + if (rq -> cmd != READ) + { + printk ("ide-cd: bad cmd %d\n", rq -> cmd); + cdrom_end_request (0, dev); + return 1; + } + + return cdrom_start_read (dev, block); +} + + + +/**************************************************************************** + * ioctl handling. + */ + +static inline +void byte_swap_word (unsigned short *x) +{ + char *c = (char *)x; + char d = c[0]; + c[0] = c[1]; + c[1] = d; +} + + +static inline +void byte_swap_long (unsigned *x) +{ + char *c = (char *)x; + char d = c[0]; + c[0] = c[3]; + c[3] = d; + d = c[1]; + c[1] = c[2]; + c[2] = d; +} + + +static +int bin2bcd (int x) +{ + return (x%10) | ((x/10) << 4); +} + + +static inline +void lba_to_msf (int lba, byte *m, byte *s, byte *f) +{ + lba += CD_BLOCK_OFFSET; + lba &= 0xffffff; /* negative lbas use only 24 bits */ + *m = lba / (CD_SECS * CD_FRAMES); + lba %= (CD_SECS * CD_FRAMES); + *s = lba / CD_FRAMES; + *f = lba % CD_FRAMES; +} + + +static inline +int msf_to_lba (byte m, byte s, byte f) +{ + return (((m * CD_SECS) + s) * CD_FRAMES + f) - CD_BLOCK_OFFSET; +} + + +static void +cdrom_check_status (ide_dev_t *dev) +{ + struct packet_command pc; + + memset (&pc, 0, sizeof (pc)); + + pc.c[0] = TEST_UNIT_READY; + + (void) cdrom_queue_packet_command (dev, &pc); +} + + +static int +cdrom_request_sense (ide_dev_t *dev, struct atapi_request_sense *reqbuf) +{ + struct packet_command pc; + + memset (&pc, 0, sizeof (pc)); + + pc.c[0] = REQUEST_SENSE; + pc.c[4] = sizeof (*reqbuf); + pc.buffer = (char *)reqbuf; + pc.buflen = sizeof (*reqbuf); + + return cdrom_queue_packet_command (dev, &pc); +} + + +#if 0 +/* Lock the door if LOCKFLAG is nonzero; unlock it otherwise. */ +static int +cdrom_lockdoor (ide_dev_t *dev, int lockflag) +{ + struct packet_command pc; + + memset (&pc, 0, sizeof (pc)); + + pc.c[0] = ALLOW_MEDIUM_REMOVAL; + pc.c[4] = (lockflag != 0); + return cdrom_queue_packet_command (dev, &pc); +} +#endif + + +/* Eject the disk if EJECTFLAG is 0. + If EJECTFLAG is 1, try to reload the disk. */ +static int +cdrom_eject (ide_dev_t *dev, int ejectflag) +{ + struct packet_command pc; + + memset (&pc, 0, sizeof (pc)); + + pc.c[0] = START_STOP; + pc.c[4] = 2 + (ejectflag != 0); + return cdrom_queue_packet_command (dev, &pc); +} + + +static int +cdrom_pause (ide_dev_t *dev, int pauseflag) +{ + struct packet_command pc; + + memset (&pc, 0, sizeof (pc)); + + pc.c[0] = SCMD_PAUSE_RESUME; + pc.c[8] = !pauseflag; + return cdrom_queue_packet_command (dev, &pc); +} + + +static int +cdrom_startstop (ide_dev_t *dev, int startflag) +{ + struct packet_command pc; + + memset (&pc, 0, sizeof (pc)); + + pc.c[0] = START_STOP; + pc.c[1] = 1; + pc.c[4] = startflag; + return cdrom_queue_packet_command (dev, &pc); +} + + +static int +cdrom_read_tocentry (ide_dev_t *dev, int trackno, int msf_flag, + char *buf, int buflen) +{ + struct packet_command pc; + + memset (&pc, 0, sizeof (pc)); + + pc.buffer = buf; + pc.buflen = buflen; + pc.c[0] = SCMD_READ_TOC; + pc.c[6] = trackno; + pc.c[7] = (buflen >> 8); + pc.c[8] = (buflen & 0xff); + if (msf_flag) pc.c[1] = 2; + return cdrom_queue_packet_command (dev, &pc); +} + + +/* Try to read the entire TOC for the disk into our internal buffer. */ +static int +cdrom_read_toc (ide_dev_t *dev) +{ + int msf_flag; + int stat, ntracks, i; + struct atapi_toc *toc = cdrom_info[DEV_HWIF][dev->select.b.drive].toc; + + if (toc == NULL) + { + /* Try to allocate space. */ + toc = (struct atapi_toc *) kmalloc (sizeof (struct atapi_toc), + GFP_KERNEL); + cdrom_info[DEV_HWIF][dev->select.b.drive].toc = toc; + } + + if (toc == NULL) + { + printk ("%s: No cdrom TOC buffer!\n", dev->name); + return -EIO; + } + + /* Check to see if the existing data is still valid. + If it is, just return. */ + if (CDROM_FLAGS (dev)->toc_valid) + cdrom_check_status (dev); + + if (CDROM_FLAGS (dev)->toc_valid) return 0; + + /* Some drives can't return TOC data in LBA format. */ + msf_flag = (CDROM_FLAGS (dev)->no_lba_toc); + + /* First read just the header, so we know how long the TOC is. */ + stat = cdrom_read_tocentry (dev, 0, msf_flag, (char *)toc, + sizeof (struct atapi_toc_header) + + sizeof (struct atapi_toc_entry)); + if (stat) return stat; + + ntracks = toc->hdr.last_track - toc->hdr.first_track + 1; + if (ntracks <= 0) return -EIO; + if (ntracks > MAX_TRACKS) ntracks = MAX_TRACKS; + + /* Now read the whole schmeer. */ + stat = cdrom_read_tocentry (dev, 0, msf_flag, (char *)toc, + sizeof (struct atapi_toc_header) + + (ntracks+1) * sizeof (struct atapi_toc_entry)); + if (stat) return stat; + byte_swap_word (&toc->hdr.toc_length); + for (i=0; i<=ntracks; i++) + { + if (msf_flag) + { + byte *adr = (byte *)&(toc->ent[i].lba); + toc->ent[i].lba = msf_to_lba (adr[1], adr[2], adr[3]); + } + else + byte_swap_long (&toc->ent[i].lba); + } + + /* Remember that we've read this stuff. */ + CDROM_FLAGS (dev)->toc_valid = 1; + + return 0; +} + + +static int +cdrom_read_subchannel (ide_dev_t *dev, + char *buf, int buflen) +{ + struct packet_command pc; + + memset (&pc, 0, sizeof (pc)); + + pc.buffer = buf; + pc.buflen = buflen; + pc.c[0] = SCMD_READ_SUBCHANNEL; + pc.c[2] = 0x40; /* request subQ data */ + pc.c[3] = 0x01; /* Format 1: current position */ + pc.c[7] = (buflen >> 8); + pc.c[8] = (buflen & 0xff); + return cdrom_queue_packet_command (dev, &pc); +} + + +/* modeflag: 0 = current, 1 = changeable mask, 2 = default, 3 = saved */ +static int +cdrom_mode_sense (ide_dev_t *dev, int pageno, int modeflag, + char *buf, int buflen) +{ + struct packet_command pc; + + memset (&pc, 0, sizeof (pc)); + + pc.buffer = buf; + pc.buflen = buflen; + pc.c[0] = MODE_SENSE_10; + pc.c[2] = pageno | (modeflag << 6); + pc.c[7] = (buflen >> 8); + pc.c[8] = (buflen & 0xff); + return cdrom_queue_packet_command (dev, &pc); +} + + +static int +cdrom_mode_select (ide_dev_t *dev, int pageno, char *buf, int buflen) +{ + struct packet_command pc; + + memset (&pc, 0, sizeof (pc)); + + pc.buffer = buf; + pc.buflen = - buflen; + pc.c[0] = MODE_SELECT_10; + pc.c[1] = 0x10; + pc.c[2] = pageno; + pc.c[7] = (buflen >> 8); + pc.c[8] = (buflen & 0xff); + return cdrom_queue_packet_command (dev, &pc); +} + + +static int +cdrom_play_lba_range_play12 (ide_dev_t *dev, int lba_start, int lba_end) +{ + struct packet_command pc; + + memset (&pc, 0, sizeof (pc)); + + pc.c[0] = SCMD_PLAYAUDIO12; + *(int *)(&pc.c[2]) = lba_start; + *(int *)(&pc.c[6]) = lba_end - lba_start; + byte_swap_long ((int *)(&pc.c[2])); + byte_swap_long ((int *)(&pc.c[6])); + + return cdrom_queue_packet_command (dev, &pc); +} + + +static int +cdrom_play_lba_range_msf (ide_dev_t *dev, int lba_start, int lba_end) +{ + struct packet_command pc; + + memset (&pc, 0, sizeof (pc)); + + pc.c[0] = SCMD_PLAYAUDIO_MSF; + lba_to_msf (lba_start, &pc.c[3], &pc.c[4], &pc.c[5]); + lba_to_msf (lba_end-1, &pc.c[6], &pc.c[7], &pc.c[8]); + + if (CDROM_FLAGS (dev)->msf_as_bcd) + { + pc.c[3] = bin2bcd (pc.c[3]); + pc.c[4] = bin2bcd (pc.c[4]); + pc.c[5] = bin2bcd (pc.c[5]); + pc.c[6] = bin2bcd (pc.c[6]); + pc.c[7] = bin2bcd (pc.c[7]); + pc.c[8] = bin2bcd (pc.c[8]); + } + + return cdrom_queue_packet_command (dev, &pc); +} + + +/* Play audio starting at LBA LBA_START and finishing with the + LBA before LBA_END. */ +static int +cdrom_play_lba_range (ide_dev_t *dev, int lba_start, int lba_end) +{ + /* This is rather annoying. + My NEC-260 won't recognize group 5 commands such as PLAYAUDIO12; + the only way to get it to play more than 64k of blocks at once + seems to be the PLAYAUDIO_MSF command. However, the parameters + the NEC 260 wants for the PLAYMSF command are incompatible with + the new version of the spec. + + So what i'll try is this. First try for PLAYAUDIO12. If it works, + great. Otherwise, if the drive reports an illegal command code, + try PLAYAUDIO_MSF using the NEC 260-style bcd parameters. */ + + if (CDROM_FLAGS (dev)->no_playaudio12) + return cdrom_play_lba_range_msf (dev, lba_start, lba_end); + else + { + int stat; + struct atapi_request_sense *reqbuf; + + stat = cdrom_play_lba_range_play12 (dev, lba_start, lba_end); + if (stat == 0) return 0; + + /* It failed. Try to find out why. */ + reqbuf = &cdrom_info[DEV_HWIF][dev->select.b.drive].sense_data; + if (reqbuf->sense_key == 0x05 && reqbuf->asc == 0x20) + { + /* The drive didn't recognize the command. + Retry with the MSF variant. */ + printk ("%s: Drive does not support PLAYAUDIO12; " + "trying PLAYAUDIO_MSF\n", dev->name); + CDROM_FLAGS (dev)->no_playaudio12 = 1; + CDROM_FLAGS (dev)->msf_as_bcd = 1; + return cdrom_play_lba_range_msf (dev, lba_start, lba_end); + } + + /* Failed for some other reason. Give up. */ + return stat; + } +} + + +static +int cdrom_get_toc_entry (ide_dev_t *dev, int track, + struct atapi_toc_entry **ent) +{ + int stat, ntracks; + struct atapi_toc *toc; + + /* Make sure our saved TOC is valid. */ + stat = cdrom_read_toc (dev); + if (stat) return stat; + + toc = cdrom_info[DEV_HWIF][dev->select.b.drive].toc; + + /* Check validity of requested track number. */ + ntracks = toc->hdr.last_track - toc->hdr.first_track + 1; + if (track == CDROM_LEADOUT) + *ent = &toc->ent[ntracks]; + else if (track < toc->hdr.first_track || + track > toc->hdr.last_track) + return -EINVAL; + else + *ent = &toc->ent[track - toc->hdr.first_track]; + + return 0; +} + + +static int ide_cdrom_ioctl (ide_dev_t *dev, struct inode *inode, + struct file *file, unsigned int cmd, unsigned long arg) +{ + switch (cmd) + { + case CDROMEJECT: + return cdrom_eject (dev, 0); + + case CDROMPAUSE: + return cdrom_pause (dev, 1); + + case CDROMRESUME: + return cdrom_pause (dev, 0); + + case CDROMSTART: + return cdrom_startstop (dev, 1); + + case CDROMSTOP: + return cdrom_startstop (dev, 0); + + case CDROMPLAYMSF: + { + struct cdrom_msf msf; + int stat, lba_start, lba_end; + + stat = verify_area (VERIFY_READ, (void *)arg, sizeof (msf)); + if (stat) return stat; + + memcpy_fromfs (&msf, (void *) arg, sizeof(msf)); + + lba_start = msf_to_lba (msf.cdmsf_min0, msf.cdmsf_sec0, + msf.cdmsf_frame0); + lba_end = msf_to_lba (msf.cdmsf_min1, msf.cdmsf_sec1, + msf.cdmsf_frame1) + 1; + + if (lba_end <= lba_start) return -EINVAL; + + return cdrom_play_lba_range (dev, lba_start, lba_end); + } + + /* Like just about every other Linux cdrom driver, we ignore the + index part of the request here. */ + case CDROMPLAYTRKIND: + { + int stat, lba_start, lba_end; + struct cdrom_ti ti; + struct atapi_toc_entry *first_toc, *last_toc; + + stat = verify_area (VERIFY_READ, (void *)arg, sizeof (ti)); + if (stat) return stat; + + memcpy_fromfs (&ti, (void *) arg, sizeof(ti)); + + stat = cdrom_get_toc_entry (dev, ti.cdti_trk0, &first_toc); + if (stat) return stat; + stat = cdrom_get_toc_entry (dev, ti.cdti_trk1, &last_toc); + if (stat) return stat; + + if (ti.cdti_trk1 != CDROM_LEADOUT) ++last_toc; + lba_start = first_toc->lba; + lba_end = last_toc->lba; + + if (lba_end <= lba_start) return -EINVAL; + + return cdrom_play_lba_range (dev, lba_start, lba_end); + } + + case CDROMREADTOCHDR: + { + int stat; + struct cdrom_tochdr tochdr; + struct atapi_toc *toc; + + stat = verify_area (VERIFY_WRITE, (void *) arg, sizeof (tochdr)); + if (stat) return stat; + + /* Make sure our saved TOC is valid. */ + stat = cdrom_read_toc (dev); + if (stat) return stat; + + toc = cdrom_info[DEV_HWIF][dev->select.b.drive].toc; + tochdr.cdth_trk0 = toc->hdr.first_track; + tochdr.cdth_trk1 = toc->hdr.last_track; + + memcpy_tofs ((void *) arg, &tochdr, sizeof (tochdr)); + + return stat; + } + + case CDROMREADTOCENTRY: + { + int stat; + struct cdrom_tocentry tocentry; + struct atapi_toc_entry *toce; + + stat = verify_area (VERIFY_READ, (void *) arg, sizeof (tocentry)); + if (stat) return stat; + stat = verify_area (VERIFY_WRITE, (void *) arg, sizeof (tocentry)); + if (stat) return stat; + + memcpy_fromfs (&tocentry, (void *) arg, sizeof (tocentry)); + + stat = cdrom_get_toc_entry (dev, tocentry.cdte_track, &toce); + if (stat) return stat; + + tocentry.cdte_ctrl = toce->control; + tocentry.cdte_adr = toce->adr; + + if (tocentry.cdte_format == CDROM_MSF) + { + /* convert to MSF */ + lba_to_msf (toce->lba, + &tocentry.cdte_addr.msf.minute, + &tocentry.cdte_addr.msf.second, + &tocentry.cdte_addr.msf.frame); + } + else + tocentry.cdte_addr.lba = toce->lba; + + memcpy_tofs ((void *) arg, &tocentry, sizeof (tocentry)); + + return stat; + } + + case CDROMSUBCHNL: + { + char buffer[16]; + int stat, abs_lba, rel_lba; + struct cdrom_subchnl subchnl; + + stat = verify_area (VERIFY_WRITE, (void *) arg, sizeof (subchnl)); + if (stat) return stat; + stat = verify_area (VERIFY_READ, (void *) arg, sizeof (subchnl)); + if (stat) return stat; + + memcpy_fromfs (&subchnl, (void *) arg, sizeof (subchnl)); + + stat = cdrom_read_subchannel (dev, buffer, sizeof (buffer)); + if (stat) return stat; + + abs_lba = *(int *)&buffer[8]; + rel_lba = *(int *)&buffer[12]; + byte_swap_long (&abs_lba); + byte_swap_long (&rel_lba); + + if (subchnl.cdsc_format == CDROM_MSF) + { + lba_to_msf (abs_lba, + &subchnl.cdsc_absaddr.msf.minute, + &subchnl.cdsc_absaddr.msf.second, + &subchnl.cdsc_absaddr.msf.frame); + lba_to_msf (rel_lba, + &subchnl.cdsc_reladdr.msf.minute, + &subchnl.cdsc_reladdr.msf.second, + &subchnl.cdsc_reladdr.msf.frame); + } + else + { + subchnl.cdsc_absaddr.lba = abs_lba; + subchnl.cdsc_reladdr.lba = rel_lba; + } + + subchnl.cdsc_audiostatus = buffer[1]; + subchnl.cdsc_ctrl = buffer[5] & 0xf; + subchnl.cdsc_trk = buffer[6]; + subchnl.cdsc_ind = buffer[7]; + + memcpy_tofs ((void *) arg, &subchnl, sizeof (subchnl)); + + return stat; + } + + case CDROMVOLCTRL: + { + struct cdrom_volctrl volctrl; + char buffer[24], mask[24]; + int stat; + + stat = verify_area (VERIFY_READ, (void *) arg, sizeof (volctrl)); + if (stat) return stat; + memcpy_fromfs (&volctrl, (void *) arg, sizeof (volctrl)); + + stat = cdrom_mode_sense (dev, 0x0e, 0, buffer, sizeof (buffer)); + if (stat) return stat; + stat = cdrom_mode_sense (dev, 0x0e, 1, mask , sizeof (buffer)); + if (stat) return stat; + + buffer[1] = buffer[2] = 0; + + buffer[17] = volctrl.channel0 & mask[17]; + buffer[19] = volctrl.channel1 & mask[19]; + buffer[21] = volctrl.channel2 & mask[21]; + buffer[23] = volctrl.channel3 & mask[23]; + + return cdrom_mode_select (dev, 0x0e, buffer, sizeof (buffer)); + } + +#ifdef TEST + case 0x1234: + { + int stat; + struct packet_command pc; + + memset (&pc, 0, sizeof (pc)); + + stat = verify_area (VERIFY_READ, (void *) arg, sizeof (pc.c)); + if (stat) return stat; + memcpy_fromfs (&pc.c, (void *) arg, sizeof (pc.c)); + + return cdrom_queue_packet_command (dev, &pc); + } + + case 0x1235: + { + int stat; + struct atapi_request_sense reqbuf; + + stat = verify_area (VERIFY_WRITE, (void *) arg, sizeof (reqbuf)); + if (stat) return stat; + + stat = cdrom_request_sense (dev, &reqbuf); + + memcpy_tofs ((void *) arg, &reqbuf, sizeof (reqbuf)); + + return stat; + } +#endif + + default: + return -EPERM; + } + +} + + + +/**************************************************************************** + * Other driver requests (open, close, check media change). + */ + +static int cdrom_check_media_change (ide_dev_t *dev) +{ + int retval; + + cdrom_check_status (dev); + + retval = CDROM_FLAGS (dev)->media_changed; + CDROM_FLAGS (dev)->media_changed = 0; + + return retval; +} + + +static int +cdrom_open (struct inode *ip, struct file *fp, ide_dev_t *dev) +{ + /* no write access */ + if (fp->f_mode & 2) return -EROFS; + +#if 0 /* With this, one cannot eject a disk with workman */ + /* If this is the first open, lock the door. */ + if (dev->usage == 1) + (void) cdrom_lockdoor (dev, 1); +#endif + + /* Should check that there's a disk in the drive? */ + return 0; +} + + +/* + * Close down the device. Invalidate all cached blocks. + */ + +static void +cdrom_release (struct inode *inode, struct file *file, ide_dev_t *dev) +{ + if (dev->usage == 0) + { + invalidate_buffers (inode->i_rdev); + +#if 0 + /* Unlock the door. */ + (void) cdrom_lockdoor (dev, 0); +#endif + } +} + + + +/**************************************************************************** + * Device initialization. + */ + +static void cdrom_setup (ide_dev_t *dev) +{ + /* Just guess at capacity for now. */ + ide_capacity[DEV_HWIF][dev->select.b.drive] = 0x1fffff; + + ide_blksizes[DEV_HWIF][dev->select.b.drive << PARTN_BITS] = CD_FRAMESIZE; + + dev->special.all = 0; + + CDROM_FLAGS (dev)->media_changed = 0; + CDROM_FLAGS (dev)->toc_valid = 0; + + CDROM_FLAGS (dev)->no_playaudio12 = 0; + CDROM_FLAGS (dev)->no_lba_toc = 0; + CDROM_FLAGS (dev)->msf_as_bcd = 0; + CDROM_FLAGS (dev)->drq_interrupt = ((dev->id->config & 0x0060) == 0x20); + + /* Accommodate some broken drives... */ + if (strcmp (dev->id->model, "CD220E") == 0) /* Creative Labs */ + CDROM_FLAGS (dev)->no_lba_toc = 1; + + else if (strcmp (dev->id->model, "TO-ICSLYAL") == 0 || /* Acer CD525E */ + strcmp (dev->id->model, "OTI-SCYLLA") == 0) + CDROM_FLAGS (dev)->no_lba_toc = 1; + + else if (strcmp (dev->id->model, "CDA26803I SE") == 0) /* Aztech */ + { + CDROM_FLAGS (dev)->no_lba_toc = 1; + + /* This drive _also_ does not implement PLAYAUDIO12 correctly. */ + CDROM_FLAGS (dev)->no_playaudio12 = 1; + } + + cdrom_info[DEV_HWIF][dev->select.b.drive].toc = NULL; + cdrom_info[DEV_HWIF][dev->select.b.drive].sector_buffer = NULL; + cdrom_info[DEV_HWIF][dev->select.b.drive].sector_buffered = 0; + cdrom_info[DEV_HWIF][dev->select.b.drive].nsectors_buffered = 0; +} + + +#undef MIN +#undef SECTOR_SIZE +#undef SECTOR_BITS + + +/* + * TODO: + * Read actual disk capacity. + * Multisession support. + * Direct reading of audio data. + * Eject-on-dismount. + * Lock door while there's a mounted volume. + * Establish interfaces for an IDE port driver, and break out the cdrom + * code into a loadable module. + */ + diff --git a/drivers/block/ide.c b/drivers/block/ide.c new file mode 100644 index 000000000..a72ea3b0a --- /dev/null +++ b/drivers/block/ide.c @@ -0,0 +1,2438 @@ +/* + * linux/drivers/block/ide.c Version 3.16 May 30, 1995 + * + * Copyright (C) 1994, 1995 Linus Torvalds & authors (see below) + */ + +/* + * This is the dual IDE interface driver, as evolved from hd.c. + * It supports up to two IDE interfaces, on one or two IRQs (usually 14 & 15). + * There can be up to two drives per interface, as per the ATA-2 spec. + * + * Primary i/f: ide0: major=3; (hda) minor=0, (hdb) minor=64 + * Secondary i/f: ide1: major=22; (hdc or hd1a) minor=0, (hdd or hd1b) minor=64 + * + * From hd.c: + * | + * | It traverses the request-list, using interrupts to jump between functions. + * | As nearly all functions can be called within interrupts, we may not sleep. + * | Special care is recommended. Have Fun! + * | + * | modified by Drew Eckhardt to check nr of hd's from the CMOS. + * | + * | Thanks to Branko Lankester, lankeste@fwi.uva.nl, who found a bug + * | in the early extended-partition checks and added DM partitions. + * | + * | Early work on error handling by Mika Liljeberg (liljeber@cs.Helsinki.FI). + * | + * | IRQ-unmask, drive-id, multiple-mode, support for ">16 heads", + * | and general streamlining by Mark Lord (mlord@bnr.ca). + * + * October, 1994 -- Complete line-by-line overhaul for linux 1.1.x, by: + * + * Mark Lord (mlord@bnr.ca) (IDE Perf.Pkg) + * Delman Lee (delman@mipg.upenn.edu) ("Mr. atdisk2") + * Petri Mattila (ptjmatti@kruuna.helsinki.fi) (EIDE stuff) + * Scott Snyder (snyder@fnald0.fnal.gov) (ATAPI IDE cd-rom) + * + * This was a rewrite of just about everything from hd.c, though some original + * code is still sprinkled about. Think of it as a major evolution, with + * inspiration from lots of linux users, esp. hamish@zot.apana.org.au + * + * Version 1.0 ALPHA initial code, primary i/f working okay + * Version 1.1 ALPHA fixes for dual i/f + * Version 1.2 ALPHA first serious attempt at sharing irqs + * Version 1.3 BETA dual i/f on shared irq tested & working! + * Version 1.4 BETA added auto probing for irq(s) + * Version 1.5 BETA added ALPHA (untested) support for IDE cd-roms, + * fixed hd.c coexistence bug, other minor stuff + * Version 1.6 BETA fix link error when cd-rom not configured + * Version 2.0 BETA lots of minor fixes; remove annoying messages; ... + * Version 2.2 BETA fixed reset_drives; major overhaul of autoprobing + * Version 2.3 BETA set DEFAULT_UNMASK_INTR to 0 again; cosmetic changes + * Version 2.4 BETA added debounce on reading of drive status reg, + * added config flags to remove unwanted features + * Version 2.5 BETA fixed problem with leftover phantom IRQ after probe, + * allow "set_geometry" even when in LBA (as per spec(?)), + * assorted miscellaneous tweaks. + * Version 2.6 BETA more config flag stuff, another probing tweak, + * (not released) multmode now defaults to status quo from boot time, + * moved >16heads check to init time, rearranged reset code + * added HDIO_DRIVE_CMD, removed standby/xfermode stuff + * hopefully fixed ATAPI probing code, added hdx=cdrom + * Version 2.7 BETA fixed invocation of cdrom_setup() + * Version 2.8 BETA fixed compile error for DISK_RECOVERY_TIME>0 + * fixed incorrect drive selection in DO_DRIVE_CMD (Bug!) + * Version 2.9 BETA more work on ATAPI CDROM recognition + * (not released) changed init order so partition checks go in sequence + * Version 3.0 BETA included ide-cd.c update from Steve with Mitsumi fixes + * attempt to fix byte-swap problem with Mitsumi id_info + * ensure drives on second i/f get initialized on boot + * preliminary compile-time support for 32bit IDE i/f chips + * added check_region() and snarf_region() to probes + * Version 3.1 BETA ensure drives on *both* i/f get initialized on boot + * fix byte-swap problem with Mitsumi id_info + * changed ide_timermask into ide_timerbit + * get rid of unexpected interrupts after probing + * don't wait for READY_STAT on cdrom drives + * Version 3.2 BETA Ooops.. mistakenly left VLB_32BIT_IDE on by default + * new ide-cd.c from Scott + * Version 3.3 BETA fix compiling with PROBE_FOR_IRQS==0 + * (sent to Linus) tweak in do_probe() to fix Delman's DRDY problem + * Version 3.4 BETA removed "444" debug message + * (sent to Linus) + * Version 3.5 correct the bios_cyl field if it's too small + * (linux 1.1.76) (to help fdisk with brain-dead BIOSs) + * Version 3.6 cosmetic corrections to comments and stuff + * (linux 1.1.77) reorganise probing code to make it understandable + * added halfway retry to probing for drive identification + * added "hdx=noprobe" command line option + * allow setting multmode even when identification fails + * Version 3.7 move set_geometry=1 from do_identify() to ide_init() + * increase DRQ_WAIT to eliminate nuisance messages + * wait for DRQ_STAT instead of DATA_READY during probing + * (courtesy of Gary Thomas gary@efland.UU.NET) + * Version 3.8 fixed byte-swapping for confused Mitsumi cdrom drives + * update of ide-cd.c from Scott, allows blocksize=1024 + * cdrom probe fixes, inspired by jprang@uni-duisburg.de + * Version 3.9 don't use LBA if lba_capacity looks funny + * correct the drive capacity calculations + * fix probing for old Seagates without HD_ALTSTATUS + * fix byte-ordering for some NEC cdrom drives + * Version 3.10 disable multiple mode by default; was causing trouble + * Version 3.11 fix mis-identification of old WD disks as cdroms + * Version 3,12 simplify logic for selecting initial mult_count + * (fixes problems with buggy WD drives) + * Version 3.13 remove excess "multiple mode disabled" messages + * Version 3.14 fix ide_error() handling of BUSY_STAT + * fix byte-swapped cdrom strings (again.. arghh!) + * ignore INDEX bit when checking the ALTSTATUS reg + * Version 3.15 add SINGLE_THREADED flag for use with dual-CMD i/f + * ignore WRERR_STAT for non-write operations + * added VLB_SYNC support for DC-2000A & others, + * (incl. some Promise chips), courtesy of Frank Gockel + * Version 3.16 convert VLB_32BIT and VLB_SYNC into runtime flags + * add ioctls to get/set VLB flags (HDIO_[SG]ET_CHIPSET) + * rename SINGLE_THREADED to SUPPORT_SERIALIZE, + * add boot flag to "serialize" operation for CMD i/f + * add optional support for DTC2278 interfaces, + * courtesy of andy@cercle.cts.com (Dyan Wile). + * add boot flag to enable "dtc2278" probe + * add probe to avoid EATA (SCSI) interfaces, + * courtesy of neuffer@goofy.zdv.uni-mainz.de. + * + * To do: + * - improved CMD support: tech info is supposedly "in the mail" + * - special 32-bit controller-type detection & support + * - figure out how to support oddball "intelligent" caching cards + * - reverse-engineer 3/4 drive support on fancy "Promise" cards + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/***************************************************************************** + * IDE driver configuration options (play with these as desired): + */ +#define REALLY_SLOW_IO /* most systems can safely undef this */ +#include + +#undef REALLY_FAST_IO /* define if ide ports are perfect */ +#define INITIAL_MULT_COUNT 0 /* off=0; on=2,4,8,16,32, etc.. */ +#ifndef SUPPORT_VLB_32BIT /* 1 to support 32bit I/O on VLB */ +#define SUPPORT_VLB_32BIT 1 /* 0 to reduce kernel size */ +#endif +#ifndef SUPPORT_VLB_SYNC /* 1 to support weird 32-bit chips */ +#define SUPPORT_VLB_SYNC 1 /* 0 to reduce kernel size */ +#endif +#ifndef DISK_RECOVERY_TIME /* off=0; on=access_delay_time */ +#define DISK_RECOVERY_TIME 0 /* for hardware that needs it */ +#endif +#ifndef OK_TO_RESET_CONTROLLER /* 1 needed for good error recovery */ +#define OK_TO_RESET_CONTROLLER 1 /* 0 for use with AH2372A/B interface */ +#endif +#ifndef SUPPORT_TWO_INTERFACES /* 1 to support one/two interfaces */ +#define SUPPORT_TWO_INTERFACES 1 /* 0 for a smaller, faster kernel */ +#endif +#ifndef OPTIMIZE_IRQS /* 1 for slightly faster code */ +#define OPTIMIZE_IRQS 1 /* 0 to reduce kernel size */ +#endif +#ifndef SUPPORT_SERIALIZE /* 1 to support CMD dual interfaces */ +#define SUPPORT_SERIALIZE 1 /* 0 to reduce kernel size */ +#endif +#ifndef SUPPORT_SHARING_IRQ /* 1 to allow two IDE i/f on one IRQ */ +#define SUPPORT_SHARING_IRQ 1 /* 0 to reduce kernel size */ +#endif +#ifndef SUPPORT_DTC2278 /* 1 to support DTC2278 chipset */ +#define SUPPORT_DTC2278 1 /* 0 to reduce kernel size */ +#endif +#ifndef FANCY_STATUS_DUMPS /* 1 for human-readable drive errors */ +#define FANCY_STATUS_DUMPS 1 /* 0 to reduce kernel size */ +#endif +#define PROBE_FOR_IRQS 1 /* 0 to force use of defaults below */ +#define DEFAULT_IDE0_IRQ 14 /* in case irq-probe fails */ +#define DEFAULT_IDE1_IRQ 15 /* in case irq-probe fails */ + +/* IDE_DRIVE_CMD is used to implement many features of the hdparm utility */ +#define IDE_DRIVE_CMD 99 /* (magic) undef to reduce kernel size*/ + +/* + * "No user-serviceable parts" beyond this point :) + ****************************************************************************** + */ + +/* + * Need to change these elsewhere in the kernel (someday) + */ +#ifndef IDE0_TIMER +#define IDE0_TIMER HD_TIMER +#define IDE1_TIMER HD_TIMER2 +#endif + +/* + * Ensure that various configuration flags have compatible settings + */ +#ifdef REALLY_SLOW_IO +#undef REALLY_FAST_IO +#endif +#ifdef CONFIG_BLK_DEV_HD +#undef SUPPORT_TWO_INTERFACES +#define SUPPORT_TWO_INTERFACES 0 +#endif /* CONFIG_BLK_DEV_HD */ + +#if SUPPORT_TWO_INTERFACES +#define HWIF hwif +#define DEV_HWIF (dev->hwif) +#if SUPPORT_SERIALIZE +#undef SUPPORT_SHARING_IRQ +#define SUPPORT_SHARING_IRQ 1 +#endif +#else +#undef SUPPORT_SERIALIZE +#define SUPPORT_SERIALIZE 0 +#undef OPTIMIZE_IRQS +#define OPTIMIZE_IRQS 0 +#undef SUPPORT_SHARING_IRQ +#define SUPPORT_SHARING_IRQ 0 +#ifdef CONFIG_BLK_DEV_HD +#define HWIF 1 +#else +#define HWIF 0 +#endif /* CONFIG_BLK_DEV_HD */ +#define DEV_HWIF HWIF +#endif /* SUPPORT_TWO_INTERFACES */ + +/* + * Definitions for accessing IDE controller registers + */ +typedef unsigned char byte; /* used everywhere */ +#define IDE_PORT(p,hwif) ((p)^((hwif)<<7)) /* IDE0: p^0x00 , IDE1: p^0x80 */ + +#ifdef REALLY_FAST_IO +#define OUT_BYTE(b,p) outb((b),IDE_PORT(p,DEV_HWIF)) +#define IN_BYTE(p,hwif) (byte)inb(IDE_PORT(p,hwif)) +#else +#define OUT_BYTE(b,p) outb_p((b),IDE_PORT(p,DEV_HWIF)) +#define IN_BYTE(p,hwif) (byte)inb_p(IDE_PORT(p,hwif)) +#endif /* REALLY_FAST_IO */ + +#if SUPPORT_VLB_32BIT +#if SUPPORT_VLB_SYNC +#define VLB_SYNC __asm__ __volatile__ ("pusha\n movl $0x01f2,%edx\n inb (%dx),%al\n inb (%dx),%al\n inb (%dx),%al\n popa\n") +#endif /* SUPPORT_VLB_SYNC */ +#endif /* SUPPORT_VLB_32BIT */ + +#if SUPPORT_DTC2278 +static uint probe_dtc2278 = 0; +#endif + +#define GET_ERR(hwif) IN_BYTE(HD_ERROR,hwif) +#define GET_STAT(hwif) IN_BYTE(HD_STATUS,hwif) +#define OK_STAT(stat,good,bad) (((stat)&((good)|(bad)))==(good)) +#define BAD_R_STAT (BUSY_STAT | ERR_STAT) +#define BAD_W_STAT (BUSY_STAT | ERR_STAT | WRERR_STAT) +#define BAD_STAT (BAD_R_STAT | DRQ_STAT) +#define DRIVE_READY (READY_STAT | SEEK_STAT) +#define DATA_READY (DRIVE_READY | DRQ_STAT) + +/* + * Some more useful definitions + */ +#define BIOS_SECTORS(dev) (dev->bios_head*dev->bios_sect*dev->bios_cyl) +#define HD_NAME "hd" /* the same for both i/f; see also genhd.c */ +#define PARTN_BITS 6 /* number of minor dev bits for partitions */ +#define PARTN_MASK ((1< 0) +static unsigned long ide_lastreq[] = {0,0}; /* completion time of last I/O */ +#define SET_DISK_RECOVERY_TIMER ide_lastreq[DEV_HWIF] = read_timer(); +static unsigned long read_timer(void) +{ + unsigned long t, flags; + int i; + + save_flags(flags); + cli(); + t = jiffies * 11932; + outb_p(0, 0x43); + i = inb_p(0x40); + i |= inb(0x40) << 8; + restore_flags(flags); + return (t - i); +} +#else +#define SET_DISK_RECOVERY_TIMER /* nothing */ +#endif /* DISK_RECOVERY_TIME */ + +/* + * The heart of the driver, referenced from lots of other routines: + */ +static void do_request (byte hwif); +#define DO_REQUEST {SET_DISK_RECOVERY_TIMER do_request(DEV_HWIF);} + +/* + * This is a macro rather than an inline to permit better gcc code. + * Caller MUST do sti() before invoking WAIT_STAT() (for jiffies to work). + * + * This routine should get fixed to not hog the cpu during extra long waits.. + * That could be done by busy-waiting for the first jiffy or two, and then + * setting a timer to wake up at half second intervals thereafter, + * until WAIT_WORSTCASE is achieved, before timing out. + */ +#define WAIT_STAT(dev,good,bad,timeout,msg,label) \ +{ \ + byte stat; \ + udelay(1); /* spec allows drive 400ns to assert "BUSY" */ \ + if (GET_STAT(DEV_HWIF) & BUSY_STAT) { \ + unsigned long timer = jiffies + timeout; \ + do { \ + if ((GET_STAT(DEV_HWIF) & BUSY_STAT) == 0) \ + break; \ + } while (timer > jiffies); \ + } \ + udelay(1); /* spec allows 400ns for status to stabilize */ \ + if (!OK_STAT(stat=GET_STAT(DEV_HWIF), good, bad)) { \ + ide_error(dev, msg " error", stat); \ + goto label; \ + } \ +} + +/* + * This is used for all data transfers *from* the IDE interface + */ +void input_ide_data (ide_dev_t *dev, void *buffer, uint wcount) +{ +#if SUPPORT_VLB_32BIT + if (dev->vlb_32bit) { +#if SUPPORT_VLB_SYNC + if (dev->vlb_sync) { + cli(); + VLB_SYNC; + insl(IDE_PORT(HD_DATA,DEV_HWIF), buffer, wcount); + if (dev->unmask) + sti(); + } else +#endif /* SUPPORT_VLB_SYNC */ + insl(IDE_PORT(HD_DATA,DEV_HWIF), buffer, wcount); + } else +#endif /* SUPPORT_VLB_32BIT */ + insw(IDE_PORT(HD_DATA,DEV_HWIF), buffer, wcount<<1); +} + +/* + * This is used for all data transfers *to* the IDE interface + */ +void output_ide_data (ide_dev_t *dev, void *buffer, uint wcount) +{ +#if SUPPORT_VLB_32BIT + if (dev->vlb_32bit) { +#if SUPPORT_VLB_SYNC + if (dev->vlb_sync) { + cli(); + VLB_SYNC; + outsl(IDE_PORT(HD_DATA,DEV_HWIF), buffer, wcount); + if (dev->unmask) + sti(); + } else + outsl(IDE_PORT(HD_DATA,DEV_HWIF), buffer, wcount); +#endif /* SUPPORT_VLB_SYNC */ + } else +#endif /* SUPPORT_VLB_32BIT */ + outsw(IDE_PORT(HD_DATA,DEV_HWIF), buffer, wcount<<1); +} + +/* + * This should get invoked on every exit path from the driver. + */ +static inline void start_ide_timer (byte hwif) +{ + if (ide_handler[HWIF] != NULL) { /* waiting for an irq? */ + timer_table[ide_timer[HWIF]].expires = jiffies + WAIT_CMD; + timer_active |= ide_timerbit[HWIF]; + } +} + +static void do_ide_reset (ide_dev_t *dev) +{ + byte tmp; + unsigned long timer, flags; + + save_flags(flags); + sti(); + for (tmp = 0; tmp < MAX_DRIVES; tmp++) { + ide_dev_t *rdev = &ide_dev[DEV_HWIF][tmp]; + rdev->special.b.set_geometry = 1; + rdev->special.b.recalibrate = 1; + rdev->special.b.set_multmode = 0; + if (OK_TO_RESET_CONTROLLER) + rdev->mult_count = 0; + if (!rdev->keep_settings) { + rdev->mult_req = 0; + rdev->unmask = 0; + } + if (rdev->mult_req != rdev->mult_count) + rdev->special.b.set_multmode = 1; + } + +#if OK_TO_RESET_CONTROLLER + cli(); + OUT_BYTE(dev->ctl|6,HD_CMD); /* set nIEN, set SRST */ + udelay(10); /* more than enough time */ + OUT_BYTE(dev->ctl|2,HD_CMD); /* clear SRST */ + udelay(10); /* more than enough time */ + sti(); /* needed for jiffies */ + for (timer = jiffies + WAIT_WORSTCASE; timer > jiffies;) { + if ((GET_STAT(DEV_HWIF) & BUSY_STAT) == 0) + break; + } + printk("%s: do_ide_reset: ", ide_name[DEV_HWIF]); + /* ATAPI devices usually do *not* assert READY after a reset */ + if (!OK_STAT(tmp=GET_STAT(DEV_HWIF), 0, BUSY_STAT)) { + printk("timed-out, status=0x%02x\n", tmp); + } else { + if ((tmp = GET_ERR(DEV_HWIF)) == 1) + printk("success\n"); + else { + printk("%s: ", ide_devname[DEV_HWIF][0]); + switch (tmp & 0x7f) { + case 1: printk("passed"); + break; + case 2: printk("formatter device error"); + break; + case 3: printk("sector buffer error"); + break; + case 4: printk("ECC circuitry error"); + break; + case 5: printk("controlling MPU error"); + break; + default:printk("error (0x%02x?)", tmp); + } + if (tmp & 0x80) + printk("; %s: error", ide_devname[DEV_HWIF][1]); + printk("\n"); + } + } +#endif /* OK_TO_RESET_CONTROLLER */ + restore_flags(flags); +} + +/* + * Clean up after success/failure of an explicit (ioctl) drive cmd + */ +static void end_drive_cmd (ide_dev_t *dev, byte stat, byte err) +{ + unsigned long flags; + struct request *rq = ide_cur_rq[DEV_HWIF]; + byte *args = (byte *) rq->buffer; + + rq->errors = !OK_STAT(stat,READY_STAT,BAD_STAT); + if (args) { + args[0] = stat; + args[1] = err; + args[2] = IN_BYTE(HD_NSECTOR,DEV_HWIF); + } + save_flags(flags); + cli(); + up(rq->sem); + ide_cur_rq[DEV_HWIF] = NULL; + restore_flags(flags); +} + +/* + * Error reporting, in human readable form (luxurious, but a memory hog). + */ +static byte dump_status (byte hwif, const char *msg, byte stat) +{ + unsigned long flags; + byte err = 0; + ide_dev_t *dev = ide_cur_dev[HWIF]; + const char *name = dev ? dev->name : ide_name[HWIF]; + + save_flags (flags); + sti(); + printk("%s: %s: status=0x%02x", name, msg, stat); +#if FANCY_STATUS_DUMPS + if (dev && dev->type == disk) { + printk(" { "); + if (stat & BUSY_STAT) + printk("Busy "); + else { + if (stat & READY_STAT) printk("DriveReady "); + if (stat & WRERR_STAT) printk("WriteFault "); + if (stat & SEEK_STAT) printk("SeekComplete "); + if (stat & DRQ_STAT) printk("DataRequest "); + if (stat & ECC_STAT) printk("CorrectedError "); + if (stat & INDEX_STAT) printk("Index "); + if (stat & ERR_STAT) printk("Error "); + } + printk("}"); + } +#endif /* FANCY_STATUS_DUMPS */ + printk("\n"); + if ((stat & (BUSY_STAT|ERR_STAT)) == ERR_STAT) { + err = GET_ERR(HWIF); + printk("%s: %s: error=0x%02x", name, msg, err); +#if FANCY_STATUS_DUMPS + if (dev && dev->type == disk) { + printk(" { "); + if (err & BBD_ERR) printk("BadSector "); + if (err & ECC_ERR) printk("UncorrectableError "); + if (err & ID_ERR) printk("SectorIdNotFound "); + if (err & ABRT_ERR) printk("DriveStatusError "); + if (err & TRK0_ERR) printk("TrackZeroNotFound "); + if (err & MARK_ERR) printk("AddrMarkNotFound "); + printk("}"); + if (err & (BBD_ERR|ECC_ERR|ID_ERR|MARK_ERR)) { + byte cur = IN_BYTE(HD_CURRENT,HWIF); + if (cur & 0x40) { /* using LBA? */ + printk(", LBAsect=%ld", (unsigned long) + ((cur&0xf)<<24) + |(IN_BYTE(HD_HCYL,HWIF)<<16) + |(IN_BYTE(HD_LCYL,HWIF)<<8) + | IN_BYTE(HD_SECTOR,HWIF)); + } else { + printk(", CHS=%d/%d/%d", + (IN_BYTE(HD_HCYL,HWIF)<<8) + + IN_BYTE(HD_LCYL,HWIF), + cur & 0xf, + IN_BYTE(HD_SECTOR,HWIF)); + } + if (ide_cur_rq[HWIF]) + printk(", sector=%ld", ide_cur_rq[HWIF]->sector); + } + } +#endif /* FANCY_STATUS_DUMPS */ + printk("\n"); + } + restore_flags (flags); + return err; +} + +/* + * ide_error() takes action based on the error returned by the controller. + */ +#define ERROR_MAX 8 /* Max read/write errors per sector */ +#define ERROR_RESET 3 /* Reset controller every 4th retry */ +#define ERROR_RECAL 1 /* Recalibrate every 2nd retry */ +static void ide_error (ide_dev_t *dev, const char *msg, byte stat) +{ + struct request *rq; + byte err; + + err = dump_status(DEV_HWIF, msg, stat); + if ((rq = ide_cur_rq[DEV_HWIF]) == NULL || dev == NULL) + return; +#ifdef IDE_DRIVE_CMD + if (rq->cmd == IDE_DRIVE_CMD) { /* never retry an explicit DRIVE_CMD */ + end_drive_cmd(dev, stat, err); + return; + } +#endif /* IDE_DRIVE_CMD */ + if (stat & BUSY_STAT) { /* other bits are useless when BUSY */ + rq->errors |= ERROR_RESET; + } else { + if (dev->type == disk && (stat & ERR_STAT)) { + /* err has different meaning on cdrom */ + if (err & BBD_ERR) /* retries won't help this! */ + rq->errors = ERROR_MAX; + else if (err & TRK0_ERR) /* help it find track zero */ + rq->errors |= ERROR_RECAL; + } + if ((stat & DRQ_STAT) && rq->cmd == READ) { + int i = dev->mult_count ? dev->mult_count<<8 : 1<<8; + while (i-- > 0) /* try to flush data */ + (void) IN_BYTE(HD_DATA, dev->hwif); + } + } + if (GET_STAT(dev->hwif) & (BUSY_STAT|DRQ_STAT)) + rq->errors |= ERROR_RESET; /* Mmmm.. timing problem */ + + if (rq->errors >= ERROR_MAX) + end_request(0, DEV_HWIF); + else { + if ((rq->errors & ERROR_RESET) == ERROR_RESET) + do_ide_reset(dev); + else if ((rq->errors & ERROR_RECAL) == ERROR_RECAL) + dev->special.b.recalibrate = 1; + ++rq->errors; + } +} + +static void read_intr (ide_dev_t *dev) +{ + byte stat; + int i; + unsigned int msect, nsect; + struct request *rq; + + if (!OK_STAT(stat=GET_STAT(DEV_HWIF),DATA_READY,BAD_R_STAT)) { + sti(); + ide_error(dev, "read_intr", stat); + DO_REQUEST; + return; + } + msect = dev->mult_count; +read_next: + rq = ide_cur_rq[DEV_HWIF]; + if (msect) { + if ((nsect = rq->current_nr_sectors) > msect) + nsect = msect; + msect -= nsect; + } else + nsect = 1; + input_ide_data(dev, rq->buffer, nsect * SECTOR_WORDS); +#ifdef DEBUG + printk("%s: read: sectors(%ld-%ld), buffer=0x%08lx, remaining=%ld\n", + dev->name, rq->sector, rq->sector+nsect-1, + (unsigned long) rq->buffer+(nsect<<9), rq->nr_sectors-nsect); +#endif + rq->sector += nsect; + rq->buffer += nsect<<9; + rq->errors = 0; + i = (rq->nr_sectors -= nsect); + if ((rq->current_nr_sectors -= nsect) <= 0) + end_request(1, DEV_HWIF); + if (i > 0) { + if (msect) + goto read_next; + ide_handler[DEV_HWIF] = &read_intr; + return; + } + /* (void) GET_STAT(DEV_HWIF); */ /* hd.c did this */ + DO_REQUEST; +} + +static void write_intr (ide_dev_t *dev) +{ + byte stat; + int i; + struct request *rq = ide_cur_rq[DEV_HWIF]; + + if (OK_STAT(stat=GET_STAT(DEV_HWIF),DRIVE_READY,BAD_W_STAT)) { +#ifdef DEBUG + printk("%s: write: sector %ld, buffer=0x%08lx, remaining=%ld\n", + dev->name, rq->sector, (unsigned long) rq->buffer, + rq->nr_sectors-1); +#endif + if ((rq->nr_sectors == 1) ^ ((stat & DRQ_STAT) != 0)) { + rq->sector++; + rq->buffer += 512; + rq->errors = 0; + i = --rq->nr_sectors; + --rq->current_nr_sectors; + if (rq->current_nr_sectors <= 0) + end_request(1, DEV_HWIF); + if (i > 0) { + ide_handler[DEV_HWIF] = &write_intr; + output_ide_data(dev, rq->buffer, SECTOR_WORDS); + return; + } + DO_REQUEST; + return; + } + } + sti(); + ide_error(dev, "write_intr", stat); + DO_REQUEST; +} + +static void multwrite (ide_dev_t *dev) +{ + struct request *rq = &ide_write_rq[DEV_HWIF]; + unsigned int mcount = dev->mult_count; + + do { + unsigned int nsect = rq->current_nr_sectors; + if (nsect > mcount) + nsect = mcount; + mcount -= nsect; + + output_ide_data(dev, rq->buffer, nsect<<7); +#ifdef DEBUG + printk("%s: multwrite: sector %ld, buffer=0x%08lx, count=%d, remaining=%ld\n", + dev->name, rq->sector, (unsigned long) rq->buffer, + nsect, rq->nr_sectors - nsect); +#endif + if ((rq->nr_sectors -= nsect) <= 0) + break; + if ((rq->current_nr_sectors -= nsect) == 0) { + if ((rq->bh = rq->bh->b_reqnext) != NULL) { + rq->current_nr_sectors = rq->bh->b_size>>9; + rq->buffer = rq->bh->b_data; + } else { + panic("%s: buffer list corrupted\n", dev->name); + break; + } + } else { + rq->buffer += nsect << 9; + } + } while (mcount); +} + +static void multwrite_intr (ide_dev_t *dev) +{ + byte stat; + int i; + struct request *rq = &ide_write_rq[DEV_HWIF]; + + if (OK_STAT(stat=GET_STAT(DEV_HWIF),DRIVE_READY,BAD_W_STAT)) { + if (stat & DRQ_STAT) { + if (rq->nr_sectors) { + if (dev->mult_count) + multwrite(dev); + ide_handler[DEV_HWIF] = &multwrite_intr; + return; + } + } else { + if (!rq->nr_sectors) { /* all done? */ + rq = ide_cur_rq[DEV_HWIF]; + for (i = rq->nr_sectors; i > 0;){ + i -= rq->current_nr_sectors; + end_request(1, DEV_HWIF); + } + DO_REQUEST; + return; + } + } + } + sti(); + ide_error(dev, "multwrite_intr", stat); + DO_REQUEST; +} + +/* + * Issue a simple drive command + * The drive must be selected beforehand. + */ +static inline void ide_cmd(ide_dev_t *dev, byte cmd, byte nsect, + void (*handler)(ide_dev_t *dev)) +{ + OUT_BYTE(dev->ctl,HD_CMD); + OUT_BYTE(nsect,HD_NSECTOR); + OUT_BYTE(cmd,HD_COMMAND); + ide_handler[DEV_HWIF] = handler; +} + +static void set_multmode_intr (ide_dev_t *dev) +{ + byte stat = GET_STAT(DEV_HWIF); + + sti(); + if (!OK_STAT(stat,READY_STAT,BAD_STAT)) { + dev->mult_req = dev->mult_count = 0; + dev->special.b.recalibrate = 1; + (void) dump_status(DEV_HWIF, "set_multmode", stat); + } else { + if ((dev->mult_count = dev->mult_req)) + printk (" %s: enabled %d-sector multiple mode\n", + dev->name, dev->mult_count); + else + printk (" %s: multiple mode turned off\n", dev->name); + } + DO_REQUEST; +} + +static void set_geometry_intr (ide_dev_t *dev) +{ + byte stat = GET_STAT(DEV_HWIF); + + sti(); + if (!OK_STAT(stat,READY_STAT,BAD_STAT)) + ide_error(dev, "set_geometry_intr", stat); + DO_REQUEST; +} + +static void recal_intr (ide_dev_t *dev) +{ + byte stat = GET_STAT(DEV_HWIF); + + sti(); + if (!OK_STAT(stat,READY_STAT,BAD_STAT)) + ide_error(dev, "recal_intr", stat); + DO_REQUEST; +} + +static void drive_cmd_intr (ide_dev_t *dev) +{ + byte stat = GET_STAT(DEV_HWIF); + + sti(); + if (!OK_STAT(stat,READY_STAT,BAD_STAT)) + ide_error(dev, "drive_cmd", stat); /* calls end_drive_cmd() */ + else + end_drive_cmd (dev, stat, GET_ERR(DEV_HWIF)); + DO_REQUEST; +} + +static void timer_expiry (byte hwif) +{ + unsigned long flags; + + save_flags(flags); + cli(); + + if (ide_handler[HWIF] == NULL || (timer_active & ide_timerbit[HWIF])) { + /* The drive must have responded just as the timer expired */ + sti(); + printk("%s: marginal timeout\n", ide_name[HWIF]); + } else { + ide_handler[HWIF] = NULL; + disable_irq(ide_irq[HWIF]); +#if SUPPORT_SERIALIZE + if (single_threaded && ide_irq[HWIF] != ide_irq[HWIF^1]) + disable_irq(ide_irq[HWIF^1]); +#endif /* SUPPORT_SERIALIZE */ + sti(); + ide_error(ide_cur_dev[HWIF], "timeout", GET_STAT(HWIF)); + do_request(HWIF); +#if SUPPORT_SHARING_IRQ + if (single_threaded) /* this line is indeed necessary */ + hwif = current_hwif; +#endif /* SUPPORT_SHARING_IRQ */ + cli(); + start_ide_timer(HWIF); + enable_irq(ide_irq[HWIF]); +#if SUPPORT_SERIALIZE + if (single_threaded && ide_irq[HWIF] != ide_irq[HWIF^1]) + enable_irq(ide_irq[HWIF^1]); +#endif /* SUPPORT_SERIALIZE */ + } + restore_flags(flags); +} + +static void ide0_timer_expiry (void) /* invoked from sched.c */ +{ + timer_expiry (0); +} + +static void ide1_timer_expiry (void) /* invoked from sched.c */ +{ + timer_expiry (1); +} + +static int do_special (ide_dev_t *dev) +{ + special_t *s = &dev->special; +#ifdef DEBUG + printk("%s: do_special: 0x%02x\n", dev->name, s->all); +#endif + if (s->b.set_geometry) { + s->b.set_geometry = 0; + if (dev->type == disk) { + OUT_BYTE(dev->sect,HD_SECTOR); + OUT_BYTE(dev->cyl,HD_LCYL); + OUT_BYTE(dev->cyl>>8,HD_HCYL); + OUT_BYTE(((dev->head-1)|dev->select.all)&0xBF,HD_CURRENT); + ide_cmd(dev, WIN_SPECIFY, dev->sect, &set_geometry_intr); + } + } else if (s->b.recalibrate) { + s->b.recalibrate = 0; + if (dev->type == disk) + ide_cmd(dev,WIN_RESTORE,dev->sect,&recal_intr); + } else if (s->b.set_multmode) { + if (dev->type == disk) { + if (dev->id && dev->mult_req > dev->id->max_multsect) + dev->mult_req = dev->id->max_multsect; + ide_cmd(dev,WIN_SETMULT,dev->mult_req,&set_multmode_intr); + } else { + dev->mult_req = 0; + printk("%s: multmode not supported by this device\n", dev->name); + } + s->b.set_multmode = 0; + } else { + if (s->all) { + printk("%s: bad special flag: 0x%02x\n", dev->name, s->all); + s->all = 0; + } + } + return (ide_handler[DEV_HWIF] == NULL) ? 1 : 0; +} + +#ifdef CONFIG_BLK_DEV_IDECD +static byte wait_stat (ide_dev_t *dev, byte good, byte bad, unsigned long timeout) +{ + unsigned long flags; + + save_flags(flags); + sti(); + WAIT_STAT(dev, good, bad, timeout, "status", error); + restore_flags(flags); + return 0; +error: + restore_flags(flags); + return 1; +} + +#include "ide-cd.c" +#endif /* CONFIG_BLK_DEV_IDECD */ + +static inline int do_rw_disk (ide_dev_t *dev, struct request *rq, unsigned long block) +{ + OUT_BYTE(dev->ctl,HD_CMD); + OUT_BYTE(rq->nr_sectors,HD_NSECTOR); + if (dev->select.b.lba) { +#ifdef DEBUG + printk("%s: %sing: LBAsect=%ld, sectors=%ld, buffer=0x%08lx\n", + dev->name, (rq->cmd==READ)?"read":"writ", + block, rq->nr_sectors, (unsigned long) rq->buffer); +#endif + OUT_BYTE(block,HD_SECTOR); + OUT_BYTE(block>>=8,HD_LCYL); + OUT_BYTE(block>>=8,HD_HCYL); + OUT_BYTE(((block>>8)&0x0f)|dev->select.all,HD_CURRENT); + } else { + unsigned int sect,head,cyl,track; + track = block / dev->sect; + sect = block % dev->sect + 1; + OUT_BYTE(sect,HD_SECTOR); + head = track % dev->head; + cyl = track / dev->head; + OUT_BYTE(cyl,HD_LCYL); + OUT_BYTE(cyl>>8,HD_HCYL); + OUT_BYTE(head|dev->select.all,HD_CURRENT); +#ifdef DEBUG + printk("%s: %sing: CHS=%d/%d/%d, sectors=%ld, buffer=0x%08lx\n", + dev->name, (rq->cmd==READ)?"read":"writ", cyl, + head, sect, rq->nr_sectors, (unsigned long) rq->buffer); +#endif + } + if (rq->cmd == READ) { + OUT_BYTE(dev->mult_count ? WIN_MULTREAD : WIN_READ, HD_COMMAND); + ide_handler[DEV_HWIF] = &read_intr; + return 0; + } + if (rq->cmd == WRITE) { + OUT_BYTE(dev->wpcom,HD_PRECOMP); /* for ancient drives */ + OUT_BYTE(dev->mult_count ? WIN_MULTWRITE : WIN_WRITE, HD_COMMAND); + WAIT_STAT(dev, DATA_READY, BAD_W_STAT, WAIT_DRQ, "DRQ", error); + if (!dev->unmask) + cli(); + if (dev->mult_count) { + ide_write_rq[DEV_HWIF] = *rq; /* scratchpad */ + multwrite(dev); + ide_handler[DEV_HWIF] = &multwrite_intr; + } else { + output_ide_data(dev, rq->buffer, SECTOR_WORDS); + ide_handler[DEV_HWIF] = &write_intr; + } + return 0; + } +#ifdef IDE_DRIVE_CMD + if (rq->cmd == IDE_DRIVE_CMD) { + byte *args = rq->buffer; + if (args) { + OUT_BYTE(args[2],HD_FEATURE); + ide_cmd(dev, args[0], args[1], &drive_cmd_intr); + printk("%s: DRIVE_CMD cmd=0x%02x sc=0x%02x fr=0x%02x\n", + dev->name, args[0], args[1], args[2]); + return 0; + } else { +#ifdef DEBUG + printk("%s: DRIVE_CMD (null)\n", dev->name); +#endif + end_drive_cmd(dev,GET_STAT(DEV_HWIF),GET_ERR(DEV_HWIF)); + return 1; + } + } +#endif /* IDE_DRIVE_CMD */ + printk("%s: bad command: %d\n", dev->name, rq->cmd); + end_request(0, DEV_HWIF); +error: + return 1; +} + +/* + * The driver enables interrupts as much as possible. In order to do this, + * (a) the device-interrupt is always masked before entry, and + * (b) the timeout-interrupt is always disabled before entry. + * + * Interrupts are still masked (by default) whenever we are exchanging + * data/cmds with a drive, because some drives seem to have very poor + * tolerance for latency during I/O. For devices which don't suffer from + * this problem (most don't), the ide_dev[][].unmask flag can be set to permit + * other interrupts during data/cmd transfers by using the "hdparm" utility. + */ +static void do_request (byte hwif) +{ + unsigned int minor, drive; + unsigned long block, blockend; + struct request *rq; + ide_dev_t *dev; +repeat: + sti(); +#if SUPPORT_SHARING_IRQ + current_hwif = hwif; /* used *only* when single_threaded==1 */ +#endif /* SUPPORT_SHARING_IRQ */ + if ((rq = ide_cur_rq[HWIF]) == NULL) { + rq = blk_dev[ide_major[HWIF]].current_request; + if ((rq == NULL) || (rq->dev < 0)) { +#if SUPPORT_SHARING_IRQ + if (single_threaded) { + if (sharing_single_irq && (dev = ide_cur_dev[hwif])) /* disable irq */ + OUT_BYTE(dev->ctl|2,HD_CMD); + rq = blk_dev[ide_major[hwif^=1]].current_request; + if ((rq != NULL) && (rq->dev >= 0)) + goto repeat; + } +#endif /* SUPPORT_SHARING_IRQ */ + return; + } + blk_dev[ide_major[HWIF]].current_request = rq->next; + ide_cur_rq[HWIF] = rq; + } +#ifdef DEBUG + printk("%s: do_request: current=0x%08lx\n",ide_name[HWIF],(unsigned long)rq); +#endif + minor = MINOR(rq->dev); + drive = minor >> PARTN_BITS; + ide_cur_dev[HWIF] = dev = &ide_dev[HWIF][drive]; + if ((MAJOR(rq->dev) != ide_major[HWIF]) || (drive >= MAX_DRIVES)) { + printk("%s: bad device number: 0x%04x\n", ide_name[HWIF], rq->dev); + end_request(0, HWIF); + goto repeat; + } + if (rq->bh && !rq->bh->b_lock) { + printk("%s: block not locked\n", ide_name[HWIF]); + end_request(0, HWIF); + goto repeat; + } + block = rq->sector; + blockend = block + rq->nr_sectors; + if ((blockend < block) || (blockend > ide_hd[HWIF][minor].nr_sects)) { + printk("%s: bad access: block=%ld, count=%ld\n", + dev->name, block, rq->nr_sectors); + end_request(0, HWIF); + goto repeat; + } + block += ide_hd[HWIF][minor].start_sect; +#if (DISK_RECOVERY_TIME > 0) + while ((read_timer() - ide_lastreq[HWIF]) < DISK_RECOVERY_TIME); +#endif + OUT_BYTE(dev->select.all,HD_CURRENT); +#ifdef CONFIG_BLK_DEV_IDECD + WAIT_STAT(dev, (dev->type == cdrom) ? 0 : READY_STAT, + BUSY_STAT|DRQ_STAT, WAIT_READY, "DRDY", repeat); +#else + WAIT_STAT(dev, READY_STAT, BUSY_STAT|DRQ_STAT, WAIT_READY, "DRDY", repeat); +#endif /* CONFIG_BLK_DEV_IDECD */ + if (!dev->special.all) { +#ifdef CONFIG_BLK_DEV_IDECD + if (dev->type == disk) { +#endif /* CONFIG_BLK_DEV_IDECD */ + if (do_rw_disk(dev, rq, block)) + goto repeat; +#ifdef CONFIG_BLK_DEV_IDECD + } else { + if (do_rw_cdrom(dev, block)) + goto repeat; + } +#endif /* CONFIG_BLK_DEV_IDECD */ + } else { + if (do_special(dev)) + goto repeat; + } +} + +/* + * This is a macro rather than an inline function to + * prevent gcc from over-optimizing accesses to current_hwif, + * which may have a different value on exit from do_request(). + */ +#define DO_IDE_REQUEST(hwif) \ +{ \ + if (ide_handler[hwif] == NULL) { \ + disable_irq(ide_irq[hwif]); \ + if (single_threaded && ide_irq[hwif] != ide_irq[hwif^1]) \ + disable_irq(ide_irq[hwif^1]); \ + do_request(hwif); \ + cli(); \ + start_ide_timer(hwif); \ + enable_irq(ide_irq[hwif]); \ + if (single_threaded && ide_irq[hwif] != ide_irq[hwif^1]) \ + enable_irq(ide_irq[hwif^1]); \ + } \ +} + +#if SUPPORT_TWO_INTERFACES +static void do_ide0_request (void) /* invoked with cli() */ +{ + DO_IDE_REQUEST(0); +} + +static void do_ide1_request (void) /* invoked with cli() */ +{ + DO_IDE_REQUEST(1); +} +#else +#define do_ide1_request do_ide0_request +static void do_ide0_request (void) /* invoked with cli() */ +{ + DO_IDE_REQUEST(HWIF); +} +#endif /* SUPPORT_TWO_INTERFACES */ + +#if SUPPORT_SHARING_IRQ +static void do_shared_request (void) /* invoked with cli() */ +{ + DO_IDE_REQUEST(current_hwif); +} +#endif /* SUPPORT_SHARING_IRQ */ + +/* + * There's nothing really useful we can do with an unexpected interrupt, + * other than reading the status register (to clear it), and logging it. + * There should be no way that an irq can happen before we're ready for it, + * so we needn't worry much about losing an "important" interrupt here. + * + * On laptops (and "green" PCs), an unexpected interrupt occurs whenever the + * drive enters "idle", "standby", or "sleep" mode, so if the status looks + * "good", we just ignore the interrupt completely. + */ +static void unexpected_intr (byte hwif) +{ + byte stat; + + if (!OK_STAT(stat=GET_STAT(HWIF), DRIVE_READY, BAD_STAT)) + (void) dump_status(HWIF, "unexpected_intr", stat); + outb_p(2,IDE_PORT(HD_CMD,hwif)); /* disable device irq */ +#if SUPPORT_SHARING_IRQ + if (single_threaded && ide_irq[hwif] == ide_irq[hwif^1]) { + if (!OK_STAT(stat=GET_STAT(hwif^1), DRIVE_READY, BAD_STAT)) + (void) dump_status(hwif^1, "unexpected_intr", stat); + outb_p(2,IDE_PORT(HD_CMD,hwif^1)); /* disable device irq */ + } +#endif /* SUPPORT_SHARING_IRQ */ +} + +/* + * This is a macro rather than an inline function to + * prevent gcc from over-optimizing accesses to current_hwif, + * which may have a different value on exit from handler(). + */ +#define IDE_INTR(hwif) \ +{ \ + ide_dev_t *dev; \ + void (*handler)(ide_dev_t *); \ + \ + timer_active &= ~ide_timerbit[hwif]; \ + if ((handler = ide_handler[hwif]) != NULL) { \ + ide_handler[hwif] = NULL; \ + dev = ide_cur_dev[hwif]; \ + if (dev->unmask) \ + sti(); \ + handler(dev); \ + } else \ + unexpected_intr(hwif); \ + cli(); \ +} + +#if SUPPORT_SERIALIZE +/* entry point for all interrupts when single_threaded==1 */ +static void ide_seq_intr (int irq, struct pt_regs *regs) +{ + byte hwif = (irq != ide_irq[0]); + IDE_INTR(HWIF); + start_ide_timer(current_hwif); +} +#endif /* SUPPORT_SERIALIZE */ + +#if OPTIMIZE_IRQS + +/* entry point for all interrupts on ide0 when single_threaded==0 */ +static void ide0_intr (int irq, struct pt_regs *regs) +{ + IDE_INTR(0); + start_ide_timer(0); +} + +/* entry point for all interrupts on ide1 when single_threaded==0 */ +static void ide1_intr (int irq, struct pt_regs *regs) +{ + IDE_INTR(1); + start_ide_timer(1); +} + +#else /* OPTIMIZE_IRQS */ + +#define ide0_intr ide_intr +#define ide1_intr ide_intr + +/* entry point for all interrupts when single_threaded==0 */ +static void ide_intr (int irq, struct pt_regs *regs) +{ +#if SUPPORT_TWO_INTERFACES + byte hwif = (irq != ide_irq[0]); +#endif /* SUPPORT_TWO_INTERFACES */ + IDE_INTR(HWIF); + start_ide_timer(HWIF); +} + +#endif /* OPTIMIZE_IRQS */ + +#if SUPPORT_SHARING_IRQ +/* entry point for all interrupts on ide0/ide1 when sharing_single_irq==1 */ +static void ide_shared_intr (int irq, struct pt_regs * regs) +{ + IDE_INTR(current_hwif); + start_ide_timer(current_hwif); +} +#endif /* SUPPORT_SHARING_IRQ */ + +static ide_dev_t *get_info_ptr (int i_rdev) +{ + unsigned int drive = DEVICE_NR(i_rdev); + ide_dev_t *dev; + + if (drive < MAX_DRIVES) { + switch (MAJOR(i_rdev)) { + case IDE0_MAJOR: dev = &ide_dev[0][drive]; + if (dev->present) return dev; + break; + case IDE1_MAJOR: dev = &ide_dev[1][drive]; + if (dev->present) return dev; + break; + } + } + return NULL; +} + +static int ide_open(struct inode * inode, struct file * filp) +{ + ide_dev_t *dev; + unsigned long flags; + + if ((dev = get_info_ptr(inode->i_rdev)) == NULL) + return -ENODEV; + save_flags(flags); + cli(); + while (dev->busy) + sleep_on(&dev->wqueue); + dev->usage++; + restore_flags(flags); +#ifdef CONFIG_BLK_DEV_IDECD + if (dev->type == cdrom) + return cdrom_open (inode, filp, dev); +#endif /* CONFIG_BLK_DEV_IDECD */ + return 0; +} + +/* + * Releasing a block device means we sync() it, so that it can safely + * be forgotten about... + */ +static void ide_release(struct inode * inode, struct file * file) +{ + ide_dev_t *dev; + + if ((dev = get_info_ptr(inode->i_rdev)) != NULL) { + sync_dev(inode->i_rdev); + dev->usage--; +#ifdef CONFIG_BLK_DEV_IDECD + if (dev->type == cdrom) + cdrom_release (inode, file, dev); +#endif /* CONFIG_BLK_DEV_IDECD */ + } +} + +/* + * This routine is called to flush all partitions and partition tables + * for a changed disk, and then re-read the new partition table. + * If we are revalidating a disk because of a media change, then we + * enter with usage == 0. If we are using an ioctl, we automatically have + * usage == 1 (we need an open channel to use an ioctl :-), so this + * is our limit. + */ +static int revalidate_disk(int i_rdev) +{ + unsigned int i, major, start, drive = DEVICE_NR(i_rdev); + ide_dev_t *dev; + struct gendisk *gd; + long flags; + + if ((dev = get_info_ptr(i_rdev)) == NULL) + return -ENODEV; + + save_flags(flags); + cli(); + if (dev->busy || (dev->usage > 1)) { + restore_flags(flags); + return -EBUSY; + }; + dev->busy = 1; + restore_flags(flags); + + gd = &ide_gendisk[DEV_HWIF]; + major = ide_major[DEV_HWIF] << 8; + start = drive << PARTN_BITS; + + for (i = 0; i < (1<part[minor].start_sect = 0; + gd->part[minor].nr_sects = 0; + }; + + gd->part[start].nr_sects = ide_capacity[DEV_HWIF][drive]; + resetup_one_dev(gd, drive); + + dev->busy = 0; + wake_up(&dev->wqueue); + return 0; +} + +#ifdef IDE_DRIVE_CMD +/* + * This function issues a specific IDE drive command onto the + * tail of the request queue, and waits for it to be completed. + * If arg is NULL, it goes through all the motions, + * but without actually sending a command to the drive. + */ +static int do_drive_cmd(int dev, char *args) +{ + unsigned long flags; + unsigned int major = MAJOR(dev); + struct request rq, *cur_rq; + struct blk_dev_struct *bdev; + struct semaphore sem = MUTEX_LOCKED; + + /* build up a special request, and add it to the queue */ + rq.buffer = args; + rq.cmd = IDE_DRIVE_CMD; + rq.errors = 0; + rq.sector = 0; + rq.nr_sectors = 0; + rq.current_nr_sectors = 0; + rq.sem = &sem; + rq.bh = NULL; + rq.bhtail = NULL; + rq.next = NULL; + rq.dev = dev; + bdev = &blk_dev[major]; + + save_flags(flags); + cli(); + cur_rq = bdev->current_request; + if (cur_rq == NULL) { /* empty request list? */ + bdev->current_request = &rq; /* service ours immediately */ + bdev->request_fn(); + } else { + while (cur_rq->next != NULL) /* find end of request list */ + cur_rq = cur_rq->next; + cur_rq->next = &rq; /* add rq to the end */ + } + + down(&sem); /* wait for it to be serviced */ + restore_flags(flags); + return rq.errors ? -EIO : 0; /* return -EIO if errors */ +} +#endif /* IDE_DRIVE_CMD */ + +static int write_fs_long (unsigned long useraddr, long value) +{ + int err; + + if (NULL == (long *)useraddr) + return -EINVAL; + if ((err = verify_area(VERIFY_WRITE, (long *)useraddr, sizeof(long)))) + return err; + put_fs_long((unsigned)value, (long *) useraddr); + return 0; +} + +static int ide_ioctl (struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct hd_geometry *loc = (struct hd_geometry *) arg; + int err; + ide_dev_t *dev; + unsigned long flags; + + if (!inode || !inode->i_rdev) + return -EINVAL; + if ((dev = get_info_ptr(inode->i_rdev)) == NULL) + return -ENODEV; + switch (cmd) { + case HDIO_GETGEO: + if (!loc || dev->type != disk) return -EINVAL; + err = verify_area(VERIFY_WRITE, loc, sizeof(*loc)); + if (err) return err; + put_fs_byte(dev->bios_head, + (char *) &loc->heads); + put_fs_byte(dev->bios_sect, + (char *) &loc->sectors); + put_fs_word(dev->bios_cyl, + (short *) &loc->cylinders); + put_fs_long((unsigned)ide_hd[DEV_HWIF][MINOR(inode->i_rdev)].start_sect, + (long *) &loc->start); + return 0; + + case BLKFLSBUF: + if(!suser()) return -EACCES; + fsync_dev(inode->i_rdev); + invalidate_buffers(inode->i_rdev); + return 0; + + case BLKRASET: + if(!suser()) return -EACCES; + if(arg > 0xff) return -EINVAL; + read_ahead[MAJOR(inode->i_rdev)] = arg; + return 0; + + case BLKRAGET: + return write_fs_long(arg, read_ahead[MAJOR(inode->i_rdev)]); + + case BLKGETSIZE: /* Return device size */ + return write_fs_long(arg, ide_hd[DEV_HWIF][MINOR(inode->i_rdev)].nr_sects); + case BLKRRPART: /* Re-read partition tables */ + return revalidate_disk(inode->i_rdev); + + case HDIO_GET_KEEPSETTINGS: + return write_fs_long(arg, dev->keep_settings); + + case HDIO_GET_UNMASKINTR: + return write_fs_long(arg, dev->unmask); + + case HDIO_GET_CHIPSET: + return write_fs_long(arg, dev->chipset); + + case HDIO_GET_MULTCOUNT: + return write_fs_long(arg, dev->mult_count); + + case HDIO_GET_IDENTITY: + if (!arg || (MINOR(inode->i_rdev) & PARTN_MASK)) + return -EINVAL; + if (dev->id == NULL) + return -ENOMSG; + err = verify_area(VERIFY_WRITE, (char *)arg, sizeof(*dev->id)); + if (err) return err; + memcpy_tofs((char *)arg, (char *)dev->id, sizeof(*dev->id)); + return 0; + + case HDIO_SET_KEEPSETTINGS: + case HDIO_SET_UNMASKINTR: + if (!suser()) return -EACCES; + if ((arg > 1) || (MINOR(inode->i_rdev) & PARTN_MASK)) + return -EINVAL; + save_flags(flags); + cli(); + if (cmd == HDIO_SET_KEEPSETTINGS) + dev->keep_settings = arg; + else + dev->unmask = arg; + restore_flags(flags); + return 0; + + case HDIO_SET_CHIPSET: + if (!suser()) return -EACCES; + if ((arg > 3) || (MINOR(inode->i_rdev) & PARTN_MASK)) + return -EINVAL; + save_flags(flags); + cli(); + dev->chipset = arg; + dev->vlb_sync = (arg & 2) >> 1; + dev->vlb_32bit = (arg & 1); + restore_flags(flags); + return 0; + + case HDIO_SET_MULTCOUNT: + if (!suser()) return -EACCES; + if (MINOR(inode->i_rdev) & PARTN_MASK) + return -EINVAL; + if ((dev->id != NULL) && (arg > dev->id->max_multsect)) + return -EINVAL; + save_flags(flags); + cli(); + if (dev->special.b.set_multmode) { + restore_flags(flags); + return -EBUSY; + } + dev->mult_req = arg; + dev->special.b.set_multmode = 1; + restore_flags(flags); +#ifdef IDE_DRIVE_CMD + do_drive_cmd (inode->i_rdev, NULL); + return (dev->mult_count == arg) ? 0 : -EIO; +#else + return 0; +#endif /* IDE_DRIVE_CMD */ + +#ifdef IDE_DRIVE_CMD + case HDIO_DRIVE_CMD: + { + unsigned long args; + + if (NULL == (long *) arg) + err = do_drive_cmd(inode->i_rdev,NULL); + else { + if (!(err = verify_area(VERIFY_WRITE,(long *)arg,sizeof(long)))) + { + args = get_fs_long((long *)arg); + err = do_drive_cmd(inode->i_rdev,(char *)&args); + put_fs_long(args,(long *)arg); + } + } + return err; + } +#endif /* IDE_DRIVE_CMD */ + + RO_IOCTLS(inode->i_rdev, arg); + + default: +#ifdef CONFIG_BLK_DEV_IDECD + if (dev->type == cdrom) + return ide_cdrom_ioctl(dev, inode, file, cmd, arg); +#endif /* CONFIG_BLK_DEV_IDECD */ + return -EPERM; + } +} + +#ifdef CONFIG_BLK_DEV_IDECD +static int ide_check_media_change (dev_t full_dev) +{ + ide_dev_t *dev; + + if ((dev = get_info_ptr(full_dev)) == NULL) + return -ENODEV; + if (dev->type != cdrom) + return 0; + return cdrom_check_media_change (dev); +} +#endif /* CONFIG_BLK_DEV_IDECD */ + + +static void fixstring (byte *s, int bytecount, int byteswap) +{ + byte *p, *end = &s[bytecount &= ~1]; /* bytecount must be even */ + + if (byteswap) { + /* convert from big-endian to little-endian */ + for (p = end ; p != s;) { + unsigned short *pp = (unsigned short *) (p -= 2); + *pp = (*pp >> 8) | (*pp << 8); + } + } + p = s; + + /* strip leading blanks */ + while (s != end && *s == ' ') + ++s; + + /* compress internal blanks and strip trailing blanks */ + while (s != end && *s) { + if (*s++ != ' ' || (s != end && *s && *s != ' ')) + *p++ = *(s-1); + } + + /* wipe out trailing garbage */ + while (p != end) + *p++ = '\0'; +} + +static int lba_capacity_is_ok (struct hd_driveid *id) +/* + * Returns: 1 if lba_capacity looks sensible + * 0 otherwise + */ +{ + unsigned long lba_sects = id->lba_capacity; + unsigned long chs_sects = id->cyls * id->heads * id->sectors; + unsigned long _10_percent = chs_sects / 10; + + /* perform a rough sanity check on lba_sects: within 10% is "okay" */ + if ((lba_sects - chs_sects) < _10_percent) + return 1; /* lba_capacity is good */ + + /* some drives have the word order reversed */ + lba_sects = (lba_sects << 16) | (lba_sects >> 16); + if ((lba_sects - chs_sects) < _10_percent) { + id->lba_capacity = lba_sects; /* fix it */ + return 1; /* lba_capacity is (now) good */ + } + return 0; /* lba_capacity value is bad */ +} + +static unsigned long probe_mem_start; /* used by drive/irq probing routines */ + +static void do_identify (ide_dev_t *dev, byte cmd) +{ + int bswap; + struct hd_driveid *id; + unsigned long capacity, check; + + id = dev->id = (struct hd_driveid *) probe_mem_start; /* kmalloc() */ + probe_mem_start += 512; + input_ide_data(dev, id, SECTOR_WORDS); /* read 512 bytes of id info */ + sti(); + + /* + * EATA SCSI controllers do a hardware ATA emulation: ignore them + */ + if ((id->model[0] == 'P' && id->model[1] == 'M') + || (id->model[0] == 'S' && id->model[1] == 'K')) { + printk("%s: EATA SCSI HBA %.10s\n", dev->name, id->model); + dev->present = 0; + return; + } + + /* + * WIN_IDENTIFY returns little-endian info, + * WIN_PIDENTIFY *usually* returns little-endian info. + */ + bswap = 1; + if (cmd == WIN_PIDENTIFY) { + if ((id->model[0] == 'N' && id->model[1] == 'E') + || (id->model[0] == 'F' && id->model[1] == 'X')) + bswap = 0; /* NEC and *some* Mitsumi units */ + } /* Vertos drives may still be weird */ + fixstring (id->model, sizeof(id->model), bswap); + fixstring (id->fw_rev, sizeof(id->fw_rev), bswap); + fixstring (id->serial_no, sizeof(id->serial_no), bswap); + + /* + * Check for an ATAPI device + */ + if (cmd == WIN_PIDENTIFY) { +#ifdef CONFIG_BLK_DEV_IDECD + byte type = (id->config >> 8) & 0x0f; +#endif /* CONFIG_BLK_DEV_IDECD */ + printk("%s: %s, ATAPI,", dev->name, id->model); +#ifdef CONFIG_BLK_DEV_IDECD + if (type == 0 || type == 5) + printk(" CDROM drive\n"); + else + printk(" UNKNOWN device\n"); + dev->type = cdrom; /* until we do it "correctly" above */ + dev->present = 1; +#else + printk(unsupported); +#endif /* CONFIG_BLK_DEV_IDECD */ + return; + } + + dev->type = disk; + /* Extract geometry if we did not already have one for the drive */ + if (!dev->present) { + dev->present = 1; + dev->cyl = dev->bios_cyl = id->cyls; + dev->head = dev->bios_head = id->heads; + dev->sect = dev->bios_sect = id->sectors; + } + /* Handle logical geometry translation by the drive */ + if ((id->field_valid & 1) && id->cur_cyls && id->cur_heads + && (id->cur_heads <= 16) && id->cur_sectors) + { + /* + * Extract the physical drive geometry for our use. + * Note that we purposely do *not* update the bios info. + * This way, programs that use it (like fdisk) will + * still have the same logical view as the BIOS does, + * which keeps the partition table from being screwed. + * + * An exception to this is the cylinder count, + * which we reexamine later on to correct for 1024 limitations. + */ + dev->cyl = id->cur_cyls; + dev->head = id->cur_heads; + dev->sect = id->cur_sectors; + capacity = dev->cyl * dev->head * dev->sect; + + /* check for word-swapped "capacity" field in id information */ + check = (id->cur_capacity0 << 16) | id->cur_capacity1; + if (check == capacity) /* was it swapped? */ + *((int *)&id->cur_capacity0) = capacity; /* fix it */ + } + /* Use physical geometry if what we have still makes no sense */ + if ((!dev->head || dev->head > 16) && id->heads && id->heads <= 16) { + dev->cyl = id->cyls; + dev->head = id->heads; + dev->sect = id->sectors; + } + /* Correct the number of cyls if the bios value is too small */ + if (dev->sect == dev->bios_sect && dev->head == dev->bios_head) { + if (dev->cyl > dev->bios_cyl) + dev->bios_cyl = dev->cyl; + } + /* Determine capacity, and use LBA if the drive properly supports it */ + if ((id->capability & 2) && lba_capacity_is_ok(id)) { + dev->select.b.lba = 1; + capacity = id->lba_capacity; + } else { + capacity = dev->cyl * dev->head * dev->sect; + } + + ide_capacity[DEV_HWIF][dev->select.b.drive] = capacity; + printk ("%s: %.40s, %ldMB w/%dKB Cache, %sCHS=%d/%d/%d", + dev->name, id->model, capacity/2048L, id->buf_size/2, + dev->select.b.lba ? "LBA, " : "", + dev->bios_cyl, dev->bios_head, dev->bios_sect); + + dev->mult_count = 0; + if (id->max_multsect) { + dev->mult_req = INITIAL_MULT_COUNT; + if (dev->mult_req > id->max_multsect) + dev->mult_req = id->max_multsect; + if (dev->mult_req || ((id->multsect_valid & 1) && id->multsect)) + dev->special.b.set_multmode = 1; + printk(", MaxMult=%d", id->max_multsect); + } + printk("\n"); +} + +static void delay_10ms (void) +{ + unsigned long timer = jiffies + 2; + while (timer > jiffies); +} + + +static int try_to_identify (ide_dev_t *dev, byte cmd) +/* + * Returns: 0 device was identified + * 1 device timed-out (no response to identify request) + * 2 device aborted the command (refused to identify itself) + */ +{ + int hd_status, rc; + unsigned long timeout; +#if PROBE_FOR_IRQS + int irqs = 0; + static byte irq_probed[2] = {0,0}; +#endif /* PROBE_FOR_IRQS */ + + OUT_BYTE(dev->ctl|2,HD_CMD); /* disable device irq */ +#if PROBE_FOR_IRQS + if (!irq_probed[DEV_HWIF]) { /* already probed for IRQ? */ + irqs = probe_irq_on(); /* start monitoring irqs */ + OUT_BYTE(dev->ctl,HD_CMD); /* enable device irq */ + } +#endif /* PROBE_FOR_IRQS */ + delay_10ms(); /* take a deep breath */ + if ((IN_BYTE(HD_ALTSTATUS,DEV_HWIF) ^ IN_BYTE(HD_STATUS,DEV_HWIF)) & ~INDEX_STAT) { + hd_status = HD_STATUS; /* an ancient Seagate drive */ + printk("%s: probing with STATUS instead of ALTSTATUS\n", dev->name); + } else + hd_status = HD_ALTSTATUS; /* use non-intrusive polling */ + OUT_BYTE(cmd,HD_COMMAND); /* ask drive for ID */ + timeout = ((cmd == WIN_IDENTIFY) ? WAIT_WORSTCASE : WAIT_PIDENTIFY) / 2; + timeout += jiffies; + do { + if (jiffies > timeout) { +#if PROBE_FOR_IRQS + if (!irq_probed[DEV_HWIF]) + (void) probe_irq_off(irqs); +#endif /* PROBE_FOR_IRQS */ + return 1; /* drive timed-out */ + } + delay_10ms(); /* give drive a breather */ + } while (IN_BYTE(hd_status,DEV_HWIF) & BUSY_STAT); + delay_10ms(); /* wait for IRQ and DRQ_STAT */ + if (OK_STAT(GET_STAT(DEV_HWIF),DRQ_STAT,BAD_R_STAT)) { + cli(); /* some systems need this */ + do_identify(dev, cmd); /* drive returned ID */ + rc = 0; /* success */ + } else + rc = 2; /* drive refused ID */ +#if PROBE_FOR_IRQS + if (!irq_probed[DEV_HWIF]) { + irqs = probe_irq_off(irqs); /* get irq number */ + if (irqs > 0) { + irq_probed[DEV_HWIF] = 1; + ide_irq[DEV_HWIF] = irqs; + } else /* Mmmm.. multiple IRQs */ + printk("%s: IRQ probe failed (%d)\n", dev->name, irqs); + } +#endif /* PROBE_FOR_IRQS */ + return rc; +} + +/* + * This routine has the difficult job of finding a drive if it exists, + * without getting hung up if it doesn't exist, without trampling on + * ethernet cards, and without leaving any IRQs dangling to haunt us later. + * + * If a drive is "known" to exist (from CMOS or kernel parameters), + * but does not respond right away, the probe will "hang in there" + * for the maximum wait time (about 30 seconds), otherwise it will + * exit much more quickly. + */ +static int do_probe (ide_dev_t *dev, byte cmd) +/* + * Returns: 0 device was identified + * 1 device timed-out (no response to identify request) + * 2 device aborted the command (refused to identify itself) + * 3 bad status from device (possible for ATAPI drives) + * 4 probe was not attempted + */ +{ + int rc; + +#ifdef CONFIG_BLK_DEV_IDECD + if (dev->present) { /* avoid waiting for inappropriate probes */ + if ((dev->type == disk) ^ (cmd == WIN_IDENTIFY)) + return 4; + } +#endif /* CONFIG_BLK_DEV_IDECD */ +#if DEBUG + printk("probing for %s: present=%d, type=%s, probetype=%s\n", + dev->name, dev->present, dev->type ? "cdrom":"disk", + (cmd == WIN_IDENTIFY) ? "ATA" : "ATAPI"); +#endif + OUT_BYTE(dev->select.all,HD_CURRENT); /* select target drive */ + delay_10ms(); /* wait for BUSY_STAT */ + if (IN_BYTE(HD_CURRENT,DEV_HWIF) != dev->select.all && !dev->present) { + OUT_BYTE(0xa0,HD_CURRENT); /* exit with drive0 selected */ + return 3; /* no i/f present: avoid killing ethernet cards */ + } + + if (OK_STAT(GET_STAT(DEV_HWIF),READY_STAT,BUSY_STAT) + || dev->present || cmd == WIN_PIDENTIFY) + { + if ((rc = try_to_identify(dev, cmd))) /* send cmd and wait */ + rc = try_to_identify(dev, cmd); /* failed: try again */ + if (rc == 1) + printk("%s: no response (status = 0x%02x)\n", + dev->name, GET_STAT(DEV_HWIF)); + OUT_BYTE(dev->ctl|2,HD_CMD); /* disable device irq */ + delay_10ms(); + (void) GET_STAT(DEV_HWIF); /* ensure drive irq is clear */ + } else { + rc = 3; /* not present or maybe ATAPI */ + } + if (dev->select.b.drive == 1) { + OUT_BYTE(0xa0,HD_CURRENT); /* exit with drive0 selected */ + delay_10ms(); + OUT_BYTE(dev->ctl|2,HD_CMD); /* disable device irq */ + delay_10ms(); + (void) GET_STAT(DEV_HWIF); /* ensure drive irq is clear */ + } + return rc; +} + +static byte probe_for_drive (ide_dev_t *dev) +/* + * Returns: 0 no device was found + * 1 device was found (note: dev->present might still be 0) + */ +{ + if (dev->dont_probe) /* skip probing? */ + return dev->present; + if (do_probe(dev, WIN_IDENTIFY) >= 2) { /* if !(success || timed-out) */ +#ifdef CONFIG_BLK_DEV_IDECD + (void) do_probe(dev, WIN_PIDENTIFY); /* look for ATAPI device */ +#endif /* CONFIG_BLK_DEV_IDECD */ + } + if (!dev->present) + return 0; /* drive not found */ + if (dev->id == NULL) { /* identification failed? */ + if (dev->type == disk) { + printk ("%s: non-IDE device, CHS=%d/%d/%d\n", + dev->name, dev->cyl, dev->head, dev->sect); + } +#ifdef CONFIG_BLK_DEV_IDECD + else if (dev->type == cdrom) { + printk("%s: ATAPI cdrom (?)\n", dev->name); + } +#endif /* CONFIG_BLK_DEV_IDECD */ + else { + dev->present = 0; /* nuke it */ + return 1; /* drive was found */ + } + } +#ifdef CONFIG_BLK_DEV_IDECD + if (dev->type == cdrom) + cdrom_setup(dev); +#endif /* CONFIG_BLK_DEV_IDECD */ + if (dev->type == disk && !dev->select.b.lba) { + if (!dev->head || dev->head > 16) { + printk("%s: cannot handle disk with %d physical heads\n", + dev->name, dev->head); + dev->present = 0; + } + } + return 1; /* drive was found */ +} + +static void probe_for_drives (byte hwif) +{ + ide_dev_t *devs = &ide_dev[HWIF][0]; /* for convenience */ + + if (check_region(IDE_PORT(HD_DATA,HWIF),8) + || check_region(IDE_PORT(HD_CMD,HWIF),1)) + { + if (devs[0].present || devs[1].present) + printk("ERROR: "); + printk("%s: port(s) already in use\n", ide_name[HWIF]); + devs[0].present = 0; + devs[1].present = 0; + } else { + unsigned long flags; + save_flags(flags); + sti(); /* needed for jiffies and irq probing */ + + /* second drive should only exist if first drive was found */ + if (probe_for_drive(&devs[0]) || devs[1].present) + (void) probe_for_drive(&devs[1]); +#if PROBE_FOR_IRQS + (void) probe_irq_off(probe_irq_on()); /* clear dangling irqs */ +#endif /* PROBE_FOR_IRQS */ + if (devs[0].present || devs[1].present) { + request_region(IDE_PORT(HD_DATA,HWIF),8,ide_name[HWIF]); + request_region(IDE_PORT(HD_CMD,HWIF),1,ide_name[HWIF]); + } + restore_flags(flags); + } +} + +static int next_drive = 0; /* used by the ide_setup() routines below */ + +void ide_setup(char *str, int *ints) +{ + ide_dev_t *dev; + const char *p[] = {"cyls","heads","sects","wpcom","irq"}; + int i, hwif, drive = next_drive++; +#ifdef CONFIG_BLK_DEV_HD + extern void hd_setup(char *, int *); + + if (drive < 2) { + hd_setup (str, ints); + return; + } +#endif /* CONFIG_BLK_DEV_HD */ + hwif = (drive > 1); + printk("%s: ", ide_name[hwif]); + if (drive > 3) { + printk("too many drives defined\n"); + return; + } + drive = drive & 1; + printk("%s: ", ide_devname[hwif][drive]); + if (!SUPPORT_TWO_INTERFACES && hwif != HWIF) { + printk(unsupported); + return; + } + dev = &ide_dev[hwif][drive]; + if (dev->present) + printk("(redefined) "); + if (ints[0] == 0) { +#if SUPPORT_DTC2278 + if (!strcmp(str,"dtc2278")) { + printk("%s\n",str); + probe_dtc2278 = 1; /* try to init DTC-2278 at boot */ + return; + } +#endif /* SUPPORT_DTC2278 */ +#if SUPPORT_SERIALIZE + if (!strcmp(str,"serialize") || !strcmp(str,"cmd")) { + printk("%s\n",str); + single_threaded = 1; /* serialize all drive access */ + return; + } +#endif /* SUPPORT_SERIALIZE */ + if (!strcmp(str,"noprobe")) { + printk("%s\n",str); + dev->dont_probe = 1; /* don't probe for this drive */ + return; + } +#ifdef CONFIG_BLK_DEV_IDECD + if (!strcmp(str,"cdrom")) { + printk("cdrom\n"); + dev->present = 1; /* force autoprobe to find it */ + dev->type = cdrom; + return; + } +#endif /* CONFIG_BLK_DEV_IDECD */ + } + if (ints[0] < 3 || ints[0] > 5) { + printk("bad parms, expected: cyls,heads,sects[,wpcom[,irq]]\n"); + } else { + for (i=0; i++ < ints[0];) + printk("%s=%d%c",p[i-1],ints[i],itype = disk; + dev->cyl = dev->bios_cyl = ints[1]; + dev->head = dev->bios_head = ints[2]; + dev->ctl = (ints[2] > 8 ? 8 : 0); + dev->sect = dev->bios_sect = ints[3]; + dev->wpcom = (ints[0] >= 4) ? ints[4] : 0; + if (ints[0] >= 5) + ide_irq[HWIF] = ints[5]; + ide_capacity[HWIF][drive] = BIOS_SECTORS(dev); + dev->present = 1; + } +} + +void hda_setup(char *str, int *ints) +{ + next_drive = 0; + ide_setup (str, ints); +} + +void hdb_setup(char *str, int *ints) +{ + next_drive = 1; + ide_setup (str, ints); +} + +void hdc_setup(char *str, int *ints) +{ + next_drive = 2; + ide_setup (str, ints); +} + +void hdd_setup(char *str, int *ints) +{ + next_drive = 3; + ide_setup (str, ints); +} + +#ifndef CONFIG_BLK_DEV_HD +/* + * We query CMOS about hard disks : it could be that we have a SCSI/ESDI/etc + * controller that is BIOS compatible with ST-506, and thus showing up in our + * BIOS table, but not register compatible, and therefore not present in CMOS. + * + * Furthermore, we will assume that our ST-506 drives are the primary + * drives in the system -- the ones reflected as drive 1 or 2. The first + * drive is stored in the high nibble of CMOS byte 0x12, the second in the low + * nibble. This will be either a 4 bit drive type or 0xf indicating use byte + * 0x19 for an 8 bit type, drive 1, 0x1a for drive 2 in CMOS. A non-zero value + * means we have an AT controller hard disk for that drive. + */ +extern struct drive_info_struct drive_info; +static void probe_cmos_for_drives (void) +{ + byte drive, cmos_disks, *BIOS = (byte *) &drive_info; + + outb_p(0x12,0x70); /* specify CMOS address 0x12 */ + cmos_disks = inb_p(0x71); /* read the data from 0x12 */ + /* Extract drive geometry from CMOS+BIOS if not already setup */ + for (drive = 0; drive < MAX_DRIVES; drive++) { + ide_dev_t *dev = &ide_dev[0][drive]; + if ((cmos_disks & (0xf0 >> (drive*4))) && !dev->present) { + dev->cyl = dev->bios_cyl = *(unsigned short *)BIOS; + dev->head = dev->bios_head = * (BIOS+2); + dev->sect = dev->bios_sect = * (BIOS+14); + dev->wpcom = (*(unsigned short *)(BIOS+5))>>2; + dev->ctl = *(BIOS+8); + dev->wpcom = 0; + dev->type = disk; + dev->present = 1; + ide_capacity[0][drive] = BIOS_SECTORS(dev); + } + BIOS += 16; + } +} +#endif /* CONFIG_BLK_DEV_HD */ + +static void init_ide_data (byte hwif) +{ + int drive; + + for (drive = 0; drive < (MAX_DRIVES<select.all = (drive<<4)|0xa0; + dev->hwif = hwif; + dev->unmask = 0; + dev->busy = 0; + dev->mult_count = 0; /* set by do_identify() */ + dev->mult_req = 0; /* set by do_identify() */ + dev->usage = 0; + dev->vlb_32bit = 0; + dev->vlb_sync = 0; + dev->id = NULL; + dev->ctl = 0x08; + dev->wqueue = NULL; + dev->special.all = 0; + dev->special.b.recalibrate = 1; + dev->special.b.set_geometry = 1; + dev->keep_settings = 0; + ide_hd[hwif][drive<name = ide_devname[hwif][drive]; + } +} + +/* + * This is the harddisk IRQ description. The SA_INTERRUPT in sa_flags + * means we enter the IRQ-handler with interrupts disabled: this is bad for + * interrupt latency, but anything else has led to problems on some + * machines. We enable interrupts as much as we can safely do in most places. + */ +static byte setup_irq (byte hwif) +{ + static byte rc = 0; + unsigned long flags; + const char *msg = "", *primary_secondary[] = {"primary", "secondary"}; + void (*handler)(int, struct pt_regs *) = HWIF ? &ide1_intr : &ide0_intr; + +#if SUPPORT_SHARING_IRQ + if (sharing_single_irq) { + if (HWIF != 0 && !rc) { /* IRQ already allocated? */ + msg = " (shared with ide0)"; + goto done; + } + handler = &ide_shared_intr; + } +#if SUPPORT_SERIALIZE + else if (single_threaded) { + handler = &ide_seq_intr; + if (HWIF != 0) + msg = " (single-threaded with ide0)"; + } +#endif /* SUPPORT_SERIALIZE */ +#endif /* SUPPORT_SHARING_IRQ */ + save_flags(flags); + cli(); + if ((rc = request_irq(ide_irq[HWIF],handler,SA_INTERRUPT,ide_name[HWIF]))) + msg = ": FAILED! unable to allocate IRQ"; + restore_flags(flags); +#if SUPPORT_SHARING_IRQ +done: +#endif /* SUPPORT_SHARING_IRQ */ + printk("%s: %s interface on irq %d%s\n", + ide_name[HWIF], primary_secondary[HWIF], ide_irq[HWIF], msg); + return rc; +} + +static void ide_geninit(byte hwif) +{ + static int drive; + + for (drive = 0; drive < MAX_DRIVES; drive++) { + ide_dev_t *dev = &ide_dev[HWIF][drive]; + if (dev->present) { + ide_hd[HWIF][drive<type == cdrom) + ide_hd[HWIF][drive< 0;) { + if (ide_gendisk[hwif].nr_real != 0 && !setup_irq(hwif)) { + const char *name = ide_name[HWIF]; + unsigned int major = ide_major[HWIF]; + if (register_blkdev(major, name, &ide_fops)) { + printk("%s: unable to get major number %d\n", name, major); + } else { + timer_table[ide_timer[HWIF]].fn + = HWIF ? ide1_timer_expiry : ide0_timer_expiry; +#if SUPPORT_SHARING_IRQ + if (single_threaded) + blk_dev[major].request_fn = &do_shared_request; + else +#endif /* SUPPORT_SHARING_IRQ */ + blk_dev[major].request_fn = + HWIF ? &do_ide1_request : &do_ide0_request; + read_ahead[major] = 8; /* (4kB) */ + ide_gendisk[HWIF].next = gendisk_head; + gendisk_head = &ide_gendisk[HWIF]; + } + } + } + return mem_start; +} diff --git a/drivers/block/ll_rw_blk.c b/drivers/block/ll_rw_blk.c index 05fcf157d..53c1f97a0 100644 --- a/drivers/block/ll_rw_blk.c +++ b/drivers/block/ll_rw_blk.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -40,16 +41,29 @@ int read_ahead[MAX_BLKDEV] = {0, }; * next-request */ struct blk_dev_struct blk_dev[MAX_BLKDEV] = { - { NULL, NULL }, /* no_dev */ - { NULL, NULL }, /* dev mem */ - { NULL, NULL }, /* dev fd */ - { NULL, NULL }, /* dev hd */ - { NULL, NULL }, /* dev ttyx */ - { NULL, NULL }, /* dev tty */ - { NULL, NULL }, /* dev lp */ - { NULL, NULL }, /* dev pipes */ - { NULL, NULL }, /* dev sd */ - { NULL, NULL } /* dev st */ + { NULL, NULL }, /* 0 no_dev */ + { NULL, NULL }, /* 1 dev mem */ + { NULL, NULL }, /* 2 dev fd */ + { NULL, NULL }, /* 3 dev ide0 or hd */ + { NULL, NULL }, /* 4 dev ttyx */ + { NULL, NULL }, /* 5 dev tty */ + { NULL, NULL }, /* 6 dev lp */ + { NULL, NULL }, /* 7 dev pipes */ + { NULL, NULL }, /* 8 dev sd */ + { NULL, NULL }, /* 9 dev st */ + { NULL, NULL }, /* 10 */ + { NULL, NULL }, /* 11 */ + { NULL, NULL }, /* 12 */ + { NULL, NULL }, /* 13 */ + { NULL, NULL }, /* 14 */ + { NULL, NULL }, /* 15 */ + { NULL, NULL }, /* 16 */ + { NULL, NULL }, /* 17 */ + { NULL, NULL }, /* 18 */ + { NULL, NULL }, /* 19 */ + { NULL, NULL }, /* 20 */ + { NULL, NULL }, /* 21 */ + { NULL, NULL } /* 22 dev ide1 */ }; /* @@ -71,6 +85,57 @@ int * blk_size[MAX_BLKDEV] = { NULL, NULL, }; */ int * blksize_size[MAX_BLKDEV] = { NULL, NULL, }; +/* + * hardsect_size contains the size of the hardware sector of a device. + * + * hardsect_size[MAJOR][MINOR] + * + * if (!hardsect_size[MAJOR]) + * then 512 bytes is assumed. + * else + * sector_size is hardsect_size[MAJOR][MINOR] + * This is currently set by some scsi device and read by the msdos fs driver + * This might be a some uses later. + */ +int * hardsect_size[MAX_BLKDEV] = { NULL, NULL, }; + +/* + * "plug" the device if there are no outstanding requests: this will + * force the transfer to start only after we have put all the requests + * on the list. + */ +static void plug_device(struct blk_dev_struct * dev, struct request * plug) +{ + unsigned long flags; + + plug->dev = -1; + plug->cmd = -1; + plug->next = NULL; + save_flags(flags); + cli(); + if (!dev->current_request) + dev->current_request = plug; + restore_flags(flags); +} + +/* + * remove the plug and let it rip.. + */ +static void unplug_device(struct blk_dev_struct * dev) +{ + struct request * req; + unsigned long flags; + + save_flags(flags); + cli(); + req = dev->current_request; + if (req && req->dev == -1 && req->cmd == -1) { + dev->current_request = req->next; + (dev->request_fn)(); + } + restore_flags(flags); +} + /* * look for a free request in the first N entries. * NOTE: interrupts must be disabled on the way in, and will still @@ -104,18 +169,40 @@ static inline struct request * get_request(int n, int dev) /* * wait until a free request in the first N entries is available. - * NOTE: interrupts must be disabled on the way in, and will still - * be disabled on the way out. */ -static inline struct request * get_request_wait(int n, int dev) +static struct request * __get_request_wait(int n, int dev) { register struct request *req; + struct wait_queue wait = { current, NULL }; - while ((req = get_request(n, dev)) == NULL) - sleep_on(&wait_for_request); + add_wait_queue(&wait_for_request, &wait); + for (;;) { + unplug_device(MAJOR(dev)+blk_dev); + current->state = TASK_UNINTERRUPTIBLE; + cli(); + req = get_request(n, dev); + sti(); + if (req) + break; + schedule(); + } + remove_wait_queue(&wait_for_request, &wait); + current->state = TASK_RUNNING; return req; } +static inline struct request * get_request_wait(int n, int dev) +{ + register struct request *req; + + cli(); + req = get_request(n, dev); + sti(); + if (req) + return req; + return __get_request_wait(n, dev); +} + /* RO fail safe mechanism */ static long ro_bits[MAX_BLKDEV][8]; @@ -157,10 +244,11 @@ static void add_request(struct blk_dev_struct * dev, struct request * req) kstat.dk_drive[disk_index]++; break; case HD_MAJOR: - case XT_DISK_MAJOR: disk_index = (MINOR(req->dev) & 0x00C0) >> 6; - if (disk_index < 4) - kstat.dk_drive[disk_index]++; + case XT_DISK_MAJOR: disk_index = (MINOR(req->dev) & 0x0040) >> 6; + kstat.dk_drive[disk_index]++; break; + case IDE1_MAJOR: disk_index = ((MINOR(req->dev) & 0x0040) >> 6) + 2; + kstat.dk_drive[disk_index]++; default: break; } @@ -219,6 +307,10 @@ static void make_request(int major,int rw, struct buffer_head * bh) bh->b_req = 0; return; } + /* Uhhuh.. Nasty dead-lock possible here.. */ + if (bh->b_lock) + return; + /* Maybe the above fixes it, and maybe it doesn't boot. Life is interesting */ lock_buffer(bh); if ((rw == WRITE && !bh->b_dirt) || (rw == READ && bh->b_uptodate)) { unlock_buffer(bh); @@ -231,22 +323,25 @@ static void make_request(int major,int rw, struct buffer_head * bh) */ max_req = (rw == READ) ? NR_REQUEST : ((NR_REQUEST*2)/3); -/* big loop: look for a free request. */ - -repeat: +/* look for a free request. */ cli(); -/* The scsi disk drivers completely remove the request from the queue when - * they start processing an entry. For this reason it is safe to continue - * to add links to the top entry for scsi devices. +/* The scsi disk drivers and the IDE driver completely remove the request + * from the queue when they start processing an entry. For this reason + * it is safe to continue to add links to the top entry for those devices. */ - if ((major == HD_MAJOR + if (( major == IDE0_MAJOR /* same as HD_MAJOR */ + || major == IDE1_MAJOR || major == FLOPPY_MAJOR || major == SCSI_DISK_MAJOR || major == SCSI_CDROM_MAJOR) && (req = blk_dev[major].current_request)) { +#ifdef CONFIG_BLK_DEV_HD if (major == HD_MAJOR || major == FLOPPY_MAJOR) +#else + if (major == FLOPPY_MAJOR) +#endif CONFIG_BLK_DEV_HD req = req->next; while (req) { if (req->dev == bh->b_dev && @@ -286,22 +381,17 @@ repeat: /* find an unused request. */ req = get_request(max_req, bh->b_dev); + sti(); -/* if no request available: if rw_ahead, forget it; otherwise try again. */ - if (! req) { +/* if no request available: if rw_ahead, forget it; otherwise try again blocking.. */ + if (!req) { if (rw_ahead) { - sti(); unlock_buffer(bh); return; } - sleep_on(&wait_for_request); - sti(); - goto repeat; + req = __get_request_wait(max_req, bh->b_dev); } -/* we found a request. */ - sti(); - /* fill up the request-info, and add it to the queue */ req->cmd = rw; req->errors = 0; @@ -316,14 +406,15 @@ repeat: add_request(major+blk_dev,req); } -void ll_rw_page(int rw, int dev, int page, char * buffer) +void ll_rw_page(int rw, int dev, unsigned long page, char * buffer) { struct request * req; unsigned int major = MAJOR(dev); + unsigned long sector = page * (PAGE_SIZE / 512); struct semaphore sem = MUTEX_LOCKED; if (major >= MAX_BLKDEV || !(blk_dev[major].request_fn)) { - printk("Trying to read nonexistent block-device %04x (%d)\n",dev,page*8); + printk("Trying to read nonexistent block-device %04x (%ld)\n",dev,sector); return; } if (rw!=READ && rw!=WRITE) @@ -332,15 +423,13 @@ void ll_rw_page(int rw, int dev, int page, char * buffer) printk("Can't page to read-only device 0x%X\n",dev); return; } - cli(); req = get_request_wait(NR_REQUEST, dev); - sti(); /* fill up the request-info, and add it to the queue */ req->cmd = rw; req->errors = 0; - req->sector = page<<3; - req->nr_sectors = 8; - req->current_nr_sectors = 8; + req->sector = sector; + req->nr_sectors = PAGE_SIZE / 512; + req->current_nr_sectors = PAGE_SIZE / 512; req->buffer = buffer; req->sem = &sem; req->bh = NULL; @@ -357,7 +446,6 @@ void ll_rw_block(int rw, int nr, struct buffer_head * bh[]) { unsigned int major; struct request plug; - int plugged; int correct_size; struct blk_dev_struct * dev; int i; @@ -407,15 +495,8 @@ void ll_rw_block(int rw, int nr, struct buffer_head * bh[]) from starting until we have shoved all of the blocks into the queue, and then we let it rip. */ - plugged = 0; - cli(); - if (!dev->current_request && nr > 1) { - dev->current_request = &plug; - plug.dev = -1; - plug.next = NULL; - plugged = 1; - } - sti(); + if (nr > 1) + plug_device(dev, &plug); for (i = 0; i < nr; i++) { if (bh[i]) { bh[i]->b_req = 1; @@ -426,12 +507,7 @@ void ll_rw_block(int rw, int nr, struct buffer_head * bh[]) kstat.pgpgout++; } } - if (plugged) { - cli(); - dev->current_request = plug.next; - (dev->request_fn)(); - sti(); - } + unplug_device(dev); return; sorry: @@ -468,9 +544,7 @@ void ll_rw_swap_file(int rw, int dev, unsigned int *b, int nb, char *buf) for (i=0; icmd = rw; req->errors = 0; req->sector = (b[i] * buffersize) >> 9; @@ -498,15 +572,24 @@ long blk_dev_init(long mem_start, long mem_end) #ifdef CONFIG_BLK_DEV_HD mem_start = hd_init(mem_start,mem_end); #endif +#ifdef CONFIG_BLK_DEV_IDE + mem_start = ide_init(mem_start,mem_end); +#endif #ifdef CONFIG_BLK_DEV_XD mem_start = xd_init(mem_start,mem_end); #endif #ifdef CONFIG_CDU31A mem_start = cdu31a_init(mem_start,mem_end); #endif +#ifdef CONFIG_CDU535 + mem_start = sony535_init(mem_start,mem_end); +#endif #ifdef CONFIG_MCD mem_start = mcd_init(mem_start,mem_end); #endif +#ifdef CONFIG_AZTCD + mem_start = aztcd_init(mem_start,mem_end); +#endif #ifdef CONFIG_BLK_DEV_FD floppy_init(); #else diff --git a/drivers/block/mcd.c b/drivers/block/mcd.c index 42dab9061..f20b4d0ab 100644 --- a/drivers/block/mcd.c +++ b/drivers/block/mcd.c @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -247,9 +248,7 @@ mcd_ioctl(struct inode *ip, struct file *fp, unsigned int cmd, struct cdrom_tocentry entry; struct mcd_Toc *tocPtr; struct cdrom_subchnl subchnl; -#if 0 struct cdrom_volctrl volctrl; -#endif if (!ip) return -EINVAL; @@ -491,42 +490,30 @@ mcd_Play.end.min, mcd_Play.end.sec, mcd_Play.end.frame); return 0; case CDROMVOLCTRL: /* Volume control */ - /* - * This is not working yet. Setting the volume by itself does - * nothing. Following the 'set' by a 'play' results in zero - * volume. Something to work on for the next release. - */ -#if 0 st = verify_area(VERIFY_READ, (void *) arg, sizeof(volctrl)); if (st) return st; memcpy_fromfs(&volctrl, (char *) arg, sizeof(volctrl)); -printk("VOL %d %d\n", volctrl.channel0 & 0xFF, volctrl.channel1 & 0xFF); outb(MCMD_SET_VOLUME, MCDPORT(0)); outb(volctrl.channel0, MCDPORT(0)); - outb(0, MCDPORT(0)); + outb(255, MCDPORT(0)); outb(volctrl.channel1, MCDPORT(0)); - outb(1, MCDPORT(0)); + outb(255, MCDPORT(0)); i = getMcdStatus(MCD_STATUS_DELAY); if (i < 0) return -EIO; { - int a, b, c, d; + char a, b, c, d; getValue(&a); getValue(&b); getValue(&c); getValue(&d); - printk("%02X %02X %02X %02X\n", a, b, c, d); } - outb(0xF8, MCDPORT(0)); - i = getMcdStatus(MCD_STATUS_DELAY); - printk("F8 -> %02X\n", i & 0xFF); -#endif return 0; case CDROMEJECT: @@ -597,7 +584,7 @@ mcd_transfer(void) */ static void -mcd_interrupt(int unused) +mcd_interrupt(int irq, struct pt_regs * regs) { int st; @@ -1168,7 +1155,7 @@ mcd_init(unsigned long mem_start, unsigned long mem_end) printk("Unable to get IRQ%d for Mitsumi CD-ROM\n", mcd_irq); return mem_start; } - snarf_region(mcd_port, 4); + request_region(mcd_port, 4,"mcd"); outb(MCMD_CONFIG_DRIVE, MCDPORT(0)); outb(0x02,MCDPORT(0)); diff --git a/drivers/block/ramdisk.c b/drivers/block/ramdisk.c index bddf8a6f4..8d0d8552d 100644 --- a/drivers/block/ramdisk.c +++ b/drivers/block/ramdisk.c @@ -8,13 +8,17 @@ */ -#include #include #include #include #include #include #include +#include + +#ifdef __mips__ +#include +#endif #include #include @@ -28,6 +32,7 @@ extern void wait_for_keypress(void); char *rd_start; int rd_length = 0; static int rd_blocksizes[2] = {0, 0}; +extern char _end; static void do_rd_request(void) { @@ -77,18 +82,30 @@ static struct file_operations rd_fops = { long rd_init(long mem_start, int length) { int i; +#if __i386__ char *cp; +#endif if (register_blkdev(MEM_MAJOR,"rd",&rd_fops)) { printk("RAMDISK: Unable to get major %d.\n", MEM_MAJOR); return 0; } blk_dev[MEM_MAJOR].request_fn = DEVICE_REQUEST; +#if __i386__ rd_start = (char *) mem_start; rd_length = length; cp = rd_start; for (i=0; i < length; i++) *cp++ = '\0'; +#elif defined(__mips__) + rd_start = (char *) &_end; + rd_length = length; + /* + * Don't reserve memory - this has already been done + * in arch/mips/kernel/setup.c. + */ + length = 0; +#endif for(i=0;i<2;i++) rd_blocksizes[i] = 1024; blksize_size[MAJOR_NR] = rd_blocksizes; @@ -125,6 +142,7 @@ static void do_load(void) */ for (tries = 0; tries < 1000; tries += 512) { block = tries; +#if defined (__i386__) bh = breada(ROOT_DEV,block+1,BLOCK_SIZE, 0, PAGE_SIZE); if (!bh) { printk("RAMDISK: I/O error while looking for super block!\n"); @@ -135,7 +153,17 @@ static void do_load(void) *((struct super_block *) &sb) = *((struct super_block *) bh->b_data); brelse(bh); - +#else /* !defined (__i386__) */ + /* + * Linux/MIPS loads ramdisk images via the host machine's + * bootroms. This enables us not only to load the ramdisk + * from floppy - we might also use CDROM or via network + * from BOOTP/TFTP, DLC/RIPL boot servers. + * The same applies for Linux/68k machines. + */ + *((struct super_block *) &sb) = + *((struct super_block *) rd_start + BLOCK_SIZE); +#endif /* !defined (__i386__) */ /* Try Minix */ nblocks = -1; @@ -167,6 +195,7 @@ static void do_load(void) nblocks, rd_length >> BLOCK_SIZE_BITS); return; } +#ifdef __i386__ printk("RAMDISK: Loading %d blocks into RAM disk", nblocks); /* We found an image file system. Load it into core! */ @@ -189,6 +218,7 @@ static void do_load(void) i++; } printk("\ndone\n"); +#endif /* __i386__ */ /* We loaded the file system image. Prepare for mounting it. */ ROOT_DEV = ((MEM_MAJOR << 8) | RAMDISK_MINOR); @@ -209,8 +239,8 @@ void rd_load(void) /* If no RAM disk specified, give up early. */ if (!rd_length) return; - printk("RAMDISK: %d bytes, starting at 0x%x\n", - rd_length, (int) rd_start); + printk("RAMDISK: %d bytes, starting at 0x%p\n", + rd_length, rd_start); /* If we are doing a diskette boot, we might have to pre-load it. */ if (MAJOR(ROOT_DEV) != FLOPPY_MAJOR) diff --git a/drivers/block/sbpcd.c b/drivers/block/sbpcd.c index a48dc6593..5f536e941 100644 --- a/drivers/block/sbpcd.c +++ b/drivers/block/sbpcd.c @@ -6,12 +6,12 @@ * Panasonic CI-101P. * Also for the Longshine LCS-7260 drive. * Also for the IBM "External ISA CD-Rom" drive. + * Also for the CreativeLabs CD200 drive (but I still need some + * detailed bug reports). + * Also for the TEAC CD-55A drive. + * Not for Funai or Sanyo drives. * - * NOTE: This is release 2.8. - * It works with my SbPro & drive CR-521 V2.11 from 2/92 - * and with the new CR-562-B V0.75 on a "naked" Panasonic - * CI-101P interface. And vice versa. - * + * NOTE: This is release 3.7. * * VERSION HISTORY * @@ -28,8 +28,8 @@ * * 0.4 use MAJOR 25 definitely. * Almost total re-design to support double-speed drives and - * "naked" (no sound) interface cards. - * Flow control should be exact now (tell me if not). + * "naked" (no sound) interface cards ("LaserMate" interface type). + * Flow control should be exact now. * Don't occupy the SbPro IRQ line (not needed either); will * live together with Hannu Savolainen's sndkit now. * Speeded up data transfer to 150 kB/sec, with help from Kai @@ -52,12 +52,12 @@ * Formal redesign to add DDI debugging. * There are still flaws in IOCTL (workman with double speed drive). * - * 1.0 Added support for all drive ids (0...3, no longer only 0) + * 1.0 Added support for all drive IDs (0...3, no longer only 0) * and up to 4 drives on one controller. * Added "#define MANY_SESSION" for "old" multi session CDs. * * 1.1 Do SpinUp for new drives, too. - * Revised for clean compile under "old" kernels (pl9). + * Revised for clean compile under "old" kernels (0.99pl9). * * 1.2 Found the "workman with double-speed drive" bug: use the driver's * audio_state, not what the drive is reporting with ReadSubQ. @@ -65,8 +65,8 @@ * 1.3 Minor cleanups. * Refinements regarding Workman. * - * 1.4 Read XA disks (PhotoCDs) with "old" drives, too (but possibly only - * the first session - I could not try a "multi-session" CD yet). + * 1.4 Read XA disks (PhotoCDs) with "old" drives, too (but only the first + * session - no chance to fully access a "multi-session" CD). * This currently still is too slow (50 kB/sec) - but possibly * the old drives won't do it faster. * Implemented "door (un)lock" for new drives (still does not work @@ -120,7 +120,7 @@ * Implemented "close tray" (done automatically during open). * * 2.4 Use different names for device registering. - * + * * 2.5 Added "#if EJECT" code (default: enabled) to automatically eject * the tray during last call to "sbpcd_release". * Added "#if JUKEBOX" code (default: disabled) to automatically eject @@ -129,11 +129,11 @@ * SOUND_BASE (in sbpcd.h) accordingly (default: disabled). * * 2.6 Nothing new. - * + * * 2.7 Added CDROMEJECT_SW ioctl to set the "EJECT" behavior on the fly: * 0 disables, 1 enables auto-ejecting. Useful to keep the tray in * during shutdown. - * + * * 2.8 Added first support (still BETA, I need feedback or a drive) for * the Longshine LCS-7260 drives. They appear as double-speed drives * using the "old" command scheme, extended by tray control and door @@ -145,28 +145,88 @@ * 16..20). * Changed default of the "JUKEBOX" define. If you use this default, * your tray will eject if you try to mount without a disk in. Next - * mount command will insert the tray - so, just insert a disk. ;-) + * mount command will insert the tray - so, just fill in a disk. ;-) + * + * 2.9 Fulfilled the Longshine LCS-7260 support; with great help and + * experiments by Serge Robyns. + * First attempts to support the TEAC CD-55A drives; but still not + * usable yet. + * Implemented the CDROMMULTISESSION ioctl; this is an attempt to handle + * multi session CDs more "transparent" (redirection handling has to be + * done within the isofs routines, and only for the special purpose of + * obtaining the "right" volume descriptor; accesses to the raw device + * should not get redirected). + * + * 3.0 Just a "normal" increment, with some provisions to do it better. ;-) + * Introduced "#define READ_AUDIO" to specify the maximum number of + * audio frames to grab with one request. This defines a buffer size + * within kernel space; a value of 0 will reserve no such space and + * disable the CDROMREADAUDIO ioctl. A value of 75 enables the reading + * of a whole second with one command, but will use a buffer of more + * than 172 kB. + * Started CD200 support. Drive detection should work, but nothing + * more. + * + * 3.1 Working to support the CD200 and the Teac CD-55A drives. + * AT-BUS style device numbering no longer used: use SCSI style now. + * So, the first "found" device has MINOR 0, regardless of the + * jumpered drive ID. This implies modifications to the /dev/sbpcd* + * entries for some people, but will help the DAU (german TLA, english: + * "newbie", maybe ;-) to install his "first" system from a CD. + * + * 3.2 Still testing with CD200 and CD-55A drives. + * + * 3.3 Working with CD200 support. + * + * 3.4 Auto-probing stops if an address of 0 is seen (to be entered with + * the kernel command line). + * Made the driver "loadable". If used as a module, "audio copy" is + * disabled, and the internal read ahead data buffer has a reduced size + * of 4 kB; so, throughput may be reduced a little bit with slow CPUs. + * + * 3.5 Provisions to handle weird photoCDs which have an interrupted + * "formatting" immediately after the last frames of some files: simply + * never "read ahead" with MultiSession CDs. By this, CPU usage may be + * increased with those CDs, and there may be a loss in speed. + * Re-structured the messaging system. + * The "loadable" version no longer has a limited READ_AUDIO buffer + * size. + * Removed "MANY_SESSION" handling for "old" multi session CDs. + * Added "private" IOCTLs CDROMRESET and CDROMVOLREAD. + * Started again to support the TEAC CD-55A drives, now that I found + * the money for "my own" drive. ;-) + * The TEAC CD-55A support is fairly working now. + * I have measured that the drive "delivers" at 600 kB/sec (even with + * bigger requests than the drive's 64 kB buffer can satisfy), but + * the "real" rate does not exceed 520 kB/sec at the moment. + * Caused by the various changes to build in TEAC support, the timed + * loops are de-optimized at the moment (less throughput with CR-52x + * drives, and the TEAC will give speed only with SBP_BUFFER_FRAMES 64). + * + * 3.6 Fixed TEAC data read problems with SbPro interfaces. + * Initial size of the READ_AUDIO buffer is 0. Can get set to any size + * during runtime. + * + * 3.7 Introduced MAX_DRIVES for some poor interface cards (seen with TEAC + * drives) which allow only one drive (ID 0); this avoids repetitive + * detection under IDs 1..3. + * Elongated cmd_out_T response waiting; necessary for photo CDs with + * a lot of sessions. + * Bettered the sbpcd_open() behavior with TEAC drives. * * TODO * * disk change detection * allow & synchronize multi-activity * (data + audio + ioctl + disk change, multiple drives) - * implement multi-controller-support with a single driver * implement "read all subchannel data" (96 bytes per frame) * - * * special thanks to Kai Makisara (kai.makisara@vtt.fi) for his fine * elaborated speed-up experiments (and the fabulous results!), for * the "push" towards load-free wait loops, and for the extensive mail * thread which brought additional hints and bug fixes. - * - * - * Copyright (C) 1993, 1994 Eberhard Moenkeberg - * or * - * The FTP-home of this driver is - * ftp.gwdg.de:/pub/linux/cdrom/drivers/sbpcd/. + * Copyright (C) 1993, 1994, 1995 Eberhard Moenkeberg * * If you change this software, you should mail a .diff * file with some description lines to emoenke@gwdg.de. @@ -187,25 +247,38 @@ * */ -#define SBPCD_ISSUE 1 /* change to 2, 3, 4 for multiple interface boards */ +#ifndef SBPCD_ISSUE +#define SBPCD_ISSUE 1 +#endif SBPCD_ISSUE #include -#include +#ifdef MODULE +#include +#include +#ifndef CONFIG_MODVERSIONS +char kernel_version[]=UTS_RELEASE; +#endif +#else +#define MOD_INC_USE_COUNT +#define MOD_DEC_USE_COUNT +#endif MODULE + +#include #include -/* #undef DS */ +#include #include #include #include #include #include #include -#include #include #include #include #include #include +#include #if !(SBPCD_ISSUE-1) #define MAJOR_NR MATSUSHITA_CDROM_MAJOR @@ -222,37 +295,8 @@ #include "blk.h" -#define VERSION "2.8 Eberhard Moenkeberg " - -#define SBPCD_DEBUG - -#ifndef CONFIG_ISO9660_FS -#error "SBPCD: \"make config\" again. File system iso9660 is necessary." -#endif - -/* - * This may come back some day.. - */ -#define DDIOCSDBG 0x9000 - -/* - * still testing around... - */ -#define JUKEBOX 1 /* tray control: eject tray if no disk is in */ -#define EJECT 1 /* tray control: eject tray after last use */ -#define LONG_TIMING 0 /* test against timeouts with "gold" CDs on CR-521 */ -#define MANY_SESSION 0 /* this will conflict with "true" multi-session! */ -#undef FUTURE -#define WORKMAN 1 /* some testing stuff to make it better */ -#define CDMKE /* makes timing independent of processor speed */ +#define VERSION "v3.7 Eberhard Moenkeberg " -#undef XA_TEST1 -#define XA_TEST2 - -#define TEST_UPC 0 -#define SPEA_TEST 0 -#define PRINTK_BUG 0 -#define TEST_STI 0 /*==========================================================================*/ /* * provisions for more than 1 driver issues @@ -275,11 +319,6 @@ #define SBPCD_INIT(a,b) sbpcd4_init(a,b) #endif /*==========================================================================*/ -#if MANY_SESSION -#undef LONG_TIMING -#define LONG_TIMING 1 -#endif -/*==========================================================================*/ #if SBPCD_DIS_IRQ #define SBPCD_CLI cli() #define SBPCD_STI sti() @@ -298,45 +337,58 @@ * The possibly conflicting ethernet card addresses get NOT probed * by default - to minimize the hang possibilities. * - * The SB Pro addresses get "mirrored" at 0x6xx - to avoid a type error, - * the 0x2xx-addresses must get checked before 0x6xx. + * The SB Pro addresses get "mirrored" at 0x6xx and some more locations - to + * avoid a type error, the 0x2xx-addresses must get checked before 0x6xx. * * send mail to emoenke@gwdg.de if your interface card is not FULLY * represented here. */ -static int autoprobe[] = -{ - CDROM_PORT, SBPRO, /* probe with user's setup first */ - 0x230, 1, /* Soundblaster Pro and 16 (default) */ - 0x300, 0, /* CI-101P (default), WDH-7001C (default), - Galaxy (default), Reveal (one default) */ - 0x250, 1, /* OmniCD default, Soundblaster Pro and 16 */ - 0x260, 1, /* OmniCD */ - 0x320, 0, /* Lasermate, CI-101P, WDH-7001C, Galaxy, Reveal (other default) */ - 0x340, 0, /* Lasermate, CI-101P */ - 0x360, 0, /* Lasermate, CI-101P */ - 0x270, 1, /* Soundblaster 16 */ - 0x670, 0, /* "sound card #9" */ - 0x690, 0, /* "sound card #9" */ - 0x330, 2, /* SPEA Media FX (default) */ - 0x320, 2, /* SPEA Media FX */ - 0x340, 2, /* SPEA Media FX */ - 0x350, 2, /* SPEA Media FX */ -/* due to incomplete address decoding of the SbPro card, these must be last */ - 0x630, 0, /* "sound card #9" (default) */ - 0x650, 0, /* "sound card #9" */ -#if 0 -/* some "hazardous" locations (ethernet cards) */ - 0x330, 0, /* Lasermate, CI-101P, WDH-7001C */ - 0x350, 0, /* Lasermate, CI-101P */ - 0x370, 0, /* Lasermate, CI-101P */ - 0x290, 1, /* Soundblaster 16 */ - 0x310, 0, /* Lasermate, CI-101P, WDH-7001C */ -#endif +#if !(SBPCD_ISSUE-1) +static int sbpcd[] = +{ + CDROM_PORT, SBPRO, /* probe with user's setup first */ +#if DISTRIBUTION + 0x230, 1, /* Soundblaster Pro and 16 (default) */ + 0x300, 0, /* CI-101P (default), WDH-7001C (default), + Galaxy (default), Reveal (one default) */ + 0x250, 1, /* OmniCD default, Soundblaster Pro and 16 */ + 0x260, 1, /* OmniCD */ + 0x320, 0, /* Lasermate, CI-101P, WDH-7001C, Galaxy, Reveal (other default), + Longshine LCS-6853 (default) */ + 0x338, 0, /* Reveal Sound Wave 32 card model #SC600 */ + 0x340, 0, /* Mozart sound card (default), Lasermate, CI-101P */ + 0x360, 0, /* Lasermate, CI-101P */ + 0x270, 1, /* Soundblaster 16 */ + 0x670, 0, /* "sound card #9" */ + 0x690, 0, /* "sound card #9" */ + 0x330, 2, /* SPEA Media FX (default) */ + 0x320, 2, /* SPEA Media FX */ + 0x340, 2, /* SPEA Media FX */ + 0x634, 0, /* some newer sound cards */ + 0x638, 0, /* some newer sound cards */ + 0x230, 1, /* some newer sound cards */ + /* due to incomplete address decoding of the SbPro card, these must be last */ + 0x630, 0, /* "sound card #9" (default) */ + 0x650, 0, /* "sound card #9" */ +#ifdef MODULE + /* + * some "hazardous" locations (no harm with the loadable version) + * (will stop the bus if a NE2000 ethernet card resides at offset -0x10) + */ + 0x330, 0, /* Lasermate, CI-101P, WDH-7001C */ + 0x350, 0, /* Lasermate, CI-101P */ + 0x350, 2, /* SPEA Media FX */ + 0x370, 0, /* Lasermate, CI-101P */ + 0x290, 1, /* Soundblaster 16 */ + 0x310, 0, /* Lasermate, CI-101P, WDH-7001C */ +#endif MODULE +#endif DISTRIBUTION }; +#else +static int sbpcd[] = {CDROM_PORT, SBPRO}; /* probe with user's setup only */ +#endif -#define NUM_AUTOPROBE (sizeof(autoprobe) / sizeof(int)) - +#define NUM_PROBE (sizeof(sbpcd) / sizeof(int)) /*==========================================================================*/ /* @@ -355,15 +407,24 @@ extern unsigned long sbpcd4_init(unsigned long, unsigned long); #endif /*==========================================================================*/ + +#define INLINE inline + /*==========================================================================*/ /* * the forward references: */ +static void sbp_sleep(u_int); +static void mark_timeout_delay(u_long); +static void mark_timeout_data(u_long); +#if 0 +static void mark_timeout_audio(u_long); +#endif static void sbp_read_cmd(void); static int sbp_data(void); static int cmd_out(void); static int DiskInfo(void); -static int check_media_change(dev_t); +static int sbpcd_chk_disk_change(dev_t); /*==========================================================================*/ @@ -379,11 +440,11 @@ static int check_media_change(dev_t); * (1<99) msgnum=0; + sprintf(buf, "%s-%d [%02d]: ", major_name, d, msgnum); + va_start(args, fmt); + vsprintf(&buf[15], fmt, args); + va_end(args); + printk(buf); + sbp_sleep(55); /* else messages get lost */ + return; } - -#else -# define DPRINTF(x) /* nothing */ - -#endif SBPCD_DEBUG - +/*==========================================================================*/ /* - * maintain trace bit pattern + * DDI interface: runtime trace bit pattern maintenance */ static int sbpcd_dbg_ioctl(unsigned long arg, int level) { - int val; - - val = get_fs_long((int *) arg); - switch(val) - { - case 0: /* OFF */ - sbpcd_debug = 0; - break; - - default: - if (val >= 128) sbpcd_debug &= ~(1 << (val - 128)); - else sbpcd_debug |= (1 << val); - } - return(0); + switch(arg) + { + case 0: /* OFF */ + sbpcd_debug = DBG_INF; + break; + + default: + if (arg>=128) sbpcd_debug &= ~(1<<(arg-128)); + else sbpcd_debug |= (1<state = TASK_INTERRUPTIBLE; - current->timeout = jiffies + jifs; - schedule(); +#ifndef MODULE + if (current == task[0]) + { + del_timer(&delay_timer); + delay_timer.expires=time; + timed_out_delay=0; + add_timer(&delay_timer); + while (!timed_out_delay) ; + return; + } +#endif MODULE + sti(); + current->state = TASK_INTERRUPTIBLE; + current->timeout = jiffies + time; + schedule(); + sti(); } - -/*==========================================================================*/ /*==========================================================================*/ /* * convert logical_block_address to m-s-f_number (3 bytes only) */ -static void lba2msf(int lba, u_char *msf) +static INLINE void lba2msf(int lba, u_char *msf) { - lba += CD_BLOCK_OFFSET; - msf[0] = lba / (CD_SECS*CD_FRAMES); - lba %= CD_SECS*CD_FRAMES; - msf[1] = lba / CD_FRAMES; - msf[2] = lba % CD_FRAMES; + lba += CD_BLOCK_OFFSET; + msf[0] = lba / (CD_SECS*CD_FRAMES); + lba %= CD_SECS*CD_FRAMES; + msf[1] = lba / CD_FRAMES; + msf[2] = lba % CD_FRAMES; } /*==========================================================================*/ /*==========================================================================*/ /* * convert msf-bin to msf-bcd */ -static void bin2bcdx(u_char *p) /* must work only up to 75 or 99 */ +static INLINE void bin2bcdx(u_char *p) /* must work only up to 75 or 99 */ { - *p=((*p/10)<<4)|(*p%10); + *p=((*p/10)<<4)|(*p%10); } /*==========================================================================*/ -static u_int blk2msf(u_int blk) +static INLINE u_int blk2msf(u_int blk) { - MSF msf; - u_int mm; - - msf.c[3] = 0; - msf.c[2] = (blk + CD_BLOCK_OFFSET) / (CD_SECS * CD_FRAMES); - mm = (blk + CD_BLOCK_OFFSET) % (CD_SECS * CD_FRAMES); - msf.c[1] = mm / CD_FRAMES; - msf.c[0] = mm % CD_FRAMES; - return (msf.n); + MSF msf; + u_int mm; + + msf.c[3] = 0; + msf.c[2] = (blk + CD_BLOCK_OFFSET) / (CD_SECS * CD_FRAMES); + mm = (blk + CD_BLOCK_OFFSET) % (CD_SECS * CD_FRAMES); + msf.c[1] = mm / CD_FRAMES; + msf.c[0] = mm % CD_FRAMES; + return (msf.n); } /*==========================================================================*/ -static u_int make16(u_char rh, u_char rl) +static INLINE u_int make16(u_char rh, u_char rl) { - return ((rh<<8)|rl); + return ((rh<<8)|rl); } /*==========================================================================*/ -static u_int make32(u_int rh, u_int rl) +static INLINE u_int make32(u_int rh, u_int rl) { - return ((rh<<16)|rl); + return ((rh<<16)|rl); } /*==========================================================================*/ -static u_char swap_nibbles(u_char i) +static INLINE u_char swap_nibbles(u_char i) { - return ((i<<4)|(i>>4)); + return ((i<<4)|(i>>4)); } /*==========================================================================*/ -static u_char byt2bcd(u_char i) +static INLINE u_char byt2bcd(u_char i) { - return (((i/10)<<4)+i%10); + return (((i/10)<<4)+i%10); } /*==========================================================================*/ -static u_char bcd2bin(u_char bcd) +static INLINE u_char bcd2bin(u_char bcd) { - return ((bcd>>4)*10+(bcd&0x0F)); + return ((bcd>>4)*10+(bcd&0x0F)); } /*==========================================================================*/ -static int msf2blk(int msfx) +static INLINE int msf2blk(int msfx) { - MSF msf; - int i; - - msf.n=msfx; - i=(msf.c[2] * CD_SECS + msf.c[1]) * CD_FRAMES + msf.c[0] - CD_BLOCK_OFFSET; - if (i<0) return (0); - return (i); + MSF msf; + int i; + + msf.n=msfx; + i=(msf.c[2] * CD_SECS + msf.c[1]) * CD_FRAMES + msf.c[0] - CD_BLOCK_OFFSET; + if (i<0) return (0); + return (i); } /*==========================================================================*/ /* * convert m-s-f_number (3 bytes only) to logical_block_address */ -static int msf2lba(u_char *msf) +static INLINE int msf2lba(u_char *msf) { - int i; - - i=(msf[0] * CD_SECS + msf[1]) * CD_FRAMES + msf[2] - CD_BLOCK_OFFSET; - if (i<0) return (0); - return (i); + int i; + + i=(msf[0] * CD_SECS + msf[1]) * CD_FRAMES + msf[2] - CD_BLOCK_OFFSET; + if (i<0) return (0); + return (i); } /*==========================================================================*/ -/* evaluate xx_ReadError code (still mysterious) */ +/* evaluate cc_ReadError code */ static int sta2err(int sta) { - if (sta<=2) return (sta); - if (sta==0x05) return (-4); /* CRC error */ - if (sta==0x06) return (-6); /* seek error */ - if (sta==0x0d) return (-6); /* seek error */ - if (sta==0x0e) return (-3); /* unknown command */ - if (sta==0x14) return (-3); /* unknown command */ - if (sta==0x0c) return (-11); /* read fault */ - if (sta==0x0f) return (-11); /* read fault */ - if (sta==0x10) return (-11); /* read fault */ - if (sta>=0x16) return (-12); /* general failure */ - DriveStruct[d].CD_changed=0xFF; - if (sta==0x11) return (-15); /* invalid disk change */ - return (-2); /* drive not ready */ -} -/*==========================================================================*/ -static void clr_cmdbuf(void) -{ - int i; - - for (i=0;i<7;i++) drvcmd[i]=0; - cmd_type=0; + if (famT_drive) + { + if (sta==0x00) return (0); + if (sta==0x01) return (-604); /* CRC error */ + if (sta==0x02) return (-602); /* drive not ready */ + if (sta==0x03) return (-607); /* unknown media */ + if (sta==0x04) return (-612); /* general failure */ + if (sta==0x05) return (0); + if (sta==0x06) return (-615); /* invalid disk change */ + if (sta==0x0b) return (-612); /* general failure */ + if (sta==0xff) return (-612); /* general failure */ + return (0); + } + else + { + if (sta<=2) return (sta); + if (sta==0x05) return (-604); /* CRC error */ + if (sta==0x06) return (-606); /* seek error */ + if (sta==0x0d) return (-606); /* seek error */ + if (sta==0x0e) return (-603); /* unknown command */ + if (sta==0x14) return (-603); /* unknown command */ + if (sta==0x0c) return (-611); /* read fault */ + if (sta==0x0f) return (-611); /* read fault */ + if (sta==0x10) return (-611); /* read fault */ + if (sta>=0x16) return (-612); /* general failure */ + D_S[d].CD_changed=0xFF; + if (sta==0x11) return (-615); /* invalid disk change (LCS: removed) */ + if (famL_drive) + if (sta==0x12) return (-615); /* invalid disk change (inserted) */ + return (-602); /* drive not ready */ + } } /*==========================================================================*/ -static void mark_timeout(unsigned long i) +static INLINE void clr_cmdbuf(void) { - timed_out=1; - DPRINTF((DBG_TIM,"SBPCD: timer expired.\n")); + int i; + + for (i=0;i<10;i++) drvcmd[i]=0; + cmd_type=0; } /*==========================================================================*/ -static struct timer_list delay_timer = { NULL, NULL, 0, 0, mark_timeout}; -#if 0 -static struct timer_list data_timer = { NULL, NULL, 0, 0, mark_timeout}; -static struct timer_list audio_timer = { NULL, NULL, 0, 0, mark_timeout}; -#endif -/*==========================================================================*/ static void flush_status(void) { -#ifdef CDMKE - int i; - - if (current == task[0]) - for (i=maxtim02;i!=0;i--) inb(CDi_status); - else - { - sbp_sleep(150); - for (i=maxtim_data;i!=0;i--) inb(CDi_status); - } + int i; + +#ifdef MODULE + sbp_sleep(150); + for (i=maxtim_data;i!=0;i--) inb(CDi_status); #else - timed_out=0; -#if 0 - del_timer(&delay_timer); -#endif - delay_timer.expires = 150; - add_timer(&delay_timer); - do { } - while (!timed_out); -#if 0 - del_timer(&delay_timer); -#endif 0 - inb(CDi_status); -#endif CDMKE + if (current == task[0]) + for (i=maxtim02;i!=0;i--) inb(CDi_status); + else + { + sbp_sleep(150); + for (i=maxtim_data;i!=0;i--) inb(CDi_status); + } +#endif MODULE } /*==========================================================================*/ static int CDi_stat_loop(void) { - int i,j; - u_long timeout; - - if (current == task[0]) - for(i=maxtim16;i!=0;i--) - { - j=inb(CDi_status); - if (!(j&s_not_data_ready)) return (j); - if (!(j&s_not_result_ready)) return (j); - if (!new_drive) if (j&s_attention) return (j); - } - else - for(timeout = jiffies + 1000, i=maxtim_data; timeout > jiffies; ) - { - for ( ;i!=0;i--) - { - j=inb(CDi_status); - if (!(j&s_not_data_ready)) return (j); - if (!(j&s_not_result_ready)) return (j); - if (!new_drive) if (j&s_attention) return (j); - } - sbp_sleep(1); - i = 1; - } - return (-1); -} -/*==========================================================================*/ -static int ResponseInfo(void) -{ - int i,j, st=0; - u_long timeout; - - DPRINTF((DBG_000,"SBPCD: ResponseInfo entered.\n")); - if (current == task[0]) - for (i=0;i jiffies; ) + { + for ( ;i!=0;i--) + { + j=inb(CDi_status); + if (!(j&s_not_data_ready)) return (j); + if (!(j&s_not_result_ready)) return (j); + if (fam0L_drive) if (j&s_attention) return (j); + } + sbp_sleep(1); + i = 1; } - } - DPRINTF((DBG_000,"SBPCD: ResponseInfo: done.\n")); - return (0); +#else + if (current == task[0]) + for(i=maxtim16;i!=0;i--) + { + j=inb(CDi_status); + if (!(j&s_not_data_ready)) return (j); + if (!(j&s_not_result_ready)) return (j); + if (fam0L_drive) if (j&s_attention) return (j); + } + else + for(timeout = jiffies + 1000, i=maxtim_data; timeout > jiffies; ) + { + for ( ;i!=0;i--) + { + j=inb(CDi_status); + if (!(j&s_not_data_ready)) return (j); + if (!(j&s_not_result_ready)) return (j); + if (fam0L_drive) if (j&s_attention) return (j); + } + sbp_sleep(1); + i = 1; + } +#endif MODULE + msg(DBG_LCS,"CDi_stat_loop failed\n"); + return (-1); } /*==========================================================================*/ -static int EvaluateStatus(int st) +#if 00000 +/*==========================================================================*/ +static int tst_DataReady(void) { - if (!new_drive) - { - DriveStruct[d].status_byte=0; - if (st&p_caddin_old) DriveStruct[d].status_byte |= p_door_closed|p_caddy_in; - if (st&p_spinning) DriveStruct[d].status_byte |= p_spinning; - if (st&p_check) DriveStruct[d].status_byte |= p_check; - if (st&p_busy_old) DriveStruct[d].status_byte |= p_busy_new; - if (st&p_disk_ok) DriveStruct[d].status_byte |= p_disk_ok; - } - else { DriveStruct[d].status_byte=st; - st=p_success_old; /* for new drives: fake "successful" bit of old drives */ - } - return (st); + int i; + + i=inb(CDi_status); + if (i&s_not_data_ready) return (0); + return (1); } /*==========================================================================*/ -static int ResponseStatus(void) +static int tst_ResultReady(void) { - int i,j; - u_long timeout; - - DPRINTF((DBG_STA,"SBPCD: doing ResponseStatus...\n")); - - if (current == task[0]) - { - if (flags_cmd_out & f_respo3) j = maxtim_8; - else if (flags_cmd_out&f_respo2) j=maxtim16; - else j=maxtim04; - for (;j!=0;j--) - { - i=inb(CDi_status); - if (!(i&s_not_result_ready)) break; - } - } - else - { - if (flags_cmd_out & f_respo3) timeout = jiffies; - else if (flags_cmd_out & f_respo2) timeout = jiffies + 1600; - else timeout = jiffies + 400; - j=maxtim_8; - do - { - for ( ;j!=0;j--) - { - i=inb(CDi_status); - if (!(i&s_not_result_ready)) break; - } - if (j != 0 || timeout <= jiffies) break; - sbp_sleep(0); - j = 1; - } - while (1); - } - if (j==0) - { if ((flags_cmd_out & f_respo3) == 0) - DPRINTF((DBG_STA,"SBPCD: ResponseStatus: timeout.\n")); - EvaluateStatus(0); - return (-1); - } - i=inb(CDi_info); - i=EvaluateStatus(i); - return (i); + int i; + + i=inb(CDi_status); + if (i&s_not_result_ready) return (0); + return (1); } /*==========================================================================*/ -static void xx_ReadStatus(void) +static int tst_Attention(void) { - int i; - - DPRINTF((DBG_STA,"SBPCD: giving xx_ReadStatus command\n")); - - if (!new_drive) OUT(CDo_command,0x81); - else - { - SBPCD_CLI; - OUT(CDo_command,0x05); - for (i=0;i<6;i++) OUT(CDo_command,0); - SBPCD_STI; - } + int i; + + i=inb(CDi_status); + if (i&s_attention) return (1); + return (0); } /*==========================================================================*/ -static int xx_ReadError(void) +#endif 00000 +/*==========================================================================*/ +static int ResponseInfo(void) { - int i; - - clr_cmdbuf(); - DPRINTF((DBG_ERR,"SBPCD: giving xx_ReadError command.\n")); - if (new_drive) - { - drvcmd[0]=0x82; - response_count=8; - flags_cmd_out=f_putcmd|f_ResponseStatus; - } - else - { - drvcmd[0]=0x82; - response_count=6; - flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus; - } - i=cmd_out(); - DriveStruct[d].error_byte=0; - DPRINTF((DBG_ERR,"SBPCD: xx_ReadError: cmd_out(82) returns %d (%02X)\n",i,i)); - if (i<0) return (i); - if (new_drive) i=2; - else i=1; - DriveStruct[d].error_byte=infobuf[i]; - DPRINTF((DBG_ERR,"SBPCD: xx_ReadError: infobuf[%d] is %d (%02X)\n",i,DriveStruct[d].error_byte,DriveStruct[d].error_byte)); - i=sta2err(infobuf[i]); - return (i); + int i,j,st=0; + u_long timeout; + +#ifdef MODULE + if (0) +#else + if (current == task[0]) +#endif MODULE + for (i=0;i0) msg(DBG_INF,"ResponseInfo: got %d trailing bytes.\n",j); +#endif 000 + for (j=0;j0) return (-j); + else return (i); } /*==========================================================================*/ -static int cmd_out(void) +static void EvaluateStatus(int st) { - int i=0; - - if (flags_cmd_out&f_putcmd) - { - DPRINTF((DBG_CMD,"SBPCD: cmd_out: put")); - for (i=0;i<7;i++) DPRINTF((DBG_CMD," %02X",drvcmd[i])); - DPRINTF((DBG_CMD,"\n")); - - SBPCD_CLI; - for (i=0;i<7;i++) OUT(CDo_command,drvcmd[i]); - SBPCD_STI; - } - if (response_count!=0) - { - if (cmd_type!=0) + D_S[d].status_bits=0; + if (fam1_drive) D_S[d].status_bits=st|p_success; + else if (fam0_drive) { - if (sbpro_type==1) OUT(CDo_sel_d_i,0x01); - DPRINTF((DBG_INF,"SBPCD: misleaded to try ResponseData.\n")); - if (sbpro_type==1) OUT(CDo_sel_d_i,0x00); - return (-22); + if (st&p_caddin_old) D_S[d].status_bits |= p_door_closed|p_caddy_in; + if (st&p_spinning) D_S[d].status_bits |= p_spinning; + if (st&p_check) D_S[d].status_bits |= p_check; + if (st&p_success_old) D_S[d].status_bits |= p_success; + if (st&p_busy_old) D_S[d].status_bits |= p_busy_new; + if (st&p_disk_ok) D_S[d].status_bits |= p_disk_ok; } - else i=ResponseInfo(); - if (i<0) return (-9); - } - if (DriveStruct[d].in_SpinUp != 0) DPRINTF((DBG_SPI,"SBPCD: to CDi_stat_loop.\n")); - if (flags_cmd_out&f_lopsta) - { - i=CDi_stat_loop(); - if ((i<0)||!(i&s_attention)) return (-9); - } - if (!(flags_cmd_out&f_getsta)) goto LOC_229; - -LOC_228: - if (DriveStruct[d].in_SpinUp != 0) DPRINTF((DBG_SPI,"SBPCD: to xx_ReadStatus.\n")); - xx_ReadStatus(); - -LOC_229: - if (flags_cmd_out&f_ResponseStatus) - { - if (DriveStruct[d].in_SpinUp != 0) DPRINTF((DBG_SPI,"SBPCD: to ResponseStatus.\n")); - i=ResponseStatus(); - /* builds status_byte, returns orig. status or p_busy_new */ - if (i<0) return (-9); - if (flags_cmd_out&(f_bit1|f_wait_if_busy)) - { - if (!st_check) - { - if (flags_cmd_out&f_bit1) if (i&p_success_old) goto LOC_232; - if (!(flags_cmd_out&f_wait_if_busy)) goto LOC_228; - if (!st_busy) goto LOC_228; - } + else if (famL_drive) + { + D_S[d].status_bits |= p_success; + if (st&p_caddin_old) D_S[d].status_bits |= p_disk_ok|p_caddy_in; + if (st&p_spinning) D_S[d].status_bits |= p_spinning; + if (st&p_check) D_S[d].status_bits |= p_check; + if (st&p_busy_old) D_S[d].status_bits |= p_busy_new; + if (st&p_lcs_door_closed) D_S[d].status_bits |= p_door_closed; + if (st&p_lcs_door_locked) D_S[d].status_bits |= p_door_locked; } - } -LOC_232: - if (!(flags_cmd_out&f_obey_p_check)) return (0); - if (!st_check) return (0); - if (DriveStruct[d].in_SpinUp != 0) DPRINTF((DBG_SPI,"SBPCD: to xx_ReadError.\n")); - i=xx_ReadError(); - if (DriveStruct[d].in_SpinUp != 0) DPRINTF((DBG_SPI,"SBPCD: to cmd_out OK.\n")); - return (i); + else if (fam2_drive) + { + D_S[d].status_bits |= p_success; + if (st&p2_check) D_S[d].status_bits |= p1_check; + if (st&p2_door_closed) D_S[d].status_bits |= p1_door_closed; + if (st&p2_disk_in) D_S[d].status_bits |= p1_disk_in; + if (st&p2_busy1) D_S[d].status_bits |= p1_busy; + if (st&p2_busy2) D_S[d].status_bits |= p1_busy; + if (st&p2_spinning) D_S[d].status_bits |= p1_spinning; + if (st&p2_door_locked) D_S[d].status_bits |= p1_door_locked; + if (st&p2_disk_ok) D_S[d].status_bits |= p1_disk_ok; + } + else if (famT_drive) + { + return; /* still needs to get coded */ + D_S[d].status_bits |= p_success; + if (st&p2_check) D_S[d].status_bits |= p1_check; + if (st&p2_door_closed) D_S[d].status_bits |= p1_door_closed; + if (st&p2_disk_in) D_S[d].status_bits |= p1_disk_in; + if (st&p2_busy1) D_S[d].status_bits |= p1_busy; + if (st&p2_busy2) D_S[d].status_bits |= p1_busy; + if (st&p2_spinning) D_S[d].status_bits |= p1_spinning; + if (st&p2_door_locked) D_S[d].status_bits |= p1_door_locked; + if (st&p2_disk_ok) D_S[d].status_bits |= p1_disk_ok; + } + return; } /*==========================================================================*/ -static int xx_Seek(u_int pos, char f_blk_msf) +static int get_state_T(void) { - int i; - - clr_cmdbuf(); - if (f_blk_msf>1) return (-3); - if (!new_drive) - { - if (f_blk_msf==1) pos=msf2blk(pos); - drvcmd[2]=(pos>>16)&0x00FF; - drvcmd[3]=(pos>>8)&0x00FF; - drvcmd[4]=pos&0x00FF; - flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta | - f_ResponseStatus | f_obey_p_check | f_bit1; - } - else - { - if (f_blk_msf==0) pos=blk2msf(pos); - drvcmd[1]=(pos>>16)&0x00FF; - drvcmd[2]=(pos>>8)&0x00FF; - drvcmd[3]=pos&0x00FF; - flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; - } - drvcmd[0]=0x01; - response_count=0; - i=cmd_out(); - return (i); + int i; + + static int cmd_out_T(void); + + msg(DBG_TE2,"doing get_state_T...\n"); + clr_cmdbuf(); + D_S[d].n_bytes=1; + drvcmd[0]=CMDT_STATUS; + i=cmd_out_T(); + if (i>=0) i=infobuf[0]; + else + { + msg(DBG_TEA,"get_state_T error %d\n", i); + return (i); + } + if (i>=0) + /* 2: closed, disk in */ + D_S[d].status_bits=p1_door_closed|p1_disk_in|p1_spinning|p1_disk_ok; + else if (D_S[d].error_state==6) + /* 3: closed, disk in, changed ("06 xx xx") */ + D_S[d].status_bits=p1_door_closed|p1_disk_in; + else if ((D_S[d].error_state!=2)||(D_S[d].b3!=0x3A)||(D_S[d].b4==0x00)) + { + /* 1: closed, no disk ("xx yy zz"or "02 3A 00") */ + D_S[d].status_bits=p1_door_closed; + D_S[d].open_count=0; + } + else if (D_S[d].b4==0x01) + { + /* 0: open ("02 3A 01") */ + D_S[d].status_bits=0; + D_S[d].open_count=0; + } + else + { + /* 1: closed, no disk ("02 3A xx") */ + D_S[d].status_bits=p1_door_closed; + D_S[d].open_count=0; + } + msg(DBG_TE2,"get_state_T done (%02X)...\n", D_S[d].status_bits); + return (D_S[d].status_bits); } /*==========================================================================*/ -static int xx_SpinUp(void) +static int ResponseStatus(void) { - int i; - - DPRINTF((DBG_SPI,"SBPCD: SpinUp.\n")); - DriveStruct[d].in_SpinUp = 1; - clr_cmdbuf(); - if (old_drive) - { - drvcmd[0]=0x05; - flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1; - } - else if (new_drive) - { - drvcmd[0]=0x02; - flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; - } - else /* lcs_drive */ - { - drvcmd[0]=0x0D; - flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1; - } - response_count=0; - i=cmd_out(); - DriveStruct[d].in_SpinUp = 0; - return (i); + int i,j; + u_long timeout; + + msg(DBG_STA,"doing ResponseStatus...\n"); + if (famT_drive) return (get_state_T()); +#ifdef MODULE + if (0) +#else + if (current == task[0]) +#endif MODULE + { + if (flags_cmd_out & f_respo3) j = maxtim_8; + else if (flags_cmd_out&f_respo2) j=maxtim16; + else j=maxtim04; + for (;j!=0;j--) + { + i=inb(CDi_status); + if (!(i&s_not_result_ready)) break; + } + } + else + { + if (flags_cmd_out & f_respo3) timeout = jiffies; + else if (flags_cmd_out & f_respo2) timeout = jiffies + 1600; + else timeout = jiffies + 400; + j=maxtim_8; + do + { + for ( ;j!=0;j--) + { + i=inb(CDi_status); + if (!(i&s_not_result_ready)) break; + } + if ((j!=0)||(timeout=drv_211)) - { - if ((volume0!=0)&&(volume1==0)) + clr_cmdbuf(); + msg(DBG_ERR,"giving cc_ReadError command.\n"); + if (fam1_drive) { - volume1=volume0; - channel1=channel0; + drvcmd[0]=CMD1_READ_ERR; + response_count=8; + flags_cmd_out=f_putcmd|f_ResponseStatus; } - else if ((volume0==0)&&(volume1!=0)) + else if (fam0L_drive) { - volume0=volume1; - channel0=channel1; + drvcmd[0]=CMD0_READ_ERR; + response_count=6; + if (famL_drive) + flags_cmd_out=f_putcmd; + else + flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus; } - } - if (channel0>1) - { - channel0=0; - volume0=0; - } - if (channel1>1) - { - channel1=1; - volume1=0; - } - - if (new_drive) - { - control0=channel0+1; - control1=channel1+1; - value0=(volume0>volume1)?volume0:volume1; - value1=value0; - if (volume0==0) control0=0; - if (volume1==0) control1=0; - drvcmd[0]=0x09; - drvcmd[1]=0x05; - drvcmd[3]=control0; - drvcmd[4]=value0; - drvcmd[5]=control1; - drvcmd[6]=value1; - flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; - } - else if (lcs_drive) - { - if ((volume0==0)||(channel0!=0)) control0 |= 0x80; - if ((volume1==0)||(channel1!=1)) control0 |= 0x40; - if (volume0|volume1) value0=0x80; - drvcmd[0]=0x84; - drvcmd[1]=0x03; - drvcmd[4]=control0; - drvcmd[5]=value0; - flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; - } - else /* old_drive, different firmware levels */ - { - if (DriveStruct[d].drv_type>=drv_300) - { - control0=volume0&0xFC; - value0=volume1&0xFC; - if ((volume0!=0)&&(volume0<4)) control0 |= 0x04; - if ((volume1!=0)&&(volume1<4)) value0 |= 0x04; - if (channel0!=0) control0 |= 0x01; - if (channel1==1) value0 |= 0x01; - } - else - { - value0=(volume0>volume1)?volume0:volume1; - if (DriveStruct[d].drv_type=drv_201) - { - if (volume0==0) control0 |= 0x80; - if (volume1==0) control0 |= 0x40; - } - if (DriveStruct[d].drv_type>=drv_211) - { - if (channel0!=0) control0 |= 0x20; - if (channel1!=1) control0 |= 0x10; - } - } - drvcmd[0]=0x84; - drvcmd[1]=0x83; - drvcmd[4]=control0; - drvcmd[5]=value0; - flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; - } - - response_count=0; - i=cmd_out(); - if (i>0) return (i); - DriveStruct[d].diskstate_flags |= volume_bit; - return (0); + else if (fam2_drive) + { + drvcmd[0]=CMD2_READ_ERR; + response_count=6; + flags_cmd_out=f_putcmd; + } + else if (famT_drive) + { + response_count=5; + drvcmd[0]=CMDT_READ_ERR; + } + i=cmd_out(); + D_S[d].error_byte=0; + msg(DBG_ERR,"cc_ReadError: cmd_out(CMDx_READ_ERR) returns %d (%02X)\n",i,i); + if (i<0) return (i); + if (fam0_drive) i=1; + else i=2; + D_S[d].error_byte=infobuf[i]; + msg(DBG_ERR,"cc_ReadError: infobuf[%d] is %d (%02X)\n",i,D_S[d].error_byte,D_S[d].error_byte); + i=sta2err(infobuf[i]); + return (i); } /*==========================================================================*/ -static int GetStatus(void) +static int cmd_out_T(void) { - int i; - - flags_cmd_out=f_getsta|f_ResponseStatus|f_obey_p_check; - response_count=0; - cmd_type=0; - i=cmd_out(); - return (i); +#undef CMDT_TRIES +#define CMDT_TRIES 1000 + + static int cc_DriveReset(void); + int i, j, l, ntries; + + D_S[d].error_state=0; + D_S[d].b3=0; + D_S[d].b4=0; + D_S[d].f_drv_error=0; + for (i=0;i<10;i++) sprintf(&msgbuf[i*3]," %02X",drvcmd[i]); + msgbuf[i*3]=0; + msg(DBG_CMD,"cmd_out_T:%s\n",msgbuf); + + OUT(CDo_sel_i_d,0); + OUT(CDo_enable,D_S[d].drv_sel); + i=inb(CDi_status); + if (!(i&s_not_result_ready)) + do + { + j=inb(CDi_info); + i=inb(CDi_status); + sbp_sleep(0); + msg(DBG_TEA,"cmd_out_T: spurious !s_not_result_ready. (%02X)\n", j); + } + while (!(i&s_not_result_ready)); + cli(); + for (i=0;i<10;i++) OUT(CDo_command,drvcmd[i]); + sti(); + for (ntries=CMDT_TRIES;ntries>0;ntries--) + { + if (drvcmd[0]==CMDT_READ_VER) sbp_sleep(100); +#if 1 + OUT(CDo_sel_i_d,0); +#endif + i=inb(CDi_status); + if (!(i&s_not_data_ready)) /* f.e. CMDT_DISKINFO */ + { + OUT(CDo_sel_i_d,1); + if (drvcmd[0]==CMDT_READ) return (0); /* handled elsewhere */ + if (drvcmd[0]==CMDT_DISKINFO) + { + l=0; + do + { + infobuf[l++]=inb(CDi_data); + i=inb(CDi_status); + } + while (!(i&s_not_data_ready)); + for (j=0;j1) msg(DBG_TEA,"cmd_out_T READ_ERR recursion (%02X): %d !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n", drvcmd[0], recursion); + clr_cmdbuf(); + drvcmd[0]=CMDT_READ_ERR; + j=cmd_out_T(); /* !!! recursive here !!! */ + --recursion; + sbp_sleep(1); + } + while (j<0); + D_S[d].error_state=infobuf[2]; + D_S[d].b3=infobuf[3]; + D_S[d].b4=infobuf[4]; + if (D_S[d].f_drv_error) + { + D_S[d].f_drv_error=0; + cc_DriveReset(); + D_S[d].error_state=2; + } + return (-D_S[d].error_state-400); + } + if (drvcmd[0]==CMDT_READ) return (0); /* handled elsewhere */ + sbp_sleep(10); + if (ntries>(CMDT_TRIES-50)) continue; + msg(DBG_TEA,"cmd_out_T: next CMDT_TRIES (%02X): %d.\n", drvcmd[0], ntries-1); + } + D_S[d].f_drv_error=1; + cc_DriveReset(); + D_S[d].error_state=2; + return (-99); } /*==========================================================================*/ -static int xy_DriveReset(void) +static int cmd_out(void) { - int i; - - DPRINTF((DBG_RES,"SBPCD: xy_DriveReset called.\n")); - if (!new_drive) OUT(CDo_reset,0x00); - else - { - clr_cmdbuf(); - drvcmd[0]=0x0A; - flags_cmd_out=f_putcmd; - response_count=0; - i=cmd_out(); - } - sbp_sleep(100); /* wait a second */ - flush_status(); - i=GetStatus(); - if (i>=0) return -1; - if (DriveStruct[d].error_byte!=aud_12) return -1; - return (0); + int i=0; + + if (famT_drive) return(cmd_out_T()); + + if (flags_cmd_out&f_putcmd) + { + for (i=0;i<7;i++) + sprintf(&msgbuf[i*3], " %02X", drvcmd[i]); + msgbuf[i*3]=0; + msg(DBG_CMD,"cmd_out:%s\n", msgbuf); + cli(); + for (i=0;i<7;i++) OUT(CDo_command,drvcmd[i]); + sti(); + } + if (response_count!=0) + { + if (cmd_type!=0) + { + if (sbpro_type==1) OUT(CDo_sel_i_d,1); + msg(DBG_INF,"misleaded to try ResponseData.\n"); + if (sbpro_type==1) OUT(CDo_sel_i_d,0); + return (-22); + } + else i=ResponseInfo(); + if (i<0) return (i); + } + if (D_S[d].in_SpinUp) msg(DBG_SPI,"in_SpinUp: to CDi_stat_loop.\n"); + if (flags_cmd_out&f_lopsta) + { + i=CDi_stat_loop(); + if ((i<0)||!(i&s_attention)) return (-8); + } + if (!(flags_cmd_out&f_getsta)) goto LOC_229; + + LOC_228: + if (D_S[d].in_SpinUp) msg(DBG_SPI,"in_SpinUp: to cc_ReadStatus.\n"); + cc_ReadStatus(); + + LOC_229: + if (flags_cmd_out&f_ResponseStatus) + { + if (D_S[d].in_SpinUp) msg(DBG_SPI,"in_SpinUp: to ResponseStatus.\n"); + i=ResponseStatus(); + /* builds status_bits, returns orig. status or p_busy_new */ + if (i<0) return (i); + if (flags_cmd_out&(f_bit1|f_wait_if_busy)) + { + if (!st_check) + { + if ((flags_cmd_out&f_bit1)&&(i&p_success)) goto LOC_232; + if ((!(flags_cmd_out&f_wait_if_busy))||(!st_busy)) goto LOC_228; + } + } + } + LOC_232: + if (!(flags_cmd_out&f_obey_p_check)) return (0); + if (!st_check) return (0); + if (D_S[d].in_SpinUp) msg(DBG_SPI,"in_SpinUp: to cc_ReadError.\n"); + i=cc_ReadError(); + if (D_S[d].in_SpinUp) msg(DBG_SPI,"in_SpinUp: to cmd_out OK.\n"); + msg(DBG_000,"cmd_out: cc_ReadError=%d\n", i); + return (i); +} +/*==========================================================================*/ +static int cc_Seek(u_int pos, char f_blk_msf) +{ + int i; + + clr_cmdbuf(); + if (f_blk_msf>1) return (-3); + if (fam0_drive) + { + drvcmd[0]=CMD0_SEEK; + if (f_blk_msf==1) pos=msf2blk(pos); + drvcmd[2]=(pos>>16)&0x00FF; + drvcmd[3]=(pos>>8)&0x00FF; + drvcmd[4]=pos&0x00FF; + flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta | + f_ResponseStatus | f_obey_p_check | f_bit1; + } + else if (fam1L_drive) + { + drvcmd[0]=CMD1_SEEK; /* same as CMD1_ and CMDL_ */ + if (f_blk_msf==0) pos=blk2msf(pos); + drvcmd[1]=(pos>>16)&0x00FF; + drvcmd[2]=(pos>>8)&0x00FF; + drvcmd[3]=pos&0x00FF; + if (famL_drive) + flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1; + else + flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_SEEK; + if (f_blk_msf==0) pos=blk2msf(pos); + drvcmd[2]=(pos>>24)&0x00FF; + drvcmd[3]=(pos>>16)&0x00FF; + drvcmd[4]=(pos>>8)&0x00FF; + drvcmd[5]=pos&0x00FF; + flags_cmd_out=f_putcmd|f_ResponseStatus; + } + else if (famT_drive) + { + drvcmd[0]=CMDT_SEEK; + if (f_blk_msf==1) pos=msf2blk(pos); + drvcmd[2]=(pos>>24)&0x00FF; + drvcmd[3]=(pos>>16)&0x00FF; + drvcmd[4]=(pos>>8)&0x00FF; + drvcmd[5]=pos&0x00FF; + D_S[d].n_bytes=1; + } + response_count=0; + i=cmd_out(); + return (i); +} +/*==========================================================================*/ +static int cc_SpinUp(void) +{ + int i; + + msg(DBG_SPI,"SpinUp.\n"); + D_S[d].in_SpinUp = 1; + clr_cmdbuf(); + if (fam0L_drive) + { + drvcmd[0]=CMD0_SPINUP; + flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta| + f_ResponseStatus|f_obey_p_check|f_bit1; + } + else if (fam1_drive) + { + drvcmd[0]=CMD1_SPINUP; + flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_TRAY_CTL; + drvcmd[4]=0x01; /* "spinup" */ + flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; + } + else if (famT_drive) + { + drvcmd[0]=CMDT_TRAY_CTL; + drvcmd[4]=0x03; /* "insert", it hopefully spins the drive up */ + } + response_count=0; + i=cmd_out(); + D_S[d].in_SpinUp = 0; + return (i); +} +/*==========================================================================*/ +static int cc_SpinDown(void) +{ + int i; + + if (fam0_drive) return (0); + clr_cmdbuf(); + response_count=0; + if (fam1_drive) + { + drvcmd[0]=CMD1_SPINDOWN; + flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_TRAY_CTL; + drvcmd[4]=0x02; /* "eject" */ + flags_cmd_out=f_putcmd|f_ResponseStatus; + } + else if (famL_drive) + { + drvcmd[0]=CMDL_SPINDOWN; + drvcmd[1]=1; + flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1; + } + else if (famT_drive) + { + drvcmd[0]=CMDT_TRAY_CTL; + drvcmd[4]=0x02; /* "eject" */ + } + i=cmd_out(); + return (i); +} +/*==========================================================================*/ +static int cc_get_mode_T(void) +{ + int i; + + clr_cmdbuf(); + response_count=10; + drvcmd[0]=CMDT_GETMODE; + drvcmd[4]=response_count; + i=cmd_out_T(); + return (i); +} +/*==========================================================================*/ +static int cc_set_mode_T(void) +{ + int i; + + clr_cmdbuf(); + response_count=1; + drvcmd[0]=CMDT_SETMODE; + drvcmd[1]=D_S[d].speed_byte; + drvcmd[2]=D_S[d].frmsiz>>8; + drvcmd[3]=D_S[d].frmsiz&0x0FF; + drvcmd[4]=D_S[d].f_XA; /* 1: XA */ + drvcmd[5]=D_S[d].type_byte; /* 0, 1, 3 */ + drvcmd[6]=D_S[d].mode_xb_6; + drvcmd[7]=D_S[d].mode_yb_7|D_S[d].volume_control; + drvcmd[8]=D_S[d].mode_xb_8; + drvcmd[9]=D_S[d].delay; + i=cmd_out_T(); + return (i); +} +/*==========================================================================*/ +static int cc_prep_mode_T(void) +{ + int i, j; + + i=cc_get_mode_T(); + if (i<0) return (i); + for (i=0;i<10;i++) + sprintf(&msgbuf[i*3], " %02X", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_TEA,"CMDT_GETMODE:%s\n", msgbuf); + D_S[d].speed_byte=0x02; /* 0x02: auto quad, 0x82: quad, 0x81: double, 0x80: single */ + D_S[d].frmsiz=make16(infobuf[2],infobuf[3]); + D_S[d].f_XA=infobuf[4]; + if (D_S[d].f_XA==0) D_S[d].type_byte=0; + else D_S[d].type_byte=1; + D_S[d].mode_xb_6=infobuf[6]; + D_S[d].mode_yb_7=1; + D_S[d].mode_xb_8=infobuf[8]; + D_S[d].delay=0; /* 0, 1, 2, 3 */ + j=cc_set_mode_T(); + i=cc_get_mode_T(); + for (i=0;i<10;i++) + sprintf(&msgbuf[i*3], " %02X", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_TEA,"CMDT_GETMODE:%s\n", msgbuf); + return (j); +} +/*==========================================================================*/ +static int cc_SetSpeed(u_char speed, u_char x1, u_char x2) +{ + int i; + + if (fam0L_drive) return (-3); + clr_cmdbuf(); + response_count=0; + if (fam1_drive) + { + drvcmd[0]=CMD1_SETMODE; + drvcmd[1]=0x03; + drvcmd[2]=speed; + drvcmd[3]=x1; + drvcmd[4]=x2; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_SETSPEED; + if (speed&speed_auto) + { + drvcmd[2]=0xFF; + drvcmd[3]=0xFF; + } + else + { + drvcmd[2]=0; + drvcmd[3]=150; + } + flags_cmd_out=f_putcmd|f_ResponseStatus; + } + else if (famT_drive) + { + return (0); + } + i=cmd_out(); + return (i); +} +/*==========================================================================*/ +static int cc_SetVolume(void) +{ + int i; + u_char channel0,channel1,volume0,volume1; + u_char control0,value0,control1,value1; + + D_S[d].diskstate_flags &= ~volume_bit; + clr_cmdbuf(); + channel0=D_S[d].vol_chan0; + volume0=D_S[d].vol_ctrl0; + channel1=control1=D_S[d].vol_chan1; + volume1=value1=D_S[d].vol_ctrl1; + control0=value0=0; + + if (((D_S[d].drv_options&audio_mono)!=0)&&(D_S[d].drv_type>=drv_211)) + { + if ((volume0!=0)&&(volume1==0)) + { + volume1=volume0; + channel1=channel0; + } + else if ((volume0==0)&&(volume1!=0)) + { + volume0=volume1; + channel0=channel1; + } + } + if (channel0>1) + { + channel0=0; + volume0=0; + } + if (channel1>1) + { + channel1=1; + volume1=0; + } + + if (fam1_drive) + { + control0=channel0+1; + control1=channel1+1; + value0=(volume0>volume1)?volume0:volume1; + value1=value0; + if (volume0==0) control0=0; + if (volume1==0) control1=0; + drvcmd[0]=CMD1_SETMODE; + drvcmd[1]=0x05; + drvcmd[3]=control0; + drvcmd[4]=value0; + drvcmd[5]=control1; + drvcmd[6]=value1; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + control0=channel0+1; + control1=channel1+1; + value0=(volume0>volume1)?volume0:volume1; + value1=value0; + if (volume0==0) control0=0; + if (volume1==0) control1=0; + drvcmd[0]=CMD2_SETMODE; + drvcmd[1]=0x0E; + drvcmd[3]=control0; + drvcmd[4]=value0; + drvcmd[5]=control1; + drvcmd[6]=value1; + flags_cmd_out=f_putcmd|f_ResponseStatus; + } + else if (famL_drive) + { + if ((volume0==0)||(channel0!=0)) control0 |= 0x80; + if ((volume1==0)||(channel1!=1)) control0 |= 0x40; + if (volume0|volume1) value0=0x80; + drvcmd[0]=CMDL_SETMODE; + drvcmd[1]=0x03; + drvcmd[4]=control0; + drvcmd[5]=value0; + flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1; + } + else if (fam0_drive) /* different firmware levels */ + { + if (D_S[d].drv_type>=drv_300) + { + control0=volume0&0xFC; + value0=volume1&0xFC; + if ((volume0!=0)&&(volume0<4)) control0 |= 0x04; + if ((volume1!=0)&&(volume1<4)) value0 |= 0x04; + if (channel0!=0) control0 |= 0x01; + if (channel1==1) value0 |= 0x01; + } + else + { + value0=(volume0>volume1)?volume0:volume1; + if (D_S[d].drv_type=drv_201) + { + if (volume0==0) control0 |= 0x80; + if (volume1==0) control0 |= 0x40; + } + if (D_S[d].drv_type>=drv_211) + { + if (channel0!=0) control0 |= 0x20; + if (channel1!=1) control0 |= 0x10; + } + } + drvcmd[0]=CMD0_SETMODE; + drvcmd[1]=0x83; + drvcmd[4]=control0; + drvcmd[5]=value0; + flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; + } + else if (famT_drive) + { + D_S[d].volume_control=0; + if (!volume0) D_S[d].volume_control|=0x10; + if (!volume1) D_S[d].volume_control|=0x20; + i=cc_prep_mode_T(); + if (i<0) return (i); + } + if (!famT_drive) + { + response_count=0; + i=cmd_out(); + if (i<0) return (i); + } + D_S[d].diskstate_flags |= volume_bit; + return (0); +} +/*==========================================================================*/ +static int GetStatus(void) +{ + int i; + + if (famT_drive) return (0); + flags_cmd_out=f_getsta|f_ResponseStatus|f_obey_p_check; + response_count=0; + cmd_type=0; + i=cmd_out(); + return (i); +} +/*==========================================================================*/ +static int cc_DriveReset(void) +{ + int i; + + msg(DBG_RES,"cc_DriveReset called.\n"); + clr_cmdbuf(); + response_count=0; + if (fam0L_drive) OUT(CDo_reset,0x00); + else if (fam1_drive) + { + drvcmd[0]=CMD1_RESET; + flags_cmd_out=f_putcmd; + i=cmd_out(); + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_RESET; + flags_cmd_out=f_putcmd; + i=cmd_out(); + OUT(CDo_reset,0x00); + } + else if (famT_drive) + { + OUT(CDo_sel_i_d,0); + OUT(CDo_enable,D_S[d].drv_sel); + OUT(CDo_command,CMDT_RESET); + for (i=1;i<10;i++) OUT(CDo_command,0); + } + if (fam0L_drive) sbp_sleep(500); /* wait 5 seconds */ + else sbp_sleep(100); /* wait a second */ +#if 1 + if (famT_drive) + { + msg(DBG_TEA, "================CMDT_RESET given=================.\n"); + sbp_sleep(300); + } +#endif 1 + flush_status(); + i=GetStatus(); + if (i<0) return i; + if (!famT_drive) + if (D_S[d].error_byte!=aud_12) return -501; + return (0); } /*==========================================================================*/ static int SetSpeed(void) { - int i, speed; - - if (!(DriveStruct[d].drv_options&(speed_auto|speed_300|speed_150))) return (0); - speed=speed_auto; - if (!(DriveStruct[d].drv_options&speed_auto)) - { - speed |= speed_300; - if (!(DriveStruct[d].drv_options&speed_300)) speed=0; - } - i=yy_SetSpeed(speed,0,0); - return (i); + int i, speed; + + if (!(D_S[d].drv_options&(speed_auto|speed_300|speed_150))) return (0); + speed=speed_auto; + if (!(D_S[d].drv_options&speed_auto)) + { + speed |= speed_300; + if (!(D_S[d].drv_options&speed_300)) speed=0; + } + i=cc_SetSpeed(speed,0,0); + return (i); } /*==========================================================================*/ static int DriveReset(void) { - int i; - - i=xy_DriveReset(); - if (i<0) return (-2); - do - { - i=GetStatus(); - if ((i<0)&&(i!=-15)) return (-2); /* i!=-15 is from sta2err */ - if (!st_caddy_in) break; - } - while (!st_diskok); + int i; + + i=cc_DriveReset(); + if (i<0) return (-22); + do + { + i=GetStatus(); + if ((i<0)&&(i!=-615)) return (-2); /* i!=-615 is from sta2err */ + if (!st_caddy_in) break; + sbp_sleep(1); + } + while (!st_diskok); #if 000 - DriveStruct[d].CD_changed=1; + D_S[d].CD_changed=1; #endif - if ((st_door_closed) && (st_caddy_in)) - { - i=DiskInfo(); - if (i<0) return (-2); - } - return (0); + if ((st_door_closed) && (st_caddy_in)) + { + i=DiskInfo(); + if (i<0) return (-23); + } + return (0); } /*==========================================================================*/ -static int xx_Pause_Resume(int pau_res) +static int cc_PlayAudio(int pos_audio_start,int pos_audio_end) { - int i; - - clr_cmdbuf(); - if (new_drive) - { - drvcmd[0]=0x0D; - flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; - } - else - { - drvcmd[0]=0x8D; - flags_cmd_out=f_putcmd|f_respo2|f_getsta|f_ResponseStatus|f_obey_p_check; - } - if (pau_res!=1) drvcmd[1]=0x80; - response_count=0; - i=cmd_out(); - return (i); + int i, j, n; + + if (D_S[d].audio_state==audio_playing) return (-EINVAL); + clr_cmdbuf(); + response_count=0; + if (famL_drive) + { + drvcmd[0]=CMDL_PLAY; + i=msf2blk(pos_audio_start); + n=msf2blk(pos_audio_end)+1-i; + drvcmd[1]=(i>>16)&0x00FF; + drvcmd[2]=(i>>8)&0x00FF; + drvcmd[3]=i&0x00FF; + drvcmd[4]=(n>>16)&0x00FF; + drvcmd[5]=(n>>8)&0x00FF; + drvcmd[6]=n&0x00FF; + flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta | + f_ResponseStatus | f_obey_p_check | f_wait_if_busy; + } + else + { + j=1; + if (fam1_drive) + { + drvcmd[0]=CMD1_PLAY_MSF; + flags_cmd_out = f_putcmd | f_respo2 | f_ResponseStatus | + f_obey_p_check | f_wait_if_busy; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_PLAY_MSF; + flags_cmd_out = f_putcmd | f_ResponseStatus; + } + else if (famT_drive) + { + drvcmd[0]=CMDT_PLAY_MSF; + j=3; + response_count=1; + } + else if (fam0_drive) + { + drvcmd[0]=CMD0_PLAY_MSF; + flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta | + f_ResponseStatus | f_obey_p_check | f_wait_if_busy; + } + drvcmd[j]=(pos_audio_start>>16)&0x00FF; + drvcmd[j+1]=(pos_audio_start>>8)&0x00FF; + drvcmd[j+2]=pos_audio_start&0x00FF; + drvcmd[j+3]=(pos_audio_end>>16)&0x00FF; + drvcmd[j+4]=(pos_audio_end>>8)&0x00FF; + drvcmd[j+5]=pos_audio_end&0x00FF; + } + i=cmd_out(); + return (i); } /*==========================================================================*/ -static int yy_LockDoor(char lock) +static int cc_Pause_Resume(int pau_res) { - int i; - - if (old_drive) return (0); - DPRINTF((DBG_LCK,"SBPCD: yy_LockDoor: %d (drive %d)\n", lock, d)); - clr_cmdbuf(); - if (new_drive) - { - drvcmd[0]=0x0C; - if (lock==1) drvcmd[1]=0x01; - flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; - response_count=0; - } - else /* lcs_drive */ - { - drvcmd[0]=0x0E; - if (lock==1) drvcmd[1]=0x01; - flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; - response_count=0; - } - i=cmd_out(); - return (i); + int i; + + clr_cmdbuf(); + response_count=0; + if (fam1_drive) + { + drvcmd[0]=CMD1_PAU_RES; + if (pau_res!=1) drvcmd[1]=0x80; + flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_PAU_RES; + if (pau_res!=1) drvcmd[2]=0x01; + flags_cmd_out=f_putcmd|f_ResponseStatus; + } + else if (fam0L_drive) + { + drvcmd[0]=CMD0_PAU_RES; + if (pau_res!=1) drvcmd[1]=0x80; + if (famL_drive) + flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus| + f_obey_p_check|f_bit1; + else + flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus| + f_obey_p_check; + } + else if (famT_drive) + { + if (pau_res==3) return (cc_PlayAudio(D_S[d].pos_audio_start,D_S[d].pos_audio_end)); + else if (pau_res==1) drvcmd[0]=CMDT_PAUSE; + else return (-56); + } + i=cmd_out(); + return (i); } /*==========================================================================*/ -static int yy_CloseTray(void) +static int cc_LockDoor(char lock) { - int i; - - if (!new_drive) return (0); - DPRINTF((DBG_LCK,"SBPCD: yy_CloseTray (drive %d)\n", d)); - - clr_cmdbuf(); - drvcmd[0]=0x07; - flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; - response_count=0; - i=cmd_out(); - return (i); + int i; + + if (fam0_drive) return (0); + msg(DBG_LCK,"cc_LockDoor: %d (drive %d)\n", lock, d); + msg(DBG_LCS,"p_door_locked bit %d before\n", st_door_locked); + clr_cmdbuf(); + response_count=0; + if (fam1_drive) + { + drvcmd[0]=CMD1_LOCK_CTL; + if (lock==1) drvcmd[1]=0x01; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_LOCK_CTL; + if (lock==1) drvcmd[4]=0x01; + flags_cmd_out=f_putcmd|f_ResponseStatus; + } + else if (famL_drive) + { + drvcmd[0]=CMDL_LOCK_CTL; + if (lock==1) drvcmd[1]=0x01; + flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1; + } + else if (famT_drive) + { + drvcmd[0]=CMDT_LOCK_CTL; + if (lock==1) drvcmd[4]=0x01; + } + i=cmd_out(); + msg(DBG_LCS,"p_door_locked bit %d after\n", st_door_locked); + return (i); } /*==========================================================================*/ -static int xx_ReadSubQ(void) +/*==========================================================================*/ +static int UnLockDoor(void) { - int i,j; - - DriveStruct[d].diskstate_flags &= ~subq_bit; - for (j=255;j>0;j--) - { - clr_cmdbuf(); - if (new_drive) + int i,j; + + j=20; + do { - drvcmd[0]=0x87; - flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; - response_count=11; + i=cc_LockDoor(0); + --j; + sbp_sleep(1); } - else + while ((i<0)&&(j)); + if (i<0) { - drvcmd[0]=0x89; - drvcmd[1]=0x02; - flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; - response_count=13; + cc_DriveReset(); + return -84; } - i=cmd_out(); - if (i<0) return (i); - DPRINTF((DBG_SQ,"SBPCD: xx_ReadSubQ:")); - for (i=0;i<(new_drive?11:13);i++) + return (0); +} +/*==========================================================================*/ +static int LockDoor(void) +{ + int i,j; + + j=20; + do { - DPRINTF((DBG_SQ," %02X", infobuf[i])); + i=cc_LockDoor(1); + --j; + sbp_sleep(1); + } + while ((i<0)&&(j)); + if (j==0) + { + cc_DriveReset(); + j=20; + do + { + i=cc_LockDoor(1); + --j; + sbp_sleep(1); + } + while ((i<0)&&(j)); } - DPRINTF((DBG_SQ,"\n")); - if (infobuf[0]!=0) break; - if ((!st_spinning) || (j==1)) + return (i); +} +/*==========================================================================*/ +static int cc_CloseTray(void) +{ + int i; + + if (fam0_drive) return (0); + msg(DBG_LCK,"cc_CloseTray (drive %d)\n", d); + msg(DBG_LCS,"p_door_closed bit %d before\n", st_door_closed); + + clr_cmdbuf(); + response_count=0; + if (fam1_drive) { - DriveStruct[d].SubQ_ctl_adr=DriveStruct[d].SubQ_trk=DriveStruct[d].SubQ_pnt_idx=DriveStruct[d].SubQ_whatisthis=0; - DriveStruct[d].SubQ_run_tot=DriveStruct[d].SubQ_run_trk=0; - return (0); + drvcmd[0]=CMD1_TRAY_CTL; + flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; } - } - DriveStruct[d].SubQ_ctl_adr=swap_nibbles(infobuf[1]); - DriveStruct[d].SubQ_trk=byt2bcd(infobuf[2]); - DriveStruct[d].SubQ_pnt_idx=byt2bcd(infobuf[3]); - i=4; - if (!new_drive) i=5; - DriveStruct[d].SubQ_run_tot=make32(make16(0,infobuf[i]),make16(infobuf[i+1],infobuf[i+2])); /* msf-bin */ - i=7; - if (!new_drive) i=9; - DriveStruct[d].SubQ_run_trk=make32(make16(0,infobuf[i]),make16(infobuf[i+1],infobuf[i+2])); /* msf-bin */ - DriveStruct[d].SubQ_whatisthis=infobuf[i+3]; - DriveStruct[d].diskstate_flags |= subq_bit; - return (0); -} -/*==========================================================================*/ -static int xx_ModeSense(void) -{ - int i; - - DriveStruct[d].diskstate_flags &= ~frame_size_bit; - clr_cmdbuf(); - if (new_drive) - { - drvcmd[0]=0x84; - drvcmd[1]=0x00; - flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; - response_count=5; - } - else - { - drvcmd[0]=0x85; - drvcmd[1]=0x00; - flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; - response_count=2; - } - i=cmd_out(); - if (i<0) return (i); - i=0; - if (new_drive) DriveStruct[d].sense_byte=infobuf[i++]; - DriveStruct[d].frame_size=make16(infobuf[i],infobuf[i+1]); - - DPRINTF((DBG_XA,"SBPCD: xx_ModeSense: ")); - for (i=0;i<(new_drive?5:2);i++) - { - DPRINTF((DBG_XA,"%02X ", infobuf[i])); - } - DPRINTF((DBG_XA,"\n")); + else if (fam2_drive) + { + drvcmd[0]=CMD2_TRAY_CTL; + drvcmd[1]=0x01; + drvcmd[4]=0x03; /* "insert" */ + flags_cmd_out=f_putcmd|f_ResponseStatus; + } + else if (famL_drive) + { + drvcmd[0]=CMDL_TRAY_CTL; + flags_cmd_out=f_putcmd|f_respo2|f_lopsta|f_getsta| + f_ResponseStatus|f_obey_p_check|f_bit1; + } + else if (famT_drive) + { + drvcmd[0]=CMDT_TRAY_CTL; + drvcmd[4]=0x03; /* "insert" */ + } + i=cmd_out(); + msg(DBG_LCS,"p_door_closed bit %d after\n", st_door_closed); + return (i); +} +/*==========================================================================*/ +static int cc_ReadSubQ(void) +{ + int i,j; - DriveStruct[d].diskstate_flags |= frame_size_bit; - return (0); + D_S[d].diskstate_flags &= ~subq_bit; + for (j=255;j>0;j--) + { + clr_cmdbuf(); + if (fam1_drive) + { + drvcmd[0]=CMD1_READSUBQ; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + response_count=11; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_READSUBQ; + drvcmd[1]=0x02; + drvcmd[3]=0x01; + flags_cmd_out=f_putcmd; + response_count=10; + } + else if (fam0L_drive) + { + drvcmd[0]=CMD0_READSUBQ; + drvcmd[1]=0x02; + if (famL_drive) + flags_cmd_out=f_putcmd; + else + flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; + response_count=13; + } + else if (famT_drive) + { + response_count=12; + drvcmd[0]=CMDT_READSUBQ; + drvcmd[1]=0x02; + drvcmd[2]=0x40; + drvcmd[3]=0x01; + drvcmd[8]=response_count; + } + i=cmd_out(); + if (i<0) return (i); + for (i=0;i>8)&0xFF; - drvcmd[4]=DriveStruct[d].frame_size&0xFF; - flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; - } - else - { - drvcmd[0]=0x84; - drvcmd[1]=0x00; - drvcmd[2]=(DriveStruct[d].frame_size>>8)&0xFF; - drvcmd[3]=DriveStruct[d].frame_size&0xFF; - drvcmd[4]=0x00; - flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; - } - response_count=0; - i=cmd_out(); - if (i<0) return (i); - DriveStruct[d].diskstate_flags |= frame_size_bit; - return (0); + int i; + + if (fam2_drive) return (0); + D_S[d].diskstate_flags &= ~frame_size_bit; + clr_cmdbuf(); + D_S[d].frame_size=framesize; + if (framesize==CD_FRAMESIZE_RAW) D_S[d].sense_byte=0x82; + else D_S[d].sense_byte=0x00; + + msg(DBG_XA1,"cc_ModeSelect: %02X %04X\n", + D_S[d].sense_byte, D_S[d].frame_size); + + if (fam1_drive) + { + drvcmd[0]=CMD1_SETMODE; + drvcmd[1]=0x00; + drvcmd[2]=D_S[d].sense_byte; + drvcmd[3]=(D_S[d].frame_size>>8)&0xFF; + drvcmd[4]=D_S[d].frame_size&0xFF; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam0L_drive) + { + drvcmd[0]=CMD0_SETMODE; + drvcmd[1]=0x00; + drvcmd[2]=(D_S[d].frame_size>>8)&0xFF; + drvcmd[3]=D_S[d].frame_size&0xFF; + drvcmd[4]=0x00; + if(famL_drive) + flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check; + else + flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; + } + else if (famT_drive) + { + return (-1); + } + response_count=0; + i=cmd_out(); + if (i<0) return (i); + D_S[d].diskstate_flags |= frame_size_bit; + return (0); } /*==========================================================================*/ -#if 0000 -static int xx_TellVolume(void) +static int cc_GetVolume(void) +{ + int i; + u_char switches; + u_char chan0=0; + u_char vol0=0; + u_char chan1=1; + u_char vol1=0; + + D_S[d].diskstate_flags &= ~volume_bit; + clr_cmdbuf(); + if (fam1_drive) + { + drvcmd[0]=CMD1_GETMODE; + drvcmd[1]=0x05; + response_count=5; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_GETMODE; + drvcmd[1]=0x0E; + response_count=5; + flags_cmd_out=f_putcmd; + } + else if (fam0L_drive) + { + drvcmd[0]=CMD0_GETMODE; + drvcmd[1]=0x03; + response_count=2; + if(famL_drive) + flags_cmd_out=f_putcmd; + else + flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; + } + else if (famT_drive) + { + i=cc_get_mode_T(); + if (i<0) return (i); + } + if (!famT_drive) + { + i=cmd_out(); + if (i<0) return (i); + } + if (fam1_drive) + { + chan0=infobuf[1]&0x0F; + vol0=infobuf[2]; + chan1=infobuf[3]&0x0F; + vol1=infobuf[4]; + if (chan0==0) + { + chan0=1; + vol0=0; + } + if (chan1==0) + { + chan1=2; + vol1=0; + } + chan0 >>= 1; + chan1 >>= 1; + } + else if (fam2_drive) + { + chan0=infobuf[1]; + vol0=infobuf[2]; + chan1=infobuf[3]; + vol1=infobuf[4]; + } + else if (famL_drive) + { + chan0=0; + chan1=1; + vol0=vol1=infobuf[1]; + switches=infobuf[0]; + if ((switches&0x80)!=0) chan0=1; + if ((switches&0x40)!=0) chan1=0; + } + else if (fam0_drive) /* different firmware levels */ + { + chan0=0; + chan1=1; + vol0=vol1=infobuf[1]; + if (D_S[d].drv_type>=drv_201) + { + if (D_S[d].drv_type=drv_211) + { + if ((switches&0x20)!=0) chan0=1; + if ((switches&0x10)!=0) chan1=0; + } + } + else + { + vol0=infobuf[0]; + if ((vol0&0x01)!=0) chan0=1; + if ((vol1&0x01)==0) chan1=0; + vol0 &= 0xFC; + vol1 &= 0xFC; + if (vol0!=0) vol0 += 3; + if (vol1!=0) vol1 += 3; + } + } + } + else if (famT_drive) + { + D_S[d].volume_control=infobuf[7]; + chan0=0; + chan1=1; + if (D_S[d].volume_control&0x10) vol0=0; + else vol0=0xff; + if (D_S[d].volume_control&0x20) vol1=0; + else vol1=0xff; + } + D_S[d].vol_chan0=chan0; + D_S[d].vol_ctrl0=vol0; + D_S[d].vol_chan1=chan1; + D_S[d].vol_ctrl1=vol1; +#if 000 + D_S[d].vol_chan2=2; + D_S[d].vol_ctrl2=0xFF; + D_S[d].vol_chan3=3; + D_S[d].vol_ctrl3=0xFF; +#endif 000 + D_S[d].diskstate_flags |= volume_bit; + return (0); +} +/*==========================================================================*/ +static int cc_ReadCapacity(void) { - int i; - u_char switches; - u_char chan0,vol0,chan1,vol1; + int i, j; + + if (famL_drive) return (0); + D_S[d].diskstate_flags &= ~cd_size_bit; + for (j=3;j>0;j--) + { + clr_cmdbuf(); + if (fam1_drive) + { + drvcmd[0]=CMD1_CAPACITY; + response_count=5; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + drvcmd[0]=CMD2_CAPACITY; + response_count=8; + flags_cmd_out=f_putcmd; + } + else if (fam0_drive) + { + drvcmd[0]=CMD0_CAPACITY; + response_count=5; + flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; + } + else if (famT_drive) + { + response_count=12; + drvcmd[0]=CMDT_DISKINFO; + drvcmd[1]=0x02; + drvcmd[6]=CDROM_LEADOUT; + drvcmd[8]=response_count; + drvcmd[9]=0x00; + } + i=cmd_out(); + if (i>=0) break; + msg(DBG_000,"cc_ReadCapacity: cmd_out: err %d\n", i); + cc_ReadError(); + } + if (j==0) return (i); + if (fam1_drive) D_S[d].CDsize_frm=msf2blk(make32(make16(0,infobuf[0]),make16(infobuf[1],infobuf[2])))+CD_BLOCK_OFFSET; + else if (fam0_drive) D_S[d].CDsize_frm=make32(make16(0,infobuf[0]),make16(infobuf[1],infobuf[2])); + else if (fam2_drive) D_S[d].CDsize_frm=make32(make16(infobuf[0],infobuf[1]),make16(infobuf[2],infobuf[3])); + else if (famT_drive) + { + D_S[d].CDsize_frm=make32(make16(infobuf[8],infobuf[9]),make16(infobuf[10],infobuf[11])); + D_S[d].n_first_track=infobuf[2]; + D_S[d].n_last_track=infobuf[3]; - DriveStruct[d].diskstate_flags &= ~volume_bit; - clr_cmdbuf(); - if (new_drive) - { - drvcmd[0]=0x84; - drvcmd[1]=0x05; - response_count=5; - flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; - } - else - { - drvcmd[0]=0x85; - drvcmd[1]=0x03; - response_count=2; - flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; - } - i=cmd_out(); - if (i<0) return (i); - if (new_drive) - { - chan0=infobuf[1]&0x0F; - vol0=infobuf[2]; - chan1=infobuf[3]&0x0F; - vol1=infobuf[4]; - if (chan0==0) - { - chan0=1; - vol0=0; - } - if (chan1==0) - { - chan1=2; - vol1=0; - } - chan0 >>= 1; - chan1 >>= 1; - } - else if (lcs_drive) - { - chan0=0; - chan1=1; - vol0=vol1=infobuf[1]; - switches=infobuf[0]; - if ((switches&0x80)!=0) chan0=1; - if ((switches&0x40)!=0) chan1=0; - } - else /* old_drive, different firmware levels */ - { - chan0=0; - chan1=1; - vol0=vol1=infobuf[1]; - if (DriveStruct[d].drv_type>=drv_201) - { - if (DriveStruct[d].drv_type=drv_211) - { - if ((switches&0x20)!=0) chan0=1; - if ((switches&0x10)!=0) chan1=0; - } - } - else - { - vol0=infobuf[0]; - if ((vol0&0x01)!=0) chan0=1; - if ((vol1&0x01)==0) chan1=0; - vol0 &= 0xFC; - vol1 &= 0xFC; - if (vol0!=0) vol0 += 3; - if (vol1!=0) vol1 += 3; - } } - } - DriveStruct[d].vol_chan0=chan0; - DriveStruct[d].vol_ctrl0=vol0; - DriveStruct[d].vol_chan1=chan1; - DriveStruct[d].vol_ctrl1=vol1; - DriveStruct[d].vol_chan2=2; - DriveStruct[d].vol_ctrl2=0xFF; - DriveStruct[d].vol_chan3=3; - DriveStruct[d].vol_ctrl3=0xFF; - DriveStruct[d].diskstate_flags |= volume_bit; - return (0); + D_S[d].diskstate_flags |= cd_size_bit; + msg(DBG_000,"cc_ReadCapacity: %d frames.\n", D_S[d].CDsize_frm); + return (0); } +/*==========================================================================*/ +static int cc_ReadTocDescr(void) +{ + int i; + + D_S[d].diskstate_flags &= ~toc_bit; + clr_cmdbuf(); + if (fam1_drive) + { + drvcmd[0]=CMD1_DISKINFO; + response_count=6; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam0L_drive) + { + drvcmd[0]=CMD0_DISKINFO; + response_count=6; + if(famL_drive) + flags_cmd_out=f_putcmd; + else + flags_cmd_out=f_putcmd|f_getsta|f_ResponseStatus|f_obey_p_check; + } + else if (fam2_drive) + { + /* possibly longer timeout periods necessary */ + D_S[d].f_multisession=0; + drvcmd[0]=CMD2_DISKINFO; + drvcmd[1]=0x02; + drvcmd[2]=0xAB; + drvcmd[3]=0xFF; /* session */ + response_count=8; + flags_cmd_out=f_putcmd; + } + else if (famT_drive) + { + D_S[d].f_multisession=0; + response_count=12; + drvcmd[0]=CMDT_DISKINFO; + drvcmd[1]=0x02; + drvcmd[6]=CDROM_LEADOUT; + drvcmd[8]=response_count; + drvcmd[9]=0x00; + } + i=cmd_out(); + if (i<0) return (i); + if ((fam1_drive)||(fam2_drive)||(famL_drive)||(fam0_drive)) + D_S[d].xa_byte=infobuf[0]; + if (fam2_drive) + { + D_S[d].first_session=infobuf[1]; + D_S[d].last_session=infobuf[2]; + D_S[d].n_first_track=infobuf[3]; + D_S[d].n_last_track=infobuf[4]; + if (D_S[d].first_session!=D_S[d].last_session) + { + D_S[d].f_multisession=1; + D_S[d].lba_multi=msf2blk(make32(make16(0,infobuf[5]),make16(infobuf[6],infobuf[7]))); + } +#if 0 + if (D_S[d].first_session!=D_S[d].last_session) + { + if (D_S[d].last_session<=20) + zwanzig=D_S[d].last_session+1; + else zwanzig=20; + for (count=D_S[d].first_session;count>16)&0xFF; - drvcmd[2]=(block>>8)&0xFF; - drvcmd[3]=block&0xFF; + drvcmd[1]=(block>>16)&0xFF; + drvcmd[2]=(block>>8)&0xFF; + drvcmd[3]=block&0xFF; #endif TEST_UPC - response_count=8; - flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; - } - else - { - drvcmd[0]=0x08; + response_count=8; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + } + else if (fam0L_drive) + { + drvcmd[0]=CMD0_READ_UPC; #if TEST_UPC - drvcmd[2]=(block>>16)&0xFF; - drvcmd[3]=(block>>8)&0xFF; - drvcmd[4]=block&0xFF; + drvcmd[2]=(block>>16)&0xFF; + drvcmd[3]=(block>>8)&0xFF; + drvcmd[4]=block&0xFF; #endif TEST_UPC - response_count=0; - flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1; - } - i=cmd_out(); - if (i<0) return (i); - if (!new_drive) - { - response_count=16; - i=xx_ReadPacket(); - if (i<0) return (i); - } + response_count=0; + flags_cmd_out=f_putcmd|f_lopsta|f_getsta|f_ResponseStatus|f_obey_p_check|f_bit1; + } + else if (fam2_drive) + { + return (-1); + } + else if (famT_drive) + { + return (-1); + } + i=cmd_out(); + if (i<0) + { + msg(DBG_000,"cc_ReadUPC cmd_out: err %d\n", i); + return (i); + } + if (fam0L_drive) + { + response_count=16; + if (famL_drive) flags_cmd_out=f_putcmd; + i=cc_ReadPacket(); + if (i<0) + { + msg(DBG_000,"cc_ReadUPC ReadPacket: err %d\n", i); + return (i); + } + } #if TEST_UPC - checksum=0; + checksum=0; #endif TEST_UPC - DPRINTF((DBG_UPC,"SBPCD: UPC info: ")); - for (i=0;i<(new_drive?8:16);i++) - { + for (i=0;i<(fam1_drive?8:16);i++) + { #if TEST_UPC - checksum |= infobuf[i]; + checksum |= infobuf[i]; #endif TEST_UPC - DPRINTF((DBG_UPC,"%02X ", infobuf[i])); - } - DPRINTF((DBG_UPC,"\n")); + sprintf(&msgbuf[i*3], " %02X", infobuf[i]); + } + msgbuf[i*3]=0; + msg(DBG_UPC,"UPC info:%s\n", msgbuf); #if TEST_UPC - if ((checksum&0x7F)!=0) break; - } + if ((checksum&0x7F)!=0) break; + } #endif TEST_UPC - DriveStruct[d].UPC_ctl_adr=0; - if (new_drive) i=0; - else i=2; - if ((infobuf[i]&0x80)!=0) - { - convert_UPC(&infobuf[i]); - DriveStruct[d].UPC_ctl_adr = (DriveStruct[d].TocEnt_ctl_adr & 0xF0) | 0x02; - } - - DPRINTF((DBG_UPC,"SBPCD: UPC code: ")); - DPRINTF((DBG_UPC,"(%02X) ", DriveStruct[d].UPC_ctl_adr)); - for (i=0;i<7;i++) - { - DPRINTF((DBG_UPC,"%02X ", DriveStruct[d].UPC_buf[i])); - } - DPRINTF((DBG_UPC,"\n")); - - DriveStruct[d].diskstate_flags |= upc_bit; - return (0); + D_S[d].UPC_ctl_adr=0; + if (fam1_drive) i=0; + else i=2; + if ((infobuf[i]&0x80)!=0) + { + convert_UPC(&infobuf[i]); + D_S[d].UPC_ctl_adr = (D_S[d].TocEnt_ctl_adr & 0xF0) | 0x02; + } + for (i=0;i<7;i++) + sprintf(&msgbuf[i*3], " %02X", D_S[d].UPC_buf[i]); + sprintf(&msgbuf[i*3], " (%02X)", D_S[d].UPC_ctl_adr); + msgbuf[i*3+5]=0; + msg(DBG_UPC,"UPC code:%s\n", msgbuf); + D_S[d].diskstate_flags |= upc_bit; + return (0); } /*==========================================================================*/ -static int yy_CheckMultiSession(void) +static int cc_CheckMultiSession(void) { - int i; - - DriveStruct[d].f_multisession=0; - clr_cmdbuf(); - if (new_drive) - { - drvcmd[0]=0x8D; - response_count=6; - flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; - i=cmd_out(); - if (i<0) return (i); - if ((infobuf[0]&0x80)!=0) - { - DPRINTF((DBG_MUL,"SBPCD: MultiSession CD detected: %02X %02X %02X %02X %02X %02X\n", - infobuf[0], infobuf[1], infobuf[2], - infobuf[3], infobuf[4], infobuf[5])); - DriveStruct[d].f_multisession=1; - DriveStruct[d].lba_multi=msf2blk(make32(make16(0,infobuf[1]), - make16(infobuf[2],infobuf[3]))); - DriveStruct[d].last_redirect=20; - /* preliminary - has to get adjusted the following way: - * look at the first byte of frames 17 ff. until 0xFF is seen - * the frame before this one is the last continuation frame - */ + int i; + + if (fam2_drive) return (0); + D_S[d].f_multisession=0; + D_S[d].lba_multi=0; + if (fam0_drive) return (0); + clr_cmdbuf(); + if (fam1_drive) + { + drvcmd[0]=CMD1_MULTISESS; + response_count=6; + flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; + i=cmd_out(); + if (i<0) return (i); + if ((infobuf[0]&0x80)!=0) + { + D_S[d].f_multisession=1; + D_S[d].lba_multi=msf2blk(make32(make16(0,infobuf[1]), + make16(infobuf[2],infobuf[3]))); + } } - } - else if (lcs_drive) - { - drvcmd[0]=0x8C; - drvcmd[1]=3; - drvcmd[2]=1; - response_count=8; - flags_cmd_out=f_putcmd|f_ResponseStatus|f_obey_p_check; - i=cmd_out(); - if (i<0) return (i); - DPRINTF((DBG_MUL,"SBPCD: MultiSession Info: %02X %02X %02X %02X %02X %02X %02X %02X\n", - infobuf[0], infobuf[1], infobuf[2], infobuf[3], - infobuf[4], infobuf[5], infobuf[6], infobuf[7])); - DriveStruct[d].lba_multi=msf2blk(make32(make16(0,infobuf[5]), - make16(infobuf[6],infobuf[7]))); - if (DriveStruct[d].lba_multi>200) - { - DPRINTF((DBG_MUL,"SBPCD: MultiSession base: %06X\n", DriveStruct[d].lba_multi)); - DriveStruct[d].f_multisession=1; - DriveStruct[d].last_redirect=20; - /* preliminary - has to get adjusted the following way: - * look at the first byte of frames 17 ff. until 0xFF is seen; - * the frame before this one is the last repetition frame. - */ + else if (famL_drive) + { + drvcmd[0]=CMDL_MULTISESS; + drvcmd[1]=3; + drvcmd[2]=1; + response_count=8; + flags_cmd_out=f_putcmd; + i=cmd_out(); + if (i<0) return (i); + D_S[d].lba_multi=msf2blk(make32(make16(0,infobuf[5]), + make16(infobuf[6],infobuf[7]))); } - } - return (0); + else if (famT_drive) + { + response_count=12; + drvcmd[0]=CMDT_DISKINFO; + drvcmd[1]=0x02; + drvcmd[6]=0; + drvcmd[8]=response_count; + drvcmd[9]=0x40; + i=cmd_out(); + if (i<0) return (i); + D_S[d].lba_multi=msf2blk(make32(make16(0,infobuf[9]),make16(infobuf[10],infobuf[11]))); + } + for (i=0;i200) + { + D_S[d].f_multisession=1; + msg(DBG_MUL,"MultiSession base: %06X\n", D_S[d].lba_multi); + } + return (0); } /*==========================================================================*/ #if FUTURE -static int yy_SubChanInfo(int frame, int count, u_char *buffer) -/* "frame" is a RED BOOK (msf-bin) address */ +static int cc_SubChanInfo(int frame, int count, u_char *buffer) + /* "frame" is a RED BOOK (msf-bin) address */ { - int i; - - if (!new_drive) return (-ENOSYS); /* drive firmware lacks it */ + int i; + + if (fam0L_drive) return (-ENOSYS); /* drive firmware lacks it */ + if (famT_drive) + { + return (-1); + } #if 0 - if (DriveStruct[d].audio_state!=audio_playing) return (-ENODATA); + if (D_S[d].audio_state!=audio_playing) return (-ENODATA); #endif - clr_cmdbuf(); - drvcmd[0]=0x11; - drvcmd[1]=(frame>>16)&0xFF; - drvcmd[2]=(frame>>8)&0xFF; - drvcmd[3]=frame&0xFF; - drvcmd[5]=(count>>8)&0xFF; - drvcmd[6]=count&0xFF; - flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; - cmd_type=READ_SC; - DriveStruct[d].frame_size=CD_FRAMESIZE_SUB; - i=cmd_out(); /* which buffer to use? */ - return (i); + clr_cmdbuf(); + drvcmd[0]=CMD1_SUBCHANINF; + drvcmd[1]=(frame>>16)&0xFF; + drvcmd[2]=(frame>>8)&0xFF; + drvcmd[3]=frame&0xFF; + drvcmd[5]=(count>>8)&0xFF; + drvcmd[6]=count&0xFF; + flags_cmd_out=f_putcmd|f_respo2|f_ResponseStatus|f_obey_p_check; + cmd_type=READ_SC; + D_S[d].frame_size=CD_FRAMESIZE_SUB; + i=cmd_out(); /* which buffer to use? */ + return (i); } #endif FUTURE /*==========================================================================*/ static void check_datarate(void) { -#ifdef CDMKE - int i=0; - - DPRINTF((DBG_IOX,"SBPCD: check_datarate entered.\n")); - datarate=0; + int i=0; + + msg(DBG_IOX,"check_datarate entered.\n"); + datarate=0; #if TEST_STI - for (i=0;i<=1000;i++) printk("."); + for (i=0;i<=1000;i++) printk("."); #endif - /* set a timer to make (timed_out!=0) after 1.1 seconds */ -#if 0 - del_timer(&delay_timer); + /* set a timer to make (timed_out_delay!=0) after 1.1 seconds */ +#if 1 + del_timer(&delay_timer); #endif - delay_timer.expires = 110; - timed_out=0; - add_timer(&delay_timer); - DPRINTF((DBG_TIM,"SBPCD: timer started (110).\n")); - do - { - i=inb(CDi_status); - datarate++; -#if 00000 - if (datarate>0x0FFFFFFF) break; + delay_timer.expires=110; + timed_out_delay=0; + add_timer(&delay_timer); + msg(DBG_TIM,"delay timer started (110).\n"); + do + { + i=inb(CDi_status); + datarate++; +#if 1 + if (datarate>0x6FFFFFFF) break; #endif 00000 - } - while (!timed_out); /* originally looping for 1.1 seconds */ -#if 0 - del_timer(&delay_timer); -#endif 0 - DPRINTF((DBG_TIM,"SBPCD: datarate: %d\n", datarate)); - if (datarate<65536) datarate=65536; - - maxtim16=datarate*16; - maxtim04=datarate*4; - maxtim02=datarate*2; - maxtim_8=datarate/32; + } + while (!timed_out_delay); + del_timer(&delay_timer); + msg(DBG_TIM,"datarate: %04X\n", datarate); + if (datarate<65536) datarate=65536; + maxtim16=datarate*16; + maxtim04=datarate*4; + maxtim02=datarate*2; + maxtim_8=datarate/32; #if LONG_TIMING - maxtim_data=datarate/100; + maxtim_data=datarate/100; #else - maxtim_data=datarate/300; + maxtim_data=datarate/300; #endif LONG_TIMING - DPRINTF((DBG_TIM,"SBPCD: maxtim_8 %d, maxtim_data %d.\n", - maxtim_8, maxtim_data)); -#endif CDMKE + msg(DBG_TIM,"maxtim_8 %d, maxtim_data %d.\n", + maxtim_8, maxtim_data); +} +/*==========================================================================*/ +#if 0 +static int c2_ReadError(int fam) +{ + int i; + + clr_cmdbuf(); + response_count=9; + clr_respo_buf(9); + if (fam==1) + { + drvcmd[0]=CMD0_READ_ERR; /* same as CMD1_ and CMDL_ */ + i=do_cmd(f_putcmd|f_lopsta|f_getsta|f_ResponseStatus); + } + else if (fam==2) + { + drvcmd[0]=CMD2_READ_ERR; + i=do_cmd(f_putcmd); + } + else return (-1); + return (i); +} +#endif +/*==========================================================================*/ +static void ask_mail(void) +{ + int i; + + msg(DBG_INF, "please mail the following lines to emoenke@gwdg.de:\n"); + msg(DBG_INF, "%s\n", VERSION); + msg(DBG_INF, "address %03X, type %s, drive %s (ID %d)\n", + CDo_command, type, D_S[d].drive_model, D_S[d].drv_id); + for (i=0;i<12;i++) + sprintf(&msgbuf[i*3], " %02X", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_INF,"infobuf =%s\n", msgbuf); + for (i=0;i<12;i++) + sprintf(&msgbuf[i*3], " %c ", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_INF,"infobuf =%s\n", msgbuf); } /*==========================================================================*/ static int check_version(void) { - int i, j; - - DPRINTF((DBG_INI,"SBPCD: check_version entered.\n")); - /* clear any pending error state */ - clr_cmdbuf(); - drvcmd[0]=0x82; - response_count=9; - flags_cmd_out=f_putcmd; - i=cmd_out(); - if (i<0) DPRINTF((DBG_INI,"SBPCD: cmd_82 returns %d (ok anyway).\n",i)); - - /* read drive version */ - clr_cmdbuf(); - for (i=0;i<12;i++) infobuf[i]=0; - drvcmd[0]=0x83; - response_count=12; - flags_cmd_out=f_putcmd; - i=cmd_out(); - if (i<0) DPRINTF((DBG_INI,"SBPCD: cmd_83 returns %d\n",i)); - - DPRINTF((DBG_INI,"SBPCD: infobuf = \"")); - for (i=0;i<12;i++) DPRINTF((DBG_INI,"%c",infobuf[i])); - DPRINTF((DBG_INI,"\"\n")); - - DriveStruct[d].drv_type=0; - for (i=0;i<4;i++) if (infobuf[i]!=drive_family[i]) break; - if (i==4) - { - DriveStruct[d].drive_model[0]=infobuf[i++]; - DriveStruct[d].drive_model[1]=infobuf[i++]; - DriveStruct[d].drive_model[2]='-'; - DriveStruct[d].drive_model[3]='x'; - DriveStruct[d].drv_type=drv_new; - } - if (!DriveStruct[d].drv_type) - { - for (i=0;i<8;i++) if (infobuf[i]!=drive_vendor[i]) break; - if (i==8) + int i, j, l; + int teac_possible=0; + + msg(DBG_INI,"check_version entered.\n"); + msg(DBG_TE2,"check_version: id=%d, d=%d.\n", D_S[d].drv_id, d); + D_S[d].drv_type=0; + + /* check for CR-52x, CR-56x and LCS-7260 */ + /* clear any pending error state */ + clr_cmdbuf(); + drvcmd[0]=CMD0_READ_ERR; /* same as CMD1_ and CMDL_ */ + response_count=9; + flags_cmd_out=f_putcmd; + i=cmd_out(); + if (i<0) msg(DBG_INI,"CMD0_READERR returns %d (ok anyway).\n",i); + /* read drive version */ + clr_cmdbuf(); + for (i=0;i<12;i++) infobuf[i]=0; + drvcmd[0]=CMD0_READ_VER; /* same as CMD1_ and CMDL_ */ + response_count=12; /* fam1: only 11 */ + flags_cmd_out=f_putcmd; + i=cmd_out(); + if (i<-1) msg(DBG_INI,"CMD0_READ_VER returns %d\n",i); + if (i==-11) teac_possible++; + j=0; + for (i=0;i<12;i++) j+=infobuf[i]; + if (j) + { + for (i=0;i<12;i++) + sprintf(&msgbuf[i*3], " %02X", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_IDX,"infobuf =%s\n", msgbuf); + msg(DBG_000,"infobuf =%s\n", msgbuf); + for (i=0;i<12;i++) + sprintf(&msgbuf[i*3], " %c ", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_IDX,"infobuf =%s\n", msgbuf); + msg(DBG_000,"infobuf =%s\n", msgbuf); + } + for (i=0;i<4;i++) if (infobuf[i]!=family1[i]) break; + if (i==4) + { + D_S[d].drive_model[0]='C'; + D_S[d].drive_model[1]='R'; + D_S[d].drive_model[2]='-'; + D_S[d].drive_model[3]='5'; + D_S[d].drive_model[4]=infobuf[i++]; + D_S[d].drive_model[5]=infobuf[i++]; + D_S[d].drive_model[6]=0; + D_S[d].drv_type=drv_fam1; + } + if (!D_S[d].drv_type) + { + for (i=0;i<8;i++) if (infobuf[i]!=family0[i]) break; + if (i==8) + { + D_S[d].drive_model[0]='C'; + D_S[d].drive_model[1]='R'; + D_S[d].drive_model[2]='-'; + D_S[d].drive_model[3]='5'; + D_S[d].drive_model[4]='2'; + D_S[d].drive_model[5]='x'; + D_S[d].drive_model[6]=0; + D_S[d].drv_type=drv_fam0; + } + } + if (!D_S[d].drv_type) + { + for (i=0;i<8;i++) if (infobuf[i]!=familyL[i]) break; + if (i==8) + { + for (j=0;j<8;j++) + D_S[d].drive_model[j]=infobuf[j]; + D_S[d].drive_model[8]=0; + D_S[d].drv_type=drv_famL; + } + } + if (!D_S[d].drv_type) + { + /* check for CD200 */ + clr_cmdbuf(); + drvcmd[0]=CMD2_READ_ERR; + response_count=9; + flags_cmd_out=f_putcmd; + i=cmd_out(); + if (i<0) msg(DBG_INI,"CMD2_READERR returns %d (ok anyway).\n",i); + if (i<0) msg(DBG_000,"CMD2_READERR returns %d (ok anyway).\n",i); + /* read drive version */ + clr_cmdbuf(); + for (i=0;i<12;i++) infobuf[i]=0; + if (sbpro_type==1) OUT(CDo_sel_i_d,0); +#if 0 + OUT(CDo_reset,0); + sbp_sleep(600); + OUT(CDo_enable,D_S[d].drv_sel); +#endif 0 + drvcmd[0]=CMD2_READ_VER; + response_count=12; + flags_cmd_out=f_putcmd; + i=cmd_out(); + if (i<0) msg(DBG_INI,"CMD2_READ_VER returns %d\n",i); + if (i==-7) teac_possible++; + j=0; + for (i=0;i<12;i++) j+=infobuf[i]; + if (j) + { + for (i=0;i<12;i++) + sprintf(&msgbuf[i*3], " %02X", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_IDX,"infobuf =%s\n", msgbuf); + for (i=0;i<12;i++) + sprintf(&msgbuf[i*3], " %c ", infobuf[i]); + msgbuf[i*3]=0; + msg(DBG_IDX,"infobuf =%s\n", msgbuf); + } + if (i>=0) + { + for (i=0;i<5;i++) if (infobuf[i]!=family2[i]) break; + if (i==5) + { + D_S[d].drive_model[0]='C'; + D_S[d].drive_model[1]='D'; + D_S[d].drive_model[2]='2'; + D_S[d].drive_model[3]='0'; + D_S[d].drive_model[4]='0'; + D_S[d].drive_model[5]=infobuf[i++]; + D_S[d].drive_model[6]=infobuf[i++]; + D_S[d].drive_model[7]=0; + D_S[d].drv_type=drv_fam2; + } + } + } + if (!D_S[d].drv_type) + { + /* check for TEAC CD-55A */ + msg(DBG_TEA,"teac_possible: %d\n",teac_possible); + for (j=1;j<=((D_S[d].drv_id==0)?3:1);j++) + { + for (l=1;l<=((D_S[d].drv_id==0)?10:1);l++) + { + msg(DBG_TEA,"TEAC reset #%d-%d.\n", j, l); + if (sbpro_type==1) OUT(CDo_reset,0); + else + { + OUT(CDo_enable,D_S[d].drv_sel); + OUT(CDo_sel_i_d,0); + OUT(CDo_command,CMDT_RESET); + for (i=0;i<9;i++) OUT(CDo_command,0); + } + sbp_sleep(50); + OUT(CDo_enable,D_S[d].drv_sel); + OUT(CDo_sel_i_d,0); + i=inb(CDi_status); + msg(DBG_TEA,"TEAC CDi_status: %02X.\n",i); +#if 0 + if (i&s_not_result_ready) continue; /* drive not present or ready */ +#endif + i=inb(CDi_info); + msg(DBG_TEA,"TEAC CDi_info: %02X.\n",i); + if (i==0x55) break; /* drive found */ + } + if (i==0x55) break; /* drive found */ + } + if (i==0x55) /* drive found */ + { + msg(DBG_TEA,"TEAC drive found.\n"); + clr_cmdbuf(); + flags_cmd_out=f_putcmd; + response_count=12; + drvcmd[0]=CMDT_READ_VER; + drvcmd[4]=response_count; + for (i=0;i<12;i++) infobuf[i]=0; + i=cmd_out_T(); + if (i!=0) msg(DBG_TEA,"cmd_out_T(CMDT_READ_VER) returns %d.\n",i); + for (i=1;i<6;i++) if (infobuf[i]!=familyT[i-1]) break; + if (i==6) + { + D_S[d].drive_model[0]='C'; + D_S[d].drive_model[1]='D'; + D_S[d].drive_model[2]='-'; + D_S[d].drive_model[3]='5'; + D_S[d].drive_model[4]='5'; + D_S[d].drive_model[5]=0; + D_S[d].drv_type=drv_famT; + } + } + } + if (!D_S[d].drv_type) { - DriveStruct[d].drive_model[0]='2'; - DriveStruct[d].drive_model[1]='x'; - DriveStruct[d].drive_model[2]='-'; - DriveStruct[d].drive_model[3]='x'; - DriveStruct[d].drv_type=drv_old; + msg(DBG_TEA,"no drive found at address %03X under ID %d.\n",CDo_command,D_S[d].drv_id); + return (-522); } - } - if (!DriveStruct[d].drv_type) - { - for (i=0;i<4;i++) if (infobuf[i]!=lcs_family[i]) break; - if (i==4) + for (j=0;j<4;j++) D_S[d].firmware_version[j]=infobuf[i+j]; + if (famL_drive) { - DriveStruct[d].drive_model[0]=infobuf[i++]; - DriveStruct[d].drive_model[1]=infobuf[i++]; - DriveStruct[d].drive_model[2]=infobuf[i++]; - DriveStruct[d].drive_model[3]=infobuf[i++]; - DriveStruct[d].drv_type=drv_lcs; + u_char lcs_firm_e1[]="A E1"; + u_char lcs_firm_f4[]="A4F4"; + + for (j=0;j<4;j++) + if (D_S[d].firmware_version[j]!=lcs_firm_e1[j]) break; + if (j==4) D_S[d].drv_type=drv_e1; + + for (j=0;j<4;j++) + if (D_S[d].firmware_version[j]!=lcs_firm_f4[j]) break; + if (j==4) D_S[d].drv_type=drv_f4; + + if (D_S[d].drv_type==drv_famL) ask_mail(); } - } - if (!DriveStruct[d].drv_type) - { - DPRINTF((DBG_INI,"SBPCD: check_version: error.\n")); - return (-1); - } - if (lcs_drive) DriveStruct[d].drv_type=drv_260; /* still needs a closer look */ - else - { - for (j=0;j<4;j++) DriveStruct[d].firmware_version[j]=infobuf[i+j]; - j = (DriveStruct[d].firmware_version[0] & 0x0F) * 100 + - (DriveStruct[d].firmware_version[2] & 0x0F) *10 + - (DriveStruct[d].firmware_version[3] & 0x0F); - if (new_drive) - { - if (j<100) DriveStruct[d].drv_type=drv_099; - else DriveStruct[d].drv_type=drv_100; - } - else if (j<200) DriveStruct[d].drv_type=drv_199; - else if (j<201) DriveStruct[d].drv_type=drv_200; - else if (j<210) DriveStruct[d].drv_type=drv_201; - else if (j<211) DriveStruct[d].drv_type=drv_210; - else if (j<300) DriveStruct[d].drv_type=drv_211; - else DriveStruct[d].drv_type=drv_300; - } - DPRINTF((DBG_INI,"SBPCD: check_version done.\n")); - return (0); + else if (famT_drive) + { + j=infobuf[4]; /* one-byte version??? - here: 0x15 */ + if (j=='5') + { + D_S[d].firmware_version[0]=infobuf[7]; + D_S[d].firmware_version[1]=infobuf[8]; + D_S[d].firmware_version[2]=infobuf[10]; + D_S[d].firmware_version[3]=infobuf[11]; + } + else + { + if (j!=0x15) ask_mail(); + D_S[d].firmware_version[0]='0'; + D_S[d].firmware_version[1]='.'; + D_S[d].firmware_version[2]='0'+(j>>4); + D_S[d].firmware_version[3]='0'+(j&0x0f); + } + } + else /* CR-52x, CR-56x, CD200 */ + { + j = (D_S[d].firmware_version[0] & 0x0F) * 100 + + (D_S[d].firmware_version[2] & 0x0F) *10 + + (D_S[d].firmware_version[3] & 0x0F); + if (fam0_drive) + { + if (j<200) D_S[d].drv_type=drv_199; + else if (j<201) D_S[d].drv_type=drv_200; + else if (j<210) D_S[d].drv_type=drv_201; + else if (j<211) D_S[d].drv_type=drv_210; + else if (j<300) D_S[d].drv_type=drv_211; + else if (j>=300) D_S[d].drv_type=drv_300; + } + else if (fam1_drive) + { + if (j<100) D_S[d].drv_type=drv_099; + else + { + D_S[d].drv_type=drv_100; + if ((j!=500)||(j!=102)) ask_mail(); + } + } + else if (fam2_drive) + { + msg(DBG_INF,"new drive CD200 (%s)detected.\n", D_S[d].firmware_version); + msg(DBG_INF,"support is not fulfilled yet - audio should work.\n"); + if ((j!=101)&&(j!=35)) ask_mail(); /* only 1.01 and 0.35 known at time */ + } + } + msg(DBG_LCS,"drive type %02X\n",D_S[d].drv_type); + msg(DBG_INI,"check_version done.\n"); + return (0); } /*==========================================================================*/ -static int switch_drive(int num) +static void switch_drive(int i) { - int i; - - d=num; - - i=num; - if (sbpro_type==1) i=(i&0x01)<<1|(i&0x02)>>1; - OUT(CDo_enable,i); - DPRINTF((DBG_DID,"SBPCD: switch_drive: drive %d activated.\n",DriveStruct[d].drv_minor)); - return (0); + d=i; + OUT(CDo_enable,D_S[d].drv_sel); + msg(DBG_DID,"drive %d (ID=%d) activated.\n", i, D_S[d].drv_id); + return; } /*==========================================================================*/ +#ifdef PATH_CHECK /* - * probe for the presence of drives on the selected controller + * probe for the presence of an interface card */ -static int check_drives(void) +static int check_card(int port) { - int i, j; - char *printk_header=""; - - DPRINTF((DBG_INI,"SBPCD: check_drives entered.\n")); - - ndrives=0; - for (j=0;j=0) - { - ndrives++; - DriveStruct[d].drv_options=drv_pattern[j]; - if (!new_drive) - DriveStruct[d].drv_options&=~(speed_auto|speed_300|speed_150); - printk("%sDrive %d: %s%.4s (%.4s)\n", printk_header, - DriveStruct[d].drv_minor, - drive_family, - DriveStruct[d].drive_model, - DriveStruct[d].firmware_version); - printk_header=" - "; - } - else DriveStruct[d].drv_minor=-1; - } - if (ndrives==0) return (-1); - return (0); +#undef N_RESPO +#define N_RESPO 20 + int i, j, k; + u_char response[N_RESPO]; + u_char save_port0; + u_char save_port3; + + msg(DBG_INI,"check_card entered.\n"); + save_port0=inb(port+0); + save_port3=inb(port+3); + + for (j=0;j0;i--) OUT(port+0,0); + for (k=0;k0;i--) + { + if (inb(port+1)&s_not_result_ready) continue; + response[k]=inb(port+0); + break; + } + } + for (i=0;i0;i--) OUT(port+0,0); + for (k=0;k0;i--) + { + if (inb(port+1)&s_not_result_ready) continue; + response[k]=inb(port+0); + break; + } + } + for (i=0;i0;i--) OUT(port+0,0); + for (k=0;k0;i--) + { + if (inb(port+1)&s_not_result_ready) continue; + response[k]=inb(port+0); + break; + } + } + for (i=0;i0;i--) OUT(port+0,0); + for (k=0;k0;i--) + { + if (inb(port+1)&s_not_result_ready) continue; + response[k]=inb(port+0); + break; + } + } + for (i=0;i>1; + else D_S[ndrives].drv_sel=j; + switch_drive(ndrives); + msg(DBG_INI,"check_drives: drive %d (ID=%d) activated.\n",ndrives,j); + msg(DBG_000,"check_drives: drive %d (ID=%d) activated.\n",ndrives,j); + i=check_version(); + if (i<0) msg(DBG_INI,"check_version returns %d.\n",i); + else + { + D_S[d].drv_options=drv_pattern[j]; + if (fam0L_drive) D_S[d].drv_options&=~(speed_auto|speed_300|speed_150); + msg(DBG_INF, "Drive %d (ID=%d): %.9s (%.4s) at 0x%03X (type %d)\n", + d, + D_S[d].drv_id, + D_S[d].drive_model, + D_S[d].firmware_version, + CDo_command, + sbpro_type); + ndrives++; + } + } + for (j=ndrives;j=0) break; + msg(DBG_INF,"DiskInfo: ReadCapacity #%d returns %d\n", j, i); + i=cc_DriveReset(); } - i=xx_ModeSense(); - if (i<0) + if (j==LOOP_COUNT) return (-33); /* give up */ + + i=cc_ReadTocDescr(); + if (i<0) { - DPRINTF((DBG_INF,"SBPCD: DiskInfo: xx_ModeSense returns %d\n", i)); - continue; + msg(DBG_INF,"DiskInfo: ReadTocDescr returns %d\n", i); + return (i); } - i=xx_ReadCapacity(); - if (i>=0) break; - DPRINTF((DBG_INF,"SBPCD: DiskInfo: ReadCapacity #%d returns %d\n", j, i)); - i=DriveReset(); - } - if (j==LOOP_COUNT) return (-2); /* give up */ - - i=xx_ReadTocDescr(); - if (i<0) - { - DPRINTF((DBG_INF,"SBPCD: DiskInfo: ReadTocDescr returns %d\n", i)); - return (i); - } - i=ReadToC(); - if (i<0) - { - DPRINTF((DBG_INF,"SBPCD: DiskInfo: ReadToC returns %d\n", i)); - return (i); - } - i=yy_CheckMultiSession(); - if (i<0) - { - DPRINTF((DBG_INF,"SBPCD: DiskInfo: yy_CheckMultiSession returns %d\n", i)); - return (i); - } - i=xx_ReadTocEntry(DriveStruct[d].n_first_track); - if (i<0) - { - DPRINTF((DBG_INF,"SBPCD: DiskInfo: xx_ReadTocEntry(1) returns %d\n", i)); - return (i); - } - i=xx_ReadUPC(); - if (i<0) - { - DPRINTF((DBG_INF,"SBPCD: DiskInfo: xx_ReadUPC returns %d\n", i)); - return (i); - } -#ifdef XA_TEST2 - if ((!new_drive) && (DriveStruct[d].xa_byte==0x20)) /* XA disk with old drive */ - { - xx_ModeSelect(CD_FRAMESIZE_XA); - xx_ModeSense(); - } -#endif XA_TEST2 - - return (0); + i=ReadToC(); + if (i<0) + { + msg(DBG_INF,"DiskInfo: ReadToC returns %d\n", i); + return (i); + } + i=cc_CheckMultiSession(); + if (i<0) + { + msg(DBG_INF,"DiskInfo: cc_CheckMultiSession returns %d\n", i); + return (i); + } + if (D_S[d].f_multisession) D_S[d].sbp_bufsiz=1; /* possibly a weird PhotoCD */ + else D_S[d].sbp_bufsiz=SBP_BUFFER_FRAMES; + i=cc_ReadTocEntry(D_S[d].n_first_track); + if (i<0) + { + msg(DBG_INF,"DiskInfo: cc_ReadTocEntry(1) returns %d\n", i); + return (i); + } + i=cc_ReadUPC(); + if (i<0) msg(DBG_INF,"DiskInfo: cc_ReadUPC returns %d\n", i); + if ((fam0L_drive) && (D_S[d].xa_byte==0x20)) + { + /* XA disk with old drive */ + cc_ModeSelect(CD_FRAMESIZE_XA); + cc_ModeSense(); + } + if (famT_drive) cc_prep_mode_T(); + msg(DBG_000,"DiskInfo done.\n"); + return (0); } /*==========================================================================*/ +#if FUTURE /* * called always if driver gets entered * returns 0 or ERROR2 or ERROR15 */ static int prepare(u_char func, u_char subfunc) { - int i; - - if (!new_drive) - { - i=inb(CDi_status); - if (i&s_attention) GetStatus(); - } - else GetStatus(); - if (DriveStruct[d].CD_changed==0xFF) - { -#if MANY_SESSION -#else - DriveStruct[d].diskstate_flags=0; -#endif MANY_SESSION - DriveStruct[d].audio_state=0; - if (!st_diskok) + int i; + + if (fam0L_drive) { - i=check_allowed1(func,subfunc); - if (i<0) return (-2); + i=inb(CDi_status); + if (i&s_attention) GetStatus(); } - else + else if (fam1_drive) GetStatus(); + else if (fam2_drive) GetStatus(); + else if (famT_drive) GetStatus(); + if (D_S[d].CD_changed==0xFF) { - i=check_allowed3(func,subfunc); - if (i<0) - { - DriveStruct[d].CD_changed=1; - return (-15); - } + D_S[d].diskstate_flags=0; + D_S[d].audio_state=0; + if (!st_diskok) + { + i=check_allowed1(func,subfunc); + if (i<0) return (-2); + } + else + { + i=check_allowed3(func,subfunc); + if (i<0) + { + D_S[d].CD_changed=1; + return (-15); + } + } } - } - else - { - if (!st_diskok) + else { -#if MANY_SESSION -#else - DriveStruct[d].diskstate_flags=0; -#endif MANY_SESSION - DriveStruct[d].audio_state=0; - i=check_allowed1(func,subfunc); - if (i<0) return (-2); - } - else - { - if (st_busy) - { - if (DriveStruct[d].audio_state!=audio_pausing) - { - i=check_allowed2(func,subfunc); - if (i<0) return (-2); - } - } - else - { - if (DriveStruct[d].audio_state==audio_playing) seek_pos_audio_end(); - DriveStruct[d].audio_state=0; - } - if (!frame_size_valid) - { - i=DiskInfo(); - if (i<0) - { -#if MANY_SESSION -#else - DriveStruct[d].diskstate_flags=0; -#endif MANY_SESSION - DriveStruct[d].audio_state=0; - i=check_allowed1(func,subfunc); - if (i<0) return (-2); + if (!st_diskok) + { + D_S[d].diskstate_flags=0; + D_S[d].audio_state=0; + i=check_allowed1(func,subfunc); + if (i<0) return (-2); + } + else + { + if (st_busy) + { + if (D_S[d].audio_state!=audio_pausing) + { + i=check_allowed2(func,subfunc); + if (i<0) return (-2); + } + } + else + { + if (D_S[d].audio_state==audio_playing) seek_pos_audio_end(); + D_S[d].audio_state=0; + } + if (!frame_size_valid) + { + i=DiskInfo(); + if (i<0) + { + D_S[d].diskstate_flags=0; + D_S[d].audio_state=0; + i=check_allowed1(func,subfunc); + if (i<0) return (-2); + } + } } - } - } - } - return (0); -} -/*==========================================================================*/ -static int xx_PlayAudio(int pos_audio_start,int pos_audio_end) -{ - int i, n; - - if (DriveStruct[d].audio_state==audio_playing) return (-EINVAL); - clr_cmdbuf(); - if (lcs_drive) - { - drvcmd[0]=0x0A; - i=msf2blk(pos_audio_start); - n=msf2blk(pos_audio_end)+1-i; - drvcmd[1]=(i>>16)&0x00FF; - drvcmd[2]=(i>>8)&0x00FF; - drvcmd[3]=i&0x00FF; - drvcmd[4]=(n>>16)&0x00FF; - drvcmd[5]=(n>>8)&0x00FF; - drvcmd[6]=n&0x00FF; - flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta | - f_ResponseStatus | f_obey_p_check | f_wait_if_busy; - } - else - { - if (new_drive) - { - drvcmd[0]=0x0E; - flags_cmd_out = f_putcmd | f_respo2 | f_ResponseStatus | - f_obey_p_check | f_wait_if_busy; - } - else /* old_drive */ - { - drvcmd[0]=0x0B; - flags_cmd_out = f_putcmd | f_respo2 | f_lopsta | f_getsta | - f_ResponseStatus | f_obey_p_check | f_wait_if_busy; - } - drvcmd[1]=(pos_audio_start>>16)&0x00FF; - drvcmd[2]=(pos_audio_start>>8)&0x00FF; - drvcmd[3]=pos_audio_start&0x00FF; - drvcmd[4]=(pos_audio_end>>16)&0x00FF; - drvcmd[5]=(pos_audio_end>>8)&0x00FF; - drvcmd[6]=pos_audio_end&0x00FF; } - response_count=0; - i=cmd_out(); - return (i); + return (0); } -/*==========================================================================*/ +#endif FUTURE /*==========================================================================*/ /*==========================================================================*/ /* @@ -2480,43 +3743,43 @@ static int xx_PlayAudio(int pos_audio_start,int pos_audio_end) */ static int sbp_status(void) { - int st; - - st=ResponseStatus(); - if (st<0) - { - DPRINTF((DBG_INF,"SBPCD: sbp_status: timeout.\n")); - return (0); - } - - if (!st_spinning) DPRINTF((DBG_SPI,"SBPCD: motor got off - ignoring.\n")); - - if (st_check) - { - DPRINTF((DBG_INF,"SBPCD: st_check detected - retrying.\n")); - return (0); - } - if (!st_door_closed) - { - DPRINTF((DBG_INF,"SBPCD: door is open - retrying.\n")); - return (0); - } - if (!st_caddy_in) - { - DPRINTF((DBG_INF,"SBPCD: disk removed - retrying.\n")); - return (0); - } - if (!st_diskok) - { - DPRINTF((DBG_INF,"SBPCD: !st_diskok detected - retrying.\n")); - return (0); - } - if (st_busy) - { - DPRINTF((DBG_INF,"SBPCD: st_busy detected - retrying.\n")); - return (0); - } - return (1); + int st; + + st=ResponseStatus(); + if (st<0) + { + msg(DBG_INF,"sbp_status: timeout.\n"); + return (0); + } + + if (!st_spinning) msg(DBG_SPI,"motor got off - ignoring.\n"); + + if (st_check) + { + msg(DBG_INF,"st_check detected - retrying.\n"); + return (0); + } + if (!st_door_closed) + { + msg(DBG_INF,"door is open - retrying.\n"); + return (0); + } + if (!st_caddy_in) + { + msg(DBG_INF,"disk removed - retrying.\n"); + return (0); + } + if (!st_diskok) + { + msg(DBG_INF,"!st_diskok detected - retrying.\n"); + return (0); + } + if (st_busy) + { + msg(DBG_INF,"st_busy detected - retrying.\n"); + return (0); + } + return (1); } /*==========================================================================*/ @@ -2528,476 +3791,546 @@ static int sbp_status(void) static int sbpcd_ioctl(struct inode *inode, struct file *file, u_int cmd, u_long arg) { - int i, st; - - DPRINTF((DBG_IO2,"SBPCD: ioctl(%d, 0x%08lX, 0x%08lX)\n", - MINOR(inode->i_rdev), cmd, arg)); - if (!inode) return (-EINVAL); - i=MINOR(inode->i_rdev); - if ( (i<0) || (i>=NR_SBPCD) ) - { - printk("SBPCD: ioctl: bad device: %d\n", i); - return (-ENODEV); /* no such drive */ - } - switch_drive(i); - - st=GetStatus(); - if (st<0) return (-EIO); - - if (!toc_valid) - { - i=DiskInfo(); - if (i<0) return (-EIO); /* error reading TOC */ - } - - DPRINTF((DBG_IO2,"SBPCD: ioctl: device %d, request %04X\n",i,cmd)); - switch (cmd) /* Sun-compatible */ - { - case DDIOCSDBG: /* DDI Debug */ - if (! suser()) return (-EPERM); - i = verify_area(VERIFY_READ, (int *) arg, sizeof(int)); - if (i>=0) i=sbpcd_dbg_ioctl(arg,1); - return (i); - - case CDROMPAUSE: /* Pause the drive */ - DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMPAUSE entered.\n")); - /* pause the drive unit when it is currently in PLAY mode, */ - /* or reset the starting and ending locations when in PAUSED mode. */ - /* If applicable, at the next stopping point it reaches */ - /* the drive will discontinue playing. */ - switch (DriveStruct[d].audio_state) - { - case audio_playing: - i=xx_Pause_Resume(1); - if (i<0) return (-EIO); - DriveStruct[d].audio_state=audio_pausing; - i=xx_ReadSubQ(); - if (i<0) return (-EIO); - DriveStruct[d].pos_audio_start=DriveStruct[d].SubQ_run_tot; - return (0); - case audio_pausing: - i=xx_Seek(DriveStruct[d].pos_audio_start,1); - if (i<0) return (-EIO); - return (0); - default: - return (-EINVAL); - } - - case CDROMRESUME: /* resume paused audio play */ - DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMRESUME entered.\n")); - /* resume playing audio tracks when a previous PLAY AUDIO call has */ - /* been paused with a PAUSE command. */ - /* It will resume playing from the location saved in SubQ_run_tot. */ - if (DriveStruct[d].audio_state!=audio_pausing) return -EINVAL; - i=xx_Pause_Resume(3); - if (i<0) return (-EIO); - DriveStruct[d].audio_state=audio_playing; - return (0); - - case CDROMPLAYMSF: - DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMPLAYMSF entered.\n")); - if (DriveStruct[d].audio_state==audio_playing) - { - i=xx_Pause_Resume(1); - if (i<0) return (-EIO); - i=xx_ReadSubQ(); - if (i<0) return (-EIO); - DriveStruct[d].pos_audio_start=DriveStruct[d].SubQ_run_tot; - i=xx_Seek(DriveStruct[d].pos_audio_start,1); - } - st=verify_area(VERIFY_READ, (void *) arg, sizeof(struct cdrom_msf)); - if (st) return (st); - memcpy_fromfs(&msf, (void *) arg, sizeof(struct cdrom_msf)); - /* values come as msf-bin */ - DriveStruct[d].pos_audio_start = (msf.cdmsf_min0<<16) | + int i, st; + + msg(DBG_IO2,"ioctl(%d, 0x%08lX, 0x%08lX)\n", + MINOR(inode->i_rdev), cmd, arg); + if (!inode) return (-EINVAL); + i=MINOR(inode->i_rdev); + if ((i<0) || (i>=NR_SBPCD) || (D_S[i].drv_id==-1)) + { + msg(DBG_INF, "ioctl: bad device: %04X\n", inode->i_rdev); + return (-ENXIO); /* no such drive */ + } + if (d!=i) switch_drive(i); + +#if 0 + st=GetStatus(); + if (st<0) return (-EIO); + + if (!toc_valid) + { + i=DiskInfo(); + if (i<0) return (-EIO); /* error reading TOC */ + } +#endif + + msg(DBG_IO2,"ioctl: device %d, request %04X\n",i,cmd); + switch (cmd) /* Sun-compatible */ + { + case DDIOCSDBG: /* DDI Debug */ + if (!suser()) return (-EPERM); + i=sbpcd_dbg_ioctl(arg,1); + return (i); + + case CDROMPAUSE: /* Pause the drive */ + msg(DBG_IOC,"ioctl: CDROMPAUSE entered.\n"); + /* pause the drive unit when it is currently in PLAY mode, */ + /* or reset the starting and ending locations when in PAUSED mode. */ + /* If applicable, at the next stopping point it reaches */ + /* the drive will discontinue playing. */ + switch (D_S[d].audio_state) + { + case audio_playing: + if (famL_drive) i=cc_ReadSubQ(); + else i=cc_Pause_Resume(1); + if (i<0) return (-EIO); + if (famL_drive) i=cc_Pause_Resume(1); + else i=cc_ReadSubQ(); + if (i<0) return (-EIO); + D_S[d].pos_audio_start=D_S[d].SubQ_run_tot; + D_S[d].audio_state=audio_pausing; + return (0); + case audio_pausing: + i=cc_Seek(D_S[d].pos_audio_start,1); + if (i<0) return (-EIO); + return (0); + default: + return (-EINVAL); + } + + case CDROMRESUME: /* resume paused audio play */ + msg(DBG_IOC,"ioctl: CDROMRESUME entered.\n"); + /* resume playing audio tracks when a previous PLAY AUDIO call has */ + /* been paused with a PAUSE command. */ + /* It will resume playing from the location saved in SubQ_run_tot. */ + if (D_S[d].audio_state!=audio_pausing) return -EINVAL; + if (famL_drive) + i=cc_PlayAudio(D_S[d].pos_audio_start, + D_S[d].pos_audio_end); + else i=cc_Pause_Resume(3); + if (i<0) return (-EIO); + D_S[d].audio_state=audio_playing; + return (0); + + case CDROMPLAYMSF: + msg(DBG_IOC,"ioctl: CDROMPLAYMSF entered.\n"); + if (D_S[d].audio_state==audio_playing) + { + i=cc_Pause_Resume(1); + if (i<0) return (-EIO); + i=cc_ReadSubQ(); + if (i<0) return (-EIO); + D_S[d].pos_audio_start=D_S[d].SubQ_run_tot; + i=cc_Seek(D_S[d].pos_audio_start,1); + } + st=verify_area(VERIFY_READ, (void *) arg, sizeof(struct cdrom_msf)); + if (st) return (st); + memcpy_fromfs(&msf, (void *) arg, sizeof(struct cdrom_msf)); + /* values come as msf-bin */ + D_S[d].pos_audio_start = (msf.cdmsf_min0<<16) | (msf.cdmsf_sec0<<8) | - msf.cdmsf_frame0; - DriveStruct[d].pos_audio_end = (msf.cdmsf_min1<<16) | - (msf.cdmsf_sec1<<8) | - msf.cdmsf_frame1; - DPRINTF((DBG_IOX,"SBPCD: ioctl: CDROMPLAYMSF %08X %08X\n", - DriveStruct[d].pos_audio_start,DriveStruct[d].pos_audio_end)); - i=xx_PlayAudio(DriveStruct[d].pos_audio_start,DriveStruct[d].pos_audio_end); - DPRINTF((DBG_IOC,"SBPCD: ioctl: xx_PlayAudio returns %d\n",i)); + msf.cdmsf_frame0; + D_S[d].pos_audio_end = (msf.cdmsf_min1<<16) | + (msf.cdmsf_sec1<<8) | + msf.cdmsf_frame1; + msg(DBG_IOX,"ioctl: CDROMPLAYMSF %08X %08X\n", + D_S[d].pos_audio_start,D_S[d].pos_audio_end); + i=cc_PlayAudio(D_S[d].pos_audio_start,D_S[d].pos_audio_end); + msg(DBG_IOC,"ioctl: cc_PlayAudio returns %d\n",i); #if 0 - if (i<0) return (-EIO); + if (i<0) return (-EIO); #endif 0 - DriveStruct[d].audio_state=audio_playing; - return (0); - - case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */ - DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMPLAYTRKIND entered.\n")); - if (DriveStruct[d].audio_state==audio_playing) - { - DPRINTF((DBG_IOX,"SBPCD: CDROMPLAYTRKIND: already audio_playing.\n")); - return (0); - return (-EINVAL); - } - st=verify_area(VERIFY_READ,(void *) arg,sizeof(struct cdrom_ti)); - if (st<0) - { - DPRINTF((DBG_IOX,"SBPCD: CDROMPLAYTRKIND: verify_area error.\n")); - return (st); - } - memcpy_fromfs(&ti,(void *) arg,sizeof(struct cdrom_ti)); - DPRINTF((DBG_IOX,"SBPCD: ioctl: trk0: %d, ind0: %d, trk1:%d, ind1:%d\n", - ti.cdti_trk0,ti.cdti_ind0,ti.cdti_trk1,ti.cdti_ind1)); - if (ti.cdti_trk0DriveStruct[d].n_last_track) return (-EINVAL); - if (ti.cdti_trk1DriveStruct[d].n_last_track) ti.cdti_trk1=DriveStruct[d].n_last_track; - DriveStruct[d].pos_audio_start=DriveStruct[d].TocBuffer[ti.cdti_trk0].address; - DriveStruct[d].pos_audio_end=DriveStruct[d].TocBuffer[ti.cdti_trk1+1].address; - i=xx_PlayAudio(DriveStruct[d].pos_audio_start,DriveStruct[d].pos_audio_end); + D_S[d].audio_state=audio_playing; + return (0); + + case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */ + msg(DBG_IOC,"ioctl: CDROMPLAYTRKIND entered.\n"); + if (D_S[d].audio_state==audio_playing) + { + msg(DBG_IOX,"CDROMPLAYTRKIND: already audio_playing.\n"); + return (0); + return (-EINVAL); + } + st=verify_area(VERIFY_READ,(void *) arg,sizeof(struct cdrom_ti)); + if (st<0) + { + msg(DBG_IOX,"CDROMPLAYTRKIND: verify_area error.\n"); + return (st); + } + memcpy_fromfs(&ti,(void *) arg,sizeof(struct cdrom_ti)); + msg(DBG_IOX,"ioctl: trk0: %d, ind0: %d, trk1:%d, ind1:%d\n", + ti.cdti_trk0,ti.cdti_ind0,ti.cdti_trk1,ti.cdti_ind1); + if (ti.cdti_trk0D_S[d].n_last_track) return (-EINVAL); + if (ti.cdti_trk1D_S[d].n_last_track) ti.cdti_trk1=D_S[d].n_last_track; + D_S[d].pos_audio_start=D_S[d].TocBuffer[ti.cdti_trk0].address; + D_S[d].pos_audio_end=D_S[d].TocBuffer[ti.cdti_trk1+1].address; + i=cc_PlayAudio(D_S[d].pos_audio_start,D_S[d].pos_audio_end); #if 0 - if (i<0) return (-EIO); + if (i<0) return (-EIO); #endif 0 - DriveStruct[d].audio_state=audio_playing; - return (0); - - case CDROMREADTOCHDR: /* Read the table of contents header */ - DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMREADTOCHDR entered.\n")); - tochdr.cdth_trk0=DriveStruct[d].n_first_track; - tochdr.cdth_trk1=DriveStruct[d].n_last_track; - st=verify_area(VERIFY_WRITE, (void *) arg, sizeof(struct cdrom_tochdr)); - if (st) return (st); - memcpy_tofs((void *) arg, &tochdr, sizeof(struct cdrom_tochdr)); - return (0); - - case CDROMREADTOCENTRY: /* Read an entry in the table of contents */ - DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMREADTOCENTRY entered.\n")); - st=verify_area(VERIFY_READ, (void *) arg, sizeof(struct cdrom_tocentry)); - if (st) return (st); - memcpy_fromfs(&tocentry, (void *) arg, sizeof(struct cdrom_tocentry)); - i=tocentry.cdte_track; - if (i==CDROM_LEADOUT) i=DriveStruct[d].n_last_track+1; - else if (iDriveStruct[d].n_last_track) return (-EINVAL); - tocentry.cdte_adr=DriveStruct[d].TocBuffer[i].ctl_adr&0x0F; - tocentry.cdte_ctrl=(DriveStruct[d].TocBuffer[i].ctl_adr>>4)&0x0F; - tocentry.cdte_datamode=DriveStruct[d].TocBuffer[i].format; - if (tocentry.cdte_format==CDROM_MSF) /* MSF-bin required */ - { tocentry.cdte_addr.msf.minute=(DriveStruct[d].TocBuffer[i].address>>16)&0x00FF; - tocentry.cdte_addr.msf.second=(DriveStruct[d].TocBuffer[i].address>>8)&0x00FF; - tocentry.cdte_addr.msf.frame=DriveStruct[d].TocBuffer[i].address&0x00FF; - } - else if (tocentry.cdte_format==CDROM_LBA) /* blk required */ - tocentry.cdte_addr.lba=msf2blk(DriveStruct[d].TocBuffer[i].address); - else return (-EINVAL); - st=verify_area(VERIFY_WRITE,(void *) arg, sizeof(struct cdrom_tocentry)); - if (st) return (st); - memcpy_tofs((void *) arg, &tocentry, sizeof(struct cdrom_tocentry)); - return (0); - - case CDROMSTOP: /* Spin down the drive */ - DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMSTOP entered.\n")); - i=xx_Pause_Resume(1); - DriveStruct[d].audio_state=0; - return (0); - - case CDROMSTART: /* Spin up the drive */ - DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMSTART entered.\n")); - xx_SpinUp(); - DriveStruct[d].audio_state=0; - return (0); - - case CDROMEJECT: - DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMEJECT entered.\n")); - if (old_drive) return (0); - do i=yy_LockDoor(0); - while (i!=0); - DriveStruct[d].open_count=0; /* to get it locked next time again */ - i=yy_SpinDown(); - DPRINTF((DBG_IOX,"SBPCD: ioctl: yy_SpinDown returned %d.\n", i)); - if (i<0) return (-EIO); - DriveStruct[d].CD_changed=0xFF; - DriveStruct[d].diskstate_flags=0; - DriveStruct[d].audio_state=0; - return (0); - - case CDROMEJECT_SW: - DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMEJECT_SW entered.\n")); - if (!new_drive) return (0); - DriveStruct[d].f_eject=arg; - return (0); - - case CDROMVOLCTRL: /* Volume control */ - DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMVOLCTRL entered.\n")); - st=verify_area(VERIFY_READ,(void *) arg,sizeof(volctrl)); - if (st) return (st); - memcpy_fromfs(&volctrl,(char *) arg,sizeof(volctrl)); - DriveStruct[d].vol_chan0=0; - DriveStruct[d].vol_ctrl0=volctrl.channel0; - DriveStruct[d].vol_chan1=1; - DriveStruct[d].vol_ctrl1=volctrl.channel1; - i=xx_SetVolume(); - return (0); - - case CDROMSUBCHNL: /* Get subchannel info */ - DPRINTF((DBG_IOS,"SBPCD: ioctl: CDROMSUBCHNL entered.\n")); - if ((st_spinning)||(!subq_valid)) { i=xx_ReadSubQ(); - if (i<0) return (-EIO); - } - st=verify_area(VERIFY_WRITE, (void *) arg, sizeof(struct cdrom_subchnl)); - if (st) return (st); - memcpy_fromfs(&SC, (void *) arg, sizeof(struct cdrom_subchnl)); - switch (DriveStruct[d].audio_state) - { - case audio_playing: - SC.cdsc_audiostatus=CDROM_AUDIO_PLAY; - break; - case audio_pausing: - SC.cdsc_audiostatus=CDROM_AUDIO_PAUSED; - break; - default: - SC.cdsc_audiostatus=CDROM_AUDIO_NO_STATUS; - break; - } - SC.cdsc_adr=DriveStruct[d].SubQ_ctl_adr; - SC.cdsc_ctrl=DriveStruct[d].SubQ_ctl_adr>>4; - SC.cdsc_trk=bcd2bin(DriveStruct[d].SubQ_trk); - SC.cdsc_ind=bcd2bin(DriveStruct[d].SubQ_pnt_idx); - if (SC.cdsc_format==CDROM_LBA) - { - SC.cdsc_absaddr.lba=msf2blk(DriveStruct[d].SubQ_run_tot); - SC.cdsc_reladdr.lba=msf2blk(DriveStruct[d].SubQ_run_trk); - } - else /* not only if (SC.cdsc_format==CDROM_MSF) */ - { - SC.cdsc_absaddr.msf.minute=(DriveStruct[d].SubQ_run_tot>>16)&0x00FF; - SC.cdsc_absaddr.msf.second=(DriveStruct[d].SubQ_run_tot>>8)&0x00FF; - SC.cdsc_absaddr.msf.frame=DriveStruct[d].SubQ_run_tot&0x00FF; - SC.cdsc_reladdr.msf.minute=(DriveStruct[d].SubQ_run_trk>>16)&0x00FF; - SC.cdsc_reladdr.msf.second=(DriveStruct[d].SubQ_run_trk>>8)&0x00FF; - SC.cdsc_reladdr.msf.frame=DriveStruct[d].SubQ_run_trk&0x00FF; - } - memcpy_tofs((void *) arg, &SC, sizeof(struct cdrom_subchnl)); - DPRINTF((DBG_IOS,"SBPCD: CDROMSUBCHNL: %1X %02X %08X %08X %02X %02X %06X %06X\n", - SC.cdsc_format,SC.cdsc_audiostatus, - SC.cdsc_adr,SC.cdsc_ctrl, - SC.cdsc_trk,SC.cdsc_ind, - SC.cdsc_absaddr,SC.cdsc_reladdr)); - return (0); - - case CDROMREADMODE1: - DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMREADMODE1 requested.\n")); - xx_ModeSelect(CD_FRAMESIZE); - xx_ModeSense(); - DriveStruct[d].mode=READ_M1; - return (0); - - case CDROMREADMODE2: /* not usable at the moment */ - DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMREADMODE2 requested.\n")); - xx_ModeSelect(CD_FRAMESIZE_XA); - xx_ModeSense(); - DriveStruct[d].mode=READ_M2; - return (0); - - case CDROMREADAUDIO: - { /* start of CDROMREADAUDIO */ - int i=0, j=0, frame, block; - u_int try=0; - u_long timeout; - u_char *p; - u_int data_tries = 0; - u_int data_waits = 0; - u_int data_retrying = 0; - int status_tries; - int error_flag; - - DPRINTF((DBG_IOC,"SBPCD: ioctl: CDROMREADAUDIO requested.\n")); -#if 0 - if (!new_drive) return (-EINVAL); -#endif - if (DriveStruct[d].aud_buf==NULL) return (-EINVAL); - i=verify_area(VERIFY_READ, (void *) arg, sizeof(struct cdrom_read_audio)); - if (i) return (i); - memcpy_fromfs(&read_audio, (void *) arg, sizeof(struct cdrom_read_audio)); - if (read_audio.nframes>SBP_BUFFER_AUDIO_FRAMES) return (-EINVAL); - i=verify_area(VERIFY_WRITE, read_audio.buf, - read_audio.nframes*CD_FRAMESIZE_RAW); - if (i) return (i); - - if (read_audio.addr_format==CDROM_MSF) /* MSF-bin specification of where to start */ - block=msf2lba(&read_audio.addr.msf.minute); - else if (read_audio.addr_format==CDROM_LBA) /* lba specification of where to start */ - block=read_audio.addr.lba; - else return (-EINVAL); - DPRINTF((DBG_AUD,"SBPCD: read_audio: lba: %d, msf: %06X\n", - block, blk2msf(block))); - DPRINTF((DBG_AUD,"SBPCD: read_audio: before xx_ReadStatus.\n")); - while (busy_data) sbp_sleep(10); /* wait a bit */ - busy_audio=1; - error_flag=0; - for (data_tries=5; data_tries>0; data_tries--) - { - DPRINTF((DBG_AUD,"SBPCD: data_tries=%d ...\n", data_tries)); - DriveStruct[d].mode=READ_AU; - xx_ModeSelect(CD_FRAMESIZE_RAW); - xx_ModeSense(); - for (status_tries=3; status_tries > 0; status_tries--) - { - flags_cmd_out |= f_respo3; - xx_ReadStatus(); - if (sbp_status() != 0) break; - sbp_sleep(1); /* wait a bit, try again */ - } - if (status_tries == 0) - { - DPRINTF((DBG_AUD,"SBPCD: read_audio: sbp_status: failed after 3 tries.\n")); - continue; - } - DPRINTF((DBG_AUD,"SBPCD: read_audio: sbp_status: ok.\n")); - - flags_cmd_out = f_putcmd | f_respo2 | f_ResponseStatus | f_obey_p_check; - if (!new_drive) - { - flags_cmd_out |= f_lopsta | f_getsta | f_bit1; - cmd_type=READ_M2; - drvcmd[0]=0x03; /* "read XA frames" command for old drives */ - drvcmd[1]=(block>>16)&0x000000ff; - drvcmd[2]=(block>>8)&0x000000ff; - drvcmd[3]=block&0x000000ff; - drvcmd[4]=0; - drvcmd[5]=read_audio.nframes; /* # of frames */ - drvcmd[6]=0; - } - else /* if new_drive */ - { - drvcmd[0]=0x10; /* "read frames" command for new drives */ - lba2msf(block,&drvcmd[1]); /* msf-bin format required */ - drvcmd[4]=0; - drvcmd[5]=0; - drvcmd[6]=1; /* # of frames */ - } - DPRINTF((DBG_AUD,"SBPCD: read_audio: before giving \"read\" command.\n")); - for (i=0;i<7;i++) OUT(CDo_command,drvcmd[i]); - sbp_sleep(0); - DPRINTF((DBG_AUD,"SBPCD: read_audio: after giving \"read\" command.\n")); - for (frame=1;frame<2 && !error_flag; frame++) - { - try=maxtim_data; - for (timeout=jiffies+900; ; ) - { - for ( ; try!=0;try--) - { - j=inb(CDi_status); - if (!(j&s_not_data_ready)) break; - if (!(j&s_not_result_ready)) break; - if (!new_drive) if (j&s_attention) break; - } - if (try != 0 || timeout <= jiffies) break; - if (data_retrying == 0) data_waits++; - data_retrying = 1; - sbp_sleep(1); - try = 1; - } - if (try==0) - { - DPRINTF((DBG_INF,"SBPCD: read_audio: sbp_data: CDi_status timeout.\n")); - error_flag++; - break; - } - DPRINTF((DBG_AUD,"SBPCD: read_audio: sbp_data: CDi_status ok.\n")); - if (j&s_not_data_ready) - { - printk("SBPCD: read_audio: sbp_data: DATA_READY timeout.\n"); - error_flag++; - break; - } - DPRINTF((DBG_AUD,"SBPCD: read_audio: before reading data.\n")); + D_S[d].audio_state=audio_playing; + return (0); + + case CDROMREADTOCHDR: /* Read the table of contents header */ + msg(DBG_IOC,"ioctl: CDROMREADTOCHDR entered.\n"); + tochdr.cdth_trk0=D_S[d].n_first_track; + tochdr.cdth_trk1=D_S[d].n_last_track; + st=verify_area(VERIFY_WRITE, (void *) arg, sizeof(struct cdrom_tochdr)); + if (st) return (st); + memcpy_tofs((void *) arg, &tochdr, sizeof(struct cdrom_tochdr)); + return (0); + + case CDROMREADTOCENTRY: /* Read an entry in the table of contents */ + msg(DBG_IOC,"ioctl: CDROMREADTOCENTRY entered.\n"); + st=verify_area(VERIFY_READ, (void *) arg, sizeof(struct cdrom_tocentry)); + if (st) return (st); + memcpy_fromfs(&tocentry, (void *) arg, sizeof(struct cdrom_tocentry)); + i=tocentry.cdte_track; + if (i==CDROM_LEADOUT) i=D_S[d].n_last_track+1; + else if (iD_S[d].n_last_track) return (-EINVAL); + tocentry.cdte_adr=D_S[d].TocBuffer[i].ctl_adr&0x0F; + tocentry.cdte_ctrl=(D_S[d].TocBuffer[i].ctl_adr>>4)&0x0F; + tocentry.cdte_datamode=D_S[d].TocBuffer[i].format; + if (tocentry.cdte_format==CDROM_MSF) /* MSF-bin required */ + { + tocentry.cdte_addr.msf.minute=(D_S[d].TocBuffer[i].address>>16)&0x00FF; + tocentry.cdte_addr.msf.second=(D_S[d].TocBuffer[i].address>>8)&0x00FF; + tocentry.cdte_addr.msf.frame=D_S[d].TocBuffer[i].address&0x00FF; + } + else if (tocentry.cdte_format==CDROM_LBA) /* blk required */ + tocentry.cdte_addr.lba=msf2blk(D_S[d].TocBuffer[i].address); + else return (-EINVAL); + st=verify_area(VERIFY_WRITE,(void *) arg, sizeof(struct cdrom_tocentry)); + if (st) return (st); + memcpy_tofs((void *) arg, &tocentry, sizeof(struct cdrom_tocentry)); + return (0); + + case CDROMRESET: /* hard reset the drive */ + msg(DBG_IOC,"ioctl: CDROMRESET entered.\n"); + i=DriveReset(); + D_S[d].audio_state=0; + return (i); + + case CDROMSTOP: /* Spin down the drive */ + msg(DBG_IOC,"ioctl: CDROMSTOP entered.\n"); + i=cc_Pause_Resume(1); + D_S[d].audio_state=0; + return (i); + + case CDROMSTART: /* Spin up the drive */ + msg(DBG_IOC,"ioctl: CDROMSTART entered.\n"); + cc_SpinUp(); + D_S[d].audio_state=0; + return (0); + + case CDROMEJECT: + msg(DBG_IOC,"ioctl: CDROMEJECT entered.\n"); + if (fam0_drive) return (0); + i=UnLockDoor(); + D_S[d].open_count=-9; /* to get it locked next time again */ + i=cc_SpinDown(); + msg(DBG_IOX,"ioctl: cc_SpinDown returned %d.\n", i); + msg(DBG_TEA,"ioctl: cc_SpinDown returned %d.\n", i); + if (i<0) return (-EIO); + D_S[d].CD_changed=0xFF; + D_S[d].diskstate_flags=0; + D_S[d].audio_state=0; + return (0); + + case CDROMEJECT_SW: + msg(DBG_IOC,"ioctl: CDROMEJECT_SW entered.\n"); + if (fam0_drive) return (0); + D_S[d].f_eject=arg; + return (0); + + case CDROMVOLCTRL: /* Volume control */ + msg(DBG_IOC,"ioctl: CDROMVOLCTRL entered.\n"); + st=verify_area(VERIFY_READ,(void *) arg,sizeof(volctrl)); + if (st) return (st); + memcpy_fromfs(&volctrl,(char *) arg,sizeof(volctrl)); + D_S[d].vol_chan0=0; + D_S[d].vol_ctrl0=volctrl.channel0; + D_S[d].vol_chan1=1; + D_S[d].vol_ctrl1=volctrl.channel1; + i=cc_SetVolume(); + return (0); + + case CDROMVOLREAD: /* read Volume settings from drive */ + msg(DBG_IOC,"ioctl: CDROMVOLREAD entered.\n"); + st=verify_area(VERIFY_WRITE,(void *)arg,sizeof(volctrl)); + if (st) return (st); + st=cc_GetVolume(); + if (st<0) return (st); + volctrl.channel0=D_S[d].vol_ctrl0; + volctrl.channel1=D_S[d].vol_ctrl1; + volctrl.channel2=0; + volctrl.channel2=0; + memcpy_tofs((void *)arg,&volctrl,sizeof(volctrl)); + return (0); + + case CDROMSUBCHNL: /* Get subchannel info */ + msg(DBG_IOS,"ioctl: CDROMSUBCHNL entered.\n"); + if ((st_spinning)||(!subq_valid)) { i=cc_ReadSubQ(); + if (i<0) return (-EIO); + } + st=verify_area(VERIFY_WRITE, (void *) arg, sizeof(struct cdrom_subchnl)); + if (st) return (st); + memcpy_fromfs(&SC, (void *) arg, sizeof(struct cdrom_subchnl)); + switch (D_S[d].audio_state) + { + case audio_playing: + SC.cdsc_audiostatus=CDROM_AUDIO_PLAY; + break; + case audio_pausing: + SC.cdsc_audiostatus=CDROM_AUDIO_PAUSED; + break; + default: + SC.cdsc_audiostatus=CDROM_AUDIO_NO_STATUS; + break; + } + SC.cdsc_adr=D_S[d].SubQ_ctl_adr; + SC.cdsc_ctrl=D_S[d].SubQ_ctl_adr>>4; + SC.cdsc_trk=bcd2bin(D_S[d].SubQ_trk); + SC.cdsc_ind=bcd2bin(D_S[d].SubQ_pnt_idx); + if (SC.cdsc_format==CDROM_LBA) + { + SC.cdsc_absaddr.lba=msf2blk(D_S[d].SubQ_run_tot); + SC.cdsc_reladdr.lba=msf2blk(D_S[d].SubQ_run_trk); + } + else /* not only if (SC.cdsc_format==CDROM_MSF) */ + { + SC.cdsc_absaddr.msf.minute=(D_S[d].SubQ_run_tot>>16)&0x00FF; + SC.cdsc_absaddr.msf.second=(D_S[d].SubQ_run_tot>>8)&0x00FF; + SC.cdsc_absaddr.msf.frame=D_S[d].SubQ_run_tot&0x00FF; + SC.cdsc_reladdr.msf.minute=(D_S[d].SubQ_run_trk>>16)&0x00FF; + SC.cdsc_reladdr.msf.second=(D_S[d].SubQ_run_trk>>8)&0x00FF; + SC.cdsc_reladdr.msf.frame=D_S[d].SubQ_run_trk&0x00FF; + } + memcpy_tofs((void *) arg, &SC, sizeof(struct cdrom_subchnl)); + msg(DBG_IOS,"CDROMSUBCHNL: %1X %02X %08X %08X %02X %02X %06X %06X\n", + SC.cdsc_format,SC.cdsc_audiostatus, + SC.cdsc_adr,SC.cdsc_ctrl, + SC.cdsc_trk,SC.cdsc_ind, + SC.cdsc_absaddr,SC.cdsc_reladdr); + return (0); + + case CDROMREADMODE1: + msg(DBG_IOC,"ioctl: CDROMREADMODE1 requested.\n"); + cc_ModeSelect(CD_FRAMESIZE); + cc_ModeSense(); + D_S[d].mode=READ_M1; + return (0); + + case CDROMREADMODE2: /* not usable at the moment */ + msg(DBG_IOC,"ioctl: CDROMREADMODE2 requested.\n"); + cc_ModeSelect(CD_FRAMESIZE_XA); + cc_ModeSense(); + D_S[d].mode=READ_M2; + return (0); + + case CDROMAUDIOBUFSIZ: /* configure the audio buffer size */ + msg(DBG_IOC,"ioctl: CDROMAUDIOBUFSIZ entered.\n"); +#ifdef MODULE + if (D_S[d].sbp_audsiz>0) + vfree(D_S[d].aud_buf); +#endif MODULE + D_S[d].aud_buf=NULL; + D_S[d].sbp_audsiz=arg; + if (D_S[d].sbp_audsiz>0) + { + D_S[d].aud_buf=(u_char *) vmalloc(D_S[d].sbp_audsiz*CD_FRAMESIZE_RAW); + if (D_S[d].aud_buf==NULL) + { + msg(DBG_INF,"audio buffer (%d frames) not available.\n",D_S[d].sbp_audsiz); + D_S[d].sbp_audsiz=0; + } + else msg(DBG_INF,"audio buffer size: %d frames.\n",D_S[d].sbp_audsiz); + } + return (D_S[d].sbp_audsiz); + + case CDROMREADAUDIO: + { /* start of CDROMREADAUDIO */ + int i=0, j=0, frame, block; + u_int try=0; + u_long timeout; + u_char *p; + u_int data_tries = 0; + u_int data_waits = 0; + u_int data_retrying = 0; + int status_tries; + int error_flag; + + msg(DBG_IOC,"ioctl: CDROMREADAUDIO entered.\n"); + if (fam0_drive) return (-EINVAL); + if (famL_drive) return (-EINVAL); + if (fam2_drive) return (-EINVAL); + if (famT_drive) return (-EINVAL); + if (D_S[d].aud_buf==NULL) return (-EINVAL); + i=verify_area(VERIFY_READ, (void *) arg, sizeof(struct cdrom_read_audio)); + if (i) return (i); + memcpy_fromfs(&read_audio, (void *) arg, sizeof(struct cdrom_read_audio)); + if (read_audio.nframes>D_S[d].sbp_audsiz) return (-EINVAL); + i=verify_area(VERIFY_WRITE, read_audio.buf, + read_audio.nframes*CD_FRAMESIZE_RAW); + if (i) return (i); + + if (read_audio.addr_format==CDROM_MSF) /* MSF-bin specification of where to start */ + block=msf2lba(&read_audio.addr.msf.minute); + else if (read_audio.addr_format==CDROM_LBA) /* lba specification of where to start */ + block=read_audio.addr.lba; + else return (-EINVAL); + i=cc_SetSpeed(speed_150,0,0); + if (i) msg(DBG_AUD,"read_audio: SetSpeed error %d\n", i); + msg(DBG_AUD,"read_audio: lba: %d, msf: %06X\n", + block, blk2msf(block)); + msg(DBG_AUD,"read_audio: before cc_ReadStatus.\n"); + while (busy_data) sbp_sleep(10); /* wait a bit */ + busy_audio=1; error_flag=0; - p = DriveStruct[d].aud_buf; - if (sbpro_type==1) OUT(CDo_sel_d_i,0x01); -#if 0 - cli(); -#endif - READ_DATA(CDi_data, p, read_audio.nframes*CD_FRAMESIZE_RAW); -#if 0 - sti(); -#endif - if (sbpro_type==1) OUT(CDo_sel_d_i,0x00); - data_retrying = 0; - } - DPRINTF((DBG_AUD,"SBPCD: read_audio: after reading data.\n")); - if (error_flag) /* must have been spurious D_RDY or (ATTN&&!D_RDY) */ - { - DPRINTF((DBG_AUD,"SBPCD: read_audio: read aborted by drive\n")); + for (data_tries=5; data_tries>0; data_tries--) + { + msg(DBG_AUD,"data_tries=%d ...\n", data_tries); + D_S[d].mode=READ_AU; + cc_ModeSelect(CD_FRAMESIZE_RAW); + cc_ModeSense(); + for (status_tries=3; status_tries > 0; status_tries--) + { + flags_cmd_out |= f_respo3; + cc_ReadStatus(); + if (sbp_status() != 0) break; + sbp_sleep(1); /* wait a bit, try again */ + } + if (status_tries == 0) + { + msg(DBG_AUD,"read_audio: sbp_status: failed after 3 tries.\n"); + continue; + } + msg(DBG_AUD,"read_audio: sbp_status: ok.\n"); + + flags_cmd_out = f_putcmd | f_respo2 | f_ResponseStatus | f_obey_p_check; + if (fam0L_drive) + { + flags_cmd_out |= f_lopsta | f_getsta | f_bit1; + cmd_type=READ_M2; + drvcmd[0]=CMD0_READ_XA; /* "read XA frames", old drives */ + drvcmd[1]=(block>>16)&0x000000ff; + drvcmd[2]=(block>>8)&0x000000ff; + drvcmd[3]=block&0x000000ff; + drvcmd[4]=0; + drvcmd[5]=read_audio.nframes; /* # of frames */ + drvcmd[6]=0; + } + else if (fam1_drive) + { + drvcmd[0]=CMD1_READ; /* "read frames", new drives */ + lba2msf(block,&drvcmd[1]); /* msf-bin format required */ + drvcmd[4]=0; + drvcmd[5]=0; + drvcmd[6]=read_audio.nframes; /* # of frames */ + } + else if (fam2_drive) /* CD200: not tested yet */ + { + } + else if (famT_drive) /* CD-55A: not tested yet */ + { + } + msg(DBG_AUD,"read_audio: before giving \"read\" command.\n"); + for (i=0;i<7;i++) OUT(CDo_command,drvcmd[i]); + sbp_sleep(0); + msg(DBG_AUD,"read_audio: after giving \"read\" command.\n"); + for (frame=1;frame<2 && !error_flag; frame++) + { + try=maxtim_data; + for (timeout=jiffies+900; ; ) + { + for ( ; try!=0;try--) + { + j=inb(CDi_status); + if (!(j&s_not_data_ready)) break; + if (!(j&s_not_result_ready)) break; + if (fam0L_drive) if (j&s_attention) break; + } + if (try != 0 || timeout <= jiffies) break; + if (data_retrying == 0) data_waits++; + data_retrying = 1; + sbp_sleep(1); + try = 1; + } + if (try==0) + { + msg(DBG_INF,"read_audio: sbp_data: CDi_status timeout.\n"); + error_flag++; + break; + } + msg(DBG_AUD,"read_audio: sbp_data: CDi_status ok.\n"); + if (j&s_not_data_ready) + { + msg(DBG_INF, "read_audio: sbp_data: DATA_READY timeout.\n"); + error_flag++; + break; + } + msg(DBG_AUD,"read_audio: before reading data.\n"); + error_flag=0; + p = D_S[d].aud_buf; + if (sbpro_type==1) OUT(CDo_sel_i_d,1); + insb(CDi_data, p, read_audio.nframes*CD_FRAMESIZE_RAW); + if (sbpro_type==1) OUT(CDo_sel_i_d,0); + data_retrying = 0; + } + msg(DBG_AUD,"read_audio: after reading data.\n"); + if (error_flag) /* must have been spurious D_RDY or (ATTN&&!D_RDY) */ + { + msg(DBG_AUD,"read_audio: read aborted by drive\n"); #if 0000 - i=DriveReset(); /* ugly fix to prevent a hang */ + i=cc_DriveReset(); /* ugly fix to prevent a hang */ +#else + i=cc_ReadError(); #endif 0000 - continue; - } - if (!new_drive) - { - i=maxtim_data; - for (timeout=jiffies+900; timeout > jiffies; timeout--) - { - for ( ;i!=0;i--) - { - j=inb(CDi_status); - if (!(j&s_not_data_ready)) break; - if (!(j&s_not_result_ready)) break; - if (j&s_attention) break; - } - if (i != 0 || timeout <= jiffies) break; - sbp_sleep(0); - i = 1; - } - if (i==0) { DPRINTF((DBG_AUD,"SBPCD: read_audio: STATUS TIMEOUT AFTER READ")); } - if (!(j&s_attention)) - { - DPRINTF((DBG_AUD,"SBPCD: read_audio: sbp_data: timeout waiting DRV_ATTN - retrying\n")); - i=DriveReset(); /* ugly fix to prevent a hang */ - continue; - } - } - do - { - if (!new_drive) xx_ReadStatus(); - i=ResponseStatus(); /* builds status_byte, returns orig. status (old) or faked p_success_old (new) */ - if (i<0) { DPRINTF((DBG_AUD, - "SBPCD: read_audio: xx_ReadStatus error after read: %02X\n", - DriveStruct[d].status_byte)); - continue; /* FIXME */ - } - } - while ((!new_drive)&&(!st_check)&&(!(i&p_success_old))); - if (st_check) - { - i=xx_ReadError(); - DPRINTF((DBG_AUD,"SBPCD: read_audio: xx_ReadError was necessary after read: %02X\n",i)); - continue; - } - memcpy_tofs((u_char *) read_audio.buf, - (u_char *) DriveStruct[d].aud_buf, - read_audio.nframes*CD_FRAMESIZE_RAW); - DPRINTF((DBG_AUD,"SBPCD: read_audio: memcpy_tofs done.\n")); - break; - } - xx_ModeSelect(CD_FRAMESIZE); - xx_ModeSense(); - DriveStruct[d].mode=READ_M1; - busy_audio=0; - if (data_tries == 0) - { - DPRINTF((DBG_AUD,"SBPCD: read_audio: failed after 5 tries.\n")); - return (-8); - } - DPRINTF((DBG_AUD,"SBPCD: read_audio: successful return.\n")); - return (0); - } /* end of CDROMREADAUDIO */ - - case BLKRASET: - if(!suser()) return -EACCES; - if(!inode->i_rdev) return -EINVAL; - if(arg > 0xff) return -EINVAL; - read_ahead[MAJOR(inode->i_rdev)] = arg; - return (0); - - default: - DPRINTF((DBG_IOC,"SBPCD: ioctl: unknown function request %04X\n", cmd)); - return (-EINVAL); - } /* end switch(cmd) */ + continue; + } + if (fam0L_drive) + { + i=maxtim_data; + for (timeout=jiffies+900; timeout > jiffies; timeout--) + { + for ( ;i!=0;i--) + { + j=inb(CDi_status); + if (!(j&s_not_data_ready)) break; + if (!(j&s_not_result_ready)) break; + if (j&s_attention) break; + } + if (i != 0 || timeout <= jiffies) break; + sbp_sleep(0); + i = 1; + } + if (i==0) msg(DBG_AUD,"read_audio: STATUS TIMEOUT AFTER READ"); + if (!(j&s_attention)) + { + msg(DBG_AUD,"read_audio: sbp_data: timeout waiting DRV_ATTN - retrying\n"); + i=cc_DriveReset(); /* ugly fix to prevent a hang */ + continue; + } + } + do + { + if (fam0L_drive) cc_ReadStatus(); + i=ResponseStatus(); /* builds status_bits, returns orig. status (old) or faked p_success (new) */ + if (i<0) { msg(DBG_AUD, + "read_audio: cc_ReadStatus error after read: %02X\n", + D_S[d].status_bits); + continue; /* FIXME */ + } + } + while ((fam0L_drive)&&(!st_check)&&(!(i&p_success))); + if (st_check) + { + i=cc_ReadError(); + msg(DBG_AUD,"read_audio: cc_ReadError was necessary after read: %02X\n",i); + continue; + } + memcpy_tofs((u_char *) read_audio.buf, + (u_char *) D_S[d].aud_buf, + read_audio.nframes*CD_FRAMESIZE_RAW); + msg(DBG_AUD,"read_audio: memcpy_tofs done.\n"); + break; + } + cc_ModeSelect(CD_FRAMESIZE); + cc_ModeSense(); + D_S[d].mode=READ_M1; + busy_audio=0; + if (data_tries == 0) + { + msg(DBG_AUD,"read_audio: failed after 5 tries.\n"); + return (-8); + } + msg(DBG_AUD,"read_audio: successful return.\n"); + return (0); + } /* end of CDROMREADAUDIO */ + + case CDROMMULTISESSION: /* tell start-of-last-session */ + msg(DBG_IOC,"ioctl: CDROMMULTISESSION entered.\n"); + st=verify_area(VERIFY_READ, (void *) arg, sizeof(struct cdrom_multisession)); + if (st) return (st); + memcpy_fromfs(&ms_info, (void *) arg, sizeof(struct cdrom_multisession)); + if (ms_info.addr_format==CDROM_MSF) /* MSF-bin requested */ + lba2msf(D_S[d].lba_multi,&ms_info.addr.msf.minute); + else if (ms_info.addr_format==CDROM_LBA) /* lba requested */ + ms_info.addr.lba=D_S[d].lba_multi; + else return (-EINVAL); + if (D_S[d].f_multisession) ms_info.xa_flag=1; /* valid redirection address */ + else ms_info.xa_flag=0; /* invalid redirection address */ + st=verify_area(VERIFY_WRITE,(void *) arg, sizeof(struct cdrom_multisession)); + if (st) return (st); + memcpy_tofs((void *) arg, &ms_info, sizeof(struct cdrom_multisession)); + msg(DBG_MUL,"ioctl: CDROMMULTISESSION done (%d, %08X).\n", + ms_info.xa_flag, ms_info.addr.lba); + return (0); + + case BLKRASET: + if(!suser()) return -EACCES; + if(!inode->i_rdev) return -EINVAL; + if(arg > 0xff) return -EINVAL; + read_ahead[MAJOR(inode->i_rdev)] = arg; + return (0); + + default: + msg(DBG_IOC,"ioctl: unknown function request %04X\n", cmd); + return (-EINVAL); + } /* end switch(cmd) */ } /*==========================================================================*/ /* @@ -3005,18 +4338,18 @@ static int sbpcd_ioctl(struct inode *inode, struct file *file, u_int cmd, */ static void sbp_transfer(void) { - long offs; - - while ( (CURRENT->nr_sectors > 0) && - (CURRENT->sector/4 >= DriveStruct[d].sbp_first_frame) && - (CURRENT->sector/4 <= DriveStruct[d].sbp_last_frame) ) - { - offs = (CURRENT->sector - DriveStruct[d].sbp_first_frame * 4) * 512; - memcpy(CURRENT->buffer, DriveStruct[d].sbp_buf + offs, 512); - CURRENT->nr_sectors--; - CURRENT->sector++; - CURRENT->buffer += 512; - } + long offs; + + while ( (CURRENT->nr_sectors > 0) && + (CURRENT->sector/4 >= D_S[d].sbp_first_frame) && + (CURRENT->sector/4 <= D_S[d].sbp_last_frame) ) + { + offs = (CURRENT->sector - D_S[d].sbp_first_frame * 4) * 512; + memcpy(CURRENT->buffer, D_S[d].sbp_buf + offs, 512); + CURRENT->nr_sectors--; + CURRENT->sector++; + CURRENT->buffer += 512; + } } /*==========================================================================*/ /* @@ -3024,345 +4357,459 @@ static void sbp_transfer(void) */ static void DO_SBPCD_REQUEST(void) { - u_int block; - int dev; - u_int nsect; - int i, status_tries, data_tries; - -request_loop: - - sti(); - - if ((CURRENT==NULL)||(CURRENT->dev<0)) goto done; - if (CURRENT -> sector == -1) goto done; - - dev = MINOR(CURRENT->dev); - if ( (dev<0) || (dev>=NR_SBPCD) ) - { - printk("SBPCD: do_request: bad device: %d\n", dev); - goto done; - } - switch_drive(dev); - - INIT_REQUEST; - block = CURRENT->sector; /* always numbered as 512-byte-pieces */ - nsect = CURRENT->nr_sectors; /* always counted as 512-byte-pieces */ - if (CURRENT->cmd != READ) - { - printk("SBPCD: bad cmd %d\n", CURRENT->cmd); - end_request(0); - goto request_loop; - } - - DPRINTF((DBG_BSZ,"SBPCD: read sector %d (%d sectors)\n", block, nsect)); -#if 0 - DPRINTF((DBG_MUL,"SBPCD: read LBA %d\n", block/4)); -#endif - - sbp_transfer(); - /* if we satisfied the request from the buffer, we're done. */ - if (CURRENT->nr_sectors == 0) - { - end_request(1); - goto request_loop; - } - - i=prepare(0,0); /* at moment not really a hassle check, but ... */ - if (i!=0) - DPRINTF((DBG_INF,"SBPCD: \"prepare\" tells error %d -- ignored\n", i)); - - while (busy_audio) sbp_sleep(100); /* wait a bit */ - busy_data=1; - - if (!st_spinning) xx_SpinUp(); - -#ifdef XA_TEST1 - if ((!new_drive) && (DriveStruct[d].xa_byte==0x20)) /* XA disk with old drive */ - { - xx_ModeSelect(CD_FRAMESIZE_XA); - xx_ModeSense(); - } -#endif XA_TEST1 - - for (data_tries=3; data_tries > 0; data_tries--) - { - for (status_tries=3; status_tries > 0; status_tries--) + u_int block; + u_int nsect; + int i, status_tries, data_tries; + + request_loop: + INIT_REQUEST; + sti(); + + if ((CURRENT==NULL)||(CURRENT->dev<0)) goto err_done; + if (CURRENT -> sector == -1) goto err_done; + if (CURRENT->cmd != READ) { - flags_cmd_out |= f_respo3; - xx_ReadStatus(); - if (sbp_status() != 0) break; - sbp_sleep(1); /* wait a bit, try again */ + msg(DBG_INF, "bad cmd %d\n", CURRENT->cmd); + goto err_done; } - if (status_tries == 0) + i = MINOR(CURRENT->dev); + if ( (i<0) || (i>=NR_SBPCD) || (D_S[i].drv_id==-1)) { - DPRINTF((DBG_INF,"SBPCD: sbp_status: failed after 3 tries\n")); - break; + msg(DBG_INF, "do_request: bad device: %04X\n", CURRENT->dev); + goto err_done; + } + while (busy_audio) sbp_sleep(100); /* wait a bit */ + busy_data=1; + + if (D_S[i].audio_state==audio_playing) goto err_done; + if (d!=i) switch_drive(i); + + block = CURRENT->sector; /* always numbered as 512-byte-pieces */ + nsect = CURRENT->nr_sectors; /* always counted as 512-byte-pieces */ + + msg(DBG_BSZ,"read sector %d (%d sectors)\n", block, nsect); +#if 0 + msg(DBG_MUL,"read LBA %d\n", block/4); +#endif + + sbp_transfer(); + /* if we satisfied the request from the buffer, we're done. */ + if (CURRENT->nr_sectors == 0) + { + end_request(1); + goto request_loop; } - sbp_read_cmd(); - sbp_sleep(0); - if (sbp_data() != 0) +#if FUTURE + i=prepare(0,0); /* at moment not really a hassle check, but ... */ + if (i!=0) + msg(DBG_INF,"\"prepare\" tells error %d -- ignored\n", i); +#endif FUTURE + + if (!st_spinning) cc_SpinUp(); + + for (data_tries=n_retries; data_tries > 0; data_tries--) { - end_request(1); - goto request_loop; + for (status_tries=3; status_tries > 0; status_tries--) + { + flags_cmd_out |= f_respo3; + cc_ReadStatus(); + if (sbp_status() != 0) break; + if (st_check) cc_ReadError(); + sbp_sleep(1); /* wait a bit, try again */ + } + if (status_tries == 0) + { + msg(DBG_INF,"sbp_status: failed after 3 tries\n"); + break; + } + + sbp_read_cmd(); + sbp_sleep(0); + if (sbp_data() != 0) + { + end_request(1); + goto request_loop; + } } - } - - end_request(0); - sbp_sleep(10); /* wait a bit, try again */ - goto request_loop; - -done: - busy_data=0; - return; + + err_done: + busy_data=0; + end_request(0); + sbp_sleep(0); /* wait a bit, try again */ + goto request_loop; } /*==========================================================================*/ /* * build and send the READ command. - * Maybe it would be better to "set mode1" before ... */ static void sbp_read_cmd(void) { - int i; - int block; - - DriveStruct[d].sbp_first_frame=DriveStruct[d].sbp_last_frame=-1; /* purge buffer */ - block=CURRENT->sector/4; - - if (new_drive) - { -#if MANY_SESSION - DPRINTF((DBG_MUL,"SBPCD: read MSF %08X\n", blk2msf(block))); - if (DriveStruct[d].f_multisession) +#undef OLD + + int i; + int block; + + D_S[d].sbp_first_frame=D_S[d].sbp_last_frame=-1; /* purge buffer */ + D_S[d].sbp_current = 0; + block=CURRENT->sector/4; + if (block+D_S[d].sbp_bufsiz <= D_S[d].CDsize_frm) + D_S[d].sbp_read_frames = D_S[d].sbp_bufsiz; + else { - DPRINTF((DBG_MUL,"SBPCD: ManySession: use %08X for %08X (msf)\n", - blk2msf(DriveStruct[d].lba_multi+block), - blk2msf(block))); - block=DriveStruct[d].lba_multi+block; + D_S[d].sbp_read_frames=D_S[d].CDsize_frm-block; + /* avoid reading past end of data */ + if (D_S[d].sbp_read_frames < 1) + { + msg(DBG_INF,"requested frame %d, CD size %d ???\n", + block, D_S[d].CDsize_frm); + D_S[d].sbp_read_frames=1; + } } -#else - if ((block<=DriveStruct[d].last_redirect) - && (DriveStruct[d].f_multisession)) - { - DPRINTF((DBG_MUL,"SBPCD: MultiSession: use %08X for %08X (msf)\n", - blk2msf(DriveStruct[d].lba_multi+block), - blk2msf(block))); - block=DriveStruct[d].lba_multi+block; - } -#endif MANY_SESSION - } - - if (block+SBP_BUFFER_FRAMES <= DriveStruct[d].CDsize_frm) - DriveStruct[d].sbp_read_frames = SBP_BUFFER_FRAMES; - else - { - DriveStruct[d].sbp_read_frames=DriveStruct[d].CDsize_frm-block; - /* avoid reading past end of data */ - if (DriveStruct[d].sbp_read_frames < 1) - { - DPRINTF((DBG_INF,"SBPCD: requested frame %d, CD size %d ???\n", - block, DriveStruct[d].CDsize_frm)); - DriveStruct[d].sbp_read_frames=1; + + flags_cmd_out = f_putcmd | f_respo2 | f_ResponseStatus | f_obey_p_check; + clr_cmdbuf(); + if (fam0L_drive) + { + flags_cmd_out |= f_lopsta | f_getsta | f_bit1; + if (D_S[d].xa_byte==0x20) + { + cmd_type=READ_M2; + drvcmd[0]=CMD0_READ_XA; /* "read XA frames", old drives */ + drvcmd[1]=(block>>16)&0x000000ff; + drvcmd[2]=(block>>8)&0x000000ff; + drvcmd[3]=block&0x000000ff; + drvcmd[5]=D_S[d].sbp_read_frames; + } + else + { + drvcmd[0]=CMD0_READ; /* "read frames", old drives */ + if (D_S[d].drv_type>=drv_201) + { + lba2msf(block,&drvcmd[1]); /* msf-bcd format required */ + bin2bcdx(&drvcmd[1]); + bin2bcdx(&drvcmd[2]); + bin2bcdx(&drvcmd[3]); + } + else + { + drvcmd[1]=(block>>16)&0x000000ff; + drvcmd[2]=(block>>8)&0x000000ff; + drvcmd[3]=block&0x000000ff; + } + drvcmd[5]=D_S[d].sbp_read_frames; + drvcmd[6]=(D_S[d].drv_type>16)&0x000000ff; - drvcmd[2]=(block>>8)&0x000000ff; - drvcmd[3]=block&0x000000ff; - drvcmd[4]=0; - drvcmd[5]=DriveStruct[d].sbp_read_frames; - drvcmd[6]=0; - } - else - { - drvcmd[0]=0x02; /* "read frames" command for old drives */ - - if (DriveStruct[d].drv_type>=drv_201) - { - lba2msf(block,&drvcmd[1]); /* msf-bcd format required */ - bin2bcdx(&drvcmd[1]); - bin2bcdx(&drvcmd[2]); - bin2bcdx(&drvcmd[3]); - } - else - { - drvcmd[1]=(block>>16)&0x000000ff; - drvcmd[2]=(block>>8)&0x000000ff; - drvcmd[3]=block&0x000000ff; - } - drvcmd[4]=0; - drvcmd[5]=DriveStruct[d].sbp_read_frames; - drvcmd[6]=(DriveStruct[d].drv_type>24)&0x0ff; + drvcmd[3]=(block>>16)&0x0ff; + drvcmd[4]=(block>>8)&0x0ff; + drvcmd[5]=block&0x0ff; + drvcmd[7]=(D_S[d].sbp_read_frames>>8)&0x0ff; + drvcmd[8]=D_S[d].sbp_read_frames&0x0ff; + } +#ifdef OLD + SBPCD_CLI; + for (i=0;i<7;i++) OUT(CDo_command,drvcmd[i]); + if (famT_drive) for (i=7;i<10;i++) OUT(CDo_command,drvcmd[i]); + SBPCD_STI; +#else + flags_cmd_out=f_putcmd; + response_count=0; + i=cmd_out(); /* immediate return here - read data "ourselves" */ + if (i<0) msg(DBG_INF,"error giving READ command: %0d\n", i); +#endif OLD + return; } /*==========================================================================*/ /* * Check the completion of the read-data command. On success, read - * the SBP_BUFFER_FRAMES * 2048 bytes of data from the disk into buffer. + * the D_S[d].sbp_bufsiz * 2048 bytes of data from the disk into buffer. */ static int sbp_data(void) { - int i=0, j=0, frame; - u_int try=0; - u_long timeout; - u_char *p; - u_int data_tries = 0; - u_int data_waits = 0; - u_int data_retrying = 0; - int error_flag; - int xa_count; - error_flag=0; - - for (frame=DriveStruct[d].sbp_current;frame= 1000) + { + msg(DBG_INF,"sbp_data() statistics: %d waits in %d frames.\n", data_waits, data_tries); + data_waits = data_tries = 0; + } + } + duration=jiffies-duration; + msg(DBG_TE2,"time to read %d frames: %d jiffies .\n",frame,duration); + if (famT_drive) { - if ((DriveStruct[d].ored_ctl_adr&0x40)==0) - printk("SBPCD: CD contains no data tracks.\n"); - else printk("SBPCD: sbp_data: DATA_READY timeout.\n"); - error_flag++; - break; + wait=8; + do + { + sbp_sleep(1); + OUT(CDo_sel_i_d,0); + i=inb(CDi_status); + if (!(i&s_not_data_ready)) + { + OUT(CDo_sel_i_d,1); + j=0; + do + { + i=inb(CDi_data); + j++; + i=inb(CDi_status); + } + while (!(i&s_not_data_ready)); + msg(DBG_TEA, "=============too much data (%d bytes)=================.\n", j); + } + if (!(i&s_not_result_ready)) + { + OUT(CDo_sel_i_d,0); + l=0; + do + { + infobuf[l++]=inb(CDi_info); + i=inb(CDi_status); + } + while (!(i&s_not_result_ready)); + if (infobuf[0]==0x00) success=1; +#if 1 + for (j=0;j1) msg(DBG_TEA,"cmd_out_T READ_ERR recursion (sbp_data): %d !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n",recursion); + else msg(DBG_TEA,"sbp_data: CMDT_READ_ERR necessary.\n"); + clr_cmdbuf(); + drvcmd[0]=CMDT_READ_ERR; + j=cmd_out_T(); /* !!! recursive here !!! */ + --recursion; + sbp_sleep(1); + } + while (j<0); + D_S[d].error_state=infobuf[2]; + D_S[d].b3=infobuf[3]; + D_S[d].b4=infobuf[4]; + } + break; + } + else + { +#if 0 + msg(DBG_TEA, "============= waiting for result=================.\n"); + sbp_sleep(1); +#endif + } + } + while (wait--); } - SBPCD_STI; - error_flag=0; - p = DriveStruct[d].sbp_buf + frame * CD_FRAMESIZE; - - if (sbpro_type==1) OUT(CDo_sel_d_i,0x01); - if (cmd_type==READ_M2) READ_DATA(CDi_data, xa_head_buf, CD_XA_HEAD); - READ_DATA(CDi_data, p, CD_FRAMESIZE); - if (cmd_type==READ_M2) READ_DATA(CDi_data, xa_tail_buf, CD_XA_TAIL); - if (sbpro_type==1) OUT(CDo_sel_d_i,0x00); - DriveStruct[d].sbp_current++; - if (cmd_type==READ_M2) - { - DPRINTF((DBG_XA,"SBPCD: xa_head:")); - for (xa_count=0;xa_count= 1000) - { - DPRINTF((DBG_INF,"SBPCD: info: %d waits in %d frames.\n", - data_waits, data_tries)); - data_waits = data_tries = 0; + if (error_flag) /* must have been spurious D_RDY or (ATTN&&!D_RDY) */ + { + msg(DBG_TEA, "================error flag: %d=================.\n", error_flag); + msg(DBG_INF,"sbp_data: read aborted by drive.\n"); +#if 1 + i=cc_DriveReset(); /* ugly fix to prevent a hang */ +#else + i=cc_ReadError(); +#endif + return (0); } - } - SBPCD_STI; - - if (error_flag) /* must have been spurious D_RDY or (ATTN&&!D_RDY) */ - { - DPRINTF((DBG_INF,"SBPCD: read aborted by drive\n")); - i=DriveReset(); /* ugly fix to prevent a hang */ - return (0); - } - - if (!new_drive) - { - SBPCD_CLI; - i=maxtim_data; - for (timeout=jiffies+100; timeout > jiffies; timeout--) - { - for ( ;i!=0;i--) - { - j=inb(CDi_status); - if (!(j&s_not_data_ready)) break; - if (!(j&s_not_result_ready)) break; - if (j&s_attention) break; - } - if (i != 0 || timeout <= jiffies) break; - sbp_sleep(0); - i = 1; - } - if (i==0) { DPRINTF((DBG_INF,"SBPCD: STATUS TIMEOUT AFTER READ")); } - if (!(j&s_attention)) - { - DPRINTF((DBG_INF,"SBPCD: sbp_data: timeout waiting DRV_ATTN - retrying\n")); - i=DriveReset(); /* ugly fix to prevent a hang */ - SBPCD_STI; - return (0); - } - SBPCD_STI; - } - - do - { - if (!new_drive) xx_ReadStatus(); - i=ResponseStatus(); /* builds status_byte, returns orig. status (old) or faked p_success_old (new) */ - if (i<0) { DPRINTF((DBG_INF,"SBPCD: xx_ReadStatus error after read: %02X\n", - DriveStruct[d].status_byte)); - return (0); - } - } - while ((!new_drive)&&(!st_check)&&(!(i&p_success_old))); - if (st_check) - { - i=xx_ReadError(); - DPRINTF((DBG_INF,"SBPCD: xx_ReadError was necessary after read: %02X\n",i)); - return (0); - } - - DriveStruct[d].sbp_first_frame = CURRENT -> sector / 4; - DriveStruct[d].sbp_last_frame = DriveStruct[d].sbp_first_frame + DriveStruct[d].sbp_read_frames - 1; - sbp_transfer(); - return (1); + + if (fam0L_drive) + { + SBPCD_CLI; + i=maxtim_data; + for (timeout=jiffies+100; timeout > jiffies; timeout--) + { + for ( ;i!=0;i--) + { + j=inb(CDi_status); + if (!(j&s_not_data_ready)) break; + if (!(j&s_not_result_ready)) break; + if (j&s_attention) break; + } + if (i != 0 || timeout <= jiffies) break; + sbp_sleep(0); + i = 1; + } + if (i==0) msg(DBG_INF,"status timeout after READ.\n"); + if (!(j&s_attention)) + { + msg(DBG_INF,"sbp_data: timeout waiting DRV_ATTN - retrying.\n"); + i=cc_DriveReset(); /* ugly fix to prevent a hang */ + SBPCD_STI; + return (0); + } + SBPCD_STI; + } + +#if 0 + if (!success) +#endif 0 + do + { + if (fam0L_drive) cc_ReadStatus(); +#if 1 + if (famT_drive) msg(DBG_TE2, "================before ResponseStatus=================.\n", i); +#endif 1 + i=ResponseStatus(); /* builds status_bits, returns orig. status (old) or faked p_success (new) */ +#if 1 + if (famT_drive) msg(DBG_TE2, "================ResponseStatus: %d=================.\n", i); +#endif 1 + if (i<0) + { + msg(DBG_INF,"bad cc_ReadStatus after read: %02X\n", D_S[d].status_bits); + return (0); + } + } + while ((fam0L_drive)&&(!st_check)&&(!(i&p_success))); + if (st_check) + { + i=cc_ReadError(); + msg(DBG_INF,"cc_ReadError was necessary after read: %d\n",i); + return (0); + } + if (fatal_err) + { + fatal_err=0; + D_S[d].sbp_first_frame=D_S[d].sbp_last_frame=-1; /* purge buffer */ + D_S[d].sbp_current = 0; + msg(DBG_INF,"sbp_data: fatal_err - retrying.\n"); + return (0); + } + + D_S[d].sbp_first_frame = CURRENT -> sector / 4; + D_S[d].sbp_last_frame = D_S[d].sbp_first_frame + D_S[d].sbp_read_frames - 1; + sbp_transfer(); +#if 1 + if (famT_drive) msg(DBG_TE2, "================sbp_transfer() done=================.\n"); +#endif 1 + return (1); } /*==========================================================================*/ /*==========================================================================*/ @@ -3371,64 +4818,84 @@ static int sbp_data(void) */ static int sbpcd_open(struct inode *ip, struct file *fp) { - int i; - - if (ndrives==0) return (-ENXIO); /* no hardware */ - - if (fp->f_mode & 2) - return -EROFS; - - i = MINOR(ip->i_rdev); - if ( (i<0) || (i>=NR_SBPCD) ) - { - printk("SBPCD: open: bad device: %d\n", i); - return (-ENODEV); /* no such drive */ - } - switch_drive(i); - - flags_cmd_out |= f_respo2; - xx_ReadStatus(); /* command: give 1-byte status */ - i=ResponseStatus(); - if (!st_door_closed) yy_CloseTray(); - if (!st_spinning) xx_SpinUp(); - - flags_cmd_out |= f_respo2; - xx_ReadStatus(); /* command: give 1-byte status */ - i=ResponseStatus(); - if (i<0) - { - DPRINTF((DBG_INF,"SBPCD: sbpcd_open: xx_ReadStatus timed out\n")); - return (-EIO); /* drive doesn't respond */ - } - DPRINTF((DBG_STA,"SBPCD: sbpcd_open: status %02X\n", DriveStruct[d].status_byte)); - if (!st_door_closed||!st_caddy_in) - { - printk("SBPCD: sbpcd_open: no disk in drive\n"); + int i; + + i = MINOR(ip->i_rdev); + if ((i<0) || (i>=NR_SBPCD) || (D_S[i].drv_id==-1)) + { + msg(DBG_INF, "open: bad device: %04X\n", ip->i_rdev); + return (-ENXIO); /* no such drive */ + } + if (fp->f_mode & 2) + return -EROFS; + + switch_drive(i); + + i=cc_ReadError(); + flags_cmd_out |= f_respo2; + cc_ReadStatus(); /* command: give 1-byte status */ + i=ResponseStatus(); + if (famT_drive&&(i<0)) + { + cc_DriveReset(); + i=ResponseStatus(); + i=ResponseStatus(); + } + if (i<0) + { + msg(DBG_INF,"sbpcd_open: ResponseStatus timed out (%d).\n",i); + return (-EIO); /* drive doesn't respond */ + } + if (famT_drive) msg(DBG_TE2,"sbpcd_open: ResponseStatus=%02X\n", i); + if (!st_door_closed) + { + if (famT_drive) msg(DBG_TE2,"sbpcd_open: !st_door_closed.\n"); + cc_CloseTray(); + flags_cmd_out |= f_respo2; + cc_ReadStatus(); + i=ResponseStatus(); + } + if (!(famT_drive)) + if (!st_spinning) + { + if (famT_drive) msg(DBG_TE2,"sbpcd_open: !st_spinning.\n"); + cc_SpinUp(); + flags_cmd_out |= f_respo2; + cc_ReadStatus(); + i=ResponseStatus(); + } + if (famT_drive) msg(DBG_TE2,"sbpcd_open: status %02X\n", D_S[d].status_bits); + if (!st_door_closed||!st_caddy_in) + { + msg(DBG_INF, "sbpcd_open: no disk in drive.\n"); + D_S[d].open_count=0; #if JUKEBOX - do - i=yy_LockDoor(0); - while (i!=0); - if (new_drive) yy_SpinDown(); /* eject tray */ + if (!fam0_drive) + { + i=UnLockDoor(); + cc_SpinDown(); /* eject tray */ + } #endif - return (-ENXIO); - } -/* - * try to keep an "open" counter here and lock the door if 0->1. - */ - DPRINTF((DBG_LCK,"SBPCD: open_count: %d -> %d\n", - DriveStruct[d].open_count,DriveStruct[d].open_count+1)); - if (++DriveStruct[d].open_count==1) - { - do - i=yy_LockDoor(1); - while (i!=0); - } - if (!st_spinning) xx_SpinUp(); - - i=DiskInfo(); - if ((DriveStruct[d].ored_ctl_adr&0x40)==0) - DPRINTF((DBG_INF,"SBPCD: CD contains no data tracks.\n")); - return (0); + return (-ENXIO); + } + /* + * try to keep an "open" counter here and lock the door if 0->1. + */ + MOD_INC_USE_COUNT; + msg(DBG_LCK,"open_count: %d -> %d\n", + D_S[d].open_count,D_S[d].open_count+1); + if (++D_S[d].open_count<=1) + { + i=LockDoor(); + D_S[d].open_count=1; + if (famT_drive) msg(DBG_TE2,"sbpcd_open: before i=DiskInfo();.\n"); + i=DiskInfo(); + if (famT_drive) msg(DBG_TE2,"sbpcd_open: after i=DiskInfo();.\n"); + if ((D_S[d].ored_ctl_adr&0x40)==0) + msg(DBG_INF,"CD contains no data tracks.\n"); + } + if (!st_spinning) cc_SpinUp(); + return (0); } /*==========================================================================*/ /* @@ -3436,36 +4903,37 @@ static int sbpcd_open(struct inode *ip, struct file *fp) */ static void sbpcd_release(struct inode * ip, struct file * file) { - int i; - - i = MINOR(ip->i_rdev); - if ( (i<0) || (i>=NR_SBPCD) ) - { - printk("SBPCD: release: bad device: %d\n", i); - return; - } - switch_drive(i); - - DriveStruct[d].sbp_first_frame=DriveStruct[d].sbp_last_frame=-1; - sync_dev(ip->i_rdev); /* nonsense if read only device? */ - invalidate_buffers(ip->i_rdev); - DriveStruct[d].diskstate_flags &= ~cd_size_bit; - -/* - * try to keep an "open" counter here and unlock the door if 1->0. - */ - DPRINTF((DBG_LCK,"SBPCD: open_count: %d -> %d\n", - DriveStruct[d].open_count,DriveStruct[d].open_count-1)); - if (DriveStruct[d].open_count!=0) /* CDROMEJECT may have been done */ - { - if (--DriveStruct[d].open_count==0) + int i; + + i = MINOR(ip->i_rdev); + if ((i<0) || (i>=NR_SBPCD) || (D_S[i].drv_id==-1)) { - do - i=yy_LockDoor(0); - while (i!=0); - if (DriveStruct[d].f_eject) yy_SpinDown(); + msg(DBG_INF, "release: bad device: %04X\n", ip->i_rdev); + return; + } + switch_drive(i); + + D_S[d].sbp_first_frame=D_S[d].sbp_last_frame=-1; + sync_dev(ip->i_rdev); /* nonsense if read only device? */ + invalidate_buffers(ip->i_rdev); + + /* + * try to keep an "open" counter here and unlock the door if 1->0. + */ + MOD_DEC_USE_COUNT; + msg(DBG_LCK,"open_count: %d -> %d\n", + D_S[d].open_count,D_S[d].open_count-1); + if (D_S[d].open_count>-2) /* CDROMEJECT may have been done */ + { + if (--D_S[d].open_count<=0) + { + i=UnLockDoor(); + if (D_S[d].audio_state!=audio_playing) + if (D_S[d].f_eject) cc_SpinDown(); + D_S[d].diskstate_flags &= ~cd_size_bit; + D_S[d].open_count=0; + } } - } } /*==========================================================================*/ /* @@ -3473,19 +4941,19 @@ static void sbpcd_release(struct inode * ip, struct file * file) */ static struct file_operations sbpcd_fops = { - NULL, /* lseek - default */ - block_read, /* read - general block-dev read */ - block_write, /* write - general block-dev write */ - NULL, /* readdir - bad */ - NULL, /* select */ - sbpcd_ioctl, /* ioctl */ - NULL, /* mmap */ - sbpcd_open, /* open */ - sbpcd_release, /* release */ - NULL, /* fsync */ - NULL, /* fasync */ - check_media_change, /* media_change */ - NULL /* revalidate */ + NULL, /* lseek - default */ + block_read, /* read - general block-dev read */ + block_write, /* write - general block-dev write */ + NULL, /* readdir - bad */ + NULL, /* select */ + sbpcd_ioctl, /* ioctl */ + NULL, /* mmap */ + sbpcd_open, /* open */ + sbpcd_release, /* release */ + NULL, /* fsync */ + NULL, /* fasync */ + sbpcd_chk_disk_change, /* media_change */ + NULL /* revalidate */ }; /*==========================================================================*/ /* @@ -3513,335 +4981,419 @@ static #endif void sbpcd_setup(char *s, int *p) { - DPRINTF((DBG_INI,"SBPCD: sbpcd_setup called with %04X,%s\n",p[1], s)); - sbpro_type=0; - if (!strcmp(s,str_sb)) sbpro_type=1; - else if (!strcmp(s,str_sp)) sbpro_type=2; - if (p[0]>0) sbpcd_ioaddr=p[1]; - - CDo_command=sbpcd_ioaddr; - CDi_info=sbpcd_ioaddr; - CDi_status=sbpcd_ioaddr+1; - CDo_reset=sbpcd_ioaddr+2; - CDo_enable=sbpcd_ioaddr+3; - if (sbpro_type==1) - { - MIXER_addr=sbpcd_ioaddr-0x10+0x04; - MIXER_data=sbpcd_ioaddr-0x10+0x05; - CDo_sel_d_i=sbpcd_ioaddr+1; - CDi_data=sbpcd_ioaddr; - } - else CDi_data=sbpcd_ioaddr+2; + setup_done++; + msg(DBG_INI,"sbpcd_setup called with %04X,%s\n",p[1], s); + sbpro_type=0; + if (!strcmp(s,str_sb)) sbpro_type=1; + else if (!strcmp(s,str_sb_l)) sbpro_type=1; + else if (!strcmp(s,str_sp)) sbpro_type=2; + else if (!strcmp(s,str_sp_l)) sbpro_type=2; + if (p[0]>0) sbpcd_ioaddr=p[1]; + + CDo_command=sbpcd_ioaddr; + CDi_info=sbpcd_ioaddr; + CDi_status=sbpcd_ioaddr+1; + CDo_sel_i_d=sbpcd_ioaddr+1; + CDo_reset=sbpcd_ioaddr+2; + CDo_enable=sbpcd_ioaddr+3; + if (sbpro_type==1) + { + MIXER_addr=sbpcd_ioaddr-0x10+0x04; + MIXER_data=sbpcd_ioaddr-0x10+0x05; + CDi_data=sbpcd_ioaddr; + } + else CDi_data=sbpcd_ioaddr+2; } /*==========================================================================*/ /* * Sequoia S-1000 CD-ROM Interface Configuration * as used within SPEA Media FX card - * The SPEA soundcard has to get jumpered for + * The SPEA soundcard has to get configured for * -> interface type "Matsushita/Panasonic" (not Sony or Mitsumi) * -> I/O base address (0x320, 0x330, 0x340, 0x350) */ static int config_spea(void) { - int n_ports=0x10; /* 2:0x00, 8:0x10, 16:0x20, 32:0x30 */ - int irq_number=0; /* 2:0x01, 7:0x03, 12:0x05, 15:0x07, OFF:0x00 */ - int dma_channel=0; /* 0:0x08, 1:0x18, 3:0x38, 5:0x58, 6:0x68, 7:0x78, OFF: 0x00 */ - int dack_polarity=0; /* L:0x00, H:0x80 */ - int drq_polarity=0x40; /* L:0x00, H:0x40 */ - - int i; - + int n_ports=0x10; /* 2:0x00, 8:0x10, 16:0x20, 32:0x30 */ + /* What is n_ports? Number of addresses or base address offset? */ + int irq_number=0; /* 2:0x01, 7:0x03, 12:0x05, 15:0x07, OFF:0x00 */ + int dma_channel=0; /* 0:0x08, 1:0x18, 3:0x38, 5:0x58, 6:0x68, 7:0x78, OFF: 0x00 */ + int dack_polarity=0; /* L:0x00, H:0x80 */ + int drq_polarity=0x40; /* L:0x00, H:0x40 */ + + int i; + #define SPEA_REG_1 sbpcd_ioaddr+4 #define SPEA_REG_2 sbpcd_ioaddr+5 - - OUT(SPEA_REG_1,0xFF); - i=inb(SPEA_REG_1); - if (i!=0x0F) - { - DPRINTF((DBG_SEQ,"SBPCD: no SPEA interface at %04X present.\n", - sbpcd_ioaddr)); - return (-1); /* no interface found */ - } - OUT(SPEA_REG_1,0x04); - OUT(SPEA_REG_2,0xC0); - - OUT(SPEA_REG_1,0x05); - OUT(SPEA_REG_2,0x10|drq_polarity|dack_polarity); - + + OUT(SPEA_REG_1,0xFF); + i=inb(SPEA_REG_1); + if (i!=0x0F) + { + msg(DBG_SEQ,"no SPEA interface at %04X present.\n", sbpcd_ioaddr); + return (-1); /* no interface found */ + } + OUT(SPEA_REG_1,0x04); + OUT(SPEA_REG_2,0xC0); + + OUT(SPEA_REG_1,0x05); + OUT(SPEA_REG_2,0x10|drq_polarity|dack_polarity); + #if 1 #define SPEA_PATTERN 0x80 #else #define SPEA_PATTERN 0x00 #endif - OUT(SPEA_REG_1,0x06); - OUT(SPEA_REG_2,dma_channel|irq_number|SPEA_PATTERN); - OUT(SPEA_REG_2,dma_channel|irq_number|SPEA_PATTERN); - - OUT(SPEA_REG_1,0x09); - i=(inb(SPEA_REG_2)&0xCF)|n_ports; - OUT(SPEA_REG_2,i); - - sbpro_type = 0; /* acts like a LaserMate interface now */ - DPRINTF((DBG_SEQ,"SBPCD: found SPEA interface at %04X.\n", - sbpcd_ioaddr)); - return (0); + OUT(SPEA_REG_1,0x06); + OUT(SPEA_REG_2,dma_channel|irq_number|SPEA_PATTERN); + OUT(SPEA_REG_2,dma_channel|irq_number|SPEA_PATTERN); + + OUT(SPEA_REG_1,0x09); + i=(inb(SPEA_REG_2)&0xCF)|n_ports; + OUT(SPEA_REG_2,i); + + sbpro_type = 0; /* acts like a LaserMate interface now */ + msg(DBG_SEQ,"found SPEA interface at %04X.\n", sbpcd_ioaddr); + return (0); } /*==========================================================================*/ /* * Test for presence of drive and initialize it. Called at boot time. */ -unsigned long SBPCD_INIT(u_long mem_start, u_long mem_end) +#ifdef MODULE +int init_module(void) +#else + unsigned long SBPCD_INIT(u_long mem_start, u_long mem_end) +#endif MODULE { - int i=0, j=0; - int addr[2]={1, CDROM_PORT}; - int port_index; - - sti(); /* necessary, has consequences for other drivers' init routines */ - - DPRINTF((DBG_INF,"SBPCD version %s\n", VERSION)); - - DPRINTF((DBG_INF,"SBPCD: Looking for a SoundBlaster/Matsushita CD-ROM drive\n")); - DPRINTF((DBG_WRN,"SBPCD: \n")); - DPRINTF((DBG_WRN,"SBPCD: = = = = = = = = = = W A R N I N G = = = = = = = = = =\n")); - DPRINTF((DBG_WRN,"SBPCD: Auto-Probing can cause a hang (f.e. touching an ethernet card).\n")); - DPRINTF((DBG_WRN,"SBPCD: If that happens, you have to reboot and use the\n")); - DPRINTF((DBG_WRN,"SBPCD: LILO (kernel) command line feature like:\n")); - DPRINTF((DBG_WRN,"SBPCD: \n")); - DPRINTF((DBG_WRN,"SBPCD: LILO boot: linux sbpcd=0x230,SoundBlaster\n")); - DPRINTF((DBG_WRN,"SBPCD: or like:\n")); - DPRINTF((DBG_WRN,"SBPCD: LILO boot: linux sbpcd=0x300,LaserMate\n")); - DPRINTF((DBG_WRN,"SBPCD: or like:\n")); - DPRINTF((DBG_WRN,"SBPCD: LILO boot: linux sbpcd=0x330,SPEA\n")); - DPRINTF((DBG_WRN,"SBPCD: \n")); - DPRINTF((DBG_WRN,"SBPCD: with your REAL address.\n")); - DPRINTF((DBG_WRN,"SBPCD: = = = = = = = = = = END of WARNING = = = = = = = = = =\n")); - DPRINTF((DBG_WRN,"SBPCD: \n")); - - autoprobe[0]=sbpcd_ioaddr; /* possibly changed by kernel command line */ - autoprobe[1]=sbpro_type; /* possibly changed by kernel command line */ - - for (port_index=0;port_index=0) break; /* drive found */ - DPRINTF((DBG_INF,"\n")); - } /* end of cycling through the set of possible I/O port addresses */ - - if (ndrives==0) - { - printk("SBPCD: No drive found.\n"); -#if PRINTK_BUG - sti(); /* to avoid possible "printk" bug */ -#endif - goto init_done; - } - - if (port_index>0) - { - printk("SBPCD: You should configure sbpcd.h for your hardware.\n"); -#if PRINTK_BUG - sti(); /* to avoid possible "printk" bug */ -#endif - } - - printk("SBPCD: %d %s CD-ROM drive(s) at 0x%04X.\n", - ndrives, type, CDo_command); -#if PRINTK_BUG - sti(); /* to avoid possible "printk" bug */ -#endif - check_datarate(); - DPRINTF((DBG_INI,"SBPCD: check_datarate done.\n")); - - OUT(CDo_reset,0); - sbp_sleep(100); + int i=0, j=0; + int addr[2]={1, CDROM_PORT}; + int port_index; + + sti(); + + msg(DBG_INF,"sbpcd.c %s\n", VERSION); +#ifndef MODULE +#if DISTRIBUTION + if (!setup_done) + { + msg(DBG_INF,"Looking for Matsushita/Panasonic, CreativeLabs, IBM, Longshine, TEAC CD-ROM drives\n"); + msg(DBG_INF,"\n= = = = = = = = = = W A R N I N G = = = = = = = = = =\n"); + msg(DBG_INF,"Auto-Probing can cause a hang (f.e. touching an ethernet card).\n"); + msg(DBG_INF,"If that happens, you have to reboot and use the\n"); + msg(DBG_INF,"LILO (kernel) command line feature like:\n"); + msg(DBG_INF," LILO boot: ... sbpcd=0x230,SoundBlaster\n"); + msg(DBG_INF,"or like:\n"); + msg(DBG_INF," LILO boot: ... sbpcd=0x300,LaserMate\n"); + msg(DBG_INF,"or like:\n"); + msg(DBG_INF," LILO boot: ... sbpcd=0x330,SPEA\n"); + msg(DBG_INF,"with your REAL address.\n"); + msg(DBG_INF,"= = = = = = = = = = END of WARNING = = = = = = = = = =\n\n"); + } +#endif DISTRIBUTION + sbpcd[0]=sbpcd_ioaddr; /* possibly changed by kernel command line */ + sbpcd[1]=sbpro_type; /* possibly changed by kernel command line */ +#endif MODULE + + for (port_index=0;port_index=0) break; /* drive found */ + } /* end of cycling through the set of possible I/O port addresses */ + + if (ndrives==0) + { + msg(DBG_INF, "No drive found.\n"); +#ifdef MODULE + return -EIO; +#else + goto init_done; +#endif MODULE + } + + if (port_index>0) + msg(DBG_INF, "You should configure sbpcd.h for your hardware.\n"); + check_datarate(); + msg(DBG_INI,"check_datarate done.\n"); + +#if 0 + if (!famL_drive) + { + OUT(CDo_reset,0); + sbp_sleep(100); + } +#endif 0 - for (j=0;j=0) DriveStruct[d].CD_changed=1; - } - -/* - * Turn on the CD audio channels. - * For "compatible" soundcards (with "SBPRO 0" or "SBPRO 2"), the addresses - * are obtained from SOUND_BASE (see sbpcd.h). - */ - if ((sbpro_type==1) || (SOUND_BASE)) - { - if (sbpro_type!=1) + + cc_ReadStatus(); + i=ResponseStatus(); /* returns orig. status or p_busy_new */ + if (famT_drive) i=ResponseStatus(); /* returns orig. status or p_busy_new */ + if (i<0) + if (i!=-402) + msg(DBG_INF,"init: ResponseStatus returns %d.\n",i); + else + { + if (st_check) + { + i=cc_ReadError(); + msg(DBG_INI,"init: cc_ReadError returns %d\n",i); + } + } + msg(DBG_INI,"init: first GetStatus: %d\n",i); + msg(DBG_LCS,"init: first GetStatus: error_byte=%d\n", + D_S[d].error_byte); + if (D_S[d].error_byte==aud_12) + { + timeout=jiffies+200; + do + { + i=GetStatus(); + msg(DBG_INI,"init: second GetStatus: %02X\n",i); + msg(DBG_LCS, + "init: second GetStatus: error_byte=%d\n", + D_S[d].error_byte); + if (i<0) break; + if (!st_caddy_in) break; + } + while ((!st_diskok)||(timeout=0) D_S[d].CD_changed=1; + } + + /* + * Turn on the CD audio channels. + * For "compatible" soundcards (with "SBPRO 0" or "SBPRO 2"), the addresses + * are obtained from SOUND_BASE (see sbpcd.h). + */ + if ((sbpro_type==1) || (SOUND_BASE)) { - MIXER_addr=SOUND_BASE+0x04; /* sound card's address register */ - MIXER_data=SOUND_BASE+0x05; /* sound card's data register */ + if (sbpro_type!=1) + { + MIXER_addr=SOUND_BASE+0x04; /* sound card's address register */ + MIXER_data=SOUND_BASE+0x05; /* sound card's data register */ + } + OUT(MIXER_addr,MIXER_CD_Volume); /* select SB Pro mixer register */ + OUT(MIXER_data,0xCC); /* one nibble per channel, max. value: 0xFF */ } - OUT(MIXER_addr,MIXER_CD_Volume); /* select SB Pro mixer register */ - OUT(MIXER_data,0xCC); /* one nibble per channel, max. value: 0xFF */ - } - - if (register_blkdev(MAJOR_NR, major_name, &sbpcd_fops) != 0) - { - printk("SBPCD: Can't get MAJOR %d for Matsushita CDROM\n", MAJOR_NR); -#if PRINTK_BUG - sti(); /* to avoid possible "printk" bug */ -#endif - goto init_done; - } - blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; - read_ahead[MAJOR_NR] = SBP_BUFFER_FRAMES * (CD_FRAMESIZE / 512); - - snarf_region(CDo_command,4); - - for (j=0;j0) D_S[j].sbp_audsiz=READ_AUDIO; +#ifdef MODULE + D_S[j].sbp_buf=(u_char *) vmalloc(D_S[j].sbp_bufsiz*CD_FRAMESIZE); + if (D_S[j].sbp_buf==NULL) + { + msg(DBG_INF,"data buffer (%d frames) not available.\n",D_S[j].sbp_bufsiz); + return -EIO; + } + msg(DBG_INF,"data buffer size: %d frames.\n",SBP_BUFFER_FRAMES); + if (D_S[j].sbp_audsiz>0) + { + D_S[j].aud_buf=(u_char *) vmalloc(D_S[j].sbp_audsiz*CD_FRAMESIZE_RAW); + if (D_S[j].aud_buf==NULL) msg(DBG_INF,"audio buffer (%d frames) not available.\n",D_S[j].sbp_audsiz); + else msg(DBG_INF,"audio buffer size: %d frames.\n",D_S[j].sbp_audsiz); + } +#else + D_S[j].sbp_buf=(u_char *)mem_start; + mem_start += D_S[j].sbp_bufsiz*CD_FRAMESIZE; + if (D_S[j].sbp_audsiz>0) + { + D_S[j].aud_buf=(u_char *)mem_start; + mem_start += D_S[j].sbp_audsiz*CD_FRAMESIZE_RAW; + } +#endif MODULE + /* + * set the block size + */ + sbpcd_blocksizes[j]=CD_FRAMESIZE; } - else DriveStruct[j].aud_buf=NULL; -/* - * set the block size - */ - sbpcd_blocksizes[j]=CD_FRAMESIZE; - } - blksize_size[MAJOR_NR]=sbpcd_blocksizes; - -init_done: + blksize_size[MAJOR_NR]=sbpcd_blocksizes; + +#ifdef MODULE + return (0); +#else + init_done: #if !(SBPCD_ISSUE-1) #ifdef CONFIG_SBPCD2 - mem_start=sbpcd2_init(mem_start, mem_end); + mem_start=sbpcd2_init(mem_start, mem_end); #endif #ifdef CONFIG_SBPCD3 - mem_start=sbpcd3_init(mem_start, mem_end); + mem_start=sbpcd3_init(mem_start, mem_end); #endif #ifdef CONFIG_SBPCD4 - mem_start=sbpcd4_init(mem_start, mem_end); -#endif + mem_start=sbpcd4_init(mem_start, mem_end); #endif -#if !(SBPCD_ISSUE-1) - DPRINTF((DBG_INF,"SBPCD: init done.\n")); #endif - return (mem_start); + return (mem_start); +#endif MODULE +} +/*==========================================================================*/ +#ifdef MODULE +void cleanup_module(void) +{ + int j; + + if (MOD_IN_USE) + { + msg(DBG_INF, "%s module in use - can't remove it.\n", major_name); + return; + } + if ((unregister_blkdev(MAJOR_NR, major_name) == -EINVAL)) + { + msg(DBG_INF, "What's that: can't unregister %s.\n", major_name); + return; + } + release_region(CDo_command,4); + + for (j=0;j0) + vfree(D_S[j].aud_buf); + } + msg(DBG_INF, "%s module released.\n", major_name); } +#endif MODULE /*==========================================================================*/ /* * Check if the media has changed in the CD-ROM drive. * used externally (isofs/inode.c, fs/buffer.c) * Currently disabled (has to get "synchronized"). */ -static int check_media_change(dev_t full_dev) +static int sbpcd_chk_disk_change(dev_t full_dev) { - int st; - - DPRINTF((DBG_CHK,"SBPCD: media_check (%d) called\n", MINOR(full_dev))); - return (0); /* "busy" test necessary before we really can check */ - - if ((MAJOR(full_dev)!=MAJOR_NR)||(MINOR(full_dev)>=NR_SBPCD)) - { - printk("SBPCD: media_check: invalid device %04X.\n", full_dev); - return (-1); - } - - switch_drive(MINOR(full_dev)); - - xx_ReadStatus(); /* command: give 1-byte status */ - st=ResponseStatus(); - DPRINTF((DBG_CHK,"SBPCD: media_check: %02X\n",DriveStruct[d].status_byte)); - if (st<0) - { - DPRINTF((DBG_INF,"SBPCD: media_check: ResponseStatus error.\n")); - return (1); /* status not obtainable */ - } - if (DriveStruct[d].CD_changed==0xFF) DPRINTF((DBG_CHK,"SBPCD: media_check: \"changed\" assumed.\n")); - if (!st_spinning) DPRINTF((DBG_CHK,"SBPCD: media_check: motor off.\n")); - if (!st_door_closed) - { - DPRINTF((DBG_CHK,"SBPCD: media_check: door open.\n")); - DriveStruct[d].CD_changed=0xFF; - } - if (!st_caddy_in) - { - DPRINTF((DBG_CHK,"SBPCD: media_check: no disk in drive.\n")); - DriveStruct[d].CD_changed=0xFF; - } - if (!st_diskok) DPRINTF((DBG_CHK,"SBPCD: media_check: !st_diskok.\n")); - + int i, st; + + msg(DBG_CHK,"media_check (%d) called\n", MINOR(full_dev)); + return (0); /* "busy" test necessary before we really can check */ + + i=MINOR(full_dev); + if ( (i<0) || (i>=NR_SBPCD) || (D_S[i].drv_id==-1) ) + { + msg(DBG_INF, "media_check: invalid device %04X.\n", full_dev); + return (-1); + } + + switch_drive(i); + + cc_ReadStatus(); /* command: give 1-byte status */ + st=ResponseStatus(); + msg(DBG_CHK,"media_check: %02X\n",D_S[d].status_bits); + if (st<0) + { + msg(DBG_INF,"media_check: ResponseStatus error.\n"); + return (1); /* status not obtainable */ + } + if (D_S[d].CD_changed==0xFF) msg(DBG_CHK,"media_check: \"changed\" assumed.\n"); + if (!st_spinning) msg(DBG_CHK,"media_check: motor off.\n"); + if (!st_door_closed) + { + msg(DBG_CHK,"media_check: door open.\n"); + D_S[d].CD_changed=0xFF; + } + if (!st_caddy_in) + { + msg(DBG_CHK,"media_check: no disk in drive.\n"); + D_S[d].open_count=0; + D_S[d].CD_changed=0xFF; + } + if (!st_diskok) msg(DBG_CHK,"media_check: !st_diskok.\n"); + #if 0000 - if (DriveStruct[d].CD_changed==0xFF) - { - DriveStruct[d].CD_changed=1; - return (1); /* driver had a change detected before */ - } + if (D_S[d].CD_changed==0xFF) + { + D_S[d].CD_changed=1; + return (1); /* driver had a change detected before */ + } #endif 0000 /* seems to give additional errors at the moment */ - - if (!st_diskok) return (1); /* disk not o.k. */ - if (!st_caddy_in) return (1); /* disk removed */ - if (!st_door_closed) return (1); /* door open */ - return (0); + + if (!st_diskok) return (1); /* disk not o.k. */ + if (!st_caddy_in) return (1); /* disk removed */ + if (!st_door_closed) return (1); /* door open */ + return (0); } /*==========================================================================*/ +/* + * Overrides for Emacs so that we follow Linus's tabbing style. + * Emacs will notice this stuff at the end of the file and automatically + * adjust the settings for this buffer only. This must remain at the end + * of the file. + * --------------------------------------------------------------------------- + * Local variables: + * c-indent-level: 8 + * c-brace-imaginary-offset: 0 + * c-brace-offset: -8 + * c-argdecl-indent: 8 + * c-label-offset: -8 + * c-continued-statement-offset: 8 + * c-continued-brace-offset: 0 + * End: + */ diff --git a/drivers/block/sbpcd2.c b/drivers/block/sbpcd2.c new file mode 100644 index 000000000..82e2c68ed --- /dev/null +++ b/drivers/block/sbpcd2.c @@ -0,0 +1,5 @@ +/* + * duplication of sbpcd.c for multiple interfaces + */ +#define SBPCD_ISSUE 2 +#include "sbpcd.c" diff --git a/drivers/block/sbpcd3.c b/drivers/block/sbpcd3.c new file mode 100644 index 000000000..0e79c4155 --- /dev/null +++ b/drivers/block/sbpcd3.c @@ -0,0 +1,5 @@ +/* + * duplication of sbpcd.c for multiple interfaces + */ +#define SBPCD_ISSUE 3 +#include "sbpcd.c" diff --git a/drivers/block/sbpcd4.c b/drivers/block/sbpcd4.c new file mode 100644 index 000000000..f4b34cc29 --- /dev/null +++ b/drivers/block/sbpcd4.c @@ -0,0 +1,5 @@ +/* + * duplication of sbpcd.c for multiple interfaces + */ +#define SBPCD_ISSUE 4 +#include "sbpcd.c" diff --git a/drivers/block/sonycd535.c b/drivers/block/sonycd535.c new file mode 100644 index 000000000..0ab6f13aa --- /dev/null +++ b/drivers/block/sonycd535.c @@ -0,0 +1,1743 @@ +/* + * Sony CDU-535 interface device driver + * + * This is a modified version of the CDU-31A device driver (see below). + * Changes were made using documentation for the CDU-531 (which Sony + * assures me is very similar to the 535) and partial disassembly of the + * DOS driver. I used Minyard's driver and replaced the the CDU-31A + * commands with the CDU-531 commands. This was complicated by a different + * interface protocol with the drive. The driver is still polled. + * + * Data transfer rate is about 110 Kb/sec, theoretical maximum is 150 Kb/sec. + * I tried polling without the sony_sleep during the data transfers but + * it did not speed things up any. + * + * 1993-05-23 (rgj) changed the major number to 21 to get rid of conflict + * with CDU-31A driver. This is the also the number from the Linux + * Device Driver Registry for the Sony Drive. Hope nobody else is using it. + * + * 1993-08-29 (rgj) remove the configuring of the interface board address + * from the top level configuration, you have to modify it in this file. + * + * 1995-01-26 Made module-capable (Joel Katz ) + * + * 1995-05-20 + * Modified to support CDU-510/515 series + * (Claudio Porfiri) + * Fixed to report verify_area() failures + * (Heiko Eissfeldt ) + * + * 1995-06-01 + * More chages to support CDU-510/515 series + * (Claudio Porfiri) + * + * Things to do: + * - handle errors and status better, put everything into a single word + * - use interrupts (code mostly there, but a big hole still missing) + * - handle multi-session CDs? + * - use DMA? + * + * Known Bugs: + * - + * + * Ken Pizzini (ken@halcyon.com) + * + * Original by: + * Ron Jeppesen (ronj.an@site007.saic.com) + * + * + *------------------------------------------------------------------------ + * Sony CDROM interface device driver. + * + * Corey Minyard (minyard@wf-rch.cirr.com) (CDU-535 complaints to Ken above) + * + * Colossians 3:17 + * + * The Sony interface device driver handles Sony interface CDROM + * drives and provides a complete block-level interface as well as an + * ioctl() interface compatible with the Sun (as specified in + * include/linux/cdrom.h). With this interface, CDROMs can be + * accessed and standard audio CDs can be played back normally. + * + * This interface is (unfortunately) a polled interface. This is + * because most Sony interfaces are set up with DMA and interrupts + * disables. Some (like mine) do not even have the capability to + * handle interrupts or DMA. For this reason you will see a lot of + * the following: + * + * retry_count = jiffies+ SONY_JIFFIES_TIMEOUT; + * while ((retry_count > jiffies) && (! +#if defined(CONFIG_CDU535) || defined(MODULE) + +#ifdef MODULE +# include +# include +# include +# ifndef CONFIG_MODVERSIONS + char kernel_version[]= UTS_RELEASE; +# endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define REALLY_SLOW_IO +#include +#include +#include + +#include +#include + +#define MAJOR_NR CDU535_CDROM_MAJOR + +#ifdef MODULE +# include "blk.h" +#else +# include "blk.h" +# define MOD_INC_USE_COUNT +# define MOD_DEC_USE_COUNT +#endif + +/* + * this is the base address of the interface card for the Sony CDU-535 + * CDROM drive. If your jumpers are set for an address other than + * this one (the default), change the following line to the + * proper address. + */ +#ifndef CDU535_ADDRESS +# define CDU535_ADDRESS 0x340 +#endif +#ifndef CDU535_INTERRUPT +# define CDU535_INTERRUPT 0 +#endif +#ifndef CDU535_HANDLE +# define CDU535_HANDLE "cdu535" +#endif +#ifndef CDU535_MESSAGE_NAME +# define CDU535_MESSAGE_NAME "Sony CDU-535" +#endif + +#ifndef MAX_SPINUP_RETRY +# define MAX_SPINUP_RETRY 3 /* 1 is sufficient for most drives... */ +#endif +#ifndef RETRY_FOR_BAD_STATUS +# define RETRY_FOR_BAD_STATUS 100 /* in 10th of second */ +#endif + +#ifndef DEBUG +# define DEBUG 1 +#endif + +/* + * SONY535_BUFFER_SIZE determines the size of internal buffer used + * by the drive. It must be at least 2K and the larger the buffer + * the better the transfer rate. It does however take system memory. + * On my system I get the following transfer rates using dd to read + * 10 Mb off /dev/cdrom. + * + * 8K buffer 43 Kb/sec + * 16K buffer 66 Kb/sec + * 32K buffer 91 Kb/sec + * 64K buffer 111 Kb/sec + * 128K buffer 123 Kb/sec + * 512K buffer 123 Kb/sec + */ +#define SONY535_BUFFER_SIZE (64*1024) + +/* + * if LOCK_DOORS is defined then the eject button is disabled while + * the device is open. + */ +#ifndef NO_LOCK_DOORS +# define LOCK_DOORS +#endif + +static int read_subcode(void); +static void sony_get_toc(void); +static int cdu_open(struct inode *inode, struct file *filp); +static inline unsigned int int_to_bcd(unsigned int val); +static unsigned int bcd_to_int(unsigned int bcd); +static int do_sony_cmd(Byte * cmd, int nCmd, Byte status[2], + Byte * response, int n_response, int ignoreStatusBit7); + +/* The base I/O address of the Sony Interface. This is a variable (not a + #define) so it can be easily changed via some future ioctl() */ +#ifndef MODULE +static +#endif +unsigned short sony535_cd_base_io = CDU535_ADDRESS; + +/* + * The following are I/O addresses of the various registers for the drive. The + * comment for the base address also applies here. + */ +static unsigned short select_unit_reg; +static unsigned short result_reg; +static unsigned short command_reg; +static unsigned short read_status_reg; +static unsigned short data_reg; + +static int initialized = 0; /* Has the drive been initialized? */ +static int sony_disc_changed = 1; /* Has the disk been changed + since the last check? */ +static int sony_toc_read = 0; /* Has the table of contents been + read? */ +static unsigned int sony_buffer_size; /* Size in bytes of the read-ahead + buffer. */ +static unsigned int sony_buffer_sectors; /* Size (in 2048 byte records) of + the read-ahead buffer. */ +static unsigned int sony_usage = 0; /* How many processes have the + drive open. */ + +static int sony_first_block = -1; /* First OS block (512 byte) in + the read-ahead buffer */ +static int sony_last_block = -1; /* Last OS block (512 byte) in + the read-ahead buffer */ + +static struct s535_sony_toc *sony_toc; /* Points to the table of + contents. */ +static struct s535_sony_subcode *last_sony_subcode; /* Points to the last + subcode address read */ +#ifndef MODULE +static Byte *sony_buffer; /* Points to the read-ahead buffer */ +#else +static Byte **sony_buffer; /* Points to the pointers + to the sector buffers */ +#endif +static int sony_inuse = 0; /* is the drive in use? Only one + open at a time allowed */ + +/* + * The audio status uses the values from read subchannel data as specified + * in include/linux/cdrom.h. + */ +static int sony_audio_status = CDROM_AUDIO_NO_STATUS; + +/* + * The following are a hack for pausing and resuming audio play. The drive + * does not work as I would expect it, if you stop it then start it again, + * the drive seeks back to the beginning and starts over. This holds the + * position during a pause so a resume can restart it. It uses the + * audio status variable above to tell if it is paused. + * I just kept the CDU-31A driver behavior rather than using the PAUSE + * command on the CDU-535. + */ +static Byte cur_pos_msf[3] = {0, 0, 0}; +static Byte final_pos_msf[3] = {0, 0, 0}; + +/* What IRQ is the drive using? 0 if none. */ +#ifndef MODULE +static +#endif +int sony535_irq_used = CDU535_INTERRUPT; + +/* The interrupt handler will wake this queue up when it gets an interrupt. */ +static struct wait_queue *cdu535_irq_wait = NULL; + + +/* + * This routine returns 1 if the disk has been changed since the last + * check or 0 if it hasn't. Setting flag to 0 resets the changed flag. + */ +static int +cdu535_check_media_change(dev_t full_dev) +{ + int retval; + + if (MINOR(full_dev) != 0) { + printk(CDU535_MESSAGE_NAME " request error: invalid device.\n"); + return 0; + } + + /* if driver is not initialized, always return 0 */ + retval = initialized ? sony_disc_changed : 0; + sony_disc_changed = 0; + return retval; +} + +static inline void +enable_interrupts(void) +{ +#ifdef USE_IRQ + /* this code snarfed from cdu31a.c; it will not + * directly work for the cdu535 as written... + */ + curr_control_reg |= ( SONY_ATTN_INT_EN_BIT + | SONY_RES_RDY_INT_EN_BIT + | SONY_DATA_RDY_INT_EN_BIT); + outb(curr_control_reg, sony_cd_control_reg); +#endif +} + +static inline void +disable_interrupts(void) +{ +#ifdef USE_IRQ + /* this code snarfed from cdu31a.c; it will not + * directly work for the cdu535 as written... + */ + curr_control_reg &= ~(SONY_ATTN_INT_EN_BIT + | SONY_RES_RDY_INT_EN_BIT + | SONY_DATA_RDY_INT_EN_BIT); + outb(curr_control_reg, sony_cd_control_reg); +#endif +} + +static void +cdu535_interrupt(int irq, struct pt_regs *regs) +{ + disable_interrupts(); + if (cdu535_irq_wait != NULL) + wake_up(&cdu535_irq_wait); + else + printk(CDU535_MESSAGE_NAME + ": Got an interrupt but nothing was waiting\n"); +} + + +/* + * Wait a little while (used for polling the drive). If in initialization, + * setting a timeout doesn't work, so just loop for a while. (We trust + * that the sony_sleep() call is protected by a test for proper jiffies count.) + */ +static inline void +sony_sleep(void) +{ + if (sony535_irq_used <= 0) { /* poll */ + current->state = TASK_INTERRUPTIBLE; + current->timeout = jiffies; + schedule(); + } else { /* Interrupt driven */ + cli(); + enable_interrupts(); + interruptible_sleep_on(&cdu535_irq_wait); + sti(); + } +} + +/*------------------start of SONY CDU535 very specific ---------------------*/ + +/**************************************************************************** + * void select_unit( int unit_no ) + * + * Select the specified unit (0-3) so that subsequent commands reference it + ****************************************************************************/ +static void +select_unit(int unit_no) +{ + unsigned int select_mask = ~(1 << unit_no); + outb(select_mask, select_unit_reg); +} + +/*************************************************************************** + * int read_result_reg( Byte *data_ptr ) + * + * Read a result byte from the Sony CDU controller, store in location pointed + * to by data_ptr. Return zero on success, TIME_OUT if we did not receive + * data. + ***************************************************************************/ +static int +read_result_reg(Byte *data_ptr) +{ + int retry_count; + int read_status; + + retry_count = jiffies + SONY_JIFFIES_TIMEOUT; + while (jiffies < retry_count) { + if (((read_status = inb(read_status_reg)) & SONY535_RESULT_NOT_READY_BIT) == 0) { +#if DEBUG > 1 + printk(CDU535_MESSAGE_NAME + ": read_result_reg(): readStatReg = 0x%x\n", read_status); +#endif + *data_ptr = inb(result_reg); + return 0; + } else { + sony_sleep(); + } + } + printk(CDU535_MESSAGE_NAME " read_result_reg: TIME OUT!\n"); + return TIME_OUT; +} + +/**************************************************************************** + * int read_exec_status( Byte status[2] ) + * + * Read the execution status of the last command and put into status. + * Handles reading second status word if available. Returns 0 on success, + * TIME_OUT on failure. + ****************************************************************************/ +static int +read_exec_status(Byte status[2]) +{ + status[1] = 0; + if (read_result_reg(&(status[0])) != 0) + return TIME_OUT; + if ((status[0] & 0x80) != 0) { /* byte two follows */ + if (read_result_reg(&(status[1])) != 0) + return TIME_OUT; + } +#if DEBUG > 1 + printk(CDU535_MESSAGE_NAME ": read_exec_status: read 0x%x 0x%x\n", + status[0], status[1]); +#endif + return 0; +} + +/**************************************************************************** + * int check_drive_status( void ) + * + * Check the current drive status. Using this before executing a command + * takes care of the problem of unsolicited drive status-2 messages. + * Add a check of the audio status if we think the disk is playing. + ****************************************************************************/ +static int +check_drive_status(void) +{ + Byte status, e_status[2]; + int CDD, ATN; + Byte cmd; + + select_unit(0); + if (sony_audio_status == CDROM_AUDIO_PLAY) { /* check status */ + outb(SONY535_REQUEST_AUDIO_STATUS, command_reg); + if (read_result_reg(&status) == 0) { + switch (status) { + case 0x0: + break; /* play in progress */ + case 0x1: + break; /* paused */ + case 0x3: /* audio play completed */ + case 0x5: /* play not requested */ + sony_audio_status = CDROM_AUDIO_COMPLETED; + read_subcode(); + break; + case 0x4: /* error during play */ + sony_audio_status = CDROM_AUDIO_ERROR; + break; + } + } + } + /* now check drive status */ + outb(SONY535_REQUEST_DRIVE_STATUS_2, command_reg); + if (read_result_reg(&status) != 0) + return TIME_OUT; + +#if DEBUG > 1 + printk(CDU535_MESSAGE_NAME ": check_drive_status() got 0x%x\n", status); +#endif + + if (status == 0) + return 0; + + ATN = status & 0xf; + CDD = (status >> 4) & 0xf; + + switch (ATN) { + case 0x0: + break; /* go on to CDD stuff */ + case SONY535_ATN_BUSY: + if (initialized) + printk(CDU535_MESSAGE_NAME " error: drive busy\n"); + return CD_BUSY; + case SONY535_ATN_EJECT_IN_PROGRESS: + printk(CDU535_MESSAGE_NAME " error: eject in progress\n"); + sony_audio_status = CDROM_AUDIO_INVALID; + return CD_BUSY; + case SONY535_ATN_RESET_OCCURRED: + case SONY535_ATN_DISC_CHANGED: + case SONY535_ATN_RESET_AND_DISC_CHANGED: +#if DEBUG > 0 + printk(CDU535_MESSAGE_NAME " notice: reset occurred or disc changed\n"); +#endif + sony_disc_changed = 1; + sony_toc_read = 0; + sony_audio_status = CDROM_AUDIO_NO_STATUS; + sony_first_block = -1; + sony_last_block = -1; + if (initialized) { + cmd = SONY535_SPIN_UP; + do_sony_cmd(&cmd, 1, e_status, NULL, 0, 0); + sony_get_toc(); + } + return 0; + default: + printk(CDU535_MESSAGE_NAME " error: drive busy (ATN=0x%x)\n", ATN); + return CD_BUSY; + } + switch (CDD) { /* the 531 docs are not helpful in decoding this */ + case 0x0: /* just use the values from the DOS driver */ + case 0x2: + case 0xa: + break; /* no error */ + case 0xc: + printk(CDU535_MESSAGE_NAME + ": check_drive_status(): CDD = 0xc! Not properly handled!\n"); + return CD_BUSY; /* ? */ + default: + return CD_BUSY; + } + return 0; +} /* check_drive_status() */ + +/***************************************************************************** + * int do_sony_cmd( Byte *cmd, int n_cmd, Byte status[2], + * Byte *response, int n_response, int ignore_status_bit7 ) + * + * Generic routine for executing commands. The command and its parameters + * should be placed in the cmd[] array, number of bytes in the command is + * stored in nCmd. The response from the command will be stored in the + * response array. The number of bytes you expect back (excluding status) + * should be passed in n_response. Finally, some + * commands set bit 7 of the return status even when there is no second + * status byte, on these commands set ignoreStatusBit7 TRUE. + * If the command was sent and data received back, then we return 0, + * else we return TIME_OUT. You still have to check the status yourself. + * You should call check_drive_status() before calling this routine + * so that you do not lose notifications of disk changes, etc. + ****************************************************************************/ +static int +do_sony_cmd(Byte * cmd, int n_cmd, Byte status[2], + Byte * response, int n_response, int ignore_status_bit7) +{ + int i; + + /* write out the command */ + for (i = 0; i < n_cmd; i++) + outb(cmd[i], command_reg); + + /* read back the status */ + if (read_result_reg(status) != 0) + return TIME_OUT; + if (!ignore_status_bit7 && ((status[0] & 0x80) != 0)) { + /* get second status byte */ + if (read_result_reg(status + 1) != 0) + return TIME_OUT; + } else { + status[1] = 0; + } +#if DEBUG > 2 + printk(CDU535_MESSAGE_NAME ": do_sony_cmd %x: %x %x\n", + *cmd, status[0], status[1]); +#endif + + /* do not know about when I should read set of data and when not to */ + if ((status[0] & ((ignore_status_bit7 ? 0x7f : 0xff) & 0x8f)) != 0) + return 0; + + /* else, read in rest of data */ + for (i = 0; 0 < n_response; n_response--, i++) + if (read_result_reg(response + i) != 0) + return TIME_OUT; + return 0; +} /* do_sony_cmd() */ + +/************************************************************************** + * int set_drive_mode( int mode, Byte status[2] ) + * + * Set the drive mode to the specified value (mode=0 is audio, mode=e0 + * is mode-1 CDROM + **************************************************************************/ +static int +set_drive_mode(int mode, Byte status[2]) +{ + Byte cmd_buff[2]; + Byte ret_buff[1]; + + cmd_buff[0] = SONY535_SET_DRIVE_MODE; + cmd_buff[1] = mode; + return do_sony_cmd(cmd_buff, 2, status, ret_buff, 1, 1); +} + +/*************************************************************************** + * int seek_and_read_N_blocks( Byte params[], int n_blocks, Byte status[2], + * Byte *data_buff, int buff_size ) + * + * Read n_blocks of data from the CDROM starting at position params[0:2], + * number of blocks in stored in params[3:5] -- both these are already + * int bcd format. + * Transfer the data into the buffer pointed at by data_buff. buff_size + * gives the number of bytes available in the buffer. + * The routine returns number of bytes read in if successful, otherwise + * it returns one of the standard error returns. + ***************************************************************************/ +static int +#ifndef MODULE +seek_and_read_N_blocks(Byte params[], int n_blocks, Byte status[2], + Byte * data_buff, int buf_size) +#else +seek_and_read_N_blocks(Byte params[], int n_blocks, Byte status[2], + Byte **buff, int buf_size) +#endif +{ + const int block_size = 2048; + Byte cmd_buff[7]; + int i; + int read_status; + int retry_count; +#ifndef MODULE + Byte *start_pos = data_buff; +#else + Byte *data_buff; + int sector_count = 0; +#endif + + if (buf_size < ((long)block_size) * n_blocks) + return NO_ROOM; + + set_drive_mode(SONY535_CDROM_DRIVE_MODE, status); + + /* send command to read the data */ + cmd_buff[0] = SONY535_SEEK_AND_READ_N_BLOCKS_1; + for (i = 0; i < 6; i++) + cmd_buff[i + 1] = params[i]; + for (i = 0; i < 7; i++) + outb(cmd_buff[i], command_reg); + + /* read back the data one block at a time */ + while (0 < n_blocks--) { + /* wait for data to be ready */ + retry_count = jiffies + SONY_JIFFIES_TIMEOUT; + while (jiffies < retry_count) { + read_status = inb(read_status_reg); + if ((read_status & SONY535_RESULT_NOT_READY_BIT) == 0) { + read_exec_status(status); + return BAD_STATUS; + } + if ((read_status & SONY535_DATA_NOT_READY_BIT) == 0) { + /* data is ready, read it */ +#ifdef MODULE + data_buff = buff[sector_count++]; +#endif + for (i = 0; i < block_size; i++) + *data_buff++ = inb(data_reg); /* unrolling this loop does not seem to help */ + break; /* exit the timeout loop */ + } + sony_sleep(); /* data not ready, sleep a while */ + } + if (retry_count <= jiffies) + return TIME_OUT; /* if we reach this stage */ + } + + /* read all the data, now read the status */ + if ((i = read_exec_status(status)) != 0) + return i; +#ifndef MODULE + return data_buff - start_pos; +#else + return block_size * sector_count; +#endif +} /* seek_and_read_N_blocks() */ + +/**************************************************************************** + * int request_toc_data( Byte status[2], struct s535_sony_toc *toc ) + * + * Read in the table of contents data. Converts all the bcd data + * into integers in the toc structure. + ****************************************************************************/ +static int +request_toc_data(Byte status[2], struct s535_sony_toc *toc) +{ + int to_status; + int i, j, n_tracks, track_no; + int first_track_num, last_track_num; + Byte cmd_no = 0xb2; + Byte track_address_buffer[5]; + + /* read the fixed portion of the table of contents */ + if ((to_status = do_sony_cmd(&cmd_no, 1, status, (Byte *) toc, 15, 1)) != 0) + return to_status; + + /* convert the data into integers so we can use them */ + first_track_num = bcd_to_int(toc->first_track_num); + last_track_num = bcd_to_int(toc->last_track_num); + n_tracks = last_track_num - first_track_num + 1; + + /* read each of the track address descriptors */ + for (i = 0; i < n_tracks; i++) { + /* read the descriptor into a temporary buffer */ + for (j = 0; j < 5; j++) { + if (read_result_reg(track_address_buffer + j) != 0) + return TIME_OUT; + if (j == 1) /* need to convert from bcd */ + track_no = bcd_to_int(track_address_buffer[j]); + } + /* copy the descriptor to proper location - sonycd.c just fills */ + memcpy(toc->tracks + i, track_address_buffer, 5); + } + return 0; +} /* request_toc_data() */ + +/*************************************************************************** + * int spin_up_drive( Byte status[2] ) + * + * Spin up the drive (unless it is already spinning). + ***************************************************************************/ +static int +spin_up_drive(Byte status[2]) +{ + Byte cmd; + + /* first see if the drive is already spinning */ + cmd = SONY535_REQUEST_DRIVE_STATUS_1; + if (do_sony_cmd(&cmd, 1, status, NULL, 0, 0) != 0) + return TIME_OUT; + if ((status[0] & SONY535_STATUS1_NOT_SPINNING) == 0) + return 0; /* it's already spinning */ + + /* otherwise, give the spin-up command */ + cmd = SONY535_SPIN_UP; + return do_sony_cmd(&cmd, 1, status, NULL, 0, 0); +} + +/*--------------------end of SONY CDU535 very specific ---------------------*/ + +/* Convert from an integer 0-99 to BCD */ +static inline unsigned int +int_to_bcd(unsigned int val) +{ + int retval; + + retval = (val / 10) << 4; + retval = retval | val % 10; + return retval; +} + + +/* Convert from BCD to an integer from 0-99 */ +static unsigned int +bcd_to_int(unsigned int bcd) +{ + return (((bcd >> 4) & 0x0f) * 10) + (bcd & 0x0f); +} + + +/* + * Convert a logical sector value (like the OS would want to use for + * a block device) to an MSF format. + */ +static void +log_to_msf(unsigned int log, Byte *msf) +{ + log = log + LOG_START_OFFSET; + msf[0] = int_to_bcd(log / 4500); + log = log % 4500; + msf[1] = int_to_bcd(log / 75); + msf[2] = int_to_bcd(log % 75); +} + + +/* + * Convert an MSF format to a logical sector. + */ +static unsigned int +msf_to_log(Byte *msf) +{ + unsigned int log; + + + log = bcd_to_int(msf[2]); + log += bcd_to_int(msf[1]) * 75; + log += bcd_to_int(msf[0]) * 4500; + log = log - LOG_START_OFFSET; + + return log; +} + + +/* + * Take in integer size value and put it into a buffer like + * the drive would want to see a number-of-sector value. + */ +static void +size_to_buf(unsigned int size, Byte *buf) +{ + buf[0] = size / 65536; + size = size % 65536; + buf[1] = size / 256; + buf[2] = size % 256; +} + + +/* + * The OS calls this to perform a read or write operation to the drive. + * Write obviously fail. Reads to a read ahead of sony_buffer_size + * bytes to help speed operations. This especially helps since the OS + * uses 1024 byte blocks and the drive uses 2048 byte blocks. Since most + * data access on a CD is done sequentially, this saves a lot of operations. + */ +static void +do_cdu535_request(void) +{ + unsigned int dev; + unsigned int read_size; + int block; + int nsect; + int copyoff; + int spin_up_retry; + Byte params[10]; + Byte status[2]; + Byte cmd[2]; + + if (!sony_inuse) { + cdu_open(NULL, NULL); + } + while (1) { + /* + * The beginning here is stolen from the hard disk driver. I hope + * it's right. + */ + if (!(CURRENT) || CURRENT->dev < 0) { + return; + } + INIT_REQUEST; + dev = MINOR(CURRENT->dev); + block = CURRENT->sector; + nsect = CURRENT->nr_sectors; + if (dev != 0) { + end_request(0); + continue; + } + switch (CURRENT->cmd) { + case READ: + /* + * If the block address is invalid or the request goes beyond the end of + * the media, return an error. + */ + + if (sony_toc->lead_out_start_lba <= (block / 4)) { + end_request(0); + return; + } + if (sony_toc->lead_out_start_lba <= ((block + nsect) / 4)) { + end_request(0); + return; + } + while (0 < nsect) { + /* + * If the requested sector is not currently in the read-ahead buffer, + * it must be read in. + */ + if ((block < sony_first_block) || (sony_last_block < block)) { + sony_first_block = (block / 4) * 4; + log_to_msf(block / 4, params); + + /* + * If the full read-ahead would go beyond the end of the media, trim + * it back to read just till the end of the media. + */ + if (sony_toc->lead_out_start_lba <= ((block / 4) + sony_buffer_sectors)) { + sony_last_block = (sony_toc->lead_out_start_lba * 4) - 1; + read_size = sony_toc->lead_out_start_lba - (block / 4); + } else { + sony_last_block = sony_first_block + (sony_buffer_sectors * 4) - 1; + read_size = sony_buffer_sectors; + } + size_to_buf(read_size, ¶ms[3]); + + /* + * Read the data. If the drive was not spinning, + * spin it up and try some more. + */ + for (spin_up_retry=0 ;; ++spin_up_retry) { + /* This loop has been modified to support the Sony + * CDU-510/515 series, thanks to Claudio Porfiri + * . + */ + /* + * This part is to deal with very slow hardware. We + * try at most MAX_SPINUP_RETRY times to read the same + * block. A check for seek_and_read_N_blocks' result is + * performed; if the result is wrong, the CDROM's engine + * is restarted and the operation is tried again. + */ + /* + * 1995-06-01: The system got problems when downloading + * from Slackware CDROM, the problem seems to be: + * seek_and_read_N_blocks returns BAD_STATUS and we + * should wait for a while before retrying, so a new + * part was added to discriminate the return value from + * seek_and_read_N_blocks for the various cases. + */ + int readStatus = seek_and_read_N_blocks(params, read_size, + status, sony_buffer, (read_size * 2048)); + if (0 <= readStatus) /* Good data; common case, placed first */ + break; + if (readStatus == NO_ROOM || spin_up_retry == MAX_SPINUP_RETRY) { + /* give up */ + if (readStatus == NO_ROOM) + printk(CDU535_MESSAGE_NAME " No room to read from CD\n"); + else + printk(CDU535_MESSAGE_NAME " Read error: 0x%.2x\n", + status[0]); + sony_first_block = -1; + sony_last_block = -1; + end_request(0); + return; + } + if (readStatus == BAD_STATUS) { + /* Sleep for a while, then retry */ + current->state = TASK_INTERRUPTIBLE; + current->timeout = jiffies + RETRY_FOR_BAD_STATUS; + schedule(); + } +#if DEBUG > 0 + printk(CDU535_MESSAGE_NAME + " debug: calling spin up when reading data!\n"); +#endif + cmd[0] = SONY535_SPIN_UP; + do_sony_cmd(cmd, 1, status, NULL, 0, 0); + } + } + /* + * The data is in memory now, copy it to the buffer and advance to the + * next block to read. + */ +#ifndef MODULE + copyoff = (block - sony_first_block) * 512; + memcpy(CURRENT->buffer, sony_buffer + copyoff, 512); +#else + copyoff = block - sony_first_block; + memcpy(CURRENT->buffer, + sony_buffer[copyoff / 4] + 512 * (copyoff % 4), 512); +#endif + + block += 1; + nsect -= 1; + CURRENT->buffer += 512; + } + + end_request(1); + break; + + case WRITE: + end_request(0); + break; + + default: + panic("Unknown SONY CD cmd"); + } + } +} + + +/* + * Read the table of contents from the drive and set sony_toc_read if + * successful. + */ +static void +sony_get_toc(void) +{ + Byte status[2]; + if (!sony_toc_read) { + /* do not call check_drive_status() from here since it can call this routine */ + if (request_toc_data(status, sony_toc) < 0) + return; + sony_toc->lead_out_start_lba = msf_to_log(sony_toc->lead_out_start_msf); + sony_toc_read = 1; + } +} + + +/* + * Search for a specific track in the table of contents. track is + * passed in bcd format + */ +static int +find_track(int track) +{ + int i; + int num_tracks; + + + num_tracks = bcd_to_int(sony_toc->last_track_num) - + bcd_to_int(sony_toc->first_track_num) + 1; + for (i = 0; i < num_tracks; i++) { + if (sony_toc->tracks[i].track == track) { + return i; + } + } + + return -1; +} + +/* + * Read the subcode and put it int last_sony_subcode for future use. + */ +static int +read_subcode(void) +{ + Byte cmd = SONY535_REQUEST_SUB_Q_DATA; + Byte status[2]; + int dsc_status; + + if (check_drive_status() != 0) + return -EIO; + + if ((dsc_status = do_sony_cmd(&cmd, 1, status, (Byte *) last_sony_subcode, + sizeof(struct s535_sony_subcode), 1)) != 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x, %d (read_subcode)\n", + status[0], dsc_status); + return -EIO; + } + return 0; +} + + +/* + * Get the subchannel info like the CDROMSUBCHNL command wants to see it. If + * the drive is playing, the subchannel needs to be read (since it would be + * changing). If the drive is paused or completed, the subcode information has + * already been stored, just use that. The ioctl call wants things in decimal + * (not BCD), so all the conversions are done. + */ +static int +sony_get_subchnl_info(long arg) +{ + struct cdrom_subchnl schi; + int err; + + /* Get attention stuff */ + if (check_drive_status() != 0) + return -EIO; + + sony_get_toc(); + if (!sony_toc_read) { + return -EIO; + } + err = verify_area(VERIFY_WRITE /* and read */ , (char *)arg, sizeof schi); + if (err) + return err; + + memcpy_fromfs(&schi, (char *)arg, sizeof schi); + + switch (sony_audio_status) { + case CDROM_AUDIO_PLAY: + if (read_subcode() < 0) { + return -EIO; + } + break; + + case CDROM_AUDIO_PAUSED: + case CDROM_AUDIO_COMPLETED: + break; + + case CDROM_AUDIO_NO_STATUS: + schi.cdsc_audiostatus = sony_audio_status; + memcpy_tofs((char *)arg, &schi, sizeof schi); + return 0; + break; + + case CDROM_AUDIO_INVALID: + case CDROM_AUDIO_ERROR: + default: + return -EIO; + } + + schi.cdsc_audiostatus = sony_audio_status; + schi.cdsc_adr = last_sony_subcode->address; + schi.cdsc_ctrl = last_sony_subcode->control; + schi.cdsc_trk = bcd_to_int(last_sony_subcode->track_num); + schi.cdsc_ind = bcd_to_int(last_sony_subcode->index_num); + if (schi.cdsc_format == CDROM_MSF) { + schi.cdsc_absaddr.msf.minute = bcd_to_int(last_sony_subcode->abs_msf[0]); + schi.cdsc_absaddr.msf.second = bcd_to_int(last_sony_subcode->abs_msf[1]); + schi.cdsc_absaddr.msf.frame = bcd_to_int(last_sony_subcode->abs_msf[2]); + + schi.cdsc_reladdr.msf.minute = bcd_to_int(last_sony_subcode->rel_msf[0]); + schi.cdsc_reladdr.msf.second = bcd_to_int(last_sony_subcode->rel_msf[1]); + schi.cdsc_reladdr.msf.frame = bcd_to_int(last_sony_subcode->rel_msf[2]); + } else if (schi.cdsc_format == CDROM_LBA) { + schi.cdsc_absaddr.lba = msf_to_log(last_sony_subcode->abs_msf); + schi.cdsc_reladdr.lba = msf_to_log(last_sony_subcode->rel_msf); + } + memcpy_tofs((char *)arg, &schi, sizeof schi); + return 0; +} + + +/* + * The big ugly ioctl handler. + */ +static int +cdu_ioctl(struct inode *inode, + struct file *file, + unsigned int cmd, + unsigned long arg) +{ + unsigned int dev; + Byte status[2]; + Byte cmd_buff[10], params[10]; + int i; + int dsc_status; + int err; + + if (!inode) { + return -EINVAL; + } + dev = MINOR(inode->i_rdev) >> 6; + if (dev != 0) { + return -EINVAL; + } + if (check_drive_status() != 0) + return -EIO; + + switch (cmd) { + case CDROMSTART: /* Spin up the drive */ + if (spin_up_drive(status) < 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMSTART)\n", + status[0]); + return -EIO; + } + return 0; + break; + + case CDROMSTOP: /* Spin down the drive */ + cmd_buff[0] = SONY535_HOLD; + do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); + + /* + * Spin the drive down, ignoring the error if the disk was + * already not spinning. + */ + sony_audio_status = CDROM_AUDIO_NO_STATUS; + cmd_buff[0] = SONY535_SPIN_DOWN; + dsc_status = do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); + if (((dsc_status < 0) && (dsc_status != BAD_STATUS)) || + ((status[0] & ~(SONY535_STATUS1_NOT_SPINNING)) != 0)) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMSTOP)\n", + status[0]); + return -EIO; + } + return 0; + break; + + case CDROMPAUSE: /* Pause the drive */ + cmd_buff[0] = SONY535_HOLD; /* CDU-31 driver uses AUDIO_STOP, not pause */ + if (do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0) != 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPAUSE)\n", + status[0]); + return -EIO; + } + /* Get the current position and save it for resuming */ + if (read_subcode() < 0) { + return -EIO; + } + cur_pos_msf[0] = last_sony_subcode->abs_msf[0]; + cur_pos_msf[1] = last_sony_subcode->abs_msf[1]; + cur_pos_msf[2] = last_sony_subcode->abs_msf[2]; + sony_audio_status = CDROM_AUDIO_PAUSED; + return 0; + break; + + case CDROMRESUME: /* Start the drive after being paused */ + set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status); + + if (sony_audio_status != CDROM_AUDIO_PAUSED) { + return -EINVAL; + } + spin_up_drive(status); + + /* Start the drive at the saved position. */ + cmd_buff[0] = SONY535_PLAY_AUDIO; + cmd_buff[1] = 0; /* play back starting at this address */ + cmd_buff[2] = cur_pos_msf[0]; + cmd_buff[3] = cur_pos_msf[1]; + cmd_buff[4] = cur_pos_msf[2]; + cmd_buff[5] = SONY535_PLAY_AUDIO; + cmd_buff[6] = 2; /* set ending address */ + cmd_buff[7] = final_pos_msf[0]; + cmd_buff[8] = final_pos_msf[1]; + cmd_buff[9] = final_pos_msf[2]; + if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) || + (do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMRESUME)\n", + status[0]); + return -EIO; + } + sony_audio_status = CDROM_AUDIO_PLAY; + return 0; + break; + + case CDROMPLAYMSF: /* Play starting at the given MSF address. */ + err = verify_area(VERIFY_READ, (char *)arg, 6); + if (err) + return err; + spin_up_drive(status); + set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status); + memcpy_fromfs(params, (void *)arg, 6); + + /* The parameters are given in int, must be converted */ + for (i = 0; i < 3; i++) { + cmd_buff[2 + i] = int_to_bcd(params[i]); + cmd_buff[7 + i] = int_to_bcd(params[i + 3]); + } + cmd_buff[0] = SONY535_PLAY_AUDIO; + cmd_buff[1] = 0; /* play back starting at this address */ + /* cmd_buff[2-4] are filled in for loop above */ + cmd_buff[5] = SONY535_PLAY_AUDIO; + cmd_buff[6] = 2; /* set ending address */ + /* cmd_buff[7-9] are filled in for loop above */ + if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) || + (do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPLAYMSF)\n", + status[0]); + return -EIO; + } + /* Save the final position for pauses and resumes */ + final_pos_msf[0] = cmd_buff[7]; + final_pos_msf[1] = cmd_buff[8]; + final_pos_msf[2] = cmd_buff[9]; + sony_audio_status = CDROM_AUDIO_PLAY; + return 0; + break; + + case CDROMREADTOCHDR: /* Read the table of contents header */ + { + struct cdrom_tochdr *hdr; + struct cdrom_tochdr loc_hdr; + + sony_get_toc(); + if (!sony_toc_read) + return -EIO; + hdr = (struct cdrom_tochdr *)arg; + err = verify_area(VERIFY_WRITE, hdr, sizeof *hdr); + if (err) + return err; + loc_hdr.cdth_trk0 = bcd_to_int(sony_toc->first_track_num); + loc_hdr.cdth_trk1 = bcd_to_int(sony_toc->last_track_num); + memcpy_tofs(hdr, &loc_hdr, sizeof *hdr); + } + return 0; + break; + + case CDROMREADTOCENTRY: /* Read a given table of contents entry */ + { + struct cdrom_tocentry *entry; + struct cdrom_tocentry loc_entry; + int track_idx; + Byte *msf_val = NULL; + + sony_get_toc(); + if (!sony_toc_read) { + return -EIO; + } + entry = (struct cdrom_tocentry *)arg; + err = verify_area(VERIFY_WRITE /* and read */ , entry, sizeof *entry); + if (err) + return err; + + memcpy_fromfs(&loc_entry, entry, sizeof loc_entry); + + /* Lead out is handled separately since it is special. */ + if (loc_entry.cdte_track == CDROM_LEADOUT) { + loc_entry.cdte_adr = 0 /*sony_toc->address2 */ ; + loc_entry.cdte_ctrl = sony_toc->control2; + msf_val = sony_toc->lead_out_start_msf; + } else { + track_idx = find_track(int_to_bcd(loc_entry.cdte_track)); + if (track_idx < 0) + return -EINVAL; + loc_entry.cdte_adr = 0 /*sony_toc->tracks[track_idx].address */ ; + loc_entry.cdte_ctrl = sony_toc->tracks[track_idx].control; + msf_val = sony_toc->tracks[track_idx].track_start_msf; + } + + /* Logical buffer address or MSF format requested? */ + if (loc_entry.cdte_format == CDROM_LBA) { + loc_entry.cdte_addr.lba = msf_to_log(msf_val); + } else if (loc_entry.cdte_format == CDROM_MSF) { + loc_entry.cdte_addr.msf.minute = bcd_to_int(*msf_val); + loc_entry.cdte_addr.msf.second = bcd_to_int(*(msf_val + 1)); + loc_entry.cdte_addr.msf.frame = bcd_to_int(*(msf_val + 2)); + } + memcpy_tofs(entry, &loc_entry, sizeof *entry); + } + return 0; + break; + + case CDROMPLAYTRKIND: /* Play a track. This currently ignores index. */ + { + struct cdrom_ti ti; + int track_idx; + + sony_get_toc(); + if (!sony_toc_read) + return -EIO; + err = verify_area(VERIFY_READ, (char *)arg, sizeof ti); + if (err) + return err; + + memcpy_fromfs(&ti, (char *)arg, sizeof ti); + if ((ti.cdti_trk0 < sony_toc->first_track_num) + || (sony_toc->last_track_num < ti.cdti_trk0) + || (ti.cdti_trk1 < ti.cdti_trk0)) { + return -EINVAL; + } + track_idx = find_track(int_to_bcd(ti.cdti_trk0)); + if (track_idx < 0) + return -EINVAL; + params[1] = sony_toc->tracks[track_idx].track_start_msf[0]; + params[2] = sony_toc->tracks[track_idx].track_start_msf[1]; + params[3] = sony_toc->tracks[track_idx].track_start_msf[2]; + /* + * If we want to stop after the last track, use the lead-out + * MSF to do that. + */ + if (bcd_to_int(sony_toc->last_track_num) <= ti.cdti_trk1) { + log_to_msf(msf_to_log(sony_toc->lead_out_start_msf) - 1, + &(params[4])); + } else { + track_idx = find_track(int_to_bcd(ti.cdti_trk1 + 1)); + if (track_idx < 0) + return -EINVAL; + log_to_msf(msf_to_log(sony_toc->tracks[track_idx].track_start_msf) - 1, + &(params[4])); + } + params[0] = 0x03; + + spin_up_drive(status); + + set_drive_mode(SONY535_AUDIO_DRIVE_MODE, status); + + /* Start the drive at the saved position. */ + cmd_buff[0] = SONY535_PLAY_AUDIO; + cmd_buff[1] = 0; /* play back starting at this address */ + cmd_buff[2] = params[1]; + cmd_buff[3] = params[2]; + cmd_buff[4] = params[3]; + cmd_buff[5] = SONY535_PLAY_AUDIO; + cmd_buff[6] = 2; /* set ending address */ + cmd_buff[7] = params[4]; + cmd_buff[8] = params[5]; + cmd_buff[9] = params[6]; + if ((do_sony_cmd(cmd_buff, 5, status, NULL, 0, 0) != 0) || + (do_sony_cmd(cmd_buff + 5, 5, status, NULL, 0, 0) != 0)) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMPLAYTRKIND)\n", + status[0]); + printk("... Params: %x %x %x %x %x %x %x\n", + params[0], params[1], params[2], + params[3], params[4], params[5], params[6]); + return -EIO; + } + /* Save the final position for pauses and resumes */ + final_pos_msf[0] = params[4]; + final_pos_msf[1] = params[5]; + final_pos_msf[2] = params[6]; + sony_audio_status = CDROM_AUDIO_PLAY; + return 0; + } + + case CDROMSUBCHNL: /* Get subchannel info */ + return sony_get_subchnl_info(arg); + + case CDROMVOLCTRL: /* Volume control. What volume does this change, anyway? */ + { + struct cdrom_volctrl volctrl; + + err = verify_area(VERIFY_READ, (char *)arg, sizeof volctrl); + if (err) + return err; + + memcpy_fromfs(&volctrl, (char *)arg, sizeof volctrl); + cmd_buff[0] = SONY535_SET_VOLUME; + cmd_buff[1] = volctrl.channel0; + cmd_buff[2] = volctrl.channel1; + if (do_sony_cmd(cmd_buff, 3, status, NULL, 0, 0) != 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMVOLCTRL)\n", + status[0]); + return -EIO; + } + } + return 0; + + case CDROMEJECT: /* Eject the drive */ + cmd_buff[0] = SONY535_STOP; + do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); + cmd_buff[0] = SONY535_SPIN_DOWN; + do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); + + sony_audio_status = CDROM_AUDIO_INVALID; + cmd_buff[0] = SONY535_EJECT_CADDY; + if (do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0) != 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (CDROMEJECT)\n", + status[0]); + return -EIO; + } + return 0; + break; + + default: + return -EINVAL; + } +} + + +/* + * Open the drive for operations. Spin the drive up and read the table of + * contents if these have not already been done. + */ +static int +cdu_open(struct inode *inode, + struct file *filp) +{ + Byte status[2], cmd_buff[2]; + + + if (sony_inuse) + return -EBUSY; + if (check_drive_status() != 0) + return -EIO; + sony_inuse = 1; + MOD_INC_USE_COUNT; + + if (spin_up_drive(status) != 0) { + printk(CDU535_MESSAGE_NAME " error 0x%.2x (cdu_open, spin up)\n", + status[0]); + sony_inuse = 0; + MOD_DEC_USE_COUNT; + return -EIO; + } + sony_get_toc(); + if (!sony_toc_read) { + cmd_buff[0] = SONY535_SPIN_DOWN; + do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); + sony_inuse = 0; + MOD_DEC_USE_COUNT; + return -EIO; + } + if (inode) { + check_disk_change(inode->i_rdev); + } + sony_usage++; + +#ifdef LOCK_DOORS + /* disable the eject button while mounted */ + cmd_buff[0] = SONY535_DISABLE_EJECT_BUTTON; + do_sony_cmd(cmd_buff, 1, status, NULL, 0, 0); +#endif + + return 0; +} + + +/* + * Close the drive. Spin it down if no task is using it. The spin + * down will fail if playing audio, so audio play is OK. + */ +static void +cdu_release(struct inode *inode, + struct file *filp) +{ + Byte status[2], cmd_no; + + sony_inuse = 0; + MOD_DEC_USE_COUNT; + + if (0 < sony_usage) { + sony_usage--; + } + if (sony_usage == 0) { + sync_dev(inode->i_rdev); + check_drive_status(); + + if (sony_audio_status != CDROM_AUDIO_PLAY) { + cmd_no = SONY535_SPIN_DOWN; + do_sony_cmd(&cmd_no, 1, status, NULL, 0, 0); + } +#ifdef LOCK_DOORS + /* enable the eject button after umount */ + cmd_no = SONY535_ENABLE_EJECT_BUTTON; + do_sony_cmd(&cmd_no, 1, status, NULL, 0, 0); +#endif + } +} + + +static struct file_operations cdu_fops = +{ + NULL, /* lseek - default */ + block_read, /* read - general block-dev read */ + block_write, /* write - general block-dev write */ + NULL, /* readdir - bad */ + NULL, /* select */ + cdu_ioctl, /* ioctl */ + NULL, /* mmap */ + cdu_open, /* open */ + cdu_release, /* release */ + NULL, /* fsync */ + NULL, /* fasync */ + cdu535_check_media_change, /* check media change */ + NULL /* revalidate */ +}; + +/* + * Initialize the driver. + */ +#ifndef MODULE +unsigned long +sony535_init(unsigned long mem_start, unsigned long mem_end) +#else +int +init_module(void) +#endif +{ + struct s535_sony_drive_config drive_config; + Byte cmd_buff[3]; + Byte ret_buff[2]; + Byte status[2]; + int retry_count; + int tmp_irq; +#ifdef MODULE + int i; +#endif + + /* Setting the base I/O address to 0xffff will disable it. */ + if (sony535_cd_base_io == 0xffff) + goto bail; + + /* Set up all the register locations */ + result_reg = sony535_cd_base_io; + command_reg = sony535_cd_base_io; + data_reg = sony535_cd_base_io + 1; + read_status_reg = sony535_cd_base_io + 2; + select_unit_reg = sony535_cd_base_io + 3; + +#ifndef USE_IRQ + sony535_irq_used = 0; /* polling only until this is ready... */ +#endif + /* we need to poll until things get initialized */ + tmp_irq = sony535_irq_used; + sony535_irq_used = 0; + +#if DEBUG > 0 + printk(CDU535_MESSAGE_NAME ": probing base address %03X\n", + sony535_cd_base_io); +#endif + if (check_region(sony535_cd_base_io,4)) { + printk(CDU535_MESSAGE_NAME ": my base address is not free!\n"); +#ifndef MODULE + return mem_start; +#else + return -EIO; +#endif + } + /* look for the CD-ROM, follows the procedure in the DOS driver */ + inb(select_unit_reg); + retry_count = jiffies + 2 * HZ; + while (jiffies < retry_count) + sony_sleep(); /* wait for 40 18 Hz ticks (from DOS driver) */ + inb(result_reg); + + outb(0, read_status_reg); /* does a reset? */ + retry_count = jiffies + SONY_JIFFIES_TIMEOUT; + while (jiffies < retry_count) { + select_unit(0); + if (inb(result_reg) != 0xff) + break; + sony_sleep(); + } + + if ((jiffies < retry_count) && (check_drive_status() != TIME_OUT)) { + /* CD-ROM drive responded -- get the drive configuration */ + cmd_buff[0] = SONY535_INQUIRY; + if (do_sony_cmd(cmd_buff, 1, status, + (Byte *)&drive_config, 28, 1) == 0) { + /* was able to get the configuration, + * set drive mode as rest of init + */ +#if DEBUG > 0 + /* 0x50 == CADDY_NOT_INSERTED | NOT_SPINNING */ + if ( (status[0] & 0x7f) != 0 && (status[0] & 0x7f) != 0x50 ) + printk(CDU535_MESSAGE_NAME + "Inquiry command returned status = 0x%x\n", status[0]); +#endif + /* now ready to use interrupts, if available */ + sony535_irq_used = tmp_irq; +#ifndef MODULE +/* This code is not in MODULEs by default, since the autoirq stuff might + * not be in the module-accessible symbol table. + */ + /* A negative sony535_irq_used will attempt an autoirq. */ + if (sony535_irq_used < 0) { + autoirq_setup(0); + enable_interrupts(); + outb(0, read_status_reg); /* does a reset? */ + sony535_irq_used = autoirq_report(10); + disable_interrupts(); + } +#endif + if (sony535_irq_used > 0) { + if (request_irq(sony535_irq_used, cdu535_interrupt, + SA_INTERRUPT, CDU535_HANDLE)) { + printk("Unable to grab IRQ%d for the " CDU535_MESSAGE_NAME + " driver; polling instead.\n", sony535_irq_used); + sony535_irq_used = 0; + } + } + cmd_buff[0] = SONY535_SET_DRIVE_MODE; + cmd_buff[1] = 0x0; /* default audio */ + if (do_sony_cmd(cmd_buff, 2, status, ret_buff, 1, 1) == 0) { + /* set the drive mode successful, we are set! */ + sony_buffer_size = SONY535_BUFFER_SIZE; + sony_buffer_sectors = sony_buffer_size / 2048; + + printk(CDU535_MESSAGE_NAME " I/F CDROM : %8.8s %16.16s %4.4s", + drive_config.vendor_id, + drive_config.product_id, + drive_config.product_rev_level); + printk(" base address %03X, ", sony535_cd_base_io); + if (tmp_irq > 0) + printk("IRQ%d, ", tmp_irq); + printk("using %d byte buffer\n", sony_buffer_size); + + if (register_blkdev(MAJOR_NR, CDU535_HANDLE, &cdu_fops)) { + printk("Unable to get major %d for %s\n", + MAJOR_NR, CDU535_MESSAGE_NAME); +#ifndef MODULE + return mem_start; +#else + return -EIO; +#endif + } + blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; + read_ahead[MAJOR_NR] = 8; /* 8 sector (4kB) read-ahead */ + +#ifndef MODULE + sony_toc = (struct s535_sony_toc *)mem_start; + mem_start += sizeof *sony_toc; + last_sony_subcode = (struct s535_sony_subcode *)mem_start; + mem_start += sizeof *last_sony_subcode; + sony_buffer = (Byte *)mem_start; + mem_start += sony_buffer_size; + +#else /* MODULE */ + sony_toc = (struct s535_sony_toc *) + kmalloc(sizeof *sony_toc, GFP_KERNEL); + last_sony_subcode = (struct s535_sony_subcode *) + kmalloc(sizeof *last_sony_subcode, GFP_KERNEL); + sony_buffer = (Byte **) + kmalloc(4 * sony_buffer_sectors, GFP_KERNEL); + for (i = 0; i < sony_buffer_sectors; i++) + sony_buffer[i] = (Byte *)kmalloc(2048, GFP_KERNEL); +#endif /* MODULE */ + initialized = 1; + } + } + } + + if (!initialized) { + printk("Did not find a " CDU535_MESSAGE_NAME " drive\n"); +#ifdef MODULE + return -EIO; +#endif + } else { + request_region(sony535_cd_base_io, 4, CDU535_HANDLE); + } +bail: +#ifndef MODULE + return mem_start; +#else + return 0; +#endif +} + +#ifndef MODULE +/* + * accept "kernel command line" parameters + * (added by emoenke@gwdg.de) + * + * use: tell LILO: + * sonycd535=0x320 + * + * the address value has to be the existing CDROM port address. + */ +void +sonycd535_setup(char *strings, int *ints) +{ + /* if IRQ change and default io base desired, + * then call with io base of 0 + */ + if (ints[0] > 0) + if (ints[0] != 0) + sony535_cd_base_io = ints[1]; + if (ints[0] > 1) + sony535_irq_used = ints[2]; + if ((strings != NULL) && (*strings != '\0')) + printk(CDU535_MESSAGE_NAME + ": Warning: Unknown interface type: %s\n", strings); +} + +#else /* MODULE */ + +void +cleanup_module(void) +{ + int i; + if (MOD_IN_USE) { + printk(CDU535_HANDLE " module in use, cannot remove\n"); + return; + } + release_region(sony535_cd_base_io, 4); + for (i = 0; i < sony_buffer_sectors; i++) + kfree_s(sony_buffer[i], 2048); + kfree_s(sony_buffer, 4 * sony_buffer_sectors); + kfree_s(last_sony_subcode, sizeof *last_sony_subcode); + kfree_s(sony_toc, sizeof *sony_toc); + if (unregister_blkdev(MAJOR_NR, CDU535_HANDLE) == -EINVAL) + printk("Uh oh, couldn't unregister " CDU535_HANDLE "\n"); + else + printk(CDU535_HANDLE " module released\n"); +} +#endif /* MODULE */ + +#endif /* CONFIG_CDU535 */ diff --git a/drivers/block/xd.c b/drivers/block/xd.c index bbe7d3332..46cb09957 100644 --- a/drivers/block/xd.c +++ b/drivers/block/xd.c @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -380,7 +381,7 @@ static void xd_recalibrate (u_char drive) } /* xd_interrupt_handler: interrupt service routine */ -static void xd_interrupt_handler (int unused) +static void xd_interrupt_handler(int irq, struct pt_regs * regs) { if (inb(XD_STATUS) & STAT_INTERRUPT) { /* check if it was our device */ #ifdef DEBUG_OTHER diff --git a/drivers/char/8x16.fnt b/drivers/char/8x16.fnt new file mode 100644 index 000000000..88fb566ef --- /dev/null +++ b/drivers/char/8x16.fnt @@ -0,0 +1,4097 @@ +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x04,0x0f,0x0f,0x0f,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x04,0x0f,0x04,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x0f,0x04,0x0f,0x04,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x0f,0x04,0x0f,0x0f,0x0f, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x04,0x0f,0x0f,0x0f,0x04,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x0f,0x04, +0x0f,0x04,0x0f,0x0f,0x0f,0x04,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x0f, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x0f, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x0f,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x04,0x0f, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x04,0x0f, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x04,0x0f, +0x0f,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x0f, +0x0f,0x04,0x04,0x04,0x04,0x04,0x04,0x0f, +0x0f,0x04,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x0f,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x04,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x0f,0x04,0x0f,0x04,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x0f,0x04,0x0f,0x04,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x0f,0x04,0x0f,0x04,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x04,0x04,0x04,0x04,0x04,0x04,0x0f, +0x0f,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x0f, +0x0f,0x04,0x0f,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x04,0x0f,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x04,0x0f,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x0f, +0x0f,0x04,0x04,0x04,0x04,0x04,0x04,0x0f, +0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04,0x0f, +0x0f,0x04,0x0f,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x0f, +0x0f,0x04,0x0f,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x04,0x0f,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x04,0x04,0x04,0x04,0x04,0x04,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x0f, +0x0f,0x04,0x04,0x0f,0x0f,0x04,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x0f,0x0f, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x0f, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f, +0x04,0x0f,0x0f,0x0f,0x04,0x0f,0x0f,0x0f, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x0f, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x0f, +0x04,0x04,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x04,0x04, +0x0f,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x0f, +0x04,0x04,0x04,0x04,0x0f,0x04,0x0f,0x0f, +0x04,0x04,0x04,0x0f,0x04,0x0f,0x04,0x0f, +0x0f,0x0f,0x0f,0x04,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x04,0x0f,0x04,0x0f, +0x0f,0x0f,0x0f,0x04,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x04,0x0f,0x04,0x0f, +0x0f,0x0f,0x0f,0x04,0x0f,0x04,0x0f,0x0f, +0x04,0x04,0x04,0x0f,0x04,0x0f,0x04,0x0f, +0x04,0x04,0x04,0x04,0x0f,0x04,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x0f,0x04,0x0f,0x04,0x0f,0x04,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x0f,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x0f,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x0f,0x04,0x04, +0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x04,0x0f,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x0f,0x04,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x04,0x0f,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x0f, +0x0f,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x0f, +0x0f,0x04,0x0f,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x04,0x0f,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x0f, +0x0f,0x04,0x0f,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x0f,0x0f, +0x0f,0x04,0x04,0x04,0x04,0x04,0x0f,0x0f, +0x0f,0x04,0x04,0x04,0x04,0x04,0x0f,0x0f, +0x0f,0x04,0x04,0x04,0x04,0x04,0x0f,0x0f, +0x0f,0x04,0x04,0x04,0x04,0x04,0x0f,0x0f, +0x0f,0x04,0x04,0x04,0x04,0x04,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x0f,0x04, +0x0f,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x0f, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x0f,0x04,0x04,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x04,0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x04,0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x0f, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x0f,0x04, +0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04,0x0f, +0x0f,0x04,0x0f,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x04,0x0f,0x04,0x04,0x04,0x04,0x0f, +0x0f,0x04,0x0f,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04,0x0f, +0x04,0x0f,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x0f,0x04, +0x0f,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x0f, +0x0f,0x04,0x0f,0x04,0x04,0x0f,0x04,0x0f, +0x0f,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x0f, +0x0f,0x04,0x0f,0x04,0x0f,0x04,0x04,0x0f, +0x0f,0x04,0x0f,0x04,0x04,0x0f,0x04,0x0f, +0x04,0x0f,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x0f, +0x04,0x0f,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x04,0x0f,0x04,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x0f,0x04,0x0f,0x04,0x0f,0x04,0x0f, +0x04,0x0f,0x04,0x0f,0x04,0x04,0x04,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x0f,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x0f,0x04,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x0f,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x0f,0x04,0x04,0x0f,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x0f,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x0f, +0x04,0x04,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x0f,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x04,0x0f,0x0f,0x0f, +0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04,0x0f, +0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04,0x0f, +0x0f,0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x0f, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x04,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x0f,0x04, +0x0f,0x04,0x04,0x04,0x04,0x04,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x04,0x0f,0x04,0x04,0x04, +0x0f,0x04,0x0f,0x04,0x0f,0x04,0x0f,0x04, +0x0f,0x0f,0x0f,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x04,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x0f,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x0f,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x0f,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x0f,0x04, +0x0f,0x0f,0x04,0x0f,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x04,0x0f,0x0f,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x0f,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x0f,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x0f,0x04, +0x0f,0x04,0x04,0x0f,0x04,0x04,0x0f,0x04, +0x0f,0x0f,0x04,0x0f,0x04,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x0f,0x0f,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x0f, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x0f,0x0f, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x0f,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x0f,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x04,0x04,0x04,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x0f,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x04,0x04,0x04,0x04, +0x0f,0x0f,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x0f,0x0f,0x04,0x04,0x04, +0x04,0x0f,0x04,0x04,0x0f,0x04,0x04,0x04, +0x04,0x04,0x0f,0x0f,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f,0x0f, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x04, +0 diff --git a/drivers/char/ChangeLog b/drivers/char/ChangeLog index 78f1fb332..da6f8befc 100644 --- a/drivers/char/ChangeLog +++ b/drivers/char/ChangeLog @@ -1,3 +1,193 @@ +Tue May 2 00:53:25 1995 + + * tty_io.c (tty_set_ldisc): Wait until the output buffer is + drained before closing the old line discipline --- needed + in only one case: XON/XOFF processing. + + * n_tty.c (n_tty_close): Don't bother waiting until the output + driver is closed; in general, the line discipline + shouldn't care if the hardware is finished + transmitting before the line discipline terminates. + + * tty_io.c (release_dev): Shutdown the line discipline after + decrementing the tty count variable; but set the + TTY_CLOSING flag so that we know that this tty structure + isn't long for this world. + + * tty_io.c (init_dev): Add sanity code to check to see if + TTY_CLOSING is set on a tty structure; if so, something + bad has happened (probably a line discipline close blocked + when it shouldn't have; so do a kernel printk and then + return an error). + +Wed Apr 26 10:23:44 1995 Theodore Y. Ts'o + + * tty_io.c (release_dev): Try to shutdown the line discipline + *before* decrementing the tty count variable; this removes + a potential race condition which occurs when the line + discipline close blocks, and another process then tries + open the same serial port. + + * serial.c (rs_hangup): When hanging up, flush the output buffer + before shutting down the UART. Otherwise the line + discipline close blocks waiting for the characters to get + flushed, which never happens until the serial port gets reused. + +Wed Apr 12 08:06:16 1995 Theodore Y. Ts'o + + * serial.c (do_serial_hangup, do_softint, check_modem_status, + rs_init): Hangups are now scheduled via a separate tqueue + structure in the async_struct structure, tqueue_hangup. + This task is pushed on to the tq_schedule queue, so that + it is processed synchronously by the scheduler. + +Sat Feb 18 12:13:51 1995 Theodore Y. Ts'o (tytso@rt-11) + + * tty_io.c (disassociate_ctty, tty_open, tty_ioctl): Clear + current->tty_old_pgrp field when a session leader + acquires a controlling tty, and after a session leader + has disassociated from a controlling tty. + +Fri Feb 17 09:34:09 1995 Theodore Y. Ts'o (tytso@rt-11) + + * serial.c (rs_interrupt_single, rs_interrupt, rs_interrupt_multi): + Change the the number of passes made from 64 to be 256, + configurable with the #define RS_ISR_PASS_LIMIT. + + * serial.c (rs_init, set_serial_info, get_serial_info, rs_close): + Remove support for closing_wait2. Instead, set + tty->closing and rely on the line discipline to prevent + echo wars. + + * n_tty.c (n_tty_receive_char): IEXTEN does not need to be + enabled in order for IXANY to be active. + + If tty->closing is set, then only process XON and XOFF + characters. + +Sun Feb 12 23:57:48 1995 Theodore Y. Ts'o (tytso@rt-11) + + * serial.c (rs_timer): Change the interrupt poll time from 60 + seconds to 10 seconds, configurable with the #define + RS_STROBE_TIME. + + * serial.c (rs_interrupt_multi, startup, shutdown, rs_ioctl, + set_multiport_struct, get_multiport_struct): Add + provisions for a new type of interrupt service routine, + which better supports multiple serial ports on a single + IRQ. + +Sun Feb 5 19:35:11 1995 Theodore Y. Ts'o (tytso@rt-11) + + * tty_ioctl.c (n_tty_ioctl, set_termios, tty_wait_until_sent): + * serial.c (rs_ioctl, rs_close): + * cyclades.c (cy_ioctl, cy_close): + * n_tty.c (n_tty_close): Rename wait_until_sent to + tty_wait_until_sent, so that it's a better name to export + in ksyms.c. + +Sat Feb 4 23:36:20 1995 Theodore Y. Ts'o (tytso@rt-11) + + * serial.c (rs_close): Added missing check for closing_wait2 being + ASYNC_CLOSING_WAIT_NONE. + +Thu Jan 26 09:02:49 1995 Theodore Y. Ts'o (tytso@rt-11) + + * serial.c (rs_init, set_serial_info, get_serial_info, + rs_close): Support close_wait in the serial driver. + This is helpful for slow devices (like serial + plotters) so that their outputs don't get flushed upon + device close. This has to be configurable because + normally we don't want ports to be hung up for long + periods of time during a close when they are not + connected to a device, or the device is powered off. + + The default is to wait 30 seconds; in the case of a + very slow device, the close_wait timeout should be + lengthened. If it is set to 0, the kernel will wait + forever for all of the data to be transmitted. + +Thu Jan 17 01:17:20 1995 Theodore Y. Ts'o (tytso@rt-11) + + * serial.c (startup, change_speed, rs_init): Add support to detect + the StarTech 16650 chip. Treat it as a 16450 for now, + because of its FIFO bugs. + +Thu Jan 5 21:21:57 1995 + + * serial.c: (receive_char): Added counter to prevent infinite loop + when a PCMCIA serial device is ejected. + +Thu Dec 29 17:53:48 1994 + + * tty_io.c (check_tty_count): New procedure which checks + tty->count to make sure that it matches with the number of + open file descriptors which point at the structure. If + the number doesn't match, it prints a warning message. + +Wed Dec 28 15:41:51 1994 + + * tty_io.c (do_tty_hangup, disassociate_ctty): At hangup time, + save the tty's current foreground process group in the + session leader's task structure. When the session leader + terminates, send a SIGHUP, SIGCONT to that process group. + This is not required by POSIX, but it's not prohibited + either, and it appears to be the least intrusive way + to fix a problem that dialup servers have with + orphaned process groups caused by modem hangups. + +Thu Dec 8 14:52:11 1994 + + * serial.c (rs_ioctl): Don't allow most ioctl's if the serial port + isn't initialized. + + * serial.c (rs_close): Don't clear the IER if the serial port + isn't initialized. + + * serial.c (block_til_ready): Don't try to block on the dialin + port if the serial port isn't initialized. + +Wed Dec 7 10:48:30 1994 Si Park (si@wimpol.demon.co.uk) + * tty_io.c (tty_register_driver): Fix bug when linking onto + the tty_drivers list. We now test that there are elements + already on the list before setting the back link from the + first element to the new driver. + + * tty_io.c (tty_unregister_driver): Fix bug in unlinking the + specified driver from the tty_drivers list. We were not + setting the back link correctly. This used to result in + a dangling back link pointer and cause panics on the next + call to get_tty_driver(). + +Tue Nov 29 10:21:09 1994 Theodore Y. Ts'o (tytso@rt-11) + + * tty_io.c (tty_unregister_driver): Fix bug in + tty_unregister_driver where the pointer to the refcount is + tested, instead of the refcount itself. This caused + tty_unregister_driver to always return EBUSY. + +Sat Nov 26 11:59:24 1994 Theodore Y. Ts'o (tytso@rt-11) + + * tty_io.c (tty_ioctl): Add support for the new ioctl + TIOCTTYGSTRUCT, which allow a kernel debugging program + direct read access to the tty and tty_driver structures. + +Fri Nov 25 17:26:22 1994 Theodore Y. Ts'o (tytso@rt-11) + + * serial.c (rs_set_termios): Don't wake up processes blocked in + open when the CLOCAL flag changes, since a blocking + open only samples the CLOCAL flag once when it blocks, + and doesn't check it again. (n.b. FreeBSD has a + different behavior for blocking opens; it's not clear + whether Linux or FreeBSD's interpretation is correct. + POSIX doesn't give clear guidance on this issue, so + this may change in the future....) + + * serial.c (block_til_ready): Use the correct termios structure to + check the CLOCAL flag. If the cuaXX device is active, + then check the saved termios for the ttySXX device. + Otherwise, use the currently active termios structure. + Sun Nov 6 21:05:44 1994 Theodore Y. Ts'o (tytso@rt-11) * serial.c (change_speed): Add support for direct access of @@ -130,7 +320,7 @@ Fri Sep 16 08:13:25 1994 Theodore Y. Ts'o (tytso@rt-11) until after block_til_ready has returned successfully. Modify block_til_ready to check the normal_termios structure directly, so it doesn't rely on termios being - set before its called. + set before it's called. Thu Sep 15 23:34:01 1994 Theodore Y. Ts'o (tytso@rt-11) diff --git a/drivers/char/Makefile b/drivers/char/Makefile index 6045cc7fc..217907a85 100644 --- a/drivers/char/Makefile +++ b/drivers/char/Makefile @@ -17,14 +17,24 @@ $(CC) $(CFLAGS) -c $< OBJS = tty_io.o n_tty.o console.o keyboard.o serial.o \ - tty_ioctl.o pty.o vt.o mem.o \ - defkeymap.o uni_to_437.o + tty_ioctl.o pty.o vt.o mem.o vc_screen.o \ + defkeymap.o consolemap.o vesa_blank.o selection.o SRCS = tty_io.c n_tty.c console.c keyboard.c serial.c \ - tty_ioctl.c pty.c vt.c mem.c \ - defkeymap.c uni_to_437.c + tty_ioctl.c pty.c vt.c mem.c vc_screen.c \ + defkeymap.c consolemap.c vesa_blank.c selection.c +ifdef CONFIG_MIPS_MAGNUM_4000 +OBJS := $(OBJS) fnt8x16.o +SRCS := $(SRCS) fnt8x16.c +endif + +ifdef CONFIG_CYCLADES +OBJS := $(OBJS) cyclades.o +SRCS := $(SRCS) cyclades.c +endif + ifdef CONFIG_ATIXL_BUSMOUSE M = y OBJS := $(OBJS) atixlmouse.o @@ -40,6 +50,8 @@ endif ifdef CONFIG_PRINTER OBJS := $(OBJS) lp.o SRCS := $(SRCS) lp.c +else +MODULES := $(MODULES) lp.o endif ifdef CONFIG_MS_BUSMOUSE @@ -67,6 +79,12 @@ ifdef M OBJS := $(OBJS) mouse.o SRCS := $(SRCS) mouse.c endif + +ifdef CONFIG_SCC +OBJS := $(OBJS) scc.o +SRCS := $(SRCS) scc.c +endif + all: char.a @@ -74,8 +92,22 @@ char.a: $(OBJS) $(AR) rcs char.a $(OBJS) sync +ifdef MODULES + +modules: $(MODULES) + (cd ../../modules;for i in $(MODULES); do ln -sf ../drivers/char/$$i .; done) + +else + +modules: + +endif + dep: $(CPP) -M $(SRCS) > .depend +ifdef MODULES + $(CPP) -M -DMODULE $(MODULES:.o=.c) >> .depend +endif dummy: diff --git a/drivers/char/README.scc b/drivers/char/README.scc new file mode 100644 index 000000000..c48c33c23 --- /dev/null +++ b/drivers/char/README.scc @@ -0,0 +1,933 @@ +This is a subset of the documentation. To use this driver you MUST have the +full package from: + +Internet: +========= + +ftp.ucsd.edu:/hamradio/packet/tcpip/incoming/z8530drv-1.8.dl1bke.tar.gz + +and various mirrors (i.e. nic.switch.ch) + +AX.25 BBS +========= + +UNIX @ DB0ACH.#NRW.DEU.EU, subject: Z8530D18.Pxx/Pyy + +(AX.25 call: DB0ACH-8) + +and various BBS that received the file through AUTO7P or 7PSERV +with the filename Z8530D18.TGZ + + +--------------------------------------------------------------------------- + +!! Version 1.8 +!! +!! Deutscher Text siehe scc_ger.doc +!! +!! perhaps somebody could correct the English documentation (grammar, +!! spelling)? +!! +!! BTW: REAL programmers don't document... +!! + + + SCC.C - Linux driver for Z8530 based HDLC cards for AX.25 + + ******************************************************************** + + (c) 1994 by Joerg Reuter DL1BKE + + portions (c) 1994 Hans Alblas PE1AYX + and (c) 1993 Guido ten Dolle PE1NNZ + + for the complete copyright notice see >> Copying.Z8530DRV << + + ******************************************************************** + + +0. Installation of the package +============================== + +Run SCC-Install. If one (or more) of the patches fails PLEASE consult +chapter 2 (and READ IT of course!) + + + +1. Initialization and attachment of the channels +================================================ + +To use the driver, 3 steps must be performed: + + 1. Global initialization of the driver in the kernel + 2. Setup of parameters with sccinit + 2. Attachment of each channel in the packet software + +The global initialization is needed to reset all SCCs and to +install a common interrupt handler. Also, the hardware addresses +of the chips are defined in this step. In the second step, each +channel is set up for the intended use. + + + +1.1. Initialization +=================== + +Initialization of the hardware is performed by setting the defines and +variables in the file "/linux/drivers/char/scc_config.h". You can change +a number of parameters. + + + +################################################################################################ +# For OptoSCC card e.g: +# + +int Nchips = 2 ; /* number of chips */ +io_port Vector_Latch = 0x168 ; /* addr. of INTACK-Latch (0 for poll mode) +*/ +int Ivec = 9 ; /* interrupt vector */ +long Clock = 4915200 ; /* frequency of the scc clock */ +char Pclk = 1 ; /* use PCLK (1) or RTxC (0) */ +char Board = PA0HZP ; /* what type of SCC card do you use? */ +int Option = 0 ; /* command for extra hardware */ +io_port Special_Port = 0 ; /* port address for special hardware */ + /* (for EAGLE, PC100, PRIMUS, DRSI) */ + + /* ^ never remove the semicolon !! */ + + +/* Channel A B Chip */ +/* ============ ======== */ +/* Control ports: */ + +io_port SCC_ctrl[MAXSCC * 2] = {0x152, 0x150, /* ...one... */ + 0x156, 0x154, /* ...two... */ + 0, 0, /* ...three... */ + 0, 0}; /* ...four... */ + + +/* Data ports: */ + +io_port SCC_data[MAXSCC * 2] = {0x153, 0x151, /* ...one... */ + 0x157, 0x155, /* ...two... */ + 0, 0, /* ...three... */ + 0, 0}; /* ...four... */ + + +/* set to '1' if you have and want ESCC chip (8580/85180/85280) support */ + +/* Chip */ +/* ======== */ +int SCC_Enhanced[MAXSCC] = {0, /* ...one... */ + 0, /* ...two... */ + 0, /* ...three... */ + 0}; /* ...four... */ + +/* some useful #defines. You might need them or not */ + +#define VERBOSE_BOOTMSG 1 +#undef SCC_DELAY /* perhaps a 486DX2 is a *bit* too fast */ +#undef SCC_LDELAY /* slow it even a bit more down */ +#undef DONT_CHECK /* don't look if the SCCs you specified are available */ + + +/*********** END OF CONFIGURATION PARAMETERS ********************************************/ + + + + +################################################################################################ +# For Baycom (U)SCC card e.g: +# + +int Nchips = 2 ; /* number of chips */ +io_port Vector_Latch = 0 ; /* addr. of INTACK-Latch (0 for poll mode) */ +int Ivec = 7 ; /* interrupt vector */ +long Clock = 4915200 ; /* frequency of the scc clock */ +char Board = BAYCOM ; /* what type of SCC card do you use? */ +int Option = 0 ; /* command for extra hardware */ +io_port Special_Port = 0 ; /* port address for special hardware */ + /* (for EAGLE, PC100, PRIMUS, DRSI) */ + + /* ^ never remove the semicolon !! */ + + + +/* Channel A B Chip */ +/* ============ ======== */ +/* Control ports: */ + +io_port SCC_ctrl[MAXSCC * 2] = {0x304, 0x305, /* ...one... */ + 0x306, 0x307, /* ...two... */ + 0, 0, /* ...three... */ + 0, 0}; /* ...four... */ + +/* Data ports: */ + +io_port SCC_data[MAXSCC * 2] = {0x300, 0x301, /* ...one... */ + 0x302, 0x303, /* ...two... */ + 0, 0, /* ...three... */ + 0, 0}; /* ...four... */ + + +/* set to '1' if you have and want ESCC chip (8580/85180/85280) support */ + +/* Chip */ +/* ======== */ +int SCC_Enhanced[MAXSCC] = {0, /* ...one... */ + 0, /* ...two... */ + 0, /* ...three... */ + 0}; /* ...four... */ + +/* some useful #defines. You might need them or not */ + +#define VERBOSE_BOOTMSG 1 +#undef SCC_DELAY /* perhaps a 486DX2 is a *bit* too fast */ +#undef SCC_LDELAY /* slow it even a bit more down */ +#undef DONT_CHECK /* don't look if the SCCs you specified are available */ + +After you changed a parameter you have to recompile a new kernel image file. + +The channel number ranges from 0 to (2 * Nchips) - 1, +where Nchips is the number of chips. + +The crystal clock is specified as 4.9152 MHz. Other frequencies +can be used, and this parameter should be adjusted accordingly. + + +You can define your scc type with Board + + SCC type value + --------------------------------- + PA0HZP SCC card PA0HZP + EAGLE card EAGLE + PC100 card PC100 + PRIMUS-PC (DG9BL) card PRIMUS + BayCom (U)SCC card BAYCOM + + +NOTE: +===== + +If you only know the parameters for the PE1CHL driver for DOS, +run gencfg. It will generate the correct port addresses (I hope). +Its parameters are exactly the same as the ones you use with +the "attach scc" command in net, except that the string "init" must +not appear. Example: + +gencfg 2 0x150 4 2 0 1 0x168 9 4915200 + +will print a short form of scc_config.h for the OptoSCC to stdout. +("short" <=> few comments). + +gencfg 2 0x300 2 4 5 -4 0 7 4915200 0x10 + +does the same for the BayCom USCC card. I my opinion it is much easier +to edit scc_config.h... + + +1.2 initializing the driver on bootup +===================================== + + +To setup a number parameters you must run /sbin/sccinit from one +of your rc.*-files. This has to be done BEFORE the start of +NET or the ax25attach. Sccinit reads the file /etc/z8530drv.rc +and sets the MODEM and KISS parameters. A sample file is +delivered with this package. Change it to your needs: + +Each channel definition is divided into three sections. An +example for /dev/sc1: + +# DEVICE + +device /dev/sc1 # the device for the following params + +# MODEM + +speed 1200 # the default baudrate +clock dpll # clock source: + # dpll = normal halfduplex operation + # external = MODEM provides own Rx/Tx clock + # divider = use fullduplex divider if + # installed (1) +mode nrzi # HDLC encoding mode + # nrzi = 1k2 MODEM, G3RUH 9k6 MODEM + # nrz = DF9IC 9k6 MODEM +# KISS (Layer 1) + +txdelay 36 # (see chapter 1.4) +persist 64 +slot 8 +tail 8 +fulldup 0 +wait 12 +min 3 +maxkey 7 +idle 3 +maxdef 120 +group 0 +txoff off +softdcd on +slip off + +The order WITHIN these sections is unimportant. The order OF these +sections IS important. The MODEM parameters are set with the first +recognized KISS paramer... + +Please note that you can initialize the board only once after boot. +You can change all paramters but "mode" and "clock" later with the +Sccparam program or through KISS. Just to avoid securety holes... + +(1) this divider is usually mounted on the SCC-PBC (PA0HZP) or not + present at all (BayCom). It feeds back the output of the DPLL + (digital pll) as transmit clock. Using this mode without a divider + installed will normally result in keying the transceiver until + maxkey expires --- of course without sending anything (useful). + + +1.3. Attach commands +==================== + +When the linux has startup, the SCC driver has been initialized, +you can attach the channels in your packet software. This is done +by open the scc devices by using the attach asy command. +The SCC-drivers emulates the scc devices as serial asy ports, +this means e.g. that the baudrate can be set in the attach command. + + +Example Wampes: + +############################################################################################# +# Wampes device attach +# NOTE: Interfacename and the device must be the same!! +# Usage: attach asy 0 0 slip|vjslip|ax25ui|ax25i|nrs|kissui