Copyright (c) Hyperion Entertainment and contributors.
Supporting Disk Change Events
Notes for file systems and other clients
Device drivers either do not implement the TD_ADDCHANGEINT/TD_REMCHANGEINT commands at all, or they implement them in a manner which is inconsistent with how device I/O commands are normally used. If you use these commands in your own software, then you should follow a specific protocol so as to minimize the risk of causing your software to hang or to crash the device driver.
- You must use the same 'struct IOStdReq' both for installing the change interrupt handler with TD_ADDCHANGEINT and for removing it later using TD_REMCHANGEINT. The IOStdReq->io_Data and IOStdReq->io_Length fields should not change between the use of the TD_ADDCHANGEINT/TD_REMCHANGEINT commands.
- You must not free the IOStdReq you used with the TD_ADDCHANGEINT command until after you have removed the change interrupt handler with the TD_REMCHANGEINT command.
- After you have sent the TD_ADDCHANGEINT command you must not use the IOStdReq for any other purpose except for eventually performing the TD_REMCHANGEINT command, which removes the change interrupt handler.
- Never use DoIO() with the TD_ADDCHANGEINT command. Either use SendIO(), or if you must use BeginIO() then you must make sure that the IOB_QUICK flag in the IOStdReq->io_Flags field is not set, like so:
change_request->io_Flags &= ~IOF_QUICK;
- When you remove the change interrupt handler with the TD_REMCHANGEINT command, you must make sure that the TD_ADDCHANGEINT command was successful, and if not, then you will have to remove the IOStdReq, like so:
/* Was the TD_ADDCHANGEINT command successful? */
if(IExec->CheckIO((struct IORequest *)change_request) == NULL)
{
change_request->io_Command = TD_REMCHANGEINT;
IExec->DoIO((struct IORequest *)change_request);
}
/* The TD_ADDCHANGEINT command was not successful and has been rejected. */
else
{
IExec->WaitIO((struct IORequest *)change_request);
}
- Never use SendIO() with the TD_REMCHANGEINT command. Always use DoIO(), and if you must use BeginIO() then you must make sure that the IOB_QUICK flag in the IOStdReq->io_Flags field is set, like so:
change_request->io_Flags |= IOF_QUICK;
- Never use the TD_REMCHANGEINT command unless you have used the TD_ADDCHANGEINT command first.
Notes for device driver implementors
Client software which uses the TD_ADDCHANGEINT/TD_REMCHANGEINT makes assumptions about how these commands act. The basic assumption is that none of these commands can fail, which is why your device driver should implement these commands in a specific fashion.
- The TD_ADDCHANGEINT command may be received with the IOB_QUICK flag set in the IOStdReq->io_Flags field. You must initialize the IOStdReq fields as follows and then store the IOStdReq in a list for later use:
/* All IOStdReqs which were installed with the TD_ADDCHANGEINT
command are expected to be stored in a list. */
struct List * change_request_list;
change_request->io_Message.mn_Node.ln_Type = NT_MESSAGE;
change_request->io_Flags &= ~IOF_QUICK;
change_request->io_Error = 0;
/* Before you start accessing the IOStdReq list you will want to
protect yourself against list modification. In this example
we use Forbid() and Permit(); you might want to use a different
method. Make sure to protect adding/removing IOStdReqs in the
same manner. */
IExec->Forbid();
IExec->AddTail(change_request_list,&change_request->io_Message.mn_Node);
IExec->Permit();
- You may want to verify that the contents of the IOStdReq->io_Data field are sound, i.e. they refer to a valid address space and the 'struct Interrupt' pointed to contains a valid Interrupt->is_Code field.
- When a medium change is detected, you should invoke each interrupt change handlers which were installed through the TD_ADDCHANGEINT command in the following fashion:
/* All IOStdReqs which were installed with the TD_ADDCHANGEINT
command are expected to be stored in a list. */
struct List * change_request_list;
struct IOStdReq * req;
/* Before you start accessing the IOStdReq list you will want to
protect yourself against list modification. In this example
we use Forbid() and Permit(); you might want to use a different
method. Make sure to protect adding/removing IOStdReqs in the
same manner. */
IExec->Forbid();
for(req = (struct IOStdReq *)IExec->GetHead(change_request_list) ;
req != NULL ;
req = (struct IOStdReq *)IExec->GetSucc(&req->io_Message.mn_Node))
{
IExec->Cause((struct Interrupt *)req->io_Data);
}
IExec->Permit();
- The TD_REMCHANGEINT command does not conform to the rules of how device commands are normally handled. The TD_REMCHANGEINT command must always be processed as if the IOB_QUICK flag were set in the IOStdReq->io_Flags field, regardless of whether this is actually the case. You must never store the IOStdReq in a list for later processing, you must always process it immediately, like so:
/* All IOStdReqs which were installed with the TD_ADDCHANGEINT
command are expected to be stored in a list. */
struct List * change_request_list;
struct IOStdReq * req;
/* This is the IOStdReq which refers to the TD_REMCHANGEINT command
which we need to perform now. */
struct IOStdReq * change_request;
/* Make sure that the TD_REMCHANGEINT command refers to an
IOStdReq which has been used. */
IExec->Forbid();
for(req = (struct IOStdReq *)IExec->GetHead(change_request_list) ;
req != NULL ;
req = (struct IOStdReq *)IExec->GetSucc(&req->io_Message.mn_Node))
{
if(req == change_request)
{
IExec->Remove(&change_request->io_Message.mn_Node);
if((change_request->io_Flags & IOF_QUICK) == 0)
IExec->ReplyMsg(&change_request->io_Message);
break;
}
}
IExec->Permit();
- It is not strictly necessary for all storage device drivers to implement the TD_ADDCHANGEINT/TD_REMCHANGEINT commands. Because existing client software assumes that TD_ADDCHANGEINT cannot fail, it may fail to clean up after a failed TD_ADDCHANGEINT command and hang/crash. For this reason you may want to consider implementing both TD_ADDCHANGEINT and TD_REMCHANGEINT commands even if your device driver does not support media change operations.