MDV Low Level Routines
MDV Low Level Routines
Hi All,
I am curious to know, how some of the copy protected programs work. I learnt about tricks of checking that a dummy file lies on a bad block. I wonder what are the actual routines manipulating single blocks on a tape, is this done by some specific IPC controller routines, could these be found documented somewhere?
Would be also interesting to learn about MDV catalogs (allocation tables) and their structure for holding information on free, bad, and allocated blocks.
Many thanks in advance.
Tom
I am curious to know, how some of the copy protected programs work. I learnt about tricks of checking that a dummy file lies on a bad block. I wonder what are the actual routines manipulating single blocks on a tape, is this done by some specific IPC controller routines, could these be found documented somewhere?
Would be also interesting to learn about MDV catalogs (allocation tables) and their structure for holding information on free, bad, and allocated blocks.
Many thanks in advance.
Tom
-
- Aurora
- Posts: 852
- Joined: Tue Dec 17, 2013 1:17 pm
Re: MDV Low Level Routines
I don't know of any online sources of information on the data structure of the microdrives and the microdrive map. But there are system vectors $124 to $12A for accessing microdrive headers/sectors. see http://www.qdosmsq.dunbar-it.co.uk/doku ... tors:start
A couple of the copy protection schemes I have seen are -
Checking for the correct random number in the block header of a sector.
Messing about with the microdrive map, marking bad or non-existent sectors as good, and in use. So that trying to do a copy them would fail.
Martin.
A couple of the copy protection schemes I have seen are -
Checking for the correct random number in the block header of a sector.
Messing about with the microdrive map, marking bad or non-existent sectors as good, and in use. So that trying to do a copy them would fail.
Martin.
Re: MDV Low Level Routines
There were also schemes that used "impossibly high" sector numbers (like 254) that would normally not occur because that number of sectors wouldn't normally fit on the tape.
Tobias
Tobias
ʎɐqǝ ɯoɹɟ ǝq oʇ ƃuᴉoƃ ʇou sᴉ pɹɐoqʎǝʞ ʇxǝu ʎɯ 'ɹɐǝp ɥO
Re: MDV Low Level Routines
Hi,
I have hand coded this assembly listing from chapter 10.8. of QL Technical Guide.
I woud like to learn how to read and possibly write MDV sectors. I have also followed a discussion on MDV and its H/W registers in the forum here http://www.qlforum.co.uk/viewtopic.php?f=19&t=1269, and read through microdrive format description in A.C.Dickens QL Advanced User Guide.
My understanding is that, before calling any vectored routines MD.READ, MD.WRITE and the like, I need to select the drive first, and start it up to get it spinning. After completing the read, I need to spin all the drives down and deselect them.
The code is adjusted for GST (non-macro) Assembler, with some typos corrected.
When I pass drive no# in D3 and call "startup" routine, nothing hapens and drive does not start spinning, as I hope it should?
My idea is to print microdrive map with its BAD / USED / FREE sectors, I believe MD.READ is an ideal procedure for it. The sector holding the map should be no# 0, file no# $F8, block no# 0 , as per A.C.Dickens Guide.
EDIT: it works now, updated code above!
Many thanks
Tom
I have hand coded this assembly listing from chapter 10.8. of QL Technical Guide.
I woud like to learn how to read and possibly write MDV sectors. I have also followed a discussion on MDV and its H/W registers in the forum here http://www.qlforum.co.uk/viewtopic.php?f=19&t=1269, and read through microdrive format description in A.C.Dickens QL Advanced User Guide.
My understanding is that, before calling any vectored routines MD.READ, MD.WRITE and the like, I need to select the drive first, and start it up to get it spinning. After completing the read, I need to spin all the drives down and deselect them.
The code is adjusted for GST (non-macro) Assembler, with some typos corrected.
Code: Select all
title microdrive routines
include ram1_qdos1_in
; include ram1_qdos2_in
moveq #1,d3 ;test drive #1..8
jmp drive
sys_wser
move.b d0,-(sp) ;save operation
wait
subq.w #1,sv_timo(a0) ;decrement timeout
blt.s set_mode ;done?
move.w #(20000*15-82)/36,d0 ;time= 18*n+42 cycles
delay1
dbra d0,delay1 ;delay
bra.s wait ;repeat until timeout expires
set_mode
clr.w sv_timo(a0) ;clear wait
andi.b #pc.notmd,sv_tmode(a0) ;not RS232
move.b (sp)+,d0
or.b d0,sv_tmode(a0) ;either mdv or net
andi.b #$ff-pc.maskt,sv_pcint(a0) ;disable transmit interrupt
exit
move.b sv_tmode(a0),pc_tctrl ;set pc
rts
sys_rser
bclr #pc..serb,sv_tmode(a0) ;set RS232 mode
ori.b #pc.maskt,sv_pcint(a0) ;enable transmit interrupt
bra.s exit
md_desel
moveq #pc.desel,d2 ;clock in deselect bit first
moveq #7,d1 ;deselect all
bra.s sedes
md_selec
moveq #pc.selec,d2 ;clock in select bit first
subq.w #1,d1 ;and clock it through n times
sedes
clk_loop
move.b d2,(a3) ;clock high
moveq #(18*15-40)/4,d0 ;time=2*n+20 cycles
ror.l d0,d0
bclr #pc..sclk,d2 ;clock low
move.b d2,(a3) ;... clocks d2.0 into first
;drive
moveq #(18*15-40)/4,d0 ;time=2*n+ 20 cycles
ror.l d0,d0
moveq #pc.desel,d2 ;clock high - deselect bit
dbra d1,clk_loop ;next
rts
drive
bsr.s startup ;start drive
bne.s exit2 ;ill_drve exit?
move.w #$ffff,d0
delay2 nop
dbra d0,delay2 ;delay for spin up
bsr.s wind_dwn ;spin down
exit2 rts
; Routine to start up a microdrive.
; NB: RETURNS IN SUPERVIS0R M0DE (if d3= 1 to 8)
; d1 d1 smashed
; d2 d2 smashed
; d3 number of microdrive d3 preserved
; a0 a0 SV_BASE
; a3 a3 pc_mctrl (=18020h)
; errors:
; OR: microdrive out of range
startup
cmpi.l #1,d3 ;legal microdrive?
blt.s ill_drve ;jump if not
cmpi.l #8,d3 ;legal microdrive?
bgt.s ill_drve ;jump if not
move.l (sp)+,a3 ;a3=return address
moveq #mt.inf,d0 ;select MT.INF
trap #1 ;a0= ^to system variables
trap #0 ;supervisor mode
move.l a3,-(sp) ;'return' (geddit?) the
;return address
moveq #$10,d0 ;microdrive mode
bsr sys_wser ;wait for RS232 to complete
ori.w #$0700,sr ;shut out rest of world
move.l d3,d1 ;d1 is microdrive to be
;started
move.l #pc_mctrl,a3 ;a3= ^control register
bsr.s md_selec ;start it up
moveq #0,d0 ;no problems
rts ;return
ill_drve
moveq #-4,d0 ;error=out of range
rts
; Routine to wind down (al!!) microdrives
; N.B. MUST BE CALLED IN SUPERVIS0R M0DE
; d1 d1 smashed
; d2 d2 smashed
; a0 a0 SV_BASE
; a3 a3 ^instruction after call to here(!!)
wind_dwn
moveq #mt.inf,d0 ;select MT.INF
trap #1 ;a0= ^to system
;variables
move.l #pc_mctrl,a3 ;a3= ^control register
bsr.s md_desel ;wind it down
bsr sys_rser ;re-enable RS232
move.l (sp)+,a3 ;a3=return address
move.w #0,sr ;interrupts off
move.l a3,-(sp) ;'return' (it's a killer!)
;return addr.
rts ;return
end
My idea is to print microdrive map with its BAD / USED / FREE sectors, I believe MD.READ is an ideal procedure for it. The sector holding the map should be no# 0, file no# $F8, block no# 0 , as per A.C.Dickens Guide.
EDIT: it works now, updated code above!
Many thanks
Tom
Last edited by tcat on Sat Dec 12, 2015 6:54 pm, edited 12 times in total.
- NormanDunbar
- Forum Moderator
- Posts: 2273
- Joined: Tue Dec 14, 2010 9:04 am
- Location: Leeds, West Yorkshire, UK
- Contact:
Re: MDV Low Level Routines
Ah! Anyone looking there for microdrive information may be disappointed as it seems I have neglected to write those routines up. I'll get around to it at some point, time permitting, most likely after Christmas.Martin_Head wrote:I don't know of any online sources of information on the data structure of the microdrives and the microdrive map. But there are system vectors $124 to $12A for accessing microdrive headers/sectors. see http://www.qdosmsq.dunbar-it.co.uk/doku ... tors:start
Cheers,
Norm.
Why do they put lightning conductors on churches?
Author of Arduino Software Internals
Author of Arduino Interrupts
No longer on Twitter, find me on https://mastodon.scot/@NormanDunbar.
Author of Arduino Software Internals
Author of Arduino Interrupts
No longer on Twitter, find me on https://mastodon.scot/@NormanDunbar.
-
- Aurora
- Posts: 852
- Joined: Tue Dec 17, 2013 1:17 pm
Re: MDV Low Level Routines
Iv'e not really gone through all your code, to try to figure out whats wrong. But this code -
Is from my MDI driver, For reading microdrive cartridges. It can be found here http://www.dilwyn.me.uk/utils/MDIDriver106.zip on Dilwyn Jones's web site.
It starts the required MDV up, then reads all the sectors into a buffer, and then stops the MDV. If you just want to start the MDV, then stop it, you just need the code up to "MDV is up to speed. Start reading sectors", and then the code from "spin_dwn".
A couple of points which come to mind from when I developed this program are.
The stack pointer, try not to mess with it, don't put things on the stack, don't branch to subroutines, except for the official system calls.
You don't say which operating system you are using, But I had a lot of trouble getting it to work with Minerva, and I gave up on using it in Minerva in the end. (Minerva has it's own vectored routines to do the motor start up's and stop's, but like I said could not get them to work)
Martin Head
Code: Select all
; Read mdv sectors NOTE JS ROM Only
; ================
; From CALL in SuperBASIC
; use CALL start,mdv_No,buffer
; on entrty D1=MDV number 1-8
; D2=start address of buffer
; Note buffer must be at least 138,000 bytes, or routine will write past
; the end of it
move.l d2,a1 ;buffer base
lea buffBase,a0
move.l a1,(a0) ;Save buffer base
; Wipe the buffer clean
move.w #$86C3,d7 ;34499 ((138000/4)-1 for DBcc)
clr_buf move.l #$0,(a1)+ ;Set buffer contents to zero
dbf d7,clr_buf
; Test Drive number
cmp.l #$09,d1
bge bad_drv ;Drive number >8
tst.l d1
beq bad_drv ;Drive number =0
trap #0 ;Enter supervisor mode
or.w #$0700,sr ;Turn off interupets
; Start MDV turning
lea $18020,a3
move.b #-$19,d0 ;$E7 Mask
and.b $280A0,d0 ;Read SV.TMODE except Minrodrive bit
ori.b #$10,d0 ;Set Microdrive bit to 1
move.b d0,$18002 ;Write to Transmit Control register
move.b d0,$280A0 ;Write to SV_TMODE
movea.l #$2c56,a0 ;Start MDV
jsr (a0)
; Wait a couple of seconds
lea $18000,a0 ;PC_CLOCK
move.l (a0),d3 ;Get time in d3
add.l #$03,d3 ;Add 3 seconds
wait cmp.l (a0),d3
bne.s wait
; MDV is up to speed. Start reading sectors
move.w #$0101,d5 ;Number of sectors to read (257)
;This is more than any cartridge can hold
;So there will be some overlap
;(same sectors twice at start and end)
movea.l buffBase,a1 ;Recover the buffer start
sec_loop lea curBuff,a0
move.l a1,(a0) ;Save current buffer position
move.w d5,4(a0) ;Save counter
; Start reading sectors from MDV
ReadSec add.l #$02,a1 ;Leave space for return result
; Read the next Sector Header
lea $18020,a3
move.w $12a,a0 ;MD.SECTR
jsr $4000(a0) ;Read header
bra.s bm ;Bad Medium
bra.s bh ;Bad Header
hd_ok add.l #$06,a1 ;Make room for checksum + block header
; Read the Sector Block
lea $18020,a3
move.w $124,a0 ;MD.READ
jsr $4000(a0) ;Read sector
bra.s rf ;Read Fail
; Sector read ok
sec_ok movea.l curBuff,a1 ;Get start of current buffer
move.w $2(a1),d0 ;Get header flag and sector number
and.w #$00FF,d0 ;Mask the Flag
move.w d0,(a1) ;Store the read sector number
move.b d1,$12(a1) ;Store File number
move.b d2,$13(a1) ;Store Block number
; Sector read complete OK
bra.s upd_ptr ;Do the next sector
;Deal with problems
bm movea.l curBuff,a1 ;Get start of current buffer
move.w #$FFFF,(a1) ;Flag bad with -1
bra.s bad_med ;Stop MDV return to BASIC with Bad Medium
bh movea.l curBuff,a1 ;Get start of current buffer
move.w #$FFFE,(a1) ;Flag bad with -2
bra.s upd_ptr ;Do next sector
rf movea.l curBuff,a1 ;Get start of current buffer
move.w #$FFFD,(a1) ;Flag bad with -3
; bra.s upd_ptr ;Do next sector (next line)
; Update pointer and loop counter
upd_ptr movea.l curBuff,a1 ;Retrieve current buffer position
move.w count,d5 ;Retrieve counter
add.l #$0218,a1 ;Update pointer for next sector. Add 536
subq.w #$01,d5 ;Decrement counter
beq.s spin_dwn ;Finished loop
bra sec_loop ;Go round the loop again
; Stop the MDV Turning
spin_dwn movea.l #$2c50,a0 ;Stop MDV
jsr (a0)
move.b #-$19,d0 ;$E7
and.b $280A0,d0 ;Set Microdrive Turing to 0
move.b d0,$18002 ;Write to Transmit Control register
move.b d0,$000280A0 ;and SV_TMODE. ULA Transmit mode
exit andi.w #$D8FF,sr ;Enable Interrupts and user mode
moveq #0,d0 ;No Error
rts ;Return to BASIC
; Stop the MDV Turning and return to basic with a Bad Medium error
bad_med movea.l #$2c50,a0 ;Stop MDV
jsr (a0)
move.b #-$19,d0 ;$E7
and.b $280A0,d0 ;Set Microdrive Turing to 0
move.b d0,$18002 ;Write to Transmit Control register
move.b d0,$000280A0 ;and SV_TMODE. ULA Transmit mode
andi.w #$D8FF,sr ;Enable Interrupts and user mode
moveq #-$20,d0 ;Bad Medium
rts ;Return to BASIC
bad_drv moveq #-$7,d0 ;Return to basic with Not Found
rts
buffBase ds.l 1 ;Base address of buffer storage space
curBuff ds.l 1 ;Address in buffer of current sector
count ds.w 1 ;Counter storage
It starts the required MDV up, then reads all the sectors into a buffer, and then stops the MDV. If you just want to start the MDV, then stop it, you just need the code up to "MDV is up to speed. Start reading sectors", and then the code from "spin_dwn".
A couple of points which come to mind from when I developed this program are.
The stack pointer, try not to mess with it, don't put things on the stack, don't branch to subroutines, except for the official system calls.
You don't say which operating system you are using, But I had a lot of trouble getting it to work with Minerva, and I gave up on using it in Minerva in the end. (Minerva has it's own vectored routines to do the motor start up's and stop's, but like I said could not get them to work)
Martin Head
Re: MDV Low Level Routines
Martin,
Thank you for your code, I am also quite curious about MDV imaging, I only read so far about RAMPRT that allows to read the whole MDV content into the RAM disk, by using its FORMAT command.
BTW, meanwhile I updated my code, it works now, I introduced some delay in the test drive procedure, and I diverted the jump to it at the code start, moveq #1,d3 selects drive one, that spins up for a second and then spins down again.
I will study your code to learn about how to call MD.READ utility, set up buffer and its parameters.
Many thanks
Tom
Thank you for your code, I am also quite curious about MDV imaging, I only read so far about RAMPRT that allows to read the whole MDV content into the RAM disk, by using its FORMAT command.
BTW, meanwhile I updated my code, it works now, I introduced some delay in the test drive procedure, and I diverted the jump to it at the code start, moveq #1,d3 selects drive one, that spins up for a second and then spins down again.
I will study your code to learn about how to call MD.READ utility, set up buffer and its parameters.
Many thanks
Tom
-
- Aurora
- Posts: 852
- Joined: Tue Dec 17, 2013 1:17 pm
Re: MDV Low Level Routines
If you check out the MDI driver above, you will find routines to make image files from Microdrive cartridges, and to examine the contents of the sectors of the image files, with the Microdrive map displaying the good/used/free sectors.
The programs for displaying/editing the image files was written in SBasic on QPC2, so if you wanted to use them in QDOS they will need a bit of hacking about. Especially to make them fit on a 512 pixel screen.
Martin Head
The programs for displaying/editing the image files was written in SBasic on QPC2, so if you wanted to use them in QDOS they will need a bit of hacking about. Especially to make them fit on a 512 pixel screen.
Martin Head
Re: MDV Low Level Routines
Hi Martin,
Thank you, this is a very good lead for me, your assembly is neatly written. I now learnt from your code to use MD.SECTR and MD.READ properly. I can read sector No# 0 which holds the map and the actual directory which is file No# 0. Buffer needed just for a single block is 14(sector header)+512(block) bytes, as the block header does not seem put out by vectored routines.
I may now try to recover a deleted file, this would involve calling MD.WRITE as I hope, something yet to learn.
What is also interesting, that reading a single sector may take almost same time as all sectors 7-9 seconds in one tape loop pass.
Tom
Thank you, this is a very good lead for me, your assembly is neatly written. I now learnt from your code to use MD.SECTR and MD.READ properly. I can read sector No# 0 which holds the map and the actual directory which is file No# 0. Buffer needed just for a single block is 14(sector header)+512(block) bytes, as the block header does not seem put out by vectored routines.
I may now try to recover a deleted file, this would involve calling MD.WRITE as I hope, something yet to learn.
What is also interesting, that reading a single sector may take almost same time as all sectors 7-9 seconds in one tape loop pass.
Tom
-
- Aurora
- Posts: 852
- Joined: Tue Dec 17, 2013 1:17 pm
Re: MDV Low Level Routines
Working from memory, I think you would have to find the file number from the directory, Then scan all the block headers to find the sectors which belong to that file, Then update the map to mark those sectors as in use, Then update the file length in the directory. Have I forgotten anything?tcat wrote:I may now try to recover a deleted file, this would involve calling MD.WRITE as I hope, something yet to learn.
If the sector you want is the wrong side of the tape head, you have to wait for it to come around again.tcat wrote:What is also interesting, that reading a single sector may take almost same time as all sectors 7-9 seconds in one tape loop pass.
Martin Head