О, это был долгий и увлекательнейший квест. Сначала на тестовых программках потренировался в программировании DMA и регистров ATA, в разных режимах, и как оно должно быть в норме, и симулируя всякие ошибки. В данном деле помогал уже упомянутый инструмент PDP11GUI. ODT, который я написал и зашил в ПЗУшку, обеспечивает стандартный диалог, поэтому в настройках PDP11GUI только указал подходящий вариант PDP-11 и получил удобное средство написания и запуска коротеньких тестовых программок. PDP11GUI имеет встроенный ассемблер, воспринимает синткасис MACRO11, генерирует бинарный файл и через диалог с ODT загружает его в память машины.Shaos wrote:А каким софтом такой IDE управляется?
Дальше, параллельно, воспользовался технологией загрузки операционки RT-11 через порт системного терминала. Трудностей также не возникло, потому что архитектура моей машинки похожа на ДВК-1. Убедился, таким образом, в работоспособности под операционкой RT-11. Эта же технология позволяет подставлять операционке образы дисков из файлов, эти же образы могут быть подставлены как диски в симуляторе SimH. И эти же образы могут подставляться и в TotalCommander при помощи специального плагина. То есть, имеется средство перемещения файлов между виртуальной машиной в SimH, моей настоящей машиной и вендовым TotalCommander.
Следующий процесс выглядел так. Изучил руководство по написанию драйверов устройств в RT-11 (там они называются device handlers, что по моему мнению более точно отображают их суть по сравнению с названием device drivers). В Notepad++ сочинял и редактировал исходный текст handler'а, в SimH ассемблировал и линковал, на своей машине пробовал, что получилось.
Когда драйвер более-менее получился, настал момент установки RT-11 на жесткий диск, что и было сделано, вместе с написанием начального загрузчика и допрограммированием его в ПЗУ.
В результате на данный момент имеется компактная машинка, почти полноценная PDP-11, самый близкий аналог PDP-11/03. Из периферии - стандартный системный терминал, таймер 50 Гц, ATA диск с обменом по DMA. На диске установлена RT-11.
Текст драйвера получился вот такой, см. ниже. Для вычисления адреса сектора из номера блока придумал алгоритм деления, который получился достаточно быстрым (подпрограмма DIVC). Скорее всего, он уже известен, но я готовые искать не стал, а сел с листом бумаги и придумал. Время деления на константу менее 170 мкс. Потом сравнил с готовым из исходников RT-11, мой раза в полтора лучше.
Code: Select all
.TITLE AD
; ATA DISK HANDLER FOR CUSTOM HARDWARE
; AUTHOR MIXA64
; PREAMBLE
.SBTTL PREAMBLE SECTION
.MCALL .DRDEF, .ADDR
.DRDEF AD, 53, FILST$, 39000., 177400, 220
DM$VEC = AD$VEC+4
ADDMCS = AD$CSR ; CSR FOR DMA CONTROLLER
ADDMWC = ADDMCS+2 ; WORD COUNT FOR DMA CONTROLLER, NEGATIVE
ADDMBA = ADDMCS+4 ; BASE ADDRESS FOR DMA BLOCK
ADACS0 = ADDMCS+6 ; ATA CS0 ACCESS
ADACS1 = ADDMCS+10 ; ATA CS1 ACCESS, BYTE
ADXREG = ADDMCS+11 ; INDEX REGISTER FOR ATA BUS ADDRESS, BYTE
ADINCS = ADDMCS+12 ; CSR FOR INTERRUPT CONTROLLER
ADDVEC = ADDMCS+14 ; VECTOR ADDRESS FOR DMA INTERRUPT
ADAVEC = ADDMCS+16 ; VECTOR ADDRESS FOR ATA INTERRUPT
ADECNT = 4 ; RETRY COUNTS IN CASE OF AN ERROR
ADNREG = 4 ; NUMBER OF AD$CSR-BASED REGS TO LOG
ATA.IM = 040 ; INTERRUPT MASK
DMA.IM = 020 ; INTERRUPT MASK
; HEADER
.SBTTL HEADER SECTION
.DRBEG AD
; I/O INITIATION
.SBTTL I/O INITIATION SECTION
; REGISTER ALLOCATION FOR DIVIDE SUBROUTINE
QUOT = %3
REMNDR = %1 ; DIVIDEND AND REMINDER
DIVSR = %2
DCNTR = %0
MOV #ADECNT, (PC)+
RETRY: .WORD 0
CLR (PC)+
FLAGS: .WORD 0
MOV ADCQE, R5 ; GET CURRENT QUEUE ELEMENT POINTER
; DDCQE POINTS TO Q.BLKN
; QUEUE ELEMENT STRUCTURE FRAGMENT:
; -4: Q.LINK
; -2: Q.CSW
; +0: Q.BLKN PHYSICAL BLOCK NUMBER (PBN)
; +2: Q.FUNC, Q.UNIT, Q.JNUM RESERVED[15], JOBNUMBER[14:11], DEVUNIT[10:8],
; +4: Q.BUFF USER BUFFER ADDRESS
; +6: Q.WCNT WORD COUNT
; WCNT > 0 MEANS READ
; WCNT < 0 MEANS WRITE
; WCNT = 0 MEANS SEEK
; NOTES:
; - UNIT NUMBER IS NOT USED IN EARLY EXPERIMENTAL VERSION, ASSUMED UNIT 0 ON
; - SEEK ACTUALLY MEANS DO NOTHING
;
; ACCEPTED GEOMETRY IS 17 SECTORS 5 HEADS 1027+ CYLINDERS
; CYLINDER IS (PBN/85)
; HEAD N IS (REMAINDER OF (PBN/85))/17, SECTOR N IS REMAINDER OF IT + 1
.ADDR #CMDBLK, R4
MOV R4, CBADDR ; SAVE TASK FILE ADDRESS FOR FUTURE USE
MOV (R5)+, (PC)+
PBN: .WORD 0 ; PHYSICAL BLOCK NUMBER, BASE OF TRANSACTION
MOV (R5)+, (PC)+
UNIT: .WORD 0 ; CONTAINS UNIT NUMBER, SPECIAL FUNCTION CODE, JOB NUMBER
MOV (R5)+, @#ADDMBA ; USER BUFFER BASE ADDRESS FOR CURRENT TRANSACTION
MOV #040, R0 ; ASSUME READ SECTOR COMMAND
MOV (R5), R1 ; WCNT COPY HERE
BNE 20$ ; TO NOP
JMP ADEXIT
20$: BPL 10$ ; TO READ
NEG R1
MOV #060, R0 ; IT WAS WRITE SECTOR COMMAND
BIS #100000, FLAGS ; SET WRITE BIT IN FLAGS
10$: MOV R0, 12(R4) ; COMMAND CODE IS READY, PUT IT IN IT'S PLACE IN CMDBLK
MOV R1, WCNT
ADD #255., R1
CLRB R1
SWAB R1
MOV R1, (R4) ; NUMBER OF SECTORS TO TRANSFER
; DIVIDE PBN BY 85
MOV PBN, REMNDR
MOV #10., DCNTR ; COUNTER
MOV #125000, DIVSR ; 85 SHIFTED TO THE LEFT END
CALL DIVC
; CYLINDER IS IN QUOT
MOV QUOT, 4(R4)
SWAB QUOT
MOV QUOT, 6(R4)
; DIVIDE REMNDR BY 17
MOV #3., DCNTR
MOV #000104, DIVSR ; 17 SHIFTED LEFT TWICE
CALL DIVC
MOV QUOT, 10(R4)
INC REMNDR
MOV REMNDR, 2(R4)
; COMMAND AND NUMBER OF SECTORS DONE.
; SECTOR ADDRESS DONE.
; TIME TO SEND THE COMMAND
; ALSO THIS IS AN ENTRY POINT FOR RETRY
MOVB #02, @#ADXREG ; SET INDEX REGISTER
MOV CBADDR, R2
MOV #ADACS0, R1
MOV #6., R0
MOV (R2)+, (R1) ; TRANSFER TASK FILE
SOB R0, .-2
TST FLAGS
BPL 30$ ; IF READ, BYPASS TO AWAIT INTERRUPT EXIT
; IF WRITE
; WAIT FOR DRQ
MOVB #17, @#ADXREG
BITB #010, @#ADACS0
BEQ .-6
MOVB #10, @#ADXREG
MOV #ADDMWC, R2
MOV #-256., (R2)
MOV #401, -(R2) ; START DMA TRANSFER, NO DMA INTERRUPT EXPECTED
30$:
; EXIT TO MONITOR, AWAIT FOR ATA INTERRUPT.
; AT THIS POINT ATA CONTROLLER IS INSTRUCTED AND BUSY WITH THE JOB
RTS PC
; INTERRUPT SERVICE
.SBTTL INTERRUPT SERVICE SECTION
.DRVTB AD, AD$VEC, ADINT
.DRVTB , DM$VEC, DMINT
.DRAST AD, 4
; CHECK FOR ERRORS
MOVB #17, @#ADXREG
MOV @#ADACS0, R3
BIT #001, R3
BNE ERDONE
MOV #ADDMWC, R1
TST (R1)
BNE ERDONE
MOVB #10, @#ADXREG
MOV WCNT, R0
TST FLAGS
BMI 10$
; READ SECTOR DONE, BUFFER IS TO TRANSFER
; WILL READ MORE?
CMP R0, #256.
BHI 20$
; NO.
MOV #505, R2 ; DMA TRANSFER W/INTERRUPTS
BR 25$ ; START DMA AND WAIT DMA INTERRUPT
; YES. TRANSFER CURRENT BUFFER WITH DMA INTERRUPTS DISABLED.
20$: MOV #256., R0
SUB R0, WCNT
MOV #405, R2 ; DMA TRANSFER NO INTERRUPTS
25$: NEG R0
MOV R0, (R1)
MOV R2, -(R1) ; START DMA TRANSFER
RTS PC ; WAIT FOR ATA INTERRUPT
10$:
; WRITE SECTOR DONE
; THE BUFFER TRANSFERRED, UPDATE WCNT
SUB #256., R0
BPL 30$ ; CHECK IF IT WAS THE LAST SECTOR IN SERIES
CLR R0 ; THE LAST
30$: MOV R0, WCNT
BEQ CKDRQC
BIT #010, R3
BEQ ERDONE ; DRQ SHOULD BE SET HERE
MOV #-256., (R1)
MOV #401, -(R1) ; START DMA TRANSFER, NO DMA INTERRUPT EXPECTED
RTS PC ; WAIT FOR ATA INTERRUPT
CKDRQC:
MOVB #17, @#ADXREG
BIT #010, @#ADACS0
BNE ERDONE ; DRQ SHOULD BE CLEAR HERE
BR DONE
.DRAST DM, 4
; FALL HERE UPON:
; - THE LAST DMA IN READ SERIES
; OR
; - FILLER DMA COMPLETION
; FILLER DMA?
MOV #ADDMCS, R1
MOV (R1), R0
CLR (R1)+
TST R0
BMI ERDONE ; CHECK BUS ERROR
TST (R1)
BNE ERDONE ; CHECK DMA COMPLETION
MOV WCNT, R0
BEQ CKDRQC ; IT WAS FILLER DMA
; THE LAST DMA
CLR WCNT
TSTB R0
BEQ CKDRQC ; NO FILLER REQUIRED
BIS #177400, R0
MOV R0, (R1)
MOV #501, -(R1) ; START FILLER DMA
RTS PC ; WAIT FOR DMA INTERRUPT
; I/O COMPLETION
.SBTTL I/O COMPLETION SECTION
ERDONE:
; XXX RESET THE CONTROLLER
MOV ADCQE,R5 ;HARD ERROR, POINT TO QUEUE ELEMENT
BIS #HDERR$,@-(R5) ;SET HARD ERROR STATUS IN CHANNEL
DONE:
ADEXIT:
.DRFIN AD
; HANDLER SUBROUTINES
; DIVC DIVIDE BY CONSTANT
; USES 4 REGISTERS
; REMINDER REPLACES THE DIVIDEND
; TDIV 168 US
DIVC:
CLR QUOT
BR DENTRY
SR1: ROR DIVSR ; C IS ZERO FROM ASL QUOT AT QUO1
DENTRY:
SUB DIVSR, REMNDR
BLO QUO0
SEC
QUO1:
ROL QUOT
SOB DCNTR, SR1
BR DDONE
SR2: ROR DIVSR ; C IS ZERO FROM ASL QUOT AT QUO0
ADD DIVSR, REMNDR
BLO QUO1 ; C IS SET IF BRANCH
QUO0:
ASL QUOT
SOB DCNTR, SR2
ADD DIVSR, REMNDR
DDONE: RTS PC
WCNT: .WORD 0 ;
CBADDR: .WORD 0
CMDBLK: .WORD 0 ; +0 SECTOR COUNT
.WORD 0 ; +2 SECTOR NUMBER
.WORD 0 ; +4 CYLINDER NUMBER LOW
.WORD 0 ; +6 CYLINDER NUMBER HIGH
.WORD 0 ; +10 DRIVE SELECT & HEAD NUMBER
.WORD 0 ; +12 COMMAND
; BOOTSTRAP DRIVER
.SBTTL BOOTSTRAP DRIVER
.DRBOT AD,BOOT1,READ
. = ADBOOT+40 ; PUT THE JUMP BOOT INTO SYSCOM AREA
BOOT1: JMP @#BOOT-ADBOOT ; START THE BOOTSTRAP
. = ADBOOT+210
; READS FROM THE DEVICE
; PARAMETERS:
; R0 BLOCK NUMBER
; R1 WORD COUNT
; R2 MEMORY BUFFER
; ATA REGS ALLOCATION
; +0 SECTOR COUNT
; +2 SECTOR NUMBER
; +4 CYLINDER NUMBER LOW
; +6 CYLINDER NUMBER HIGH
; +10 DRIVE SELECT & HEAD NUMBER
; +12 COMMAND
READ:
MOVB #02, @#ADXREG ; SET INDEX REGISTER
MOV #ADACS0, R4 ; ATA CS0
MOV R1, R3 ; WCNT
ADD #255., R3
CLRB R3
SWAB R3
MOV R3, (R4) ; +0, NUMBER OF SECTORS TO TRANSFER
; DIVIDE PBN IN R0 BY 85
JSR R4, BDIVC ; R0 PBN
.WORD 10. ; DCNTR
.WORD 125000 ; DIVSR 85 SHIFTED TO THE LEFT END
; CYLINDER IS IN R3, SAVE IT TO R5
MOV R5, R5
; DIVIDE REMNDR IN R0 BY 17
JSR R4, BDIVC
.WORD 3. ; DCNTR
.WORD 000104 ; DIVSR 17 SHIFTED LEFT TWICE
; HEAD IS IN R3, SECTOR IS IN R0
INC R0 ; SECTOR NO STARTS WITH 1
MOV R0, (R4) ; +2, SECTOR NO
MOV R5, (R4) ; +4, CYLINDER LOW
SWAB R5
MOV R5, (R4) ; +6, CYLINDER HIGH
MOV R3, (R4) ; +10, HEAD NO
; NUMBER OF SECTORS DONE.
; SECTOR ADDRESS DONE.
; TIME TO SEND THE COMMAND
MOV #040, (R4) ; +12, READ SECTORS COMMAND
; WAIT FOR DRQ
DRQWAI: MOVB #17, @#ADXREG ; SET INDEX REGISTER
BIT #010, (R4)
BEQ .-4
; CHECK FOR AN ERROR
BIT #001, (R4)
BNE RDERR
; READ DATA FROM BUFFER INTO MEMORY
1$: MOVB #07, @#ADXREG ; SET INDEX REGISTER
BIT #010, (R4)
BEQ DRQWAI
MOV (R4), (R2)+
SOB R1, 1$
; EMPTY THE REST OF THE BUFFER
2$: MOVB #07, @#ADXREG ; SET INDEX REGISTER
BIT #010, (R4)
BEQ RDONE
CMP (R4), (R4)
BR 2$
RDONE:
CLC
RTS PC
RDERR:
JMP @#<BIOERR-ADBOOT>
; DIVIDEND, REMINDER R0
; QUOT R3
; DIVSR (SP)
; DCNTR 2(SP)
BDIVC:
CLR R3
MOV (R4)+, -(SP) ; DCNTR
MOV (R4)+, -(SP) ; DIVSR
BDENTR:
SUB (SP)+, R0 ; _DIVSR_ -> DCNTR
BLO BQUO0
SEC
BQUO1: ROL R3
DEC (SP) ; _DCNTR_
BEQ BDDONE
ROR -(SP) ; C IS ZERO FROM ASL R3 AT QUO1 DCNTR -> _DIVSR_
BR BDENTR
BSR: ROR -(SP) ; C IS ZERO FROM ASL R3 AT QUO0 DCNTR -> _DIVSR_
ADD (SP)+, R0 ; _DIVSR_ -> DCNTR
BLO BQUO1 ; C IS SET IF BRANCH
BQUO0:
ASL R3
DEC (SP) ; _DCNTR_
BNE BSR
ADD -(SP), R0 ; DCNTR -> _DIVSR_
TST (SP)+ ; _DIVSR_ -> DCNTR
BDDONE: TST (SP)+ ; _DCNTR_ -> R4_SAV
RTS R4
.=ADBOOT+612
BOOT: MOV #10000,SP ; SET UP THE STACK POINTER
MOV #2,R0 ; BLOCK NUMBER OF SECONDARY BOOTSTRAP
MOV #<4*256.>,R1 ; WORD COUNT OF 4 BLOCKS (2-5)
MOV #1000,R2 ; MEMORY ADDRESS OF SECONDARY BOOT (B$BOOT)
CALL READ ; LOAD THE SECONDARY BOOT
MOV #<READ-ADBOOT>,@#B$READ ; STORE POINTER TO READ ROUTINE
MOV #B$DNAM,@#B$DEVN ; STORE RAD50 DEVICE NAME
CLR @#B$DEVU ; STORE UNIT NUMBER (ALWAYS 0)
JMP @#B$BOOT ; ENTER THE SECONDARY BOOT
; HANDLER TERMINATION
.SBTTL HANDLER TERMINATION SECTION
.DREND AD
.END