Truy nhập bộ nhớ trực tiếp (Direct Memory Access )
Windows XP hỗ trợ việc truy cập trực tiếp bộ nhớ thong qua một mô hình máy tính trừu tượng được mô tả ở hình 7.6. Trong hình này, máy tính được coi như là có một tập hợp sơ đồ các thanh ghi cái mà được chuyển đổi giữa địa chỉ vật lý của CPU và địa chỉ bus. ...
Windows XP hỗ trợ việc truy cập trực tiếp bộ nhớ thong qua một mô hình máy tính trừu tượng được mô tả ở hình 7.6. Trong hình này, máy tính được coi như là có một tập hợp sơ đồ các thanh ghi cái mà được chuyển đổi giữa địa chỉ vật lý của CPU và địa chỉ bus. Mỗi sơ đồ địa chỉ thanh ghi này lưu giữ địa chỉ của một trang vật lý nào đó. Phần cứng truy cập bộ nhớ để đọc hay ghi bằng cách chỉ ra địa chỉ bus hay địa chỉ logic. Sơ đồ các thanh ghi này thực hiện cùng một vai trò khi tiếp nhận bảng trang cho phần mềm bằng cách cho phép phần cứng sử dụng các giá trị số khác nhau cho các địa chỉ của nó hơn là để cho CPU hiểu.
Hình 7-6. Mô hình máy tính trừu tượng của bộ chuyển MDA.
Một số CUP như Alpha chẳng hạn thì có sơ đồ thanh ghi phần cứng thực tế. Một trong các bước khởi đầu cho bộ chuyển DMA dự trữ một số thanh ghi cho quá trình sử dụng của bạn- tôi sẽ thảo luận vấn đề này trong phần sơ đồ chuyển. Một số loại CPU khác, như x86 chẳng hạn thì không có sơ đồ thanh ghi, bạn viết trình điều khiển của bạn tương tự như họ làm. Sơ đồ chuyển thực hiện từng bước có thể dảo ngược từ dưới lên của các vùng đệm bộ nhớ vật lý, cái mà phụ thuộc vào hệ thống. Trong một số trường hợp thì hoạt động của DMA sẽ được xử lý sử dụng vùng đệm đảo ngược. Rõ rang là một số người đã copy dữ liệu đến hoặc từ vùng đệm DMA trước hoặc sau khi dịch. Trong trường hợp cụ thể, ví dụ như là khi chúng ta lien hệ một đường bus của thiết bị chủ, cái mà là noi tụ họp của các cáp- các giai đoạn của sơ đồ chuyển có thể không làm gì trong cấu trúc mà không có sơ đồ thanh ghi.
Nhân của Windows XP sử dụng một cấu trúc dữ liệu được biết đến như là đối tượng điều hợp để mô tả các đặc tính DMA của thiết bị và để điều khiển truy cập đến các nguồn được chia sẻ, như là hệ thống các kênh DMA và sơ đồ các thanh ghi. Bạn sẽ lấy một con trỏ trỏ tới đối tượng điều hợp bởi lời gọi IOGetDmAAdapter trong suốt quá trình StartDevice của bạn. Đối tượng điều hợp sẽ có một con trỏ để trỏ tới một cấu trúc được gọi là DmaOperations cái mà khi nó bật thì chứa các con trỏ để trỏ tới các hàm mà bạn muốn gọi. Hãy xem bảng 7.4. Các hàm này chỉ ra vị trí đích của hàm (ví dụ như …..) cái mà bạn phải sử dụng phiên bản trước của Windown NT. Thực tế, tên đích này có trong các Macro các mà được khai báo trong hàm DmaOperations.
Table 7-4. DmaOperations Function Pointers for DMA Helper Routines | |
DmaOperations Function Pointer | Description |
PutDmaAdapter | Destroys adapter object |
AllocateCommonBuffer | Allocates a common buffer |
FreeCommonBuffer | Releases a common buffer |
AllocateAdapterChannel | Reserves adapter and map registers |
FlushAdapterBuffers | Flushes intermediate data buffers after transfer |
FreeAdapterChannel | Releases adapter object and map registers |
FreeMapRegisters | Releases map registers only |
MapTransfer | Programs one stage of a transfer |
GetDmaAlignment | Gets address alignment required for adapter |
ReadDmaCounter | Determines residual count |
GetScatterGatherList | Reserves adapter and constructs scatter/gather list |
PutScatterGatherList | Releases scatter/gather list |
Chiến lược chuyển đổi (Transfer Strategies):
Cách bạn thựchiện chuyển đổi DMa phụ thuộc vào nmột số nhân tố sau:
- Nếu thiết bị của bạn có Bus-Mastering capability, tất nhiên là nó cần có điện để truy cập vào bộ nhớ chính nếu như bạn yêu cầu nó một số chức năng có bản. như là nơi bắt đầu, bao nhiêu đơn vị dữ liệu được chuyển, bạn đang thực hiện việc vào hay ra dữ liệu và nhiều điều khác. Bạn sẽ phải hội ý với những người thiết kế ra phần cứng của bạn để lọc ra được các chi tiết này hoặc là bạn sẽ phải làm việc với bảng hướng dẫn để biết được bạn cần phải làm gì với các mức phần cứng này.
- Một thiết bị với khả năng tập hợp/trải ra có thể chuyển các khối lớn dữ liệu đến hoặc đi các vùng không cấu hình của bộ nhớ vật lý. Sử dụng scatter/gather là một lợi thể của phần mềmbởi vì nó giới hạn yêu cầu về với các khối dữ liệu lớn của các trang Frame cấu hình. CÁc trang này có thể đơn giản là bị khoá khi mà chúng được tìm thấy trong bộ nhớ vật lý và thiết bị có thể bị mô tả bởi chúng.
- Nếu thiết bị của bạn không có Bus chủ, bạn sẽ sử dụng hệ thống điều khiển DMa trên bo mạch chủ của máy tính. Kiểu của DMA đó đôi khi được gọi là DMA nô lệ (Slave). Hệ thống điều khiển DMA lien kết với các bus ISA có một số giới hạn về bộ nhớ nào nó được truy cập và độ rộng của một bộ chuyển nó có thể thực hiện mà không có chương trình định trước. Trình điều khiển này cí dụ như là IESA thiếu các giới hạn này. Ít nhất là trong Windows XP, bạn sẽ không cần phải biết kiểu bus phần cứng cảu bạn cắm vào bởi vì hệ thống có thể lấy ra các hạn chế này một cách tự động.
- Thông thường, hệ thống DMA bao gồm chương trình sơ đồ các thanh ghi phần cứng hoặc bản copy dữ liệu trước hay sau của hệ thống. nếu thiết bịcủa bạn cần đọc hay ghi dữ liệu lien tục, bạn không cần phải thực hiện các bước này với mỗi yêu cầu vào.ra, nó có thể làm chậm đi quá trình được chấp nhận trong trường hợp cụ thể rất nhiều. Vì vậy bạn có thể chỉ định cái nào được biết như là vùng đệm chung, nơi mà các thiết bị và các trình điều khiển của bạn có thể đông thời truy cập tại nhiều thời điểm.
Tuy nhiên trong thực tế nhiều chi tiết này sẽ bị phụ thuộc khác nhau vào cách mà 4 tác nhân này ảnh hưởng lẫn nhau, các bước mà bạn thực hiện sẽ có những đặc tính chung. Hình 7.7 đã minh hoạc qua tổ chức của một chuyển đổi. Bạn bắt đầu chuyển đổi từ công việc StartIo bằng cách yêu cầu quyền sở hữu của chính đối tượng điều hợp.Quyền sở hữu này chỉ có giá trị khi bạn chia sẻ một kênh DMA hệ thống với một thiết bị khác, nhưng mà mô hình DMA của Windows XP yêu cầu bạn cần phải thực hiện các bước này. Khi trình quản lý vào/ra có thể cung cấp cho bạn quyền này, nó sẽ chỉ định cho bạn một số sơ đồ các thanh ghi cho quá trình sử dụng tạm thời cảu bạnvà gọi lại hành động điều khiển bộ điều hợp bạn cung cấp. TRong hành động điều khiển bộ điều hợp của bạn, bạn thực hiện một sơ đồ chuyển đổi từng bước đế sắp xếp phạm vi chuyển đổi đầu tiên (cũng có thể là chỉ có một). Một số phạm vi có thể cần thiết nếu khả năng sơ đồ các thanh ghi là không thể. Thiết bị của bạn phải có thể cản trở và điều khiển những điều có thể xáy ra giữa các phạm vi.
Figure 7-7. Flow of ownership during DMA.
Một thủ tục điều khiển bộ điều hợp của bạn có thể khởi tạo sơ đồ các thanh ghi cho phạm vi đầu tiên, bạn báo hiệu cho thiết bị của mình băt đầu hoạt động. Thiết bị của bạn sẽ thúc đẩy một ngắt khi mà quá trình chuyển đổi ban đầu được hoàn tất. VÀ sau đó thì bạn sẽ liệt kê được một DPC. Thủ tục của DPC sẽ khởi đầu một phạm vi chuyển đổi khácnếu cần thiết hoặc nếu không thì nó sẽ hoàn thành yêu cầu.
Đôi khi theo cách này, bạn sẽ giải phóng sơ đồ các thanh ghi và đối tượng điều hợp. Sự tính toán thời gian của hai sự kiện này là một trong số các chi tiết khác cái mà phục thuộc vào các tác nhân tôi đã nêu ra ở đầu của phần này.
Thực hiện chuyển đổi DM (Performing DMA Transfers):
Bây giờ tôi sẽ đi vào chi tiết về những cái máy cơ học cái mà vẫn được gọi là một gói cơ sở DMA chuyển đổi, ở khía cạnh nào đó bạn chuyển đổi một số dữ liệu riêng biệt bằng cách sử dụng vùng đệm dữ liệu cái mà đi cùng với gói yêu cầu vào/ra. Hãy bắt đầu một cách đơn giản và giả sử rằng bạn đối diện với một trường hợp rất chung ngày nay: Thiết bị của bạn là một bus PCI master nhưng không có khả năng phân giải/ tụ tập
Khi bạn tạo ra đối tượng thiết bị của mình, để bắt đầu bạn thong thường sẽ biểu thị điều bạn muốn để sử dụng phương thức truy cập trực tiếp vùng đệm dữ liệu bằng việc thiết lập cờ DO_DIRECT_IO. Bạn chọn phương thức trực tiếp bởi vì bạn thậm chí sẽ phải thong qua địa chỉ của một kí hiệu bộ nhớ lập danh sách khi một trong số các tham số hàm MapTransfẻ bạn sẽ gọi. Sự lựa chọn này sẽ đưa ra một số vấn đề đáng quan tâm về sự sắp hang của vùng đệm. Trừ phi mà ứng dụng sử dụng cờ FILE_FLAG_NO_BUFFERING trong lời gọi của nó tới hàm CreatFile, trình quản lý vào ra sẽ không bắt buộc yêu cầu xếp hang của đối tượng thiết bị trên các vùng đệm dữ liệu ở chế độ người dùng (nó không bắt buộc các yêu cầu cho nhân ở chế độ lời gọi trong ……). Nếu Hal hoặc thiết bị của bạn yêu cầu các vùng đệm DMA để bắt đầu trong một ranh giới cụ thể, vì thế bạn có thể copy từ duới lên một phần nhỏ của dữ liệu người dùng tới hang chính xác bên trong vùng đệm để có được một hang đợi yêu cầu- hoặc là điều đó hoặc nguyên nhân để làm sai và yêu cầu điều đó có một vùng đệm không sắp hàng.
TRong hàm StartDevice, bạn tạo một đối tượng điềuhợp bằng cách sử dụng đoạn code như sau:
DEVICE_DESCRIPTION dd;
RtlZeroMemory(&dd, sizeof(dd));
dd.Version = DEVICE_DESCRIPTION_VERSION;
dd.Master = TRUE;
dd.InterfaceType = InterfaceTypeUndefined;
dd.MaximumLength = MAXTRANSFER;
dd.Dma32BitAddresses = TRUE;
pdx->AdapterObject = IoGetDmaAdapter(pdx->Pdo, &dd,
&pdx->nMapRegisters);
Câu lệnh cuối cùng trong đoạn code này là quan trọng bậc nhất. IoGetDmaAdapter sẽ giao tiếp với bus điều khiển hoặc Hal để tạo ra một đối tượng điều hợp, cái mà địa chỉ của nó được trả về cho bạn. Tham số đầu tiên (pdx→Pdo) định nghĩa đối tượng thiết bị vật lý cho đối tượng của bạn. Tham số thứ hai chỉ tới cấu trúc DEVICE_DESCRIPTION cái mà bạn khởi tạo để mô tả một DMA tiêu biếu cho thiết bị của bạn. Tham số cuối cùng chỉ ra nơi mà hệ thống nên lưu giữ số lượng lớn nhất các số của sơ đồ các thanh ghi mà bạn sẽ được phép cố gắng lưu trữ để dự trữ trong suốt quá trình chuyển đơn. Bạn sẽ cần phải lưu ý rằng tôi đã lưu trữ hai trường trong thiết bị mở rộng để nhận lấy hai dữ liệu ra từ hàm này.
Để khởi đầu cho hoạt động vào/ra, thủ tục StartIo đầu tiên cần phải dự trữ một đối tượng bằng lời gọi thủ tục AllocateAdapterChannel của đối tượng. Một trong các tham số truyền tới hàm AllocateAdapterChannel là địa chỉ của một thủ tục điều khiển điều hợp, cái mà trình quản lý vào ra sẽ gọi khi sự lưu trữ xong xuôi. Đây là đoạn code mẫu mà bạn sử dụng để chuẩn bị và thực hiện lời gọi tới AllocateAdapterChannel:
typedef struct _DEVICE_EXTENSION {
PADAPTER_OBJECT AdapterObject; // device's adapter object
ULONG nMapRegisters; // max # map registers
ULONG nMapRegistersAllocated; // # allocated for this xfer
ULONG numxfer; // # bytes transferred so far
ULONG xfer; // # bytes to transfer during this stage
ULONG nbytes; // # bytes remaining to transfer
PVOID vaddr; // virtual address for current stage
PVOID regbase; // map register base for this stage
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
VOID StartIo(PDEVICE_OBJECT fdo, PIRP Irp)
{
PDEVICE_EXTENSION pdx =
(PDEVICE_EXTENSION) fdo->DeviceExtension;
PMDL mdl = Irp->MdlAddress;
pdx->numxfer = 0;
pdx->xfer = pdx->nbytes = MmGetMdlByteCount(mdl);
pdx->vaddr = MmGetMdlVirtualAddress(mdl);
ULONG nregs = ADDRESS_AND_SIZE_TO_SPAN_PAGES(pdx->vaddr,
pdx->nbytes);
if (nregs > pdx->nMapRegisters)
{
nregs = pdx->nMapRegisters;
pdx->xfer = nregs * PAGE_SIZE - MmGetMdlByteOffset(mdl);
}
pdx->nMapRegistersAllocated = nregs;
NTSTATUS status = (*pdx->AdapterObject->DmaOperations
->AllocateAdapterChannel)(pdx->AdapterObject, fdo, nregs,
(PDRIVER_CONTROL) AdapterControl, pdx);
if (!NT_SUCCESS(status))
{
CompleteRequest(Irp, status, 0);
StartNextPacket(&pdx->dqReadWrite, fdo);
}
}
- thiết bị mở rộng của bạn cần phải lưu trữ một số trường có lien quan đến các chuyển đổi DMA. Phần chú thích chỉ ra các trường hợp cho các trường này.
- Đây là một số câu lệnh khởi tạo cho các trường trong thiết bị mở rộng cho phạm vi đầu tiên của sự chuyển đổi.
- Ở đây chúng ta tính toán số lượng các sơ đồ các thanh ghi chúng ta yêu cầu hệ thống lưu trữ cho chúng ta trong suốt quá trình chuyển đổi này. Chúng ta bắt đầu bằng việc tính toán số lượng được yêu cầu cho toàn bộ sự chuyển đổi này. Macro ADDRESS_AND_SIZE_TO_SPAN_PAGES có thể đưa vào một bản kê khai vùng đệm có thể kéo dài qua ranh giới của một trang. Tuy nhiên con số mà chúng ta đi ngược từ dưới lên với khả năng có thể vượt qua số lớn nhất mà chúng ta được cho phépbởi lời gọi thong thường tới hàm IoGetDmAAdapter. TRong trường hợp này chúng ta cần thực hiện một chuyển đổi trong nhiều phạm vi. Do vậy chúng ta đảo ngược tỉlệ của trang đầu tiên để chỉ sử dụng con số chấp nhận được của sơ đồ các thanh ghi. Chúng ta cũng cấn phải nhớ có bao nhiêu sơ đồ các thanh ghi mà chúng ta đang cho phép để chúng ta có thể huỷ chính xác con số này về sau.
- TRong lời gọi tới hàm AllocateAdapterChannel, chúng ta chỉ ra địa chỉ của đ ối tượng Adapter, địa chỉ của đối tượng thiết bị của chúng ta, số lượng đã được tính toán của sơ đồ các thanh ghi, và địa chỉ của thủ tục điều khiển điều hợ của chúng ta. Tham số cuối cùng pdx là tham số ngữ cảnh cho thủ tục điều khiển điều hợp của chúng ta.
Thông thường thì một vài thiết bị có thể chia sẻ chung một đối tượng điều hợp đơn, đối tượng điều hợp đang chia sẻ trong cuộc sống thực tế chỉ khi bạn tin tưởng vào hệ thống điều khiển DMA. Các thiết bị Bus-Master tận dụng các đối tượng điều hợp thuộc quyền sở hữu.Nhưng bởi vì bạn không cần phải biết về hệ thống các thiết bị khi bạn tạo ra các đối tượng điều hợp, nên bạn cũng không cần phải đưa ra bất cứ chứng minhnào vềnó. Khi đó thong thường đối tượng điều hợp của chúng ta sẽ rất bận rộn khi bạn gọi tới AllocateAdapterChannel, và yêu cầu của bạn vì thế có thể bị đặt vào trong một hang đợi cho tới khi đối tượng điềuhợp trở nên có thể xử lý (rảnh rỗi). Tất cả khoảng trễ này xảy ra bên trong của AllocateAdapterChannel cái mà sẽ gọi thủ tục điều khiển điều hợp của bạn khi đối tượng điều hợp và tất cả sơ đồ các thanh ghi mà bạn yêu cầu rảng rỗi.
Nhiệm vụ tiếp theo của bạn là thực hiện những gì mà thiết bị phụ thuộc của bạn yêu cầu để nói cho thiết bị của bạn biết về địa chỉ vật lý để bắt đầu hoạt động trong phần cứng của bạn.
IO_ALLOCATION_ACTION AdapterControl(PDEVICE_OBJECT fdo,
PIRP junk, PVOID regbase, PDEVICE_EXTENSION pdx)
{
PIRP Irp = GetCurrentIrp(&pdx->dqReadWrite);
PMDL mdl = Irp->MdlAddress;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
BOOLEAN isread = stack->MajorFunction == IRP_MJ_READ;
pdx->regbase = regbase;
KeFlushIoBuffers(mdl, isread, TRUE);
PHYSICAL_ADDRESS address =
(*pdx->AdapterObject->DmaOperations->MapTransfer)
(pdx->AdapterObject, mdl, regbase, pdx->vaddr, pdx->xfer,
!isread);
return DeallocateObjectKeepRegisters;
}
- Tham số thứ 2 cái mà tôi đặt tên là junk to AdapterControl is whatever(bất cứ khi nào) was in the CurrentIrp field of the device object khi mà bạn gọi hàm AllocateAdapterChannel. Khi bạn sử dụng DEVQUEUE cho hang đợi IRP, bạn cần phải yêu cầu đối tượng DEVQUEUE cái mà là IRP hiện tại. Nếu bạn sử dụng hang đợi các thủ tục của Microsoft IoStartPacker & IoStartNextPacket để quảnlý hang đợi của bạn thì junk sẽ đúng là một IRP. Trong trường hợp này tôi sẽ gọi tên Irp để thay thế.
- Có một số điều khác biệt giữa đoạn code điều khiển hoạt động vào và ra sử dụng DMA, vì thế nó rất thuận tiện cho việc điều khiển cả hai hoạt động này trong thủ tục con đơn. Dòng code này kiểm tra code cho hàm chính cho Irp để quyết định khi nào thì hành động đọc hoặc ghĩe xảy ra.
- Tham số regbase tới hàm này là một điều khiển không rõ ràngtrong việc chỉ định tập hợp của các thanh ghi sẽ được lưu trữ trong suốt quá trình sử dụng của bạn. Sau này bạn sẽ cần tới giá trị này, vì thế bạn cần lưu trữ nó trong thiết bị mở rộng của mình.
- KeFlushIoBuffers chắc chắn rằng nội dung của toàn bộ các xửlý trong các bộ nhớ Cache cho vùng đệm bộ nhớ bạn đang sử dụng được làm sạch bộ nhớ. Tham số thứ 3 (TRUE)chỉ ra rằng bạn đã làm sạch bộ nhớ Cache chuẩn bị cho hoạt động của DMA. Kiểu kiến trúc của CPU có thể yêu cầu bước này, bởi vì thong thường thì hoạt động của DMA hướng trực tiếp đến hoặc đi từ bộ nhớ mà không nhất thiết phải kéo theo các bộ nhớ Cache.
- Thủ tục Map Transfer thực hiện phần cứng của DMA cho một phạm vi của chuyển đổi và trả về địa chỉ vật lý khi mà việc chuyển đổi bắt đầu. Chú ý rằng bạn cung cấp địa chỉ của một MDL như là tham số thứ hai của hàm này. Bởi vì bạn cần một MDL tại điểm này, nên thong thường thì bạn cần phải lựa chọn phương thức DO_DIRECT_IOkhi lần đầu tiên bạn tạo đối tượng thiết bị của mình, và trình quản lý vào ra vì thế sẽ tự động tạo một MDL cho bạn. Bạn cũng cần thong qua cùng với sơ đồ thanh ghi địa chỉ cơ sở (regbase). Bạn chỉ ra phần nào của MDL bị dính líu đến phạm vi của hoạt động này bằng cách cung cấp một địa chỉ ảo (pxd→vaddr) và một bute đếm (pxd→xfer). Sơ đồ chuyển đổi sẽ sử dụng tham số địa chỉ ảo để tính toán điạ chỉ offset của vùng đệm. Từ đó nó có thể xác định các trang vật lý đang chứa đựng dữ liệu của bạn.
- Đó là một điểm mà tại đó bạn lập trình phần cứng của mình trong thiết bị theo một cách đặc biệt là được yêu cầu. Ví dụ như là bạn có thể sử dụng một trong số các thủ tục của WRITE_Xxx Hal để gửi địa chỉ vật lý và các giá trị của byte đếm đến các thanh ghi ở trong card của bạn, và từ đó về sau thì bạn có thể điều khiển thanh ghi nhấp nháy để bắt đầu chuyển đổi dữ liệu.
- Chúng ta trả ra một hằng số DeAllocateKeepObjectRegister để chỉ ra rằng chúng ta đã không sử dụng đối tượng điều hợp nhưng chúng ta vẫn sử dụng sơ đồ các thanh ghi. Trong mví dụ cụ thể (bus chủ PCI), ở đây không bao giờ có sự ganh đua với đối tượng điều hợp tại vị trí đầu tiên vì khó khăn của nó là vấn đề chúng ta giải phóng đối tượng điều hợp đó. Trong một số trường hợp bus đang làm chủ khác (bus-mastering) chúng ta có thể chia sẻ điều khiển DMA với các thiết bị khác. Việc giải phóng đối tượng điều hợp này cho phép các thiết bị khác bắt đầu được thực hiện chuyển đổi bằng việc sử dụng các phần tập hợp rời nhau của sơ đồ thanh ghi từ một khối liền mà chúng ta vẫn đang sử dụng.
Một ngắt thường xuất hiện rất nhanh sau khi mà chúng ta bắt đầu chuyển đổi.Và thủ tục ngắt dịch vụ luôn luôn yêu cầu một DPC để liên hệ với sự hoàn thành của phạm vi đầu tiên của chuyển đổi. Thủ tục DPC của bạncó thể tương tự như sau:
VOID DpcForIsr(PKDPC Dpc, PDEVICE_OBJECT fdo,
PIRP junk, PDEVICE_EXTENSION pdx)
{
PIRP Irp = GetCurrentIrp(&pdx->dqReadWrite);
PMDL mdl = Irp->MdlAddress;
BOOLEAN isread = IoGetCurrentIrpStackLocation(Irp)
->MajorFunction == IRP_MJ_READ;
(*pdx->AdapterObject->DmaOperations->FlushAdapterBuffers)
(pdx->AdapterObject, mdl, pdx->regbase, pdx->vaddr,
pdx->xfer, !isread);
pdx->nbytes -= pdx->xfer;
pdx->numxfer += pdx->xfer;
NTSTATUS status = STATUS_SUCCESS;
if (pdx->nbytes && NT_SUCCESS(status))
{
pdx->vaddr = (PVOID) ((PUCHAR) pdx->vaddr + pdx->xfer);
pdx->xfer = pdx->nbytes;
ULONG nregs = ADDRESS_AND_SIZE_TO_SPAN_PAGES(pdx->vaddr,
pdx->nbytes);
if (nregs > pdx->nMapRegistersAllocated)
{
nregs = pdx->nMapRegistersAllocated;
pdx->xfer = nregs * PAGE_SIZE;
}
PHYSICAL_ADDRESS address =
(*pdx->AdapterObject->DmaOperations->MapTransfer)
(pdx->AdapterObject, mdl, pdx->regbase, pdx->vaddr,
pdx->xfer, !isread);
}
else
{
ULONG numxfer = pdx->numxfer;
(*pdx->AdapterObject->DmaOperations->FreeMapRegisters)
(pdx->AdapterObject, pdx->regbase,
pdx->nMapRegistersAllocated);
StartNextPacket(&pdx->dqReadWrite, fdo);
CompleteRequest(Irp, status, numxfer);
}
}
1. Khi bạn sử dụng DEVQUEUE cho hàng đợi IRP, bạn tin tưởng vào đối tượng hàng đợi sẽ giữ đường đi của IRP hiện thời.
2. Thủ tục FlushAdapterBuffer điều khiển vị trí trong đó chuyển đổi được yêu cầu sử dụng các vùng đệm trung gian được sở hữu bởi hệ thống. Nếu như bạn thực hiện một hành động input mà kéo dài qua ranh giới của trang thì dữ liệu vào nằm ở vùng đệm trung gian và cần phải được copy vào vùng đệm ở chế độ người dùng.
3. Ở đây chúng ta cập nhật số dư và số dữ liệu còn lại sau khi mà phạm vi chuyển đổi đã hoàn tất.
4. Tại vị trí này, bạn xác định phạm vi hiện thời của chuyển đổi đã được hoàn tất thành công hoặc có lỗi xảy ra. Ví dụ như là bạn có thể đọc trạng thái của cổng hay là xem xét kỹ kết quả của một hoạt động tương tự được thực hiện bởi thủ tục ngắt của bạn. Ở trong ví dụ này, tôi thiết lập thuộc tính variable cho trạng thái thành công (STATUS-SUCCESS) với sự mong đợi rằng bạn đã thay đổi nó nếu tìm ra lỗi ở đây.
5. Nếu chuyển đổi này không dừng lại, bạn cần phải lập trình cho một phạm vi khác. Bước đầu tiên trong tiến trình này là tính toán địa chỉ ảo của vị trí tiếp theo của bộ đệm ở chế độ người dùng. Theo tôi thì việc tính toán này đơn thuần chỉ là làm việc với một số- thực tế chúng ta chưa cố gắng để truy cập vào bộ nhớ bằng cách sử dụng địa chỉ ảo này. Tất nhiên, truy cập bộ nhớ có thể là một ý kiến tồi bởi vì chúng ta đang thực hiện một luồng ngữ cảnh tuỳ biến.
6. Các câu lệnh tiếp theo hầu như y hệt như việc chúng ta thực hiện trong phạm vi đầu tiên cho StartIo và AdapterControl. Kết quả cuối cùng sẽ là một địa chỉ logic cái mà có thể được lập trình trong thiết bị của bạn. Nó có thể phù hợp hay không phù hợp với địa chỉ vật lý như đã được hiểu bởi CPU. Một lời khuyên nhỏ là chúng ta bị miễn cưỡng chỉ sử dụng số các sơ đồ thanh ghi như là chúng ta được phép bởi thủ tục điều khiển điều hợp; StartIo đã lưu trữ con số này ở trong trường nMapRegistersAllocate của thiết bị mở rộng.
7. Nếu như bây giờ toàn bộ chuyển đổi hoàn tất, chúng ta cần giải phóng sơ đồ các thanh ghi mà chúng ta vừa sử dụng.
8. Các câu lệnh còn lại trong thủ tục DPC điều khiển các máy cơ học này hoàn tất IRP cái mà đã đưa cho chúng ta taị vị trí đầu tiên.
Các chuyển đổi sử dụng các danh sách tụ họp/tỏa ra
Nếu phần cứng của bạn có hỗ trợ tụ họp/toả ra, thì hệ thống này có một thời gian thực hiện chuyển đổi DMA đến và đi khỏi thiết bị của bạn dễ dàng hơn. Khả năng toả ra/tụ họp cho phép thiết bị thực hiện một chuyển đổi dính líu đến các trang cái mà không liên tục trong bộ nhớ vật lý.
Sử dụng cấu trúc Scatter_Gather_List đã được định nghĩa trong WDM.H để tạo danh sách này:
typedef struct _SCATTER_GATHER_ELEMENT {
PHYSICAL_ADDRESS Address;
ULONG Length;
ULONG_PTR Reserved;
} SCATTER_GATHER_ELEMENT, *PSCATTER_GATHER_ELEMENT;
typedef struct _SCATTER_GATHER_LIST {
ULONG NumberOfElements;
ULONG_PTR Reserved;
SCATTER_GATHER_ELEMENT Elements[];
} SCATTER_GATHER_LIST, *PSCATTER_GATHER_LIST;
Cuối cùng tôi sẽ giả sử rằng bạn có thể đơn giản chỉ định một kích thước lớn nhất danh sách Scatter/Gather trong hàm AddDevice của bạn và di chuyển nó để sử dụng khi mà bạn cần:
pdx->sglist = (PSCATTER_GATHER_LIST)
ExAllocatePool(NonPagedPool, sizeof(SCATTER_GATHER_LIST) +
MAXSG * sizeof(SCATTER_GATHER_ELEMENT));
Với các cơ sở hạ tầng ở tại vị trí này, thủ tuch AdapterControl của bạn sẽ trông giống như là:
IO_ALLOCATION_ACTION AdapterControl(PDEVICE_OBJECT fdo,
PIRP junk, PVOID regbase, PDEVICE_EXTENSION pdx)
{
PIRP Irp = GetCurrentIrp(&pdx->dqReadWrite);
PMDL mdl = Irp->MdlAddress;
BOOLEAN isread = IoGetCurrentIrpStackLocation(Irp)
->MajorFunction == IRP_MJ_READ;
pdx->regbase = regbase;
KeFlushIoBuffers(mdl, isread, TRUE);
PSCATTER_GATHER_LIST sglist = pdx->sglist;
ULONG xfer = pdx->xfer;
PVOID vaddr = pdx->vaddr;
pdx->xfer = 0;
ULONG isg = 0;
while (xfer && isg < MAXSG)
{
ULONG elen = xfer;
sglist->Elements[isg].Address =
(*pdx->AdapterObject->DmaOperations->MapTransfer)
(pdx->AdapterObject, mdl, regbase, pdx->vaddr,
&elen, !isread);
sglist->Elements[isg].Length = elen;
xfer -= elen;
pdx->xfer += elen;
vaddr = (PVOID) ((PUCHAR) vaddr + elen);
++isg;
}
sglist->NumberOfElements = isg;
return DeallocateObjectKeepRegisters;
}
- Hãy xem mô tả ban đầu của cách để lấy một con trỏ tới IRP đúng trong thủ tục điều khiển điều hợp
- Trước chúng ta đã tính toán pdx→xfer dựa vào số lượng được cho phép của sơ đồ các thanh ghi. Bây giờ chúng ta sẽ cố gắng để có thể chuyển đổi được nhiều dữ liệu, nhưng số lượng cho phép của các phần tử của Scatter/Gather có thể vượt quá giới hạn một vài (trong số đó)chúng ta có thể chuyển đổi trong suốt phạm vi này. Trong suốt vòng lặp dưới đây, xfer sẽ là số lượng các byte cái mà chúng ta chưa lập sơ đồ, và chúng ta sẽ phải tính toán lại pdx→xfer như chúng ta đã đi (đã tiến hành thực hiện)
- Đây là vòng lặp mà tôi đã hứa với các bạn, nơi mà chúng ta có thể gọi Map transfer để đặt các phần tử của Scatter/Gather. Chúng ta sẽ tiếp tục vòng lặp cho tới tận khi chúng ta sơ đồ hoá toàn bộ phạm vi của chuyển đổi này hoặc là cho đến tận khi chúng ta ra khỏi các phần tử của Scatter/Gather, bất cứ điều nào xảy ra đầu tiên.
- Khi chúng ta gọi tới sơ đồ chuyển đổi (map transfer) cho một thiết bị Scatter/Gather, nó sẽ sửa đổi chiều dài tham số để chỉ ra có bao nhiêu MDL bắt đầu tại địa chỉ ảo đã đựơc cung cấp (vaddr) là liên tiếp nhau theo quy luật tự nhiên va có thể vì thế mà được sơ đồ hoá bằng một phần tử danh sách Scatter/Gather đơn. Nó cũng trả ra địa chỉ vật lý của điểm bắt đầu của vùng kế tiếp.
- Đây chính là nơi mà chúng ta cập nhật các biến cái mà mô tả phạm vi hiện thời của chuyển đổi. Khi chúng ta rời khỏi vòng lặp, xfer sẽ bị giảm xuống bằng 0 (hoặc là chúng ta sẽ chạy ra ngoài các phần tử của Scatter/Gather), pxd→xfer sẽ tăng đến tổng số của tất cả các phần tử chúng ta có thể sơ đồ hoá được. Chúng ta không cập nhật trường pdx→xfer trong thiết bị mmở rộng- chúng ta đang làm việc trong thủ tục DPC. Đó là một trong những chi tiết phiền hà khác.
- Đây chính là nơi mà chúng ta tăng chỉ số phần tử của Scatter/Gather để phản ánh thực tế là chúng ta đã sử dụng nó tăng lên.
- Tại vị trí này, chúng ta có phần tử Scatter/Gather isg cái mà chúng ta nên lập trình trong thiết bị của mình trong các thiết bị phụ thuộc theo cách thích đáng. Sau đó chúng ta nên khởi động hoạt động của thiết bị này trong yêu cầu.
- Trả về DeAllocateObjectKeepRegisters là hợp lý cho một thiết bị bus đang làm chủ. Về mặt lý thuyết bạn có thể có một thiết bị không làm chủ với khả năng Scatter/Gather, và nó sẽ trả ra KeepObject thay vì các giá trị khác.
Thủ tục StartIo sẽ có dạng như:
VOID StartIo(PDEVICE_OBJECT fdo, PIRP Irp)
{
PDEVICE_EXTENSION pdx =
(PDEVICE_EXTENSION) fdo->DeviceExtension;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
NTSTATUS status;
PMDL mdl = Irp->MdlAddress;
ULONG nbytes = MmGetMdlByteCount(mdl);
PVOID vaddr = MmGetMdlVirtualAddress(mdl);
BOOLEAN isread = stack->MajorFunction == IRP_MJ_READ;
pdx->numxfer = 0;
pdx->nbytes = nbytes;
status =
(*pdx->AdapterObject->DmaOperations->GetScatterGatherList)
(pdx->AdapterObject, fdo, mdl, vaddr, nbytes,
(PDRIVER_LIST_CONTROL) DmaExecutionRoutine, pdx, !isread);
if (!NT_SUCCESS(status))
{
CompleteRequest(Irp, status, 0);
StartNextPacket(&pdx->dqReadWrite, fdo);
}
}
Trong StartDevice, bạn có một bít nhỏ của phần code thêm vào để chỉ định kênh DMA mà trình quản lý PnP phân công cho bạn, và bạn cũng cần khởi tạo nhiều hơn các trường của cấu trúc DEVICE_DESCRIPTION cho IoGetDmAAdapter:
NTSTATUS StartDevice(...)
{
ULONG dmachannel; // system DMA channel #
ULONG dmaport; // MCA bus port number
for (ULONG i = 0; i < nres; ++i, ++resource)
{
switch (resource->Type)
{
case CmResourceTypeDma:
dmachannel = resource->u.Dma.Channel;
dmaport = resource->u.Dma.Port;
break;
}
}
DEVICE_DESCRIPTION dd;
RtlZeroMemory(&dd, sizeof(dd));
dd.Version = DEVICE_DESCRIPTION_VERSION;
dd.InterfaceType = InterfaceTypeUndefined;
dd.MaximumLength = MAXTRANSFER;
dd.DmaChannel = dmachannel;
dd.DmaPort = dmaport;
dd.DemandMode = ??;
dd.AutoInitialize = ??;
dd.IgnoreCount = ??;
dd.DmaWidth = ??;
dd.DmaSpeed = ??;
pdx->AdapterObject = IoGetDmaAdapter(...);
}
- Danh sách các nguồn tài nguyên vào/ra sẽ có một tài nguyên DMA, từnhững gì mà bạn cần để trích ra kênh và các số hiệu cổng. Số kênh định nghĩa một trong số các kênh được hỗ trợ bởi một hệ thống điều khiển DMA. Số hiệu cổng chỉ là duy nhất trong bus thiết bị MCA
- Bắt đầu từ đây, bạn buộc phải khởi tạo một số trường của cấu trúc DEVICE_DESCRIPTION dựa vào hiểu biết của bạn về thiết bị của bạn. Hãy xem bảng 7.5
Tất cả mọi điều về điều khiển điều hợp của bạn và các thủ tục DPC sẽ giống hệt với đoạn code mà chúng ta được xem xét ở phần đầu của phần điều khiển thiết bị bus-mastering không có khả năng sơ đồ các thanh ghi ngoại trừ hai chi tiết nhỏ. Thứ nhất là, adaptercontrol trả ra một giá trị khác
IO_ALLOCATION_ACTION AdapterControl(...)
{
return KeepObject;
}
Giá trị trả ra KeepObject chỉ ra rằng chúng ta muốn giữ lại điều khiển thông qua sơ đồ các thanh ghi và kênh DMA chúng ta đang sử dụng. Thứ hai là, từ khi chúng ta không giải phóng đối tượng điều hợp khi mà AdapterControl được trả ra, chúng ta phải làm việc này trong thủ tục DPC bởi lời gọi FreeAdapterChannel thay vì FreeMapRegister:
VOID DpcForIsr(...)
{
(*pdx->AdapterObject->DmaOperations->FreeAdapterChannel)
(pdx->AdapterObject);
}
Sử dụng một bộ đệm chung (Using a Common Buffer).
Bạn thường chỉ định bộ đệm chung của bạn tại thời điểm StartDevice sau khi tạo ra đối tượng điều hợp của bạn:
typedef struct _DEVICE_EXTENSION {
PVOID vaCommonBuffer;
PHYSICAL_ADDRESS paCommonBuffer;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
dd.Dma32BitAddresses = ??;
dd.Dma64BitAddresses = ??;
pdx->AdapterObject = IoGetDmaAdapter(...);
pdx->vaCommonBuffer =
(*pdx->AdapterObject->DmaOperations->AllocateCommonBuffer)
(pdx->AdapterObject, <length>, &pdx->paCommonBuffer, FALSE);
Ưu tiên cho gọi IoGetDmAAdapter, bạn thiết lập các cờ Dma32BitAddresses & Dma64BitAddresses trong cấu trúc DEVICE_DESCRIPTION để nói rõ về các khả năng địa chỉ của thiết bị của bạn. Điều đó là: nếu thiết bị của bạn có thể định địa chỉ một bộ đệm sử dụng điạ chỉ vật lý 32 bít, hãy thiết lập Dma32BitAddresses thành true. Nếu nó có thể định địa chỉ bộ đệm sử dụng địa chỉ 64 bít thì hãy thiết lập Dma64BitAddresses thành True.
Trong lời gọi tới hàm AllocateCommonBuffer, tham số thứ hai là byte độ dài của bộ đệm bạn muốn chỉ định. Đối số thứ 4 là giá trị Boolean cái mà chỉ định bạn muốn bộ nhớ được chỉ định là được chấp nhận trong bộ nhớ cache của CPU (TRUE) hay không (FALSE)
AllocateCommonBuffer trả ra một địa chỉ ảo. Đây là một địa chỉ bạn sử dụng trình điều khiển của bạn để truy cập vào vùng đệm được chỉ định. AllocateCommonBuffer cũng thiết lập PHYSICAL_ADDRESS được trỏ tới bởi tham số thứ 3 : là địa chỉ logic được sử dụng vởi thiết bị của bạn cho chính vùng đệm của nó truy cập
Thiết bị Bus-Master đơn giản
bộ phận điều khiển mẫu PKTDMA trong nội dung sách hướng dẫn như thế nào để thực hiện hoạt động bus-master DMA không cần hỗ trợ scatter/gather bằng sử sụng mai mối chíp (matchmaker chip) AMCC S5933 PCI. Bạn đã thảo luận chi tiết như thế nào bộ điều khiển khởi tạo thiết bị trong StartDevice và như thế nào để khởi tạo một chuyển giao DMA trong StartIo. Bạn cũng cần phải thảo luận về tất cả các điề xảy ra trong AdapterControl trong thiết bị và thủ tục DpcForIsr . tôi đã trình bày ngắn gọn ở đầu là thủ tục này cần phải có một vài dòng code thiết bị - phụ thuộc cho việc bắt đầu 1 hoạt động của thiết bị . Bạn có thể viết 1 hàm helper có tên là StartTransfer với mục đích:
VOID StartTransfer(PDEVICE_EXTENSION pdx,
PHYSICAL_ADDRESS address, BOOLEAN isread)
{
ULONG mcsr = READ_PORT_ULONG((PULONG)(pdx->portbase + MCSR);
ULONG intcsr =
READ_PORT_ULONG((PULONG)(pdx->portbase + INTCSR);
if (isread)
{
mcsr │= MCSR_WRITE_NEED4 │ MCSR_WRITE_ENABLE;
intcsr │= INTCSR_WTCI_ENABLE;
WRITE_PORT_ULONG((PULONG)(pdx->portbase + MWTC), pdx->xfer);
WRITE_PORT_ULONG((PULONG)(pdx->portbase + MWAR),
address.LowPart);
}
else
{
mcsr │= MCSR_READ_NEED4 │ MCSR_READ_ENABLE;
intcsr │= INTCSR_RTCI_ENABLE;
WRITE_PORT_ULONG((PULONG)(pdx->portbase + MRTC), pdx->xfer);
WRITE_PORT_ULONG((PULONG)(pdx->portbase + MRAR),
address.LowPart);
}
WRITE_PORT_ULONG((PULONG)(pdx->portbase + INTCSR), intcsr);
WRITE_PORT_ULONG((PULONG)(pdx->portbase + MCSR), mcsr);
}
Thủ tục cài đặt thanh ghi hoạt động S5933 cho chuyển giao DMA và sau đó chuyển giao bắt đầu chạy . Các Bước của quá trình như sau :
1. Địa chỉ chương trình ( MxAR ) và tổng số chuyển giao (MxTC) thanh ghi thích hợp để định hướng cho luồng dữ liệu . AMCC được chọn cho giới hạn đọc để miêu tả hoạt động truyền dữ liệu ra thiết bị . Bởi vậy khi chúng ta thực hiện đầy đủ IRP_MJ_WRITE, chương trình sẽ đọc hoạt động tại chip level . địa chỉ chúng ta sửa dụng là địa chỉ logic được quay trở lại bởi MapTransfer.
2. Cho phép một ngắt khi mà sự chuyển đổi dần tới 0 bởi việc viết Intcsr
- Bắt đầu sự chuyển đổi bằng cách thiết lập một bít cho phép chuyển đổi trong MCSR.
Đoạn code này không được rõ ràng cho lắm, nhưng S5933 là một khả năng thực tế của việc một DMA đọc và một DMA viết tại cùng một thời điểm. Tôi đã viết PKTdma theo cách đó chỉ với một hoạt động (hoặc là đọc hoặc là viết) có thể xảy ra. Để khái quát hoá trình điều khiển cho phép cả hai loại hoạt động xảy ra đồng thời, bạn cần tới phương tiện để tách các hàng đợi cho việc đọc và ghi IRP và tạo ra hai đối tượng thiết bị và hai đối tượng điều hợp- một cặp để đọc và cặp còn lại để viết. Để tránh tình trạng lúng túng của việc cố gắng để đợi gấp đôi các đối tượng giống nhau bên trong AllocateAdapterChannel. Tôi nghĩ rằng việc thêm sự phức tạp vào trong ví dụ mẫu sẽ gây khó khăn cho bạn. (tôi biết rằng tôi đã cảm thấy lạc quan về kĩ năng mô tả để hàm ý rằng tôi không làm cho bạn thấy lẫn lộn, nhưng nó có thể sẽ tồi tệ hơn)
Điều khiển các ngắt trong PKTDMA
PCI42 đã bao gồm một thủ tục ngắt cái mà đã thực hiện một bit nhỏ của công việc để di chuyển dữ liệu. Thủ tục ngắt của PKTDMA thì đơn giản hơn.
BOOLEAN OnInterrupt(PKINTERRUPT InterruptObject,
PDEVICE_EXTENSION pdx)
{
ULONG intcsr =
READ_PORT_ULONG((PULONG) (pdx->portbase + INTCSR));
if (!(intcsr & INTCSR_INTERRUPT_PENDING))
return FALSE;
ULONG mcsr = READ_PORT_ULONG((PULONG) (pdx->portbase + MCSR));
WRITE_PORT_ULONG((PULONG) (pdx->portbase + MCSR),
mcsr & ~(MCSR_WRITE_ENABLE │ MCSR_READ_ENABLE));
intcsr &= ~(INTCSR_WTCI_ENABLE │ INTCSR_RTCI_ENABLE);
BOOLEAN dpc = GetCurrentIrp(&pdx->dqReadWrite) != NULL;
while (intcsr & INTCSR_INTERRUPT_PENDING)
{
InterlockedOr(&pdx->intcsr, intcsr);
WRITE_PORT_ULONG((PULONG) (pdx->portbase + INTCSR), intcsr);
intcsr = READ_PORT_ULONG((PULONG) (pdx->portbase + INTCSR));
}
if (dpc)