Implementing a Driver Namespace

来源:百度文库 编辑:神马文学网 时间:2024/04/28 20:37:09

Implementing a Driver Namespace
September 15, 2003
Walter Oney

Copyright © 2003 by Walter Oney. All rights reserved

On occasion, your hardware has more than onefunctional unit that applications need to access. For example, you might have aUSB device with two bulk output endpoints. You might want to allow anapplication to open a handle to just one of the endpoints. Or, you might have amultifunction device that doesn't conform to the bus standard (for whatever busyour device plugs into), and you might want to let applications access just oneof the functions.

One possible solution is to require applications to specify which endpoint orfunction they want to access as an argument to an IOCTL of some kind. Thissolution is workable, but the resulting application code can look pretty baroque-- think about every call needing a parameter structure that contains a memberto indicate the target for the operation. Furthermore, this design rules outusing ReadFile or WriteFile to read or write data.

A more elegant solution is to allow applications to open function-specifichandles using a namespace that your driver implements. That is, theapplication's call to CreateFile specifies which function it wants toaccess. To see how this might work from theapplication side, suppose you're writing an MFC application and that you'vealready determined the symbolic name of the device (linkname in thefollowing code fragments). You might, for example, have used SetupDiXxxfunction calls to enumerate instances of a device interface that your deviceexports using IoRegisterDeviceInterface. An application could open ahandle to one of your functions like this:

CString linkname;
HANDLE hFred = CreateFile(linkname + _T("\\Fred"), . . .);

A different application could open a handle to another function like this:

CString linkname;
HANDLE hBarney = CreateFile(linkname + _T("\\Barney"), . . .);

I'll show you how to support this use of CreateFile. In summary, I'llshow you how to:

  • Parse the name that the application uses in its call to CreateFile.
  • Keep track of per-handle information using the FsContext field of a FILE_OBJECT.

Parsing the filename:

To understand how a driver namespace works, you need to know some detailsabout the kernel Object Manager. A good place to get lots of informationabout the Object Manager is the current edition of Inside Windows Xxx byDavid Solomon and Mark Russinovich. But here are the basics.

The first argument to CreateFile will be of the form\\.\FlintstoneDevice0\Fred or \\.\FlintstoneDevice0\Barney. The"FlintstoneDevice0" part of this name comes from a call to IoCreateSymbolicLinkthat your AddDevice function makes:

UNICODE_STRING devname;
RtlInitUnicodeString(&devname, L"\\Device\\FLINTSTONE0");
PDEVICE_OBJECT fdo;
IoCreateDevice(pdo, sizeof(DEVICE_EXTENSION), &devname, FILE_DEVICE_UNKNOWN,FILE_SECURE_OPEN, FALSE, &fdo);

UNICODE_STRING linkname;
RtlInitUnicodeString(&linkname,L"\\DosDevices\\FlintstoneDevice0");
IoCreateSymbolicLink(&linkname, &devname);

Of course, the preferred way to name your device is to call IoRegisterDeviceInterfacein your AddDevice routine:

IoRegisterDeviceInterface(pdo, &GUID_DEVINTERFACE_FLINTSTONE,NULL, &pdx->InterfaceName);

When you do this, the I/O Manager internally creates a symbolic link thatpoints to the PDO. User-mode calls to CreateFile use that symbolic linkname to open handles to the device. The name is even more unguessable than"Rumpelstiltskin", though, and I didn't want to clutter thisdiscussion. That's why I chose to use the older NT-4 style of device naming.

To return to the example, the user-mode call to CreateFile turns intoa kernel-mode call to the native API function NtCreateFile, which, inturn, calls the Object Manager to locate the object named\\.\FlintstoneDevice0\Fred. To simplify matters a bit, the object managerreplaces "\\." with "\DosDevices" and starts parsing thename "\DosDevices\FlintstoneDevice0\Fred". DosDevices is the name of adirectory at the top level of the kernel namespace.1 FlintstoneDevice0is the name of a symbolic link object within DosDevices. When the Object Managerreaches that object, it substitutes the name of the link's target for thatportion of the pathname it's already parsed, yielding \Device\FLINTSTONE0\Fred.

So, now the Object Manager looks in the top-level Device directory to findthe FLINTSTONE0 object. It will call the "open method" routineassociated with DEVICE_OBJECT objects. The open method will create anIRP_MJ_CREATE request and send it to the device driver that owns the deviceobject. The as-yet unparsed portion of the file name will appear in the FileNamemember of the FILE_OBJECT, as illustrated in this picture:

To find the unparsed portion of the name, your code might read like this:

NTSTATUS DispatchCreate(PDEVICE_OBJECT fdo,PIRP Irp)
  {
  PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
  PFILE_OBJECT fop = stack->FileObject;
  PUNICODE_STRING filename = &fop->FileName;
  . . .
  }

At this point, you should be on familiar ground after years of programming inC. If your namespace will contain just two entries (Fred and Barney), you couldcontinue along these lines:

  . . .
  UNICODE_STRING FredName;
  RtlInitUnicodeString(&FredName, L"\\Fred");
  UNICODE_STRING BarneyName;
  RtlInitUnicodeString(&BarneyName, L"\\Barney");
  BOOLEAN bIsFred = RtlEqualUnicodeString(filename, &FredName, TRUE);
  BOOLEAN bIsBarney = RtlEqualUnicodeString(filename, &BarneyName,TRUE);
  . . .

(Note that the TRUE argument in the two calls to RtlEqualUnicodeStringspecifies a case-insensitive comparison.)

Distinguishing between handles

By specifying additional name components beyond the name of your device, anapplication is able to open a handle for some separately addressable entitysupported by your driver. That handle maps to a specific file object, as shownin this figure:

Whenever App-1 makes a call to ReadFile, WriteFile, DeviceIoControl,or CloseHandle, the I/O Manager will send an IRP to your driver with theIO_STACK_LOCATION's FileObject member pointing to the FILE_OBJECT for Fred.Similarly, when App-2 calls one of those APIs, the IRP's stack location willpoint to the FILE_OBJECT for Barney. It's not easy in the driver dispatchroutine to tell which FILE_OBJECT is which, of course. What is easy,however, is for you to remember some private information when you initiallyprocess the IRP_MJ_CREATE. Suppose, therefore, that you define a per-handle datastructure like this one:

typedef struct _HANDLE_INFO {
  BOOLEAN bIsFred;
  BOOLEAN bIsBarney;
  . . . 
  } HANDLE_INFO, *PHANDLE_INFO;

In your DispatchCreate function, you create an instance of this per-handlestructure . . .

NTSTATUS DispatchCreate(. . .)
  {
  . . .
  PHANDLE_INFO phi = (PHANDLE_INFO) ExAllocatePool(NonPagedPool,sizeof(HANDLE_INFO));
  RtlZeroMemory(phi, sizeof(*phi));
  phi->bIsFred = bIsFred;
  phi->bIsBarney = bIsBarney;
  . . .
  }

What do you do with this structure? You save a pointer to it in theFILE_OBJECT:

NTSTATUS DispatchCreate(. . .)
  {
  . . .
  fop->FsContext = (PVOID) phi;
  . . .
  }

Then, in every other dispatch routine, you extract the HANDLE_INFO pointerfrom the IRP's file object:

NTSTATUS DispatchSomething(PDEVICE_OBJECTfdo, PIRP Irp)
  {
  PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
  PFILE_OBJECT fop = stack->FileObject;
  PHANDLE_INFO phi = (PHANDLE_INF0) fop->FsContext;
  . . .
  }

You release the memory when you handle IRP_MJ_CLOSE:

NTSTATUS DispatchClosePDEVICE_OBJECT fdo,PIRP Irp)
  {
  PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
  PFILE_OBJECT fop = stack->FileObject;
  PHANDLE_INFO phi = (PHANDLE_INF0) fop->FsContext;
  ExFreePool(phi);
  . . .
  }

The FsContext field (and the similar FsContext2 field) arethere for use by the function driver. You use this to keep a pointer to"your stuff" as it relates to a particular open handle.

About the author:

Walter Oney is a freelance driver programmer, seminar leader, and authorbased in Boston, Massachusetts. You can reach him by e-mail at waltoney@oneysoft.com.Information about the Walter Oney Software seminar series, and other services,is available online at http://www.oneysoft.com. 


1 -- Starting with Windows XP, the kernel namespace is more complicated thanthis discussion would imply. Read Solomon & Russinovich, or take a look atpage 72 in the 2d edition of my WDM book if you want to know about the details.But don't get bogged down in the fact that different sessions have private"DosDevices" directories that the Object Manager will search beforesearching the global one.