OSDev.org

The Place to Start for Operating System Developers
It is currently Thu Mar 28, 2024 12:14 pm

All times are UTC - 6 hours




Post new topic Reply to topic  [ 12 posts ] 
Author Message
 Post subject: wait_for_disk_interrupt
PostPosted: Sat Feb 27, 2016 1:35 am 
Offline
Member
Member

Joined: Mon Feb 12, 2007 4:45 am
Posts: 146
USER: readfile( readsector ( push lba; interrupt_to_disk_service) )

------------------------------------------------------------------------------------------

KERNEL: disk_service( pop lba; port_in (lba); wait_for_disk_interrupt; )

If don't want into polling whether disk_interrupt occurred, what can I do when step in wait_for_disk_interrupt?


Top
 Profile  
 
 Post subject: Re: wait_for_disk_interrupt
PostPosted: Sat Feb 27, 2016 1:50 am 
Online
Member
Member

Joined: Mon Mar 25, 2013 7:01 pm
Posts: 5099
Block the user thread until the data is ready.

(Or, use an asynchronous API and make the user thread worry about what to do until the data is ready.)


Top
 Profile  
 
 Post subject: Re: wait_for_disk_interrupt
PostPosted: Sat Feb 27, 2016 2:50 am 
Offline
Member
Member
User avatar

Joined: Sat Mar 31, 2012 3:07 am
Posts: 4591
Location: Chichester, UK
It's the same as when any thread is waiting for a resource. You block the thread and switch to the next ready one. It's the major cause of task switches. There's nothing special about the disk interrupt, and there are far longer waits involved for the keyboard interrupt.


Top
 Profile  
 
 Post subject: Re: wait_for_disk_interrupt
PostPosted: Sat Feb 27, 2016 3:25 am 
Offline
Member
Member

Joined: Mon Feb 12, 2007 4:45 am
Posts: 146
disk_service:
pop ( device, lba, buffer );
set ( device, lba , buffer , ret_addr ) in disk_interrupt;
mark the thread as suspended one
clear stack
jmp to task_queue


disk_interrupt:
determine( read | write, device , buffer);
port data from/to buffer
unmark the thread to be ready
interrupt_ret( thread )


Top
 Profile  
 
 Post subject: Re: wait_for_disk_interrupt
PostPosted: Sat Feb 27, 2016 7:50 am 
Offline
Member
Member

Joined: Mon Feb 12, 2007 4:45 am
Posts: 146
It works fine in Bochs. Keystroke triggers readsector(), then int48 issues parameter to ata_reg_set, then back to main loop.
When harddisk interrupt 46 occurs, port_in data to buffer.


Code:
            mov [KB_Scancode],byte 0
SystemLoop:         hlt
            cmp [KB_Scancode],byte 0
            je wait
            mov [KB_Scancode],byte 0
            push dword [VideoAddr]
            push dword 0
            call ATA_ReadSector
            add esp,8
wait:            jmp SystemLoop

InterruptHandler_46:
            pop ecx
            pop ecx
            pop ecx
            push dword [_eflags]
            push dword [_cs]
            push dword [_eip]

            pushad
            in al,ATA_Port_Primary_Status
            movzx eax,al
            mov [ATA_STATUS],eax
            push dword [ATA_BUFFER]
            push dword [ATA_STATUS]
            call ATA_Interrupt
            add esp,4

            mov al,EOI
            out PIC8259B_Port_Command,al
            out PIC8259A_Port_Command,al
            popad
            iret

InterruptHandler_48:      pop dword [_eip]
            pop dword [_cs]
            pop dword [_eflags]
            jmp SystemLoop

void   ATA_Interrupt(UINT Status,UWORD[256]* MEM)
{
   #asm
      {
      cld
      mov ecx,256
      mov edi,[MEM]
      mov dx,[ATA_PORT_DATA]
      rep insw
      }
}

void   ATA_ReadSector(UINT LBA,UWORD[256]* MEM)
{
   ATA_PORT_DATA      =ATA_Port_Primary_Data;
   ATA_PORT_ERROR      =ATA_Port_Primary_Error;
   ATA_PORT_FEATURE   =ATA_Port_Primary_Feature;
   ATA_PORT_SECTOR    =ATA_Port_Primary_Sectors;
   ATA_PORT_LBA0      =ATA_Port_Primary_LBA0;
   ATA_PORT_LBA8      =ATA_Port_Primary_LBA8;
   ATA_PORT_LBA16      =ATA_Port_Primary_LBA16;
   ATA_PORT_LBA24      =ATA_Port_Primary_LBA24;
   ATA_PORT_STATUS    =ATA_Port_Primary_Status;
   ATA_PORT_CMD      =ATA_Port_Primary_Command;
   ATA_PORT_ALTSTATUS   =ATA_Port_Primary_AlterStatus;
   ATA_PORT_DEVCTRL   =ATA_Port_Primary_DeviceCtrl;

   ATA_LBA0=LBA<<24>>24;
   ATA_LBA8=LBA<<16>>24;
   ATA_LBA16=LBA<<8>>24;
   ATA_LBA24=(LBA>>24) | ATA_DEV | ATA_Bit_Command_LBA;
   ATA_SECTOR=1;

   OutPortB(ATA_PORT_LBA0,ATA_LBA0);
   OutPortB(ATA_PORT_LBA8,ATA_LBA8);
   OutPortB(ATA_PORT_LBA16,ATA_LBA16);
   OutPortB(ATA_PORT_LBA24,ATA_LBA24);
   OutPortB(ATA_PORT_SECTOR,ATA_SECTOR);
   OutPortB(ATA_PORT_CMD,ATA_Cmd_Read);

   #asm int 48
}


Top
 Profile  
 
 Post subject: Re: wait_for_disk_interrupt
PostPosted: Sat Feb 27, 2016 9:13 am 
Offline
Member
Member

Joined: Tue Nov 08, 2011 11:35 am
Posts: 453
Ough.
to blackoil
This code, erm, it may work but there are a lot of things in it that should never ever be used^W^W^W^W be improved. Would you mind if someone lists them, so you can improve your style?


Top
 Profile  
 
 Post subject: Re: wait_for_disk_interrupt
PostPosted: Sat Feb 27, 2016 9:27 am 
Offline
Member
Member

Joined: Mon Feb 12, 2007 4:45 am
Posts: 146
yes


Top
 Profile  
 
 Post subject: Re: wait_for_disk_interrupt
PostPosted: Sat Feb 27, 2016 10:00 am 
Offline
Member
Member

Joined: Tue Nov 08, 2011 11:35 am
Posts: 453
Ok, here are some points that shouldn't bring any harm:

1. Global variables are evil in most cases. It's hard to control where and when they are assigned, they make functions non-reentrant (i.e. you can't call a function from it's callees or when it's already called in another thread), they easily lead to problems when you define variables with the same name in different modules. It looks like you use them to pass parameters and return values between ASM and C code but there are better ways to do it (it's time to read about calling conventions).
2. Where did you get those magic numbers (46, 48, etc)? Such non-trivial numbers are also kind of evil things - one should either find that these are universal constants (and give them sensible names) or suddenly understand that these numbers are not constants at all (such situation is very common) and these numbers should be somehow determined or configured on each machine (by reading/writing some registers or calling firmware and parsing structures with system information) and saved to variables.
3. It's better to stick to some known naming convention. It helps in bringing order in your code and avoiding some mistakes. Btw, in most conventions names in all-capitals (also called "CAPS" after CapsLock) style are for constants, so it's quite surprising to read when someone assings values to such variables.
4. It's better to avoid manual register allocation and anything that is longer than 1-2 lines in inline assembly. Here are good examples: http://wiki.osdev.org/Inline_Assembly/Examples#INx
5. It's better to use standard data types with well-defined parameters and behaviour (such as uint{8,16,32,64}_t from <stdint.h>) instead of unknown beasts such as UINT, UWORD, etc. Relying on integer overflow (such as in ATA_LBA0=LBA<<24>>24;) instead of using masks (look at this byte extraction: (LBA >> 24) & 0xFF) is also a harmful thing.
6. It may be OK for the beginning but it's better to separate working with ATA from working with PIC, so that you wouldn't have to change ATA code when you want to use PIC or APIC.
7. What compiler are you using? Is it MS's one? It may be not-the-best-choice for bare-metal development.


Top
 Profile  
 
 Post subject: Re: wait_for_disk_interrupt
PostPosted: Sat Feb 27, 2016 2:17 pm 
Offline
Member
Member
User avatar

Joined: Tue Mar 06, 2007 11:17 am
Posts: 1225
Nable wrote:
Ok, here are some points that shouldn't bring any harm:

1. Global variables are evil in most cases. It's hard to control where and when they are assigned, they make functions non-reentrant (i.e. you can't call a function from it's callees or when it's already called in another thread), they easily lead to problems when you define variables with the same name in different modules. It looks like you use them to pass parameters and return values between ASM and C code but there are better ways to do it (it's time to read about calling conventions).
If you already have code, even if it's procedural, what you can do is create a simple subsystem to load data.

Then you could encapsulate the values of the global variables as if it was a data object.

Then you could select between data sets and it would matter less to have many global variables if you can control them and fill them with those data objects or data packets that are encapsulated and actually separated from the actual application.

_________________
Live PC 1: Image Live PC 2: Image

YouTube:
http://youtube.com/@AltComp126/streams
http://youtube.com/@proyectos/streams

http://master.dl.sourceforge.net/projec ... 7z?viasf=1


Top
 Profile  
 
 Post subject: Re: wait_for_disk_interrupt
PostPosted: Sat Feb 27, 2016 8:51 pm 
Offline
Member
Member

Joined: Mon Feb 12, 2007 4:45 am
Posts: 146
Nable wrote:
Ok, here are some points that shouldn't bring any harm:

1. Global variables are evil in most cases. It's hard to control where and when they are assigned, they make functions non-reentrant (i.e. you can't call a function from it's callees or when it's already called in another thread), they easily lead to problems when you define variables with the same name in different modules. It looks like you use them to pass parameters and return values between ASM and C code but there are better ways to do it (it's time to read about calling conventions).
2. Where did you get those magic numbers (46, 48, etc)? Such non-trivial numbers are also kind of evil things - one should either find that these are universal constants (and give them sensible names) or suddenly understand that these numbers are not constants at all (such situation is very common) and these numbers should be somehow determined or configured on each machine (by reading/writing some registers or calling firmware and parsing structures with system information) and saved to variables.
3. It's better to stick to some known naming convention. It helps in bringing order in your code and avoiding some mistakes. Btw, in most conventions names in all-capitals (also called "CAPS" after CapsLock) style are for constants, so it's quite surprising to read when someone assings values to such variables.
4. It's better to avoid manual register allocation and anything that is longer than 1-2 lines in inline assembly. Here are good examples: http://wiki.osdev.org/Inline_Assembly/Examples#INx
5. It's better to use standard data types with well-defined parameters and behaviour (such as uint{8,16,32,64}_t from <stdint.h>) instead of unknown beasts such as UINT, UWORD, etc. Relying on integer overflow (such as in ATA_LBA0=LBA<<24>>24;) instead of using masks (look at this byte extraction: (LBA >> 24) & 0xFF) is also a harmful thing.
6. It may be OK for the beginning but it's better to separate working with ATA from working with PIC, so that you wouldn't have to change ATA code when you want to use PIC or APIC.
7. What compiler are you using? Is it MS's one? It may be not-the-best-choice for bare-metal development.


Thanks for your programming advice. I use my own language & bootstrapped compiler to build/test my logic.

My current ATA access logic is: One thread will be marked/unmarked to suspended in task queue, when the resource it requires is unavailble/available respectively. To support more concurrent threads, a queue mechanism or something etc. must be established.


Top
 Profile  
 
 Post subject: Re: wait_for_disk_interrupt
PostPosted: Sun Feb 28, 2016 10:38 am 
Offline
Member
Member
User avatar

Joined: Tue Mar 06, 2007 11:17 am
Posts: 1225
blackoil wrote:
Thanks for your programming advice. I use my own language & bootstrapped compiler to build/test my logic.

My current ATA access logic is: One thread will be marked/unmarked to suspended in task queue, when the resource it requires is unavailble/available respectively. To support more concurrent threads, a queue mechanism or something etc. must be established.
If you use any language of a higher level than assembly, starting with C, Java, JavaScript, BASIC, Visual Basic... you can do things much easier if you reimplement stack operations for push, pop, read, etc., be it for the low level CPU stack or for a higher level stack.

Have you ever thought about using stack operations to save GDT descriptor fields or to push/pop paging structures so that you can interpret and rebuild them later?

By reimplementing the stack functions and using a custom stack, you can easily treat all of the local and global variables (and even structures or structure fields) as regular CPU registers that you can preserve and restore. That stack can then contain any sort of mixed data. All you need is make sure that you have at least one of those stacks per thread and that you pop the stored values in order just like for the stack kept in SS:ESP so that the process state doesn't get destroyed.

If you think about it, using a stack again (which is in general very rare to do normally once we get to use a language with the level of C and above) allows you to simplify any component of the program. Thinking about it, there are few things that cannot be saved with a stack (for example, reserving more memory, the I/O ports of a hardware device), and even those things could be pushed and popped in the higher level stack to simplify things, reduce the number of variables required, implement a fundamental memory management sublayer managed more by the user than just by the kernel.




You could probably use 2 high level stacks (at the very least). One would be to keep the pending processes/threads and other would be to keep the processes you haven't run.

Then in the middle you could have the single running task for the current CPU. You would start with a stack with a single process. When requested by the scheduler, you would send it the pointer of the process stack. If there is more than 1 thread or task apart from the main process, you would need to pop the stack of threads from the process you are already running field by field and put it in the stack of suspended threads (you can save the fields or packed subfields one by one and rebuild a task descriptor or structure later or have the task/thread scheduler do so). Once you are left with the main thread (which probably would do little more than handle/start/stop/create/delete the rest of the threads, and do basic processing on system/user events to send them such events), you can pop the fields of the suspended tasks back to the tasks to run and you could pass a structure to a pointer of the built task structure of the most urgent task/thread ID while doing so (for instance inspecting the relevance or wake time of a task while popping back to the running processes task), or run every task while moving structures from the stack of the ones that are supposed to run and those that are suspended.


You could have an event stack per thread, then you would always have the most recent event on top of the stack.

You could mark an event in the stack as INVALID_EVENT followed by the number of bytes it contains. For example you could have the following header for all of the events of the stack:

Code:
dword EVENT_NAME
dword SIZE_IN_BYTES


Then the stack would be increased or decreased according to the size of the data including the size of the event name DWORD and header.


In this way you can mark an event you wish to discard and you can leave discarding it pending for later. It will distribute both time and reduce fragmentation because you just need to free the offset of the latest invalid event and then find the next valid one. Then the program will process pending free operations only when there is the immediate demand to do so (on demand).

As you can see, you get a simple and automatic sublayer that can allocate and free memory.

For a higher level stack, it's probably to have a forward stack instead of an inverted stack such as that of the x86 CPU itself. It's better for a regular application level because it allows you to allocate more memory and increase it and it also allows to do so without having to contain mostly inverted structure fields.

You can later implement functions to realign fields in the stack in a coherent way (for instance keeping the size of the most recent padding in the latest byte itself or in an external field), and in general to navigate in any order even when you have just one header per contained stack element/event/variable, etc...

The stack can have mixed data, raw bytes, words, dwords and structures such as events as long as only your thread itself handles it and knows what it left there since last time.

_________________
Live PC 1: Image Live PC 2: Image

YouTube:
http://youtube.com/@AltComp126/streams
http://youtube.com/@proyectos/streams

http://master.dl.sourceforge.net/projec ... 7z?viasf=1


Top
 Profile  
 
 Post subject: Re: wait_for_disk_interrupt
PostPosted: Sun Feb 28, 2016 9:43 pm 
Offline
Member
Member

Joined: Mon Feb 12, 2007 4:45 am
Posts: 146
Stacking context structure is good idea for FILO logic. Is it necessary to switch descriptors in GDT often?


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 12 posts ] 

All times are UTC - 6 hours


Who is online

Users browsing this forum: No registered users and 26 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group