Page 2 of 2

Posted: Sun Mar 16, 2008 1:56 pm
by Dex
Philip wrote:anybody has a sample code to access hdd using port instructions like in and out in assembly?
Now when you say this do you mean C sample code ?, as there was sample code in the link i posted.
Just in case

NOTE: THE THIS CODE WILL OVER WRITE YOUR MBR, so do not run it with these settings.

Code: Select all

;       Reading the harddisk using ports!
;       +-------------------------------+   by qark
;
;
;  This took me months to get working but I finally managed it.
;
;  This code only works for the 286+ so you must detect for 8088's somewhere
;  in your code.
;
;  Technical Information on the ports:
;      Port    Read/Write   Misc
;     ------  ------------ -------------------------------------------------
;       1f0       r/w       data register, the bytes are written/read here
;       1f1       r         error register  (look these values up yourself)
;       1f2       r/w       sector count, how many sectors to read/write
;       1f3       r/w       sector number, the actual sector wanted
;       1f4       r/w       cylinder low, cylinders is 0-1024
;       1f5       r/w       cylinder high, this makes up the rest of the 1024
;       1f6       r/w       drive/head
;                              bit 7 = 1
;                              bit 6 = 0
;                              bit 5 = 1
;                              bit 4 = 0  drive 0 select
;                                    = 1  drive 1 select
;                              bit 3-0    head select bits
;       1f7       r         status register
;                              bit 7 = 1  controller is executing a command
;                              bit 6 = 1  drive is ready
;                              bit 5 = 1  write fault
;                              bit 4 = 1  seek complete
;                              bit 3 = 1  sector buffer requires servicing
;                              bit 2 = 1  disk data read corrected
;                              bit 1 = 1  index - set to 1 each revolution
;                              bit 0 = 1  previous command ended in an error
;       1f7       w         command register
;                            commands:
;                              50h format track
;                              20h read sectors with retry
;                              21h read sectors without retry
;                              22h read long with retry
;                              23h read long without retry
;                              30h write sectors with retry
;                              31h write sectors without retry
;                              32h write long with retry
;                              33h write long without retry
;
;  Most of these should work on even non-IDE hard disks.
;  This code is for reading, the code for writing is the next article.



	mov     dx,1f6h         ;Drive and head port
	mov     al,0a0h         ;Drive 0, head 0
	out     dx,al

	mov     dx,1f2h         ;Sector count port
	mov     al,1            ;Read one sector
	out     dx,al

	mov     dx,1f3h         ;Sector number port
	mov     al,1            ;Read sector one
	out     dx,al

	mov     dx,1f4h         ;Cylinder low port
	mov     al,0            ;Cylinder 0
	out     dx,al

	mov     dx,1f5h         ;Cylinder high port
	mov     al,0            ;The rest of the cylinder 0
	out     dx,al

	mov     dx,1f7h         ;Command port
	mov     al,20h          ;Read with retry.
	out     dx,al
still_going:
	in      al,dx
	test    al,8            ;This means the sector buffer requires
				;servicing.
	jz      still_going     ;Don't continue until the sector buffer
				;is ready.

	mov     cx,512/2        ;One sector /2
	mov     di,offset buffer
	mov     dx,1f0h         ;Data port - data comes in and out of here.
	rep     insw

;   ------

	mov     ax,201h         ;Read using int13h then compare buffers.
	mov     dx,80h
	mov     cx,1
	mov     bx,offset buffer2
	int     13h

	mov     cx,512
	mov     si,offset buffer
	mov     di,offset buffer2
	repe    cmpsb
	jne     failure
	mov     ah,9
	mov     dx,offset readmsg
	int     21h
	jmp     good_exit
failure:
	mov     ah,9
	mov     dx,offset failmsg
	int     21h
good_exit:
	mov     ax,4c00h        ;Exit the program
	int     21h

	readmsg db      'The buffers match.  Hard disk read using ports.$'
	failmsg db      'The buffers do not match.$'

buffer  db      512 dup ('V')
buffer2 db      512 dup ('L')




;
;       Writing to the hard disk using the ports!     by qark
;       +---------------------------------------+
;
;  The only differences between reading and writing using the ports is
;  that 30h is sent to the command register, and instead of INSW you
;  OUTSW.  
;


	mov     dx,1f6h         ;Drive and head port
	mov     al,0a0h         ;Drive 0, head 0
	out     dx,al

	mov     dx,1f2h         ;Sector count port
	mov     al,1            ;Write one sector
	out     dx,al

	mov     dx,1f3h         ;Sector number port
	mov     al,1           ;Wrote to sector one
	out     dx,al

	mov     dx,1f4h         ;Cylinder low port
	mov     al,0            ;Cylinder 0
	out     dx,al

	mov     dx,1f5h         ;Cylinder high port
	mov     al,0            ;The rest of the cylinder 0
	out     dx,al

	mov     dx,1f7h         ;Command port
	mov     al,30h          ;Write with retry.
	out     dx,al
oogle:
	in      al,dx
	test    al,8            ;Wait for sector buffer ready.
	jz      oogle

	mov     cx,512/2        ;One sector /2
	mov     si,offset buffer
	mov     dx,1f0h         ;Data port - data comes in and out of here.
	rep     outsw           ;Send it.

;    ------------

	mov     ax,201h                 ;We'll read in sector 1 using
	mov     bx,offset buffer2       ;int13h and see if we are successful.
	mov     cx,1
	mov     dx,80h
	int     13h

	mov     cx,512
	mov     si,offset buffer
	mov     di,offset buffer2
	repe    cmpsb                   ;Compare the buffers.
	jne     failure

	mov     ah,9
	mov     dx,offset write_msg
	int     21h
	jmp     w_exit
failure:
	mov     ah,9
	mov     dx,offset fail
	int     21h

w_exit:
	mov     ax,4c00h        ;Exit the program
	int     21h

	write_msg       db      'Sector one written to using the ports, OH NO! there goes XP.$'
	fail            db      'Writing using ports failed.$'

buffer  db      512 dup ('A')
buffer2 db      512 dup ('D')

Posted: Sun Mar 16, 2008 2:07 pm
by jzgriffin
Dex: You might want to put your warning before the code. Some people don't bother reading past it; they just copy & paste. ;-)

Posted: Sun Mar 16, 2008 2:13 pm
by Dex
Jeremiah Griffin wrote:Dex: You might want to put your warning before the code. Some people don't bother reading past it; they just copy & paste. ;-)
Good point Jeremiah Griffin, i have modded it, as you suggested.

Posted: Mon Mar 17, 2008 2:39 am
by zaleschiemilgabriel
Ready4Dis wrote:Yes, like I said, basic PIO is simple[...]. So, I suggest that you, like many others, start with some code that scans the standard locations for devices, and write some code to read/write to them at a specific sector. Then something that can parse and MBR for partitions, then a FS driver so you can read files. Writing isn't important until you get a lot of bugs worked out (and I would suggest leaving it disabled until that point, unless you absolutely know it works, and you need to log something to disk for debugging, in which case I would suggest writing a serial port driver with another machine cnostantly reading inputs).
Thanks for all the good advice. I already did everything you said, and tested it inside virtual machines by reading an entire hard disk and displaying it "page-by-page" on the screen, so testing and bug fixing is over for now. However, when I deployed my OS to my real PC, the problem I found was that it had no legacy drives (I only have SATA drives), so detecting PCI configuration is a must. I also have that handy. It's just a matter of putting the two together. I think you got the wrong idea of what PCI does . PCI support both legacy and compatible drives. It does not impose that you use DMA only transfer. You can just use PIO mode for transfer, only the ports you read/write have to be read from PCI configuration space, if I'm not mistaken.
So for now, I'll do with PIO mode, as you suggest. I wasn't really planing on DMA transfer. I was just reading about them and I got curious. Thanks again.
One more question: What about PIO interrupts? Has anyone used them? Or polling for command completion is enough? Is this also the case if multiprocessing is enabled? If I post a command from a processor and i t start polling, can I be assured that another processor won't be doing the same and stealing away the results?

Posted: Mon Mar 17, 2008 3:35 am
by Ready4Dis
zaleschiemilgabriel wrote:
Ready4Dis wrote:Yes, like I said, basic PIO is simple[...]. So, I suggest that you, like many others, start with some code that scans the standard locations for devices, and write some code to read/write to them at a specific sector. Then something that can parse and MBR for partitions, then a FS driver so you can read files. Writing isn't important until you get a lot of bugs worked out (and I would suggest leaving it disabled until that point, unless you absolutely know it works, and you need to log something to disk for debugging, in which case I would suggest writing a serial port driver with another machine cnostantly reading inputs).
Thanks for all the good advice. I already did everything you said, and tested it inside virtual machines by reading an entire hard disk and displaying it "page-by-page" on the screen, so testing and bug fixing is over for now. However, when I deployed my OS to my real PC, the problem I found was that it had no legacy drives (I only have SATA drives), so detecting PCI configuration is a must. I also have that handy. It's just a matter of putting the two together. I think you got the wrong idea of what PCI does . PCI support both legacy and compatible drives. It does not impose that you use DMA only transfer. You can just use PIO mode for transfer, only the ports you read/write have to be read from PCI configuration space, if I'm not mistaken.
So for now, I'll do with PIO mode, as you suggest. I wasn't really planing on DMA transfer. I was just reading about them and I got curious. Thanks again.
One more question: What about PIO interrupts? Has anyone used them? Or polling for command completion is enough? Is this also the case if multiprocessing is enabled? If I post a command from a processor and i t start polling, can I be assured that another processor won't be doing the same and stealing away the results?
Sorry, I didn't mean it to come out that way, yes, you can use PIO with PCI, I was just saying that when you are tying EVERYTHING in together (including DMA) that it becomes very complex. I do recommend taking it one step at a time. By the way, SATA can be done via legacy (if the IDE controller supports it of course), a SATA controller is backwards compatible, and most of the ones i'm aware of support the legacy I/O ports and detection methods (well, detection methods vary greatly, and as mentioned, aren't always accurate to the specs, so be careful). Enable PCI mode (instead of legacy mode) means the ports listed in the PCI BAR's for that device are to be used, while leaving it in legacy mode, you/the controller ignore what the BAR's have in them. Either mode can use PIO or DMA, it's just a matter of what the ports and memory is used (and what commands you use).

About interrupts, yes I use them, however not 100% (like my task manager doesn't manage putting tasks to sleep until interrupts occur), I just have a function that waits for an interrupt with a variable for timeout period, it simply relinquishes the tasks time slice each time in the loop until the interrupt fires (or the time expired), so it's not much more efficient than polling and doing the same thing, but it will be useful once i finish up my task manager more.

--- Edit ---
Forgot to mention the last part, if you are going to use polling, I would, read from the alternate status port, because once you read from the regular status port, you clear it, so if you try reading it twice before checking for a specific condition, you will lose the data from the first read. Typically with something like a disk driver, you would only have a single interface, and a message queue. Since you can't physically read 2 devices on the same channel at the same time, it is enough to queue up commands to issue (and memory locations to write to, and possibly a way to wakeup a thread, or a call-back, or flag to set). This way, your application would request 10 blocks of data from hard disk, and another could be asking for some too. Your disk driver will process one than the other (based on which came in first, or possibly a priority list), so there is no chance of 2 app's checking the same status register at the same time. You could also sort the queue based on I/O address, because you CAN access 2 devices on different ports simultaneously. Anyways, once the read or whatever is finished it would set a flag, or wake up a thread, or call a call-back, or send a message. The cool thing is, this is how asynchronous transfers work... your application can be loading something, while doing other things, while checking a flag, or receive a message saying it was complete. Block I/O would just be an abstraction on top of this (like putting the process to sleep until the I/O request is done).