1394 Isochronous Transfers -- Part 1
September 15, 2003
Bill McKenzie

Copyright © 2003 by Bill McKenzie. All rights reserved

This article is the third installment in a multi-part series covering topics related to Windows device driver development for IEEE 1394tm (1394) devices.  Some knowledge of the previous installments in this series will likely prove helpful in understanding the discussions in this article.  Articles from previous issues of WD3 are available in the archive.  It is also assumed that the reader has a fair amount of experience with the Windows Driver Model (WDM).  In previous installments, I covered the basics of 1394 operation.  I have also discussed quite a bit about 1394 asynchronous transfers on Windows platforms.  In this article, I want to shift away from asynchronous transfers and start covering 1394 isochronous transfers.  I hope that the following discussion will help to illuminate 1394 isochronous transfer operation in a WDM device driver.

Overview:

The word isochronous comes from two Greek roots: iso, which means same, and chronous, which means time.  Isochronous transfers on the 1394 bus guarantee timely delivery of data.  Specifically, isochronous transfers are scheduled by the bus so that they occur once every 125µs.  Each 125µs timeslot on the bus is called a frame.  Isochronous transfers, unlike asynchronous transfers, do not in any way guarantee the integrity of data through a transfer.  No response packet is sent for an isochronous transfer.  Isochronous transfers are useful for situations that require a constant data rate but not necessarily data integrity.  Examples include video or audio data transfers.  

Isochronous transfers on the 1394 bus do not target a specific node.  Isochronous transfers are broadcast transfers which use channel numbers to determine destination.  Channel numbers are 6-bit values which means there are a maximum of 2^6 or 64 channels per bus.  A single node on the 1394 bus acts as talker, or data deliverer, on a given channel at any one time.  As many nodes on the bus as desired can act as listeners, or data receivers, on a given channel at any one time.  The roles of talker and listener on a given channel are not fixed.  A node acting as talker at one time on a given channel may subsequently initiate listen transfers on the same channel at some later time.  The only restriction is that only one node can talk on a channel at a time.  Channels are managed on the 1394 bus by a node acting as an isochronous resource manager.  How the isochronous resource manager node is chosen and exactly how it performs its tasks is beyond the scope of this article.  For more information see the IEEE Standard 1394-1995.

Isochronous transfers are packet based.  The size of isochronous packets transferred on a given channel can vary from frame to frame.  The 1394 bus can use up to 80% of each 125 µs frame, or 100µs, to transmit isochronous data.  In order for the bus to guarantee time slots for isochronous data, the size of isochronous packets is limited by the available isochronous bandwidth. In order to satisfy a reasonable number of isochronous devices, the 1394a specification places additional limits on isochronous packets.  The following table shows the maximum isochronous packet sizes for 1394 devices:

 Speed  Maximum Payload Size In Bytes
 100 MB/s  1024
 200 MB/s  2048
 400 MB/s  4096

These maximum values are still subject to available bandwidth.  Bandwidth must be reserved in advance for an isochronous transfer.  Bandwidth, like an isochronous channel, is managed by and allocated from the isochronous resource manager.

Once the proper isochronous resources are allocated, isochronous transfers can execute on the given channel.  Although the 1394 bus sets up an isochronous resource manager to manage the isochronous resources, the Windows 1394 bus driver stack isolates the WDM driver from having to deal with this mechanism directly.  In the following paragraphs, I hope to cover the basics of getting an isochronous transfer setup and active from within a WDM device driver.  Specifically, I will cover basic isochronous transfer setup, basic transfer buffer management, and bus reset handling.  1394 isochronous transfers is a rather broad subject.  I will only be able to scratch the surface in this first article.  Topics such as isochronous buffer synchronization in the driver, isochronous packet synchronization methods, fixed and variable size data packets, and many other related topics will have to wait for future articles.

Setting Up An Isochronous Transfer:

As I mentioned in the first article in this series, 1394 WDM drivers don’t communicate with 1394 devices directly.  A 1394 bus driver supplied with the Windows operating system handles communication on the 1394 bus.  WDM drivers communicate with the 1394 bus using a special structure called an IEEE 1394 Request Block (IRB).  An IRB is built up by the driver and sent to the 1394 bus driver using an IRP of  IRP_MJ_INTERNAL_DEVICE_CONTROL type with an IOCTL value of IOCTL_1394_CLASS.  Each IRB is sufficient to describe one 1394 operation.  The type of 1394 operation described by the IRB is specified by the FunctionNumber field of the IRB structure.  Please see the Device Driver Development Kit (DDK) for more information on IRBs. 

As stated above, two isochronous resources, bandwidth and channel, must be allocated prior to executing an isochronous transfer on the 1394 bus.  In addition, the Windows 1394 bus driver stack requires a WDM driver to allocate a handle for the isochronous resources called an isochronous resource handle. 

It is important to note here something that really isn't stated clearly in any 1394 related documentation.  Exactly which node allocates the isochronous resources for a particular isochronous transfer is not specified.  For instance, I worked on a driver for an industrial scanner once which was smart enough to allocate isochronous resources from the bus on its own.  In order to communicate with the scanner the driver had to read the channel number and speed information from the scanner using asynchronous transfers.  The scanner, the device under control of the driver, acted as talker on the channel at all times.  In contrast, 1394 digital cameras, which follow the 1394-based Digital Camera Specification available from the 1394 Trade Association, require isochronous resources to be allocated by some other node.  The channel number and speed information are programmed into the camera using asynchronous transfers.  In this case, like that of the scanner, the camera, the device under control of the driver, is the talker on the channel at all times.  In one case, the talker node allocates the isochronous resources.  In the other case, the listener node allocates the isochronous resources.  Allocation of isochronous resources in general is accomplished by the talker node, a listener node, or some other node altogether.  However, the node that allocates the isochronous resources must free the resources when they are no longer in use or ensure that some other node frees these resources on its behalf. 

Isochronous Resource Allocation:

The following sections describe allocating isochronous resources from a WDM driver in more detail.

Allocating Bandwidth:

A WDM driver which must allocate isochronous bandwidth can do so by sending an IRB to the 1394 bus with function code REQUEST_ISOCH_ALLOCATE_BANDWIDTH.  The IRB parameters of interest for the REQUEST_ISOCH_ALLOCATE_BANDWIDTH request are listed below:

typedef struct _IRB {
  ULONG FunctionNumber;
  ULONG Flags;
    .
    .
    .
  union {
    struct {
      ULONG     nMaxBytesPerFrameRequested;
      ULONG     fulSpeed;
      HANDLE    hBandwidth;
      ULONG     BytesPerFrameAvailable;
      ULONG     SpeedSelected;
    } IsochAllocateBandwidth;
    .
    .
    .
  } u;
} IRB;

On input, the u.IsochAllocateBandwidth.nMaxBytesPerFrameRequested field should contain the maximum number of bytes per frame that the 1394 device will want to transmit on the bus.  Also, the u.IsochAllocateBandwidth.fulSpeed field should be set to the connection speed to use for allocating bandwidth.  Depending on the topology of the bus and what other isochronous devices are present, the maximum number of bytes per frame requested may well exceed the available bandwidth capabilities of the bus at the point when the request is made.  The driver must be prepared for a failure of this request to occur.  The driver should leave itself in a state where it can retry the request at a later time.  In addition, because isochronous resources are finite and somewhat scarce, the driver should only allocate isochronous resource when needed.  Similarly, a driver should free any allocated isochronous resources as soon as they are no longer needed.  If the REQUEST_ISOCH_ALLOCATE_BANDWIDTH request succeeds, the value of the u.IsochAllocateBandwidth.SpeedSelect field indicates the speed selected for the bandwidth allocation.  The u.IsochAllocateBandwidth.hBandwidth field contains a handle to the bandwidth allocated.  This handle is typically stored in the device driver's device extension, so it can be retrieved at a later time in order to free the bandwidth. 

Whether the request is successful or not, the u.IsochAllocateBandwidth.BytesPerFrameAvailable field is filled in with the bandwidth available immediately after the bandwidth allocation attempt.  If the request fails, knowing the amount of available bandwidth allows a driver to attempt an allocation of a lesser amount of bandwidth that might have a chance of succeeding.  However, the bytes per frame value is only a snapshot.  The available bandwidth can change at anytime.

One other IRB field I have should talk about here is the Flags field.  This particular field is not documented at all for the REQUEST_ISOCH_ALLOCATE_BANDWIDTH request in some versions of the DDK help, and it is not documented under the REQUEST_ISOCH_ALLOCATE_BANDWIDTH heading in still other versions of the DDK help.  It seems it is worth mentioning here.  Generally, the Flags field is set to zero for this request.  However, there is a special flag define, IRB_FLAG_ALLOW_REMOTE_FREE, that can be used to relieve the driver from having to free the bandwidth allocated.  If this flag is used, the bus driver handles freeing of the bandwidth.  I will talk more about this flag later when I get to bus reset handling.  One rather significant drawback to using this flag is that it prevents the requestor from receiving a handle to the bandwidth.  In other words, the hBandwidth field of the IRB is NULL upon return from a REQUEST_ISOCH_ALLOCATE_BANDWIDTH request if the IRB_FLAG_ALLOW_REMOTE_FREE flag is used for the request.  Without a handle the driver cannot free the bandwidth when it is done using the bandwidth.  Keeping bandwidth allocated when not in use can starve other isochronous devices on the bus which is generally bad practice.

Allocating An Isochronous Channel:

A WDM driver allocates an isochronous channel by sending an IRB to the 1394 bus with function code REQUEST_ISOCH_ALLOCATE_CHANNEL.  The IRB parameters of interest for the REQUEST_ISOCH_ALLOCATE_CHANNEL request are listed below:

typedef struct _IRB {
  ULONG FunctionNumber;
    .
    .
    .
  union {
    struct {
      ULONG            nRequestedChannel;
      ULONG            Channel;
      LARGE_INTEGER    ChannelsAvailable;
    } IsochAllocateChannel;
    .
    .
    .
  } u;
} IRB;

Channel allocations are pretty simple and straightforward.  On input, the u.IsochAllocateChannel.nRequestedChannel field is set to the desired channel or to ISOCH_ANY_CHANNEL if any channel will do.  Since there are a limited number of channels on the bus, if an isochronous device can live without specifying a particular channel, it is always a good idea to use the ISOCH_ANY_CHANNEL value.  If the request is successful, the u.IsochAllocateChannel.Channel field of the IRB contains the allocated channel number on return.  This value is generally stored in the device driver's device extension, so it can be retrieved at a later time for bus reset resource management.  I will discuss bus reset handling in a later section.  The u.IsochAllocateChannel.ChannelsAvailable field contains a bitmap which indicates the available channels immediately after the channel allocation attempt.  This field is filled in even if the request fails, and it can be used to try a subsequent allocation specifying a channel number or numbers that might be available.  This value is just a snapshot of the available channels and can change at any time.

Allocating An Isochronous Resource Handle:

A WDM driver allocates an isochronous resource handle by sending an IRB to the 1394 bus with function code REQUEST_ISOCH_ALLOCATE_ISOCHRONOUS_RESOURCES.  The IRB parameters of interest for the REQUEST_ISOCH_ALLOCATE_ISOCHRONOUS_RESOURCES request are listed below:

typedef struct _IRB {
  ULONG FunctionNumber;
    .
    .
    .
  union {
    struct {
      ULONG          fulSpeed;
      ULONG          fulFlags;
      ULONG          nChannel;
      ULONG          nMaxBytesPerFrame;
      ULONG          nNumberOfBuffers;
      ULONG          nMaxBufferSize;
      ULONG          nQuadletsToStrip;
      HANDLE         hResource;
      ULARGE_INTEGER ChannelMask
    } IsochAllocateResources;
    .
    .
    .
  } u;
} IRB;

The resource handle is used by the 1394 bus driver to manage the isochronous transfers on a channel and the data buffers provided by the WDM driver for these transfers.  The bus driver stack needs quite a bit of information for this allocation. 

On input, the u.IsochAllocateResources.fulSpeed field specifies the connection speed to use for communication on the channel.  The u.IsochAllocateResources.nChannel field specifies the channel that will be used for all isochronous transfers involving the handle returned from this request.  This request requires the channel number, so the channel must be allocated prior to allocating the isochronous resource handle.  The u.IsochAllocateResources.nMaxBytesPerFrame field specifies the maximum number bytes that will be transmitted or received per 1394 frame.  This value should generally be equal to but never greater than the u.IsochAllocateBandwidth.nMaxBytesPerFrameRequested value used in allocating the bandwidth.  The u.IsochAllocateResources.nNumberOfBuffers IRB field indicates the maximum number of buffers the driver will ever have attached to the resource handle at one time.  I will talk more about attaching buffers in a later section.  The u.IsochAllocateResources.nMaxBufferSize IRB field is used to indicate the maximum size of the buffers that will ever be attached to the resource handle.  The IRB field u.IsochAllocateResources.nQuadletsToStrip indicates the number of quadlets to strip from the beginning of every packet in an incoming isochronous stream. This is used to strip packet headers.  This parameter is ignored unless the device driver sets the RESOURCE_STRIP_ADDITIONAL_QUADLETS flag in u.IsochAllocateResources.fulFlags. The u.IsochAllocateResources.fulFlags IRB field is set to some combination of RESOURCE_XXX flags for the transfer.  See the DDK documentation listed under REQUEST_ISOCH_ALLOCATE_ISOCHRONOUS_RESOURCES for a full listing of these flags and their definitions. 

A couple of the flags I will mention here are the RESOURCE_USED_IN_LISTENING and the RESOURCE_USED_IN_TALKING flags.  These flags determine if this resource handle will be used to receive or deliver isochronous data respectively.  A resource handle can only be set to transmit data in one direction. 

Another flag of interest is the RESOURCE_USE_PACKET_BASED flag.  This flag determines the DMA strategy used for the isochronous transfer.  This is very important for determining efficiency and determining how

           

received data is handled for the transfer.  Stream-based transfers cause the host controller to treat all of the device driver's buffers as one contiguous buffer.  Data that is received in one isochronous packet is written to the driver's buffers where the last isochronous packet's data left off.  Packet-based transfers cause the host controller to write one isochronous packet per buffer starting at the beginning of the buffer.  If the isochronous packet is smaller than the driver's buffer, then the contents of the remaining space at the end of the driver's buffer is undefined.  Figure 1 shows an example of the difference in these two DMA strategies.  In order to find a packet in the stream-based case, the device driver will have to keep track of all preceding packet sizes.   

One other important point about packet versus stream based transfers is host controller capabilities.  Not all 1394 host controllers are created equal, and some non-OpenHCI (OHCI) compliant host controllers may only support one DMA strategy or the other.  The 1394 device driver needs to send a REQUEST_GET_LOCAL_HOST_INFO request of type GET_HOST_CAPABILITIES to the 1394 bus driver to get the capabilities of the host controller.  This request will return quite a bit of useful information about the host controller including what DMA strategies it supports.  In general, Windows only supports OHCI compliant 1394 host controllers which always support Packet and Stream based transfers.  However, Windows does allow some non-OHCI compliant host controller exceptions which warrants checking the host controller capabilities.

On return from this request, the u.IsochAllocateResources.hResource IRB field will contain the isochronous resource handle if the request was successful.  This value is generally stored in the driver's device extension where it can be retrieved later for buffer management.

Resource handle allocations are fairly detailed, and I have obviously not covered all aspects of this request.  I will cover more in upcoming articles.  As always, see the DDK documentation for REQUEST_ISOCH_ALLOCATE_ISOCHRONOUS_RESOURCES for more details.
 

Important Notice:  Resources on the 1394 bus, as already stated, are managed by the isochronous resource manager.  However, this management cannot be enforced by the bus in some respects.  Resource allocation is somewhat of an honor system setup.  It is possible to transmit isochronous data without allocating any resources from the 1394 bus.  The results may and likely will be undesirable, but it is possible.  As such, it is important in a WDM device driver to make sure that all resources are correctly in place before attempting to transfer isochronous data.  Make sure the driver checks the return status and values of all resource allocations to ensure that what is requested is indeed what is received.

Attaching Data Buffers:

Once an isochronous resource handle has been allocated, the device driver can attach data buffers to this handle which are used to transmit or receive isochronous data.  These buffers must be described by an MDL in order to be attached.  Often MDLs are obtained through some type of Direct buffered I/O coming into the driver, but any MDLs will do.  Each MDL must further be described by a structure called an ISOCH_DESCRIPTOR.  This structure is available in the DDK header 1394.h.  The ISOCH_DESCRIPTOR is rather large and I will not cover all members of it, but a couple of the members are pertinent.  The following shows the fields of the structure I will discuss here:

typedef struct _ISOCH_DESCRIPTOR {
    .
    .
    .
  PMDL  Mdl;
  ULONG  ulLength;
  ULONG  nMaxBytesPerFrame;
    .
    .
    .
  PBUS_ISOCH_DESCRIPTOR_ROUTINE  Callback;
  PVOID  Context1;
  PVOID  Context2;
  NTSTATUS  status;
    .
    .
    .
} ISOCH_DESCRIPTOR, *PISOCH_DESCRIPTOR;

The Mdl field should be set to point to the MDL describing the isochronous data buffer provided by the device driver.  The ulLength field is set to the number of bytes in the MDL that the driver wants to receive or transmit.  The nMaxBytesPerFrame field should be set to the number of bytes per frame value used when allocating the isochronous resource handle.  The Callback field is used if the device driver wants to be notified when DMA to or from this particular buffer has been completed.  The callback routine should have the following prototype:

void Callback(IN PVOID Context1, IN PVOID Context2);

The Context1 and Context2 fields of the ISOCH_DESCRIPTOR structure are obviously what get sent to the buffer callback routine as its contexts.  The status field of the  ISOCH_DESCRIPTOR structure is filled in by the 1394 bus driver with the status of the DMA to or from this buffer when the buffer is completed.

Typically a device driver will attach several buffers for a given transfer.  The  ISOCH_DESCRIPTOR structures used to describe these buffers are setup in an array.  In order to attach these buffers to the isochronous resource handle, a device driver sends an IRB to the 1394 bus with function code REQUEST_ISOCH_ATTACH_BUFFERS.  The IRB parameters of interest for the REQUEST_ISOCH_ATTACH_BUFFERS request are listed below:

typedef struct _IRB {
  ULONG FunctionNumber;
    .
    .
    .
  union {
    struct {
      HANDLE               hResource;
      ULONG                nNumberOfDescriptors;
      PISOCH_DESCRIPTOR    pIsochDescriptor;
    } IsochAttachBuffers;
    .
    .
    .
  } u;
} IRB;

The u.IsochAttachBuffers.hResource IRB field should be set to the previously allocated isochronous resource handle.  The u.IsochAttachBuffers.nNumberOfDescriptors field is set to the number of buffers being attached.  The sum of all attached buffers can never exceed the maximum number of buffers specified when allocating the isochronous resource handle.  The u.IsochAttachBuffers.nNumberOfDescriptors field is set to point to the array of ISOCH_DESCRIPTOR structures which describe the buffers to be attached.  Once buffers are attached to the isochronous resource handle, the isochronous transfer can be started.  Attempting to start an isochronous transfer with no buffers attached will fail.

Starting the transfer:

A WDM device driver starts a listen or talk transfer by sending an IRB to the 1394 bus with function code REQUEST_ISOCH_LISTEN or REQUEST_ISOCH_TALK respectively.  The IRB parameters of interest for these requests are essentially the same listed below for the REQUEST_ISOCH_LISTEN request:

typedef struct _IRB {
  ULONG FunctionNumber;
    .
    .
    .
  union {
    struct {
      HANDLE        hResource;
      ULONG         fulFlags;
      CYCLE_TIME    StartTime
    } IsochListen;
    .
    .
    .
  } u;
} IRB;

The driver sets the isochronous resource handle previously allocated in the u.IsochListen.hResource IRB field.  The u.IsochListen.hResource IRB field must be set to zero.  The u.IsochListen.StartTime IRB field is used to specify the cycle time to begin operation.  I am not going to cover cycle time in this article.  This request will return immediately, and does not wait for the listen to complete.  Once this request completes successfully, the transfer may be in progress.  A buffer callback could be called immediately.  No assumptions should be made as to whether this call will return before or after the first buffer callback is called.

Stopping the transfer:

Any isochronous request that is started has to be stopped at some point.  To accomplish this, the WDM device driver sends an IRB to the 1394 bus with function code REQUEST_ISOCH_STOP.  The IRB parameters of interest for the REQUEST_ISOCH_STOP request are listed below:

typedef struct _IRB {
  ULONG FunctionNumber;
    .
    .
    .
  union {
    struct {
      HANDLE    hResource;
      ULONG     fulFlags;
    } IsochStop;
    .
    .
    .
  } u;
} IRB;

The u.IsochStop.hResource IRB field is set to the previously allocated isochronous resource handle being used for the transfer.  The u.IsochStop.fulFlags IRB field must be set to zero.  If successful, the REQUEST_ISOCH_STOP request stops all isochronous activity on the isochronous channel.

Managing Buffers:

Once an isochronous transfer is started, it will continue until stopped.  When DMA is completed to a buffer attached to the isochronous resource handle, that buffer is not used again by the 1394 bus driver.  The driver must detach the buffer from the isochronous resource handle and re-attach the buffer if it wishes for the buffer to be used again.  If the transfer uses up all of the attached buffers, it will halt waiting for more buffers to be attached.  In order to prevent a halt in the transfer, a driver must attach buffers at an equal or greater rate to that at which they are completed. 

Important Notice:  There was a feature documented in some older DDKs called circular buffers.  This feature was used by setting the RESOURCE_BUFFERS_CIRCULAR flag in the u.IsochAllocateResources.fulFlags IRB field of the REQUEST_ISOCH_ALLOCATE_ISOCHRONOUS_RESOURCES request.  Circular buffers was supposed to allow the driver to attach buffers once, and the bus driver would continue looping through the array of buffers until the isochronous transfer was stopped.  The discussion here should highlight what a nice feature circular buffers would have been.  Unfortunately, the feature just never worked correctly.  Callbacks in the buffers attached in this manner were not called or were called out of order, so it was impossible to determine what buffer data was valid and when.  All mention of circular buffers has subsequently been stripped from the DDK documentation.  If you run into an older DDK, do not attempt to use this feature.

As stated previously, a device driver can use a buffer callback to be signaled when a buffer is complete.  The device driver can use this buffer callback to detach and re-attach the buffer to the isochronous resource handle.  The buffer callback, specified in the ISOCH_DESCRIPTOR structure, is called at DISPATCH_LEVEL IRQL.  As such, the device driver cannot block waiting on its detach and attach requests to complete.  Fortunately, although not documented, both REQUEST_ISOCH_DETACH_BUFFERS and REQUEST_ISOCH_DETACH_BUFFERS requests can be called at DISPATCH_LEVEL IRQL.  Fortunately, the device driver doesn't have to use a work item or similar mechanism to get to PASSIVE_LEVEL IRQL.  Buffer management is a time critical task, and the time it would take to get to PASSIVE_LEVEL would kill performance of the isochronous transfer.  The driver can call down to the bus but cannot block, so a completion routine has to be used for each request. 

If a device driver uses an MDL from an I/O request in an isochronous transfer, the driver cannot complete the I/O request until the MDL buffer is detached from the isochronous resource handle.  The MDL should be considered property of the 1394 bus driver once it is attached to the isochronous resource handle and until it is detached.

A quick discussion of buffer callback usage is in order here.  The DDK documentation, under the heading "Buffering Isochronous DMA Transfers for IEEE 1394 Devices", correctly points out that using a buffer callback for each attached buffer will adversely affect performance.  However, this section goes on to suggest that only the last attached buffer should have a callback.  This suggestion likely will not work for many applications.  If the device driver only uses a callback in the last buffer attached, the isochronous transfer will halt after the last buffer is complete.  The transfer will stay halted until such a time as the device driver can detach and re-attach all of the attached buffers.  It would be better to use a minimum of two

callbacks, one in the middle buffer and one in the last buffer.  Each callback could then be in the process of detaching and re-attaching half of the buffers while the other half of the buffers is being processed.  Figure 2 shows this graphically.  It may also be necessary for a driver to signal that a buffer is complete to some waiting party more quickly than after all or even half of the buffers are processed.  In such a case, some buffer callbacks may just signal buffer complete while others signal and manage the buffers.  Given applications are likely to vary on this a good bit.

Bus Reset Handling:

Bus resets occur on the 1394 bus anytime the topology of the bus changes.  If a 1394 device is unplugged and then plugged back in, two bus resets will result.  For each bus reset that occurs, the 1394 bus reclaims all channel and bandwidth isochronous resources.  Thus, a bus reset effectively stops all isochronous transfers on the bus until all isochronous devices can reallocate their isochronous resources. 

A WDM device driver can request notification of bus resets by sending a REQUEST_BUS_RESET_NOTIFICATION request to the Windows 1394 bus driver stack.  The IRB parameters of interest for the REQUEST_BUS_RESET_NOTIFICATION request are listed below:

typedef struct _IRB {
  ULONG FunctionNumber;
    .
    .
    .
  union {
    struct {
      ULONG                       fulFlags;
      PBUS_BUS_RESET_NOTIFICATION ResetRoutine;
      PVOID                       ResetContext;
    } BusResetNotification;
    .
    .
    .
  } u;
} IRB;

The IRB field u.BusResetNotification.fulFlags is set to REGISTER_NOTIFICATION_ROUTINE or DEREGISTER_NOTIFICATION_ROUTINE to indicate whether to register or deregister for bus reset notification respectively.  A driver must eventually deregister any notification it registers.  The IRB field u.BusResetNotification.ResetRoutine is a pointer which is set to point to the device driver's bus reset notification routine.  This routine will has the following prototype:

void BusResetNotificationRoutine(IN PVOID Context);

If the device driver fails to deregister its bus reset notification routine, the bus driver could potentially attempt to call this routine after the device driver has been unloaded for memory.  The system will crash if this occurs.  The IRB field u.BusResetNotification.ResetContext is any context the device driver wishes to be passed to the bus reset notification routine.  Usually, this is the device driver's device extension.

Once a bus reset notification routine is properly registered, it is called once for each reset of the 1394 bus.  If a driver is managing isochronous resource allocation for the device, the driver must reallocate those resources when a bus reset occurs.  These resources include the isochronous channel and bandwidth for the device.  The isochronous resources must be reallocated, because they were reclaimed by the 1394 bus in response to the bus reset.  The Windows 1394 bus driver requires some additional steps beyond what is required by the 1394 bus.

First, the REQUEST_ISOCH_ALLOCATE_CHANNEL request, which must be sent to the bus driver in order to allocate a new channel, has to be sent at IRQL PASSIVE_LEVEL.  The bus reset notification routine is called at DISPATCH_LEVEL IRQL.  Some DDK sample device drivers use a handler for the IRP_MN_BUS_RESET Plug-and-Play IRP, that gets sent on bus reset, to handle bus reset resource allocation.  Unfortunately, the IRP_MN_BUS_RESET IRP cannot be depended upon to arrive in a timely manner.  Further, the DDK has labeled this IRP obsolete, and it should not be used.  The device driver then has to use some method to get to PASSIVE_LEVEL IRQL in order to reallocate the channel and other isochronous resources.  One of the easiest ways to accomplish this is through the use of a work item.  When reallocating the isochronous channel, the WDM device driver should attempt to reallocate the same channel number that was received upon the previous successful channel allocation.  This is necessary so as not to invalidate the isochronous resource handle that was previously allocated.  However, there is no guarantee that the device driver will be able to reallocate the same channel after a bus reset.

Another bit of complexity has to do with bandwidth.  Even though the bandwidth is automatically reclaimed by the 1394 bus upon bus reset, the Windows 1394 bus driver stack cannot very well invalidate the bandwidth handle the device driver received in a previous REQUEST_ISOCH_ALLOCATE_BANDWIDTH request.  The device driver must explicitly free the bandwidth by making a REQUEST_ISOCH_FREE_BANDWIDTH request.  The REQUEST_ISOCH_FREE_BANDWIDTH request is documented in the DDK help as also requiring IRQL PASSIVE_LEVEL.  As such, the driver cannot make this request directly from the bus reset notification routine either.  If the devices driver uses the IRB_FLAG_ALLOW_REMOTE_FREE flag when allocating the bandwidth, the bus driver will handle reallocation of the bandwidth upon bus reset.  However, as mentioned earlier, this flag prevents the device driver from being able to free the bandwidth when the driver is done with it.  If a device driver has to allocate bandwidth at startup and keep that bandwidth as long as the driver is loaded, this flag may be worthwhile to use.

There is one more item that makes bus reset handling kind of tricky for isochronous devices. That item is isochronous resource handle invalidation.  I have not seen this documented in any DDK nor have I seen any DDK sample deal with this issue.  The isochronous resource handle does not generally have to be reallocated upon a bus reset.  However, there are exceptions.  If the device driver allocates a different channel number after bus reset, than the driver was using before the bus reset occurred, the isochronous resource handle is invalid.  In addition, some device drivers use a method of bandwidth allocation that will retry on fail with scaled down maximum bytes per frame values until some minimum is reached or the request succeeds.  If a device driver allocates bandwidth, after a bus reset, that has a lower maximum bytes per frame value than was used for allocating the original isochronous resource handle, the isochronous resource handle is invalid.  An invalid isochronous resource handle has to be freed and reallocated.  If the device driver has to free and reallocate the isochronous resource handle, all attached buffers must be detached first.

Upon bus reset, the device driver should reallocate the channel, free and reallocate the bandwidth if necessary, and free and reallocate the isochronous resource handle if necessary.  Once the resources are reallocated, the driver should request a start for listen or talk.

Summary:

Whew!  I hope no one fell asleep going through this one.  Hopefully though, this article has provided a solid grounding in isochronous transfers so that subsequent articles can fill in some of the details that I brushed over here.  If the basic mechanics of setting up and executing an isochronous transfer from within a WDM device driver are a little clearer now, this article has done its job.  I hope that is the case.  Admittedly, a lot of the content here is a regurgitation of bits and pieces of the DDK documentation.  I tried to draw together the pertinent pieces, and throw in a little experience I have gained over the last few years of working with 1394 related drivers and devices.  As always, reader feedback is welcome and encouraged.

About The Author:

Bill McKenzie bill.mckenzie@compuware.com has been developing system level software for over seven years, including just under five years experience developing device drivers for Windows platforms. His primary background is in the development of software products targeted for device driver developers. He is currently working on driver development related software products for Compuware Corporation, maker of some of the finest Windows device driver tools and utilities available.