MDV Low Level Routines

Anything QL Software or Programming Related.
Post Reply
tcat
Super Gold Card
Posts: 633
Joined: Fri Jan 18, 2013 5:27 pm
Location: Prague, Czech Republic

MDV Low Level Routines

Post by tcat »

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


Martin_Head
Aurora
Posts: 847
Joined: Tue Dec 17, 2013 1:17 pm

Re: MDV Low Level Routines

Post by Martin_Head »

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.


User avatar
tofro
Font of All Knowledge
Posts: 2685
Joined: Sun Feb 13, 2011 10:53 pm
Location: SW Germany

Re: MDV Low Level Routines

Post by tofro »

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


ʎɐqǝ ɯoɹɟ ǝq oʇ ƃuᴉoƃ ʇou sᴉ pɹɐoqʎǝʞ ʇxǝu ʎɯ 'ɹɐǝp ɥO
tcat
Super Gold Card
Posts: 633
Joined: Fri Jan 18, 2013 5:27 pm
Location: Prague, Czech Republic

Re: MDV Low Level Routines

Post by tcat »

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.

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     
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
Last edited by tcat on Sat Dec 12, 2015 6:54 pm, edited 12 times in total.


User avatar
NormanDunbar
Forum Moderator
Posts: 2251
Joined: Tue Dec 14, 2010 9:04 am
Location: Leeds, West Yorkshire, UK
Contact:

Re: MDV Low Level Routines

Post by NormanDunbar »

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
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.


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.
Martin_Head
Aurora
Posts: 847
Joined: Tue Dec 17, 2013 1:17 pm

Re: MDV Low Level Routines

Post by Martin_Head »

Iv'e not really gone through all your code, to try to figure out whats wrong. But this code -

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
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


tcat
Super Gold Card
Posts: 633
Joined: Fri Jan 18, 2013 5:27 pm
Location: Prague, Czech Republic

Re: MDV Low Level Routines

Post by tcat »

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


Martin_Head
Aurora
Posts: 847
Joined: Tue Dec 17, 2013 1:17 pm

Re: MDV Low Level Routines

Post by Martin_Head »

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


tcat
Super Gold Card
Posts: 633
Joined: Fri Jan 18, 2013 5:27 pm
Location: Prague, Czech Republic

Re: MDV Low Level Routines

Post by tcat »

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


Martin_Head
Aurora
Posts: 847
Joined: Tue Dec 17, 2013 1:17 pm

Re: MDV Low Level Routines

Post by Martin_Head »

tcat wrote:I may now try to recover a deleted file, this would involve calling MD.WRITE as I hope, something yet to learn.
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: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.
If the sector you want is the wrong side of the tape head, you have to wait for it to come around again.

Martin Head


Post Reply