Indrekis
Indrekis

Categories

  • retrocomputing
  • ibm_pc_compat

Tags

  • retrocomputing
  • IBM PC та сумісні
  • плати оновлення

Материнська плата XT-класу, Juko ST (SuperTurbo) з’явилася в мене кілька років тому, завдяки віртуальному музею комп’ютерного андеграунду, але якщо сумісні з 8-бітовими ISA VGA картки в мене знайшлися, то працездатні контролери вводу-виводу – не вдавалося добути. Так і стояла вона ‘‘безхозною’’.

В Інтернеті практично тотожні плати трапляються під іменем Unique UX-12. На жаль, я поки не зміг розібратися, чи це клон – і тоді, хто чий клон, чи просто інша назва? Виглядає, що була доволі популярною – в Інеті, зокрема, на Vogons – багато постів про неї (1, 2, 3 – лише як початок).

З появою PicoMEM, зміг трохи з нею побавитися – підкинула пару цікавинок.

Опис

  • Пізня плата у своєму класі, BIOS – версія від 1989. Гарантійна наклейка вказує серпень 1991 – ймовірно, час завершення гарантії.
  • CPU NEC V20.
    • 4.77 МГц в “основному” режимі – для сумісності, 12 МГц в turbo-режимі.
    • На відміну від більшості фото таких плат з Інтернету, впаяний, а не в “ліжечку”.
    • Керується і програмно і апаратно – див. далі.
  • 640 Кб RAM на платі.
    • Можна встановити 1 Мб – тоді 84 Кб, вищі за 640 Кб, можна буде використати як віртуальний диск, дуже хитрозбоченим способом – розкажу трохи нижче.
  • BIOS версії 2.32, від 10-06-89, останню відому, в Інеті також можна знайти версію 2.31 від 05-30-89 та 2.30, від 1988 – дати не бачу.
    • Наклейка на мікросхемі ROM каже: JUKO ST, 1988, NEL ELECTRONICS LTD.

Інша периферія:

  • Природно, потребує перехідника з клавіатури AT на XT.
  • Використав 16-бітові відеокарту, сумісну з 8-бітовою ISA1: Trident 9016×2/4 LT2, 256 Кб2.
    • Вона майже сучасна платі, 1990.
  • В ролі контролера вводу-виводу, FDD i HDD, мишки, мережевої карти і додаткової пам’яті – PicoMEM.
Фото плати. Трохи припала пилюкою – не маю фото після миття, та не хочу діставати з комп’ютера.
Гарантійна наклейка.
Наклейка на BIOS.
Завантаження – видно версію BIOS-ів та налаштування PicoMEM.

Продуктивність

Вимірював CheckIt 4.10, як і раніше.

Freq., MHz FPU Dhrystones Whetstones BIOS video Direct video
    (цілочисельні) (floating point) CPS3 CPS
4.77 - 364 7100 695 4578
vs XT   1.06 1.07 1.03 0.924
12 (11.98) - 943 18400 1890 11708
vs XT   2.74 2.79 2.79 2.34
4.77 8087 364 120100    
vs XT   1.06 18.19    
12 (11.98) 8087 943 298000    
vs XT   2.74 45.16    
  • В турбо режимі – частота 12 МГц. В “режимі сумісності”, назвемо його так, лише 4.77 МГц.
    • При цьому, продуктивність складає 1.06 від XT з 8088 – думаю, ця різниця у 6% спричинена більшою ефективністю V20.
  • Продуктивність відеопідсистеми та цілочисельної арифметики не змінилася з появою співпроцесора.
    • Співпроцесор, традиційно, дуже гріється. Всі мої 8087/80287 дуже гріються, як в DIP, так і напаяні на плату в інших корпусах.
Використаний співпроцесор, 8087-2.
Максимальна отримана продуктивність.

Пропрієтарні розширення

Шукав вирішення однієї проблеми, спричиненої моєю помилкою, та й натрапив на згадку дискети з системними утилітами від виробника: Unique (Juko ST) UX Turbo XT Mainboard - utility diskette. У ньому вдалося частково прочитати дискету (копія тут, інша копія, дещо повніша, образ диску, який, щоправда, не вантажиться, тут).

Вціліли:

  • TURBO.COM та TURBO.DOC – програмне керування turbo-режимом. Що цікаво, має пріоритет над апаратним перемикачем (джампером).
  • CDISK.SYS та CDISK.DOC – драйвер віртуального диску, розташованого у пам’яті вище 640 Кб.
    • Той драйвер вирішував загадку, яка мене турбувала – плата підтримує 1 Mb RAM, але ДЕ буде та пам’ять?! Дизасемблювання показало – див. далі.
  • TIMER.COM – підозрюю, керування RTC. На жаль, каже “No clock found”. Тому випробувати не зміг.
  • TU.COM – крихітний файл для керування циклами очікування (wait states), див. далі.
  • Всі файли першої копії мають трохи сміття в кінці – прочитані цілими секторами. Це не мало б заважати. Файли другої збігаються, але мають правильний розмір.

Дизасемблювання TURBO.COM

Окрім режимів ON, OFF, HARD (використовувати положення перемикача), має контроль швидкості диску. Дизасемблювання показало наступне: цей контроль швидкості вказує, вимикати турбо-режим перед звертанням до дисків (перериванням int 13h), чи ні. Ймовірно, це робилося для сумісності з якимись пристроями чи дисками.

Утиліта стає резидентом, перехоплює int 9h – для клавіатурних комбінацій і int 13h – для вмикання-вимикання режиму турбо перед викликом справжнього обробника.

  • Ctrl Alt + – ввімкнути турбо,
  • Ctrl Alt - – вимкнути,
  • Ctrl Alt * – вибирати джампером.
    • Клавіші +, -, * – з малої цифрової.

При цьому, починає надавати сервіс для переключення:

  • Int 13h/AX=FFFFh/BH=AAh,
  • BL=00h – повертає в AX 1234h – перевірка інсталяції.
  • BL=01h – Turbo On.
  • BL=02h – Turbo Off.
  • BL=03h – використовувати апаратний джампер Turbo.
  • BL=04h – використовувати Turbo під час доступу до диску.
  • BL=05h – використовувати нормальний режим під час доступу до диску.

Безпосередньо переключення здійснюється викликом BIOS:

  • Int 15h/AH=0DFh,
  • AL=00h – Turbo On,
  • AL=01h – Turbo Off,
  • AL=02h – вибирати джампером.

І те, і те було документовано в Ralf Brown Iterrupt list, хоч і дещо дивно:

Дизасемблювання CDISK.SYS та цікава ‘‘віртуальна’’ пам’ять

Власне, дизасемблювати я взявся, щоб зрозуміти, ЯК воно тими зайвими 384 Кб керує, вони ж не можуть відображатися в адресний простір безпосередньо – там відеопам’ять, BIOS, та та ж PicoMEM. TURBO.COM під роздачу вже за компанію потрапив.

Все виявилося зовсім просто, для читання чи запису секторів на диск, драйвер здійснює наступне:

  • Копіює передане у внутрішній буфер.
  • Виводить у порт 0xE0 значення 1.
  • Вважає, що віртуальний диск починається з адреси 0x2000:0 (лінійна адреса 0x20000).
    • Це відповідає початку 128-го кілобайта.
  • Копіює зі свого внутрішнього буфера чи в нього, з адреси 0x20000 + (номер початкового сектора) * 512.
    • Оскільки це драйвер, він може бути впевнений, що його буфер знаходиться нижче 128-го кілобайта (на відміну від користувацького буфера). Мінус UMB, звичайно – але вони тут проблемою не будуть, оскільки вони вище 640Кб – вище 128+384 Кб. Чи не буде проблемою refill – заповнення відсутньої на материнській платі основної RAM, за допомогою ISA-плати – не скажу.
  • Виводить в порт 0xE0 значення 0.
    • Виглядає, що вивести в цей порт 1 – вмикає альтернативне відобаження пам’яті на платі, яка більша за 640 Кб, на адресу 128 Кб, а виведення нуля – повертає все назад.
    • В Інтернеті не знайшов нічого про таке. Можливо, в новітній час я першим про це, якщо не зауважив, то пишу.
    • В різних джерелах, 1, 2, цей порт вказаній як зарезервований. В 3 згадується, але для інших плат і з не схожими функціями.

Документація на порт E0h для плати Juko ST:

  • Вивести 1 – відобразити Bank 3 (пам’ять від 640 Кб до 1 Мб) на адресу, починаючи з 128 Кб.
  • Вивести 0 – повернути відображення за замовчуванням.

TU.COM

Крихітний файл:

    org 100h 

    cli
    in      al, 70h 
    mov     al, 95h ; 1001`0101b 
    out     70h, al 
    sti
    sti
    int     20h 

    db      0B1h, 0x02h, 0E8h, 0E2h, 0CEh

Зразу я підвис. Згідно опису доступу до CMOS і вмісту порта 0x70, можу хіба припустити, що він забороняє NMI ‘‘з кінцями’’. Однак, документація плати (стор. B-1) підказала, це керування циклами очікування пам’яті – для RAM, ROM та I/O, окремо – вбудованих та зовнішніх. Редагуючи в файлі 0x95 (п’ятий байт) можна змінювати використовуване програмою значення. І так, документація каже робити саме два sti.

Дизасембльовані файли

І драйвер і TURBO – цікавий приклад тогочасного асемблерного коду, я теж щось таке писав в другій половині 90-х, тільки мій був потворнішим. Тому вирішив поділитися.

Дизасемблював IDA Free 5.00, трішки почистив коментарі, але без фанатизму. Мої коментарі, у свою чергу, можуть містити помилки – особливо ретельним не був.

Результати – бази даних IDA та асемблерні файли є тут.

TURBO.COM

Компіляція (використав MASM 6.11 та exe2bin з Turbo C 2.00 – збіг з коментарем у файлі випадковий, але кумедний):

masm turbo.asm
link turbo.obj;
exe2bin turbo.exe turbo.com

Результат рекомпіляції збігається з оригінадлом, за винятком двох байт – MASM 6.11 використав інший опкод для cmp ax, 0FFFFh.

;
; ╔═════════════════════════════════════════════════════════════════════════╗
; ║	This file is generated by The Interactive Disassembler (IDA)	    ║
; ║	Copyright (c) 2010 by Hex-Rays SA, <support@hex-rays.com>	    ║
; ║			 Licensed to: Freeware version			    ║
; ╚═════════════════════════════════════════════════════════════════════════╝
;

; File Name   :	......\Juko_util\TURBO.COM
; Format      :	MS-DOS COM-file
; Base Address:	1000h Range: 10100h-10900h Loaded length: 800h

		.8086
		.model tiny

; ═══════════════════════════════════════════════════════════════════════════

; Segment type:	Pure code
seg000		segment	byte public 'CODE'
		assume cs:seg000
		org 100h
		assume es:nothing, ss:nothing, ds:seg000

; ███████████████ S U B	R O U T	I N E ███████████████████████████████████████


		public start
start		proc near
		jmp	start1
; ───────────────────────────────────────────────────────────────────────────
aTurboV2_0020Ma	db 'TURBO V2.00   20 MAY 88',0Dh,0Ah
		db  1Ah
; ───────────────────────────────────────────────────────────────────────────

new_int_9:				
		push	ax
		push	bx
		push	cx
		push	dx
		push	ds
		push	es
		push	di
		push	si
		push	bp
		cli
		in	al, 60h		; AT Keyboard controller 8042.
		cmp	al, 37h
		jnz	short nextch
		call	StarIsPressed
		jmp	short exit_int_9
; ───────────────────────────────────────────────────────────────────────────

nextch:					; 
		cmp	al, 4Eh
		jnz	short nextch2
		call	PlusIsPressed
		jmp	short exit_int_9
; ───────────────────────────────────────────────────────────────────────────

nextch2:				; 
		cmp	al, 4Ah
		jnz	short exit_int_9
		call	MinusIsPressed

exit_int_9:				; 	 ...
		pop	bp
		pop	si
		pop	di
		pop	es
		pop	ds
		pop	dx
		pop	cx
		pop	bx
		pop	ax
		jmp	dword ptr cs:old_int9_ofs
; ───────────────────────────────────────────────────────────────────────────

new_int_13:				;
		pushf
		cmp	ax, 0FFFFh
		jnz	short notOurCode
		cmp	bx, 0AA00h
		jnz	short notInstCheck
		mov	ax, 1234h
		jmp	short exit_int_13
; ───────────────────────────────────────────────────────────────────────────

notInstCheck:				; 
		cmp	bx, 0AA01h
		jz	short setTurboOn0
		cmp	bx, 0AA02h
		jz	short setTurboOff0
		cmp	bx, 0AA03h
		jz	short setTurboHard0
		cmp	bx, 0AA04h
		jz	short setTurboOnHDD0
		cmp	bx, 0AA05h
		jnz	short exit_int_13
		mov	byte ptr cs:setTurboHDDState, 0
		nop
		jmp	short exit_int_13
; ───────────────────────────────────────────────────────────────────────────

setTurboOnHDD0:				; 
		mov	byte ptr cs:setTurboHDDState, 0FFh
		nop
		jmp	short exit_int_13
; ───────────────────────────────────────────────────────────────────────────

setTurboHard0:				; 
		call	setTurboHardImpl
		jmp	short exit_int_13
; ───────────────────────────────────────────────────────────────────────────

setTurboOn0:				; 
		call	setTurboOnImpl
		jmp	short exit_int_13
; ───────────────────────────────────────────────────────────────────────────

setTurboOff0:				; 
		call	setTurboOff1

exit_int_13:				; 	 ...
		popf
		iret
; ───────────────────────────────────────────────────────────────────────────

notOurCode:				; 
		cmp	byte ptr cs:setTurboHDDState, 0
		jnz	short callOrigInt13NRestore
		push	ax
		push	bx
		push	cx
		push	dx
		push	ds
		push	es
		push	di
		push	si
		push	bp
		call	setTurboOffImpl
		pop	bp
		pop	si
		pop	di
		pop	es
		pop	ds
		pop	dx
		pop	cx
		pop	bx
		pop	ax

callOrigInt13NRestore:			; 
		popf
		pushf
		call	dword ptr cs:old_int13_ofs
		pushf
		push	ax
		push	bx
		push	cx
		push	dx
		push	ds
		push	es
		push	di
		push	si
		push	bp
		mov	al, byte ptr cs:TSRTurboOn
		cmp	al, 0FFh
		jz	short restorRegsNExit
		cmp	al, 1
		jz	short TurboOnNExit
		call	setTurboOff1
		jmp	short restorRegsNExit
; ───────────────────────────────────────────────────────────────────────────

TurboOnNExit:				; 
		call	setTurboOnImpl

restorRegsNExit:			; 	
		pop	bp
		pop	si
		pop	di
		pop	es
		pop	ds
		pop	dx
		pop	cx
		pop	bx
		pop	ax
		popf
		retf	2
; ───────────────────────────────────────────────────────────────────────────

StarIsPressed:				; 
		call	isCtrlAltPressed
		jnz	short skip

setTurboHardImpl:		
		mov	ax, 0DF02h
		int	15h
		mov	byte ptr cs:TSRTurboOn,	0FFh

skip:					; 
		retn
; ───────────────────────────────────────────────────────────────────────────

PlusIsPressed:				; 
		call	isCtrlAltPressed
		jnz	short skip3

setTurboOnImpl:			
		mov	ax, 0DF00h
		int	15h
		mov	byte ptr cs:TSRTurboOn,	1

skip3:					; 
		retn
; ───────────────────────────────────────────────────────────────────────────

MinusIsPressed:				; 
		call	isCtrlAltPressed
		jnz	short skip4

setTurboOff1:			
		mov	byte ptr cs:TSRTurboOn,	0

setTurboOffImpl:			; 
		mov	ax, 0DF01h
		int	15h

skip4:					; 
		retn
; ───────────────────────────────────────────────────────────────────────────

isCtrlAltPressed:		
		xor	ax, ax
		mov	ds, ax
		assume ds:nothing
		mov	al, ds:417h	; 40:17	-- kbd status
		and	al, 0Ch
		cmp	al, 0Ch
		retn
; ───────────────────────────────────────────────────────────────────────────
TSRTurboOn:				
		db 0FFh
setTurboHDDState:							
		db 0FFh
old_int9_ofs:			
		dw 0
old_int9_seg:
		dw 0
old_int13_ofs:			
		dw 0
old_int13_seg:			
		dw 0
; ───────────────────────────────────────────────────────────────────────────

start1:					
					
		cld
		mov	ax, 0FFFFh
		mov	bx, 0AA00h
		int	13h		; DISK -
		cmp	ax, 1234h
		jz	short loc_10275
		mov	ax, 3509h
		int	21h		; DOS -	2+ - GET INTERRUPT VECTOR
					; AL = interrupt number
					; Return: ES:BX	= value	of interrupt vector
		mov	word ptr ds:old_int9_ofs, bx
		mov	word ptr ds:232h, es
		mov	dx, offset new_int_9
		mov	ax, 2509h
		int	21h		; DOS -	SET INTERRUPT VECTOR
					; AL = interrupt number
					; DS:DX	= new vector to	be used	for specified interrupt
		mov	ax, 3513h
		int	21h		; DOS -	2+ - GET INTERRUPT VECTOR
					; AL = interrupt number
					; Return: ES:BX	= value	of interrupt vector
		mov	word ptr ds:old_int13_ofs, bx
		mov	word ptr ds:old_int13_seg, es
		mov	dx, offset new_int_13
		mov	ax, 2513h
		int	21h		; DOS -	SET INTERRUPT VECTOR
					; AL = interrupt number
					; DS:DX	= new vector to	be used	for specified interrupt
		mov	byte ptr ds:398h, 1

loc_10275:				
		mov	cl, ds:80h	; Command line size
		xor	ch, ch
		or	cx, cx
		jnz	short scanForDskOpt
		jmp	no_args
; ───────────────────────────────────────────────────────────────────────────

scanForDskOpt:				
		mov	si, 81h

nextSlash:				
		mov	al, [si]
		cmp	al, '/'
		jz	short slashFound
		and	byte ptr [si], 0DFh

slashFound:				
		inc	si
		loop	nextSlash
		mov	si, 81h
		mov	cx, 2

parseNext:			
		lodsb
		cmp	al, 0
		jz	short parseNext
		cmp	al, 9
		jz	short parseNext
		cmp	al, 'O'
		jz	short onOFound
		cmp	al, 'H'
		jz	short onHFound
		cmp	al, '/'
		jz	short slashFound0
		cmp	cx, 2
		jz	short noArgs0
		jmp	exitNoTSR
; ───────────────────────────────────────────────────────────────────────────

noArgs0:				
		jmp	no_args
; ───────────────────────────────────────────────────────────────────────────

onOFound:				
		lodsb
		cmp	al, 'N'
		jz	short onONFound
		cmp	al, 'F'
		jz	short onF1Found
		jmp	unrecognOpt
; ───────────────────────────────────────────────────────────────────────────

onF1Found:				
		lodsb
		cmp	al, 'F'
		jz	short onOFFFound
		jmp	unrecognOpt
; ───────────────────────────────────────────────────────────────────────────

onOFFFound:				
		lodsb
		cmp	al, 0
		jz	short onOFFFound2
		cmp	al, 9
		jz	short onOFFFound2
		cmp	al, 0Dh
		jz	short onOFFFound2
		jmp	unrecognOpt
; ───────────────────────────────────────────────────────────────────────────

onOFFFound2:				
		mov	ax, 0FFFFh
		mov	bx, 0AA02h
		int	13h		; DISK -
		mov	dx, offset aTurboModeIsN_0 ; "\r\nTurbo	mode is	now off	!\r\n$"
		jmp	short loc_10366
; ───────────────────────────────────────────────────────────────────────────

onONFound:				
		lodsb
		cmp	al, 0
		jz	short onONFound2
		cmp	al, 9
		jz	short onONFound2
		cmp	al, 0Dh
		jnz	short unrecognOpt

onONFound2:			
		mov	ax, 0FFFFh
		mov	bx, 0AA01h
		int	13h		; DISK -
		mov	dx, offset aTurboModeIsNow ; "\r\nTurbo	mode is	now on !\r\n$"
		jmp	short loc_10366
; ───────────────────────────────────────────────────────────────────────────

onHFound:				
		lodsb
		cmp	al, 41h
		jnz	short unrecognOpt
		lodsb
		cmp	al, 52h
		jnz	short unrecognOpt
		lodsb
		cmp	al, 44h
		jnz	short unrecognOpt
		lodsb
		cmp	al, 0
		jz	short loc_1031E
		cmp	al, 9
		jz	short loc_1031E
		cmp	al, 0Dh
		jnz	short unrecognOpt

loc_1031E:				
		mov	ax, 0FFFFh
		mov	bx, 0AA03h
		int	13h		; DISK -
		mov	dx, offset aModeIsNowSetAc ; "\r\nMode is now set according to hard swi"...
		jmp	short loc_10366
; ───────────────────────────────────────────────────────────────────────────

slashFound0:				
		lodsb
		cmp	al, 'N'
		jz	short foundSlashN
		cmp	al, 'T'
		jnz	short unrecognOpt
		lodsb
		cmp	al, 0		; If last letter -- set	option,	otherwise -- error
		jz	short setDiskTurboSpeed
		cmp	al, 9
		jz	short setDiskTurboSpeed
		cmp	al, 0Dh
		jnz	short unrecognOpt

setDiskTurboSpeed:			
		mov	ax, 0FFFFh
		mov	bx, 0AA04h
		int	13h		; DISK -
		mov	dx, offset aDiskAccessIs_0 ; "\r\nDisk access is now in	turbo speed !\r\n"...
		jmp	short loc_10366
; ───────────────────────────────────────────────────────────────────────────

foundSlashN:				
		lodsb
		cmp	al, 0
		jz	short setDiskNormalSpeed
		cmp	al, 9
		jz	short setDiskNormalSpeed
		cmp	al, 0Dh
		jnz	short unrecognOpt

setDiskNormalSpeed:			
		mov	ax, 0FFFFh
		mov	bx, 0AA05h
		int	13h		; DISK -
		mov	dx, offset aDiskAccessIsNo ; "\r\nDisk access is now in	normal speed !\r"...

loc_10366:				
		mov	ah, 9
		int	21h		; DOS -	PRINT STRING
					; DS:DX	-> string terminated by	"$"
		loop	loc_1036E
		jmp	short exitNoTSR
; ───────────────────────────────────────────────────────────────────────────

loc_1036E:				
		jmp	parseNext
; ───────────────────────────────────────────────────────────────────────────

unrecognOpt:				
		mov	dx, offset aUnrecognizedPa ; "\r\nUnrecognized parameter found in comma"...
		mov	ah, 9
		int	21h		; DOS -	PRINT STRING
					; DS:DX	-> string terminated by	"$"
		jmp	short exitNoTSR
; ───────────────────────────────────────────────────────────────────────────

no_args:				
		mov	dx, offset aHelpMsg ; "\r\nTurbo switch	V2.00,	20 May 88\r\n\r\nUSA"...
		mov	ah, 9
		int	21h		; DOS -	PRINT STRING
					; DS:DX	-> string terminated by	"$"

exitNoTSR:				
		mov	al, ds:byte_10398
		or	al, ds:byte_10398
		jnz	short ExitTSR
		int	20h		; DOS -	PROGRAM	TERMINATION
					; returns to DOS--identical to INT 21/AH=00h
; ───────────────────────────────────────────────────────────────────────────

ExitTSR:				
		mov	dx, offset aTurboSwitchV2_ ; "\r\nTurbo	switch V2.00 installed\r\n$"
		mov	ah, 9
		int	21h		; DOS -	PRINT STRING
					; DS:DX	-> string terminated by	"$"
		mov	dx, offset start1
		int	27h		; DOS -	TERMINATE BUT STAY RESIDENT
start		endp			; CS = current program segment
					; DX = last program byte + 1
; ───────────────────────────────────────────────────────────────────────────
byte_10398	db 0			
					
aTurboSwitchV2_	db 0Dh,0Ah		
		db 'Turbo switch V2.00 installed',0Dh,0Ah,'$'
aTurboModeIsNow	db 0Dh,0Ah		
		db 'Turbo mode is now on !',0Dh,0Ah,'$'
aTurboModeIsN_0	db 0Dh,0Ah		
		db 'Turbo mode is now off !',0Dh,0Ah,'$'
aModeIsNowSetAc	db 0Dh,0Ah		
		db 'Mode is now set according to hard switch !',0Dh,0Ah,'$'
aDiskAccessIsNo	db 0Dh,0Ah		
		db 'Disk access is now in normal speed !',0Dh,0Ah,'$'
aDiskAccessIs_0	db 0Dh,0Ah		
		db 'Disk access is now in turbo speed !',0Dh,0Ah,'$'
aUnrecognizedPa	db 0Dh,0Ah		
		db 'Unrecognized parameter found in command line !',0Dh,0Ah
aHelpMsg	db 0Dh,0Ah		
		db 'Turbo switch V2.00,  20 May 88',0Dh,0Ah
		db 0Dh,0Ah
		db 'USAGE : TURBO [switch]  [disk access speed control]',0Dh,0Ah
		db 0Dh,0Ah
		db 9,'[switch] = ON   : Turn on turbo mode',0Dh,0Ah
		db 9,'         = OFF  : Turn off turbo mode',0Dh,0Ah
		db 9,'         = HARD : Set according to hard switch',0Dh,0Ah
		db 0Dh,0Ah
		db 'To switch mode with keyboard',0Dh,0Ah
		db 0Dh,0Ah
		db 'Hold down "Ctrl" and "Alt" then press',0Dh,0Ah
		db 0Dh,0Ah
		db '           Grey +  :  Turn on turbo mode',0Dh,0Ah
		db '           Grey -  :  Turn off turbo mode',0Dh,0Ah
		db '           Grey *  :  Set according to hard switch',0Dh,0Ah
		db 0Dh,0Ah
		db 'Disk access speed control :',0Dh,0Ah
		db 0Dh,0Ah
		db '           /N  :  Normal',0Dh,0Ah
		db '           /T  :  Turbo',0Dh,0Ah,'$'
seg000		ends


		end start

CDISK.SYS

Компіляція:

masm cdisk.asm
link cdisk.obj;
exe2bin cdisk.exe cdisk.sys

Результат рекомпіляції повністю збігається з оригіналом. Знову ж, мої коментарі можуть містити помилки.

Окрім перемикання відображення пам’яті, цікаво глянути, як форматує диск. Та й навчальний приклад драйвера DOS непоганий, хоча чи ідеальний – не скажу.


;
; ╔═════════════════════════════════════════════════════════════════════════╗
; ║	This file is generated by The Interactive Disassembler (IDA)	    ║
; ║	Copyright (c) 2010 by Hex-Rays SA, <support@hex-rays.com>	    ║
; ║			 Licensed to: Freeware version			    ║
; ╚═════════════════════════════════════════════════════════════════════════╝
;
; Input	MD5   :	48753800C37485943415AFDE2D1B3A37

; File Name   :	D:\WorkWWW\Blog\ComputersHistory.S\CompMuseum\86_2\Juko_util\CDISK.SYS
; Format      :	MS-DOS SYS-file	(perhaps device	driver)
; Base Address:	0h Range: 0h-800h Loaded length: 800h

		.8086
		.model small

; ═══════════════════════════════════════════════════════════════════════════

; Segment type:	Pure code
seg000		segment	byte public 'CODE'
		assume cs:seg000
		assume es:nothing, ss:nothing, ds:nothing
NextDevice_0	dw 0FFFFh		; The last device
		dw 0FFFFh
DevAttr_0	dw 2000h		; non-IBM block	device
					; block	device
Strategy_0	dw offset Strategy_Routine_0
Interrupt_0	dw offset Interrupt_Routine_0
Subunits	db 1			; May be device	number
		db 0,0,0,0,0,0,0
ReqPtr		dd 0			; 
BPB_data	dw 200h			; 
					; Bytes	per sector
		db    1			; Sectors per cluster
		dw 1			; Reserved sectors
		db    2			; FATs
		dw 70h			; Root dir entries
		dw 300h			; Total	logical	secs.
unk_20		db 0FEh	    ; Media	descr.
		dw 3			; Sectors in FAT
BPB_ptr		dw 16h			; 
req_bytes_cnt	dw 0			; 
doVerifyAftWr	db 0			; 
req_sect_num	dw 0			; 
VirtRAMBaseSeg	dw 2000h		; 
tranferAddrOff	dw 0			; 
tranferAddrSeg	dw 0			; 
VirtDiskBootBPB	db 3 dup(0)		; 0 ; 
aNelIntl_OEM	db 'NEL INTL'           ; 
		dw 200h			; Bytes	per sector - 512d
		db    1			; Logical sectors per cluster
		dw 1			; Reserved logical sectors
		db    2			; Number of FATs
		dw 40h			; Maximum number of root directory entries.
		dw 300h			; Total	logical	sectors	- 768, 384 Кб
		db 0FCh	; №		; Media	descriptor FC -- (?!) 5.25-inch	single sided, 40 tracks	per side, 9 sectors per	track (180 KB)
		dw 3			; Logical sectors per FAT
DispatchTbl	dw 1E0h			; 
					; Init
		dw 39Ch			; Media_Chk
		dw 3AFh			; Build_BPB
		dw 3FEh			; Ioctl_Inp	  ;  3 = I/O ctrl read from dev
		dw 40Ch			; Input		  ;  4 = normal	destructive read
		dw 3FEh			; Nd_Input	  ;  5 = non-destructive read,no wait
		dw 3FEh			; Inp_Stat	  ;  6 = return	current	input status
		dw 3FEh			; Inp_Flush	  ;  7 = flush device input buffers
		dw 43Ah			; Output	  ;  8 = normal	output to device
		dw 478h			; Outp_Vfy	  ;  9 = output	with verify
		dw 3FEh			; Outp_Stat	  ; 10 = return	current	output status
		dw 3FEh			; Outp_Flush	  ; 11 = flush output buffers
		dw 3FEh			; Ioctl_Outp	  ; 12 = I/O control output
		dw 3FEh			; Dev_Open	  ; 13 = device	open	  (MS-DOS 3.x)
		dw 3FEh			; Dev_Close	  ; 14 = device	close	  (MS-DOS 3.x)
		dw 3FEh			; Rem_Media	  ; 15 = removeable media (MS-DOS 3.x)
		dw 3FEh
aJukoStRamdrive	db 0Dh,0Ah
		db '*** Juko ST RAMDrive 384K V2.10 ***',0Dh,0Ah
		db ' 17 MAY 1988 '
		db  1Ah

; ███████████████ S U B	R O U T	I N E ███████████████████████████████████████


ParseReq4IOv	proc near		
		mov	ax, es:[bx+0Eh]	; Save transfer	addr. Lo from Req, hdr
		mov	cs:tranferAddrOff, ax
		mov	ax, es:[bx+10h]	; Save transfer	addr. Hi from Req, hdr
		mov	cs:tranferAddrSeg, ax
		mov	ax, es:[bx+12h]	; Save sectors count (blk dev.)
		xor	ah, ah		; WTF? No more than 255	sectors	= 127.5	Кб
		mov	cs:req_bytes_cnt, ax
		retn
ParseReq4IOv	endp


; ███████████████ S U B	R O U T	I N E ███████████████████████████████████████

; Parses IO request, on	ret:
; ds --	initial	sector offset in virtual mem
; cx --	bytes count, 0xFFFF if too many	to fit in 16-bit reg
; si --	0

SectNumToVirtMemSeg proc near		
		mov	ax, cs:req_sect_num
		mov	cx, 20h	; ' '
		mul	cx
		mov	dx, cs:VirtRAMBaseSeg
		add	dx, ax
		mov	ds, dx
		xor	si, si
		mov	ax, cs:req_bytes_cnt
		mov	cx, 200h
		mul	cx
		or	ax, ax
		jnz	short loc_DE
		mov	ax, 0FFFFh

loc_DE:					
		xchg	ax, cx
		retn
SectNumToVirtMemSeg endp


; ███████████████ S U B	R O U T	I N E ███████████████████████████████████████


input_impl	proc near		
		call	SectNumToVirtMemSeg ; Parses IO	request, on ret:
					; ds --	initial	sector offset in virtual mem
					; cx --	bytes count, 0xFFFF if too many	to fit in 16-bit reg
					; si --	0
		mov	es, cs:tranferAddrSeg
		mov	di, cs:tranferAddrOff
		mov	ax, di
		add	ax, cx
		jnb	short loc_FA
		mov	ax, 0FFFFh
		sub	ax, di
		mov	cx, ax

loc_FA:					
		mov	ax, cx
		mov	cl, 9		; : Div	by 512 -- ax = sectors
		shr	ax, cl

loc_100:				; Put sectors to read to CX
		mov	cx, ax
		mov	bx, cs

copySectorToBuf:			
		push	cx
		cli
		push	es
		push	di
		call	EnablTrans
		mov	es, bx		; ES = CS (DS -- seg. in virtual mem, SI = 0)
		mov	di, offset Buffer1
		mov	cx, 100h
		rep movsw		; Copy 256 words = 512 bytes, 1	sector,
					; from virtual mem to internal buffer
		call	DisablTrans
		pop	di
		pop	es		; Now ES -- target buffer
		sti
		push	ds
		push	si
		mov	ds, bx
		mov	si, offset Buffer1
		mov	cx, 100h
		rep movsw		; Copy from internal buffer to target buffer
		pop	si
		pop	ds
		pop	cx
		loop	copySectorToBuf
		retn
input_impl	endp


; ███████████████ S U B	R O U T	I N E ███████████████████████████████████████

; More details -- see input_impl function

output_impl	proc near		
		call	SectNumToVirtMemSeg ; Parses IO	request, on ret:
					; ds --	initial	sector offset in virtual mem
					; cx --	bytes count, 0xFFFF if too many	to fit in 16-bit reg
					; si --	0
		push	ds
		pop	es
		mov	di, si
		mov	ds, cs:tranferAddrSeg
		mov	si, cs:tranferAddrOff
		mov	ax, si
		add	ax, cx
		jnb	short loc_14B
		mov	ax, 0FFFFh
		sub	ax, si
		mov	cx, ax

loc_14B:				
		mov	ax, cx
		mov	cl, 9
		shr	ax, cl
		mov	cx, ax
		mov	bx, cs

copyBufToSector:			
		push	cx
		push	es
		push	di
		mov	es, bx
		assume es:seg000
		mov	di, offset Buffer1
		mov	cx, 100h
		rep movsw		; Copy sector from source to internal buffer
		pop	di
		pop	es
		assume es:nothing
		cli
		call	EnablTrans	; Copy from internal buffer to Virtual mem
		push	ds
		push	si
		mov	ds, bx
		mov	si, offset Buffer1
		mov	cx, 100h
		rep movsw
		pop	si
		pop	ds
		call	DisablTrans
		sti
		pop	cx
		loop	copyBufToSector
		retn
output_impl	endp


; ███████████████ S U B	R O U T	I N E ███████████████████████████████████████


EnablTrans	proc near		
					
		mov	al, 1
		out	0E0h, al
		retn
EnablTrans	endp


; ███████████████ S U B	R O U T	I N E ███████████████████████████████████████


DisablTrans	proc near		
					
		xor	al, al
		out	0E0h, al
		retn
DisablTrans	endp


; ███████████████ S U B	R O U T	I N E ███████████████████████████████████████


CheckVirtRam	proc near		
		mov	ax, es:[di]
		push	ax
		mov	word ptr es:[di], 0
		call	EnablTrans
		mov	word ptr es:[di], 1234h
		cmp	word ptr es:[di], 1234h
		jnz	short loc_1AF
		call	DisablTrans
		pop	ax
		cmp	word ptr es:[di], 0
		mov	es:[di], ax
		jnz	short loc_1B3
		clc
		retn
; ───────────────────────────────────────────────────────────────────────────

loc_1AF:				
		pop	ax
		mov	es:[di], ax

loc_1B3:				
		stc
		retn
CheckVirtRam	endp


; ███████████████ S U B	R O U T	I N E ███████████████████████████████████████


Strategy_Routine_0 proc	far		; 
		mov	word ptr cs:ReqPtr+2, es ; ES:BX -> Device Request Block
		mov	word ptr cs:ReqPtr, bx
		retf
Strategy_Routine_0 endp


; ███████████████ S U B	R O U T	I N E ███████████████████████████████████████


Interrupt_Routine_0 proc near		; 
		cld			; Device Request Block:
					; 0 db length
					; 1 db unit number
					; 2 db command code
					; 5 d? reserved
					; 0D d?	command	specific data
		push	bp
		push	ds
		push	es
		push	ax
		push	bx
		push	cx
		push	dx
		push	di
		push	si
		push	cs
		pop	ds
		assume ds:seg000
		les	bx, ReqPtr
		mov	al, es:[bx+2]	; Req block command code
		rol	al, 1		; Index	in DispatchTbl
		lea	di, DispatchTbl	; Init
		xor	ah, ah
		add	di, ax
		jmp	word ptr [di]
Interrupt_Routine_0 endp


; ███████████████ S U B	R O U T	I N E ███████████████████████████████████████


Init		proc far
		mov	dx, cs		; Format of request block (depends on command code)
					; https://www.delorie.com/djgpp/doc/rbinter/it/97/25.html
					; See also: https://kunyichen.wordpress.com/2008/06/21/bookmark-a-template-code-for-ms-dos-installable-device-driver/
		lea	ax, aYYearEveryItem ; After the end 
		mov	cl, 4
		ror	ax, cl
		add	dx, ax
		inc	dx
		mov	cs:VirtRAMBaseSeg, 2000h
		mov	word ptr es:[bx+0Eh], 0
		mov	es:[bx+10h], dx	; Save driver size into	req. block
		mov	byte ptr es:[bx+0Dh], 1	; 1 disk
		lea	dx, BPB_ptr
		mov	es:[bx+12h], dx	; Save ptr to BPB
		mov	word ptr es:[bx+14h], cs
		mov	bp, es
		mov	es, cs:VirtRAMBaseSeg
		xor	di, di
		call	CheckVirtRam
		jnb	short VirtRAMfound
		mov	es, bp
		mov	byte ptr es:[bx+0Dh], 0	; No disks
		mov	word ptr es:[bx+10h], cs
		mov	dx, offset aVirtualRamNotP ; "Virtual RAM not present.\r\nRAM Disk not "...
		jmp	OnLastMesg1
; ───────────────────────────────────────────────────────────────────────────

VirtRAMfound:				
		push	ds
		mov	ax, 40h	; '@'
		mov	ds, ax
		assume ds:nothing
		cmp	word ptr ds:72h, 1234h ; "1234H	means Ctrl+Alt+Del reboot is in	progress"
					; To preserve virt disk	on Ctrl+Alt+Del	reboot?
		pop	ds
		assume ds:nothing
		jnz	short formatVirtDisk ; Not cold	reboot
		call	EnablTrans
		push	ds
		push	es
		push	si
		push	di
		mov	si, 2000h	; Virt mem base	seg. (Instead of using the corresponding var...)
		mov	ds, si
		assume ds:nothing
		mov	si, cs
		mov	es, si
		assume es:seg000
		mov	si, 3
		mov	di, offset aNelIntl_OEM	; "NEL INTL"
		mov	cx, 4
		cld
		repe cmpsw		; Compares "NEL	INTL" string with the 3-11 bytes of the	virtual	mem
					; OEM string in	virtual	disk boot sector?
		pop	di
		pop	si
		pop	es
		assume es:nothing
		pop	ds
		assume ds:nothing
		jnz	short formatVirtDisk ; No ID string
		call	DisablTrans
		mov	dx, offset aJukoStRamdri_1 ; "Juko ST RAMDrive PRESERVED.\r\n\r\n$"
		jmp	short OnLastMesg1
; ───────────────────────────────────────────────────────────────────────────
		db  90h	; Р
; ───────────────────────────────────────────────────────────────────────────

formatVirtDisk:				
		call	EnablTrans
		lea	si, VirtDiskBootBPB
		mov	cx, 18h
		rep movsb		; Create boot sector
		mov	cs:req_sect_num, 1
		mov	cs:req_bytes_cnt, 3
		call	SectNumToVirtMemSeg ; Parses IO	request, on ret:
					; ds --	initial	sector offset in virtual mem
					; cx --	bytes count, 0xFFFF if too many	to fit in 16-bit reg
					; si --	0
		push	ds
		pop	es
		mov	di, si
		xor	al, al
		rep stosb		; Init FAT-1
		mov	byte ptr [si], 0FCh ; '№'
		mov	byte ptr [si+1], 0FFh
		mov	byte ptr [si+2], 0FFh
		push	ds
		push	si
		mov	cs:req_sect_num, 4
		mov	cs:req_bytes_cnt, 3
		call	SectNumToVirtMemSeg ; Parses IO	request, on ret:
					; ds --	initial	sector offset in virtual mem
					; cx --	bytes count, 0xFFFF if too many	to fit in 16-bit reg
					; si --	0
		push	ds
		pop	es
		mov	di, si
		pop	si
		pop	ds
		rep movsb		; Init FAT-2
		mov	cs:req_sect_num, 7
		mov	cs:req_bytes_cnt, 4
		call	SectNumToVirtMemSeg ; Parses IO	request, on ret:
					; ds --	initial	sector offset in virtual mem
					; cx --	bytes count, 0xFFFF if too many	to fit in 16-bit reg
					; si --	0
		xor	al, al
		push	ds
		pop	es
		xor	di, di
		push	cs
		pop	ds
		assume ds:seg000
		mov	si, offset virtDiskVolLabel ; "NEL DISK	01\bЖJЭ\x0F"
		push	cx
		mov	cx, 10h
		rep movsw		; Create volume	label
		pop	cx
		sub	cx, 20h	; ' '
		rep stosb		; Create other part of root dir
		call	DisablTrans
		mov	dx, offset aJukoStRamdri_0 ; "Juko ST RAMDrive 384K V2.10 INSTALLED.\r"...

OnLastMesg1:				
		mov	ah, 9
		int	21h		; DOS -	PRINT STRING
					; DS:DX	-> string terminated by	"$"
		mov	es, word ptr cs:ReqPtr+2
		mov	bx, word ptr cs:ReqPtr
		or	word ptr es:[bx+3], 100h ; set status, "busy", https://www.delorie.com/djgpp/doc/rbinter/it/96/25.html
		or	word ptr es:[bx+3], 0
		jmp	ExitDriver
; ───────────────────────────────────────────────────────────────────────────
virtDiskVolLabel db 'NEL DISK 01',8,0,0,0,0,0,0,0,0,0,0,'ЖJЭ',0Fh,0,0,0,0,0,0
					; 
					; Non-zero bytes in second half	-- modification	date-time
aJukoStRamdri_0	db 'Juko ST RAMDrive 384K V2.10 INSTALLED.',0Dh,0Ah ; 
		db 0Dh,0Ah,'$'
aJukoStRamdri_1	db 'Juko ST RAMDrive PRESERVED.',0Dh,0Ah ; 
		db 0Dh,0Ah,'$'
aVirtualRamNotP	db 'Virtual RAM not present.',0Dh,0Ah ; 
		db 'RAM Disk not installed.',0Dh,0Ah,'$'
; ───────────────────────────────────────────────────────────────────────────

Media_Chk:				; Media	not changed
		mov	byte ptr es:[bx+0Eh], 1
		or	word ptr es:[bx+3], 100h
		or	word ptr es:[bx+3], 0
		jmp	ExitDriver
; ───────────────────────────────────────────────────────────────────────────

Build_BPB:
		push	es
		push	bx
		mov	cs:req_sect_num, 0
		mov	cs:req_bytes_cnt, 1
		call	SectNumToVirtMemSeg ; Parses IO	request, on ret:
					; ds --	initial	sector offset in virtual mem
					; cx --	bytes count, 0xFFFF if too many	to fit in 16-bit reg
					; si --	0
		cli
		call	EnablTrans
		push	cs
		pop	es
		assume es:seg000
		lea	di, BPB_data
		add	si, 0Bh
		mov	cx, 0Dh
		rep movsb
		pop	bx
		pop	es
		assume es:nothing
		call	DisablTrans
		sti
		lea	dx, BPB_data
		mov	es:[bx+12h], dx
		mov	word ptr es:[bx+14h], cs
		mov	es:[bx+0Eh], dx
		mov	word ptr es:[bx+10h], cs
		or	word ptr es:[bx+3], 100h
		or	word ptr es:[bx+3], 0
		push	cs
		pop	ds
		jmp	ExitDriver
; ───────────────────────────────────────────────────────────────────────────

Ioctl_Inp:
		or	word ptr es:[bx+3], 100h
		or	word ptr es:[bx+3], 0
		jmp	short ExitDriver
; ───────────────────────────────────────────────────────────────────────────
		db  90h	; Р
; ───────────────────────────────────────────────────────────────────────────

Input:					
		call	ParseReq4IOv
		mov	ax, es:[bx+14h]	; Sector number
		mov	cs:req_sect_num, ax
		mov	ax, es:[bx+12h]	; Byte count
		mov	cs:req_bytes_cnt, ax
		call	input_impl
		mov	bx, word ptr cs:ReqPtr
		mov	es, word ptr cs:ReqPtr+2
		or	word ptr es:[bx+3], 100h
		or	word ptr es:[bx+3], 0
		jmp	short ExitDriver
; ───────────────────────────────────────────────────────────────────────────
		db  90h	; Р
; ───────────────────────────────────────────────────────────────────────────

Output:					
		call	ParseReq4IOv
		mov	ax, es:[bx+14h]
		mov	cs:req_sect_num, ax
		mov	ax, es:[bx+12h]
		mov	cs:req_bytes_cnt, ax
		call	output_impl	; More details -- see input_impl function
		mov	bx, word ptr cs:ReqPtr
		mov	es, word ptr cs:ReqPtr+2
		cmp	cs:doVerifyAftWr, 0
		jz	short VerifyAftW
		mov	cs:doVerifyAftWr, 0
		jmp	short Input
; ───────────────────────────────────────────────────────────────────────────

VerifyAftW:				
		or	word ptr es:[bx+3], 100h
		or	word ptr es:[bx+3], 0 ;	
		jmp	short ExitDriver
; ───────────────────────────────────────────────────────────────────────────
		db  90h	; Р
; ───────────────────────────────────────────────────────────────────────────

Outp_Vfy:
		mov	cs:doVerifyAftWr, 1
		jmp	short Output
; ───────────────────────────────────────────────────────────────────────────

ExitDriver:				
		pop	si
		pop	di
		pop	dx
		pop	cx
		pop	bx
		pop	ax
		pop	es
		pop	ds
		assume ds:nothing
		pop	bp
		retf
Init		endp ; sp =  12h

; ───────────────────────────────────────────────────────────────────────────
		db    0
		db    0
		db    0
		db    0
		db    0
		db    0
Buffer1		db 100h	dup(0)		; 0 ; 
		db 100h	dup(0)		; 
aYYearEveryItem:		
		; Looks like code below -- from the	sector slack space
seg000		ends


		end

Посилання

  • Інструкція до плати, під іменем UX Turbo, ver. 44256. Цікаво, що в документації помилка на титулці – ‘‘UT Tubo’’.
  • Трапляється також під назвою типу ‘‘JUKO BABY XT BXM/12’’ – або це теж клон.

Виноски

  1. Джампер JP8, коли відкритий, дозволяє обрати автодетектування ширини ISA. 

  2. На жаль, запит з допомогою int 10h не спрацював, тому покладаюся на вивід HWINFO16.EXE

  3. Chars per second. 

  4. І відеокарта сучасна, і процесор трохи швидший – то результат дивний… Більша ефективність BIOS? Труднощі відображення пам’яті?