24/05/2018, 16:34

Phục vụ ngắt (Servicing an Interrupt )

Cấu hình một ngắt (Configuring an Interrupt ) Bạn cấu hình một ngắt nguồn trong hàm StartDevice bằng cách gọi hàm IoconnectInterrupt sử dụng các đối số những cái mà bạn có thể trích ra đơn giản từ kí hiệu của CmResourceTypeInterrupt. Trình điều khiển ...

Cấu hình một ngắt (Configuring an Interrupt )

Bạn cấu hình một ngắt nguồn trong hàm StartDevice bằng cách gọi hàm IoconnectInterrupt sử dụng các đối số những cái mà bạn có thể trích ra đơn giản từ kí hiệu của CmResourceTypeInterrupt. Trình điều khiển và thiết bị của bạn cầm phải hoàn toàn sẵn sàng làm việc một cách thích hợp khi bạn gọi hàm IoConnectInterrupt, bạn thậm chí có khi phải phục vị các ngắt này trước khi mà hàm này trả ra giá trị- do vậy bạn thường tạo ra một lời gọi gần cuối của tiến trình cấu hình. Một số thiết bị có đặc điểm phần cứng cho phép bạn ngăn cản chúng từ ngắt. Nếu thiết bị của bạn có các đặc tính này, ẩn các ngắt trước khi gọi IoConnectInterrupt và cho phép các ngắt sau đó. Các câu chú thích và câu lệnh cấu hình cho một ngắt tương tự như đoạn code sau:

typedef struct _DEVICE_EXTENSION {

  PKINTERRUPT InterruptObject;

  } DEVICE_EXTENSION, *PDEVICE_EXTENSION;

ULONG vector;             // interrupt vector

KIRQL irql;               // interrupt level

KINTERRUPT_MODE mode;     // latching mode

KAFFINITY affinity;       // processor affinity

BOOLEAN irqshare;         // shared interrupt?

for (ULONG i = 0; i < nres; ++i, ++resource)

  {

  switch (resource->Type)

    {

  case CmResourceTypeInterrupt:

    irql = (KIRQL) resource->u.Interrupt.Level;

    vector = resource->u.Interrupt.Vector;

    affinity = resource->u.Interrupt.Affinity;

    mode = (resource->Flags == CM_RESOURCE_INTERRUPT_LATCHED)

      ? Latched : LevelSensitive;

    irqshare =

      resource->ShareDisposition == CmResourceShareShared;

    break;

  }

status = IoConnectInterrupt(&pdx->InterruptObject,

  (PKSERVICE_ROUTINE) OnInterrupt, (PVOID) pdx, NULL,

  vector, irql, irql, mode, irqshare, affinity, FALSE);

  1. Tham số Level chỉ ra rằng mức độ yêu cầu ngắt cho ngắt này (IRQL)
  2. Tham số Vector chỉ ta rằng ngắt cứng điều khiển cho ngắt này. Chúng ta không để ý đến đây là số mấy vì chúng ta thực hiện hành động này như là đường ống nối giữa trình quản lý PnP và IoConnectInterrupt. Tất cả các vấn đề này là để cho HAL hiểu được số đó có ý nghĩa gì.
  3. Affinity là một bit mặt nạ cái mà biểu thị cho biết những CPU nào sẽ được phép điều khiển ngắt này.
  4. Chúng ta cần phải nói về IoConnectInterrupt mỗi khi ngắt của chúng ta là edge-triggered hay là một level-triggered. Nếu trường cờ của nguồn này là Cm-resource-interrupt-latched thì chúng ta có một ngắt edge-triggered. Còn không thì chúng ta sẽ có một ngắt level_triggered.
  5. Sử dụng câu lệnh này để có thể nhận ra khi nào thì ngắt được chia sẻ.

Trong lời gọi tới hàm IoConnectInterrupt ở cuối của đoạn code này, chúng ta sẽ đơn giản là nhắc lại các giá trị chúng ta đã lấy ra khỏi kí hiệu nguồn của ngắt. Tham số đầu tiên (&pxd→interruptObject) chỉ tên ra nơi lưu trữ kết quả của hành động kết nối, một con trỏ tới nhân của đối tượng ngắt là cái mà được mô tả trong ngắt của bạn. Tham số thứ hai (OnInterrupt) là tên của dịch vụ ngắt của bạn. Tôi sẽ thảo luận ISR một bít kĩ hơn ở trong một chương. Tham số thứ 3 (pdx) là giá trị ngữu cảnh cái mà sẽ được vượt qua khi một tham số tới ISR tại mỗi một thời điểm thiết bị của bạn ngắt. Tôi xin nói them về tham số ngữ cảnh này sau tại phần “Selecting an appropriate Context Argument”.

Tham số thứ 5 và thứ 6 (vector và irql) theo thứ tự, chỉ ra số các vector ngắt và mức của yêu cầu ngắt, cho các ngắt bạn kết nối. Tham số thứ 8 (mode) là Latched hoặc LevelSensitive để biểu thị ngắt đó là edge-triggered hay là level-triggered. Tham số thứ 9 là true nếu ngắt của bạn được chia sẻ với thiết bị khác và là False nếu ngược lại. Tham số thứ 10 (affinity) là quan hệ mặt nạ tiến trình cho ngắt này. Tham số thứ 11 và cũng là tham số cuối cùng chủ ra hệ điều hành có cần phải lưu lậi các ngữ cảnh Floating-pointer hay không khi mà thiết bị này bị ngắt. Bởi vì bạn không đuợc phép thực hiện các tính toán Floating-poiter trong ISR trong hệ nền x86, trình điều khiển có thể mang theo sẽ luôn thiết lập cờ này là False.

Ngắt điều khiển (Handling Interrupts):

Thiết bị của bạn có thể ngắt trên mọi CPU định trước trong mặt nạ tương đồng (affinity mask) mà bạn chỉ định trong lời gọi tới IoConnectInterrupt. Khi có một sự kiện ngắt xảy ra, hệ thống sẽ chuyển tới IRQL của CPU tới mức đồng bộ hóa thích hợp và yêu cầu khóa quay(spin lock) hỗ trợ liên hợp với đối tượng ngắt. sau đó nó gọi ISR của bạn, ISR có form định nghĩa như dưới đây:

BOOLEAN OnInterrupt(PKINTERRUPT InterruptObject, PVOID Context)

  {

  if (<device not interrupting>)

    return FALSE;

  <handle interrupt>

  return TRUE;

  }

Cơ cấu ngắt điều khiển của Windows NT là các ngắt cứng (Hardware interupts) có thể được chia sẻ bởi nhiều thiết bị. Theo đó công việc đầu tiên trong ISR là quyết định xem có hay không thiết bị của bạn đang ngắt ở thời điểm hiện tại. Nếu không, Bạn sẽ trả về giá trị FASLE ngay tức thì vì thế nhân (kernel) có thể gửi lệnh ngắt tới các driver của các thiết bị khác. Còn nếu có, bạn sẽ xóa ngắt tại mức thiết bị và trả về giá trị là TRUE. Liệu rằng sau đó nhân có gọi đến ISR của các thiết bị khác hay không thì nó sẽ phụ thuộc vào việc liệu rằng thiết bị ngắt là edge-triggered hay trigger mức( level-triggered) và trên các chi tiết hệ nền khác.

Lập trình giới hạn trong ISR (Programming Restrictions in the ISR )ISR thực thi tại một IRQL cao hơn so với DISPATCH_LEVEL. Tất cả các đoạn code và dữ liệu sử dụng trong một ISR theo đó sẽ phải ở trong bộ nhớ nonpaged (nonpaged memory). Hơn nữa, việc thiết lập các hàm ở chế độ nhân (kernel-mode functions) mà một ISR có thể gọi là rất hạn chế.

Kể từ khi một ISR thực thi tại một IRQL mức cao, nó cho ra các hoạt động khác trên chính CPU của nó mà yêu cầu IRQL tương tự hoặc thấp hơn. Với hệ thống mà việc thi hành là tốt nhất thì theo đó, ISR phải thực thi nhanh nhất mức độ có thể. Về cơ bản, là làm số lượng công việc nhỏ nhất được yêu cầu để phục vụ phần cứng của bạn và trả về. trừ phi có thêm công việc để làm (chẳng hạn hoàn thành một IRP), lập lịch cho một DPC để nắm giữ, xử lý việc này.

Nhưng đừng có vội vã trong việc tính toán số pi với hàng ngàn điểm thập phân trên ISR của bạn, ( trừ phi thiết bị của bạn yêu cầu bạn phải làm một điều gì đó thì thật “tức cười” và nó chắc hẳn là không thể). Một khả năng phán đoán tốt sẽ nói cho bạn biết điều gì là đúng đắn khi bạn lựa chọn làm việc giữa một thủ tục ISR và một DPC.

BOOLEAN OnInterrupt(PKINTERRUPT InterruptObject,

  PDEVICE_EXTENSION pdx)

  {

  UCHAR devstatus = READ_PORT_UCHAR(pdx->portbase);

  if ((devstatus & 1))

    return FALSE;

  <etc.>

  }

Lời triệu gọi thủ tục trì hoãn (Deferred Procedure calls):

Việc phục vụ đầy đủ một ngắt thiết bị thường yêu cầu bạn phải thực thi các thao tác không đúng luật trong một ISR hoặc là quá đắt để có thể tiến hành tại một IRQL cao của một ISR. Để có thể tránh được những vấn đề này, những nhà phát triển Windows NT đã cung cấp cơ chế lời gọi thủ tục trì hoãn. DPC là một cơ chế đa mục đích, nhưng thông thuờng nhất nó được sử dụng trong việc kết nối với một nắm giữ các ngắt. Trong hầu hết các viễn cảnh phổ biến, thiết bị ISR của bạn mà yêu cầu hiện hành được hòan thành và yêu cầu một DPC. Hơn thế lời triệu gọi đến nhân trong DPC thường lệ của bạn tại DISPATCH_LEVEL. Dẫu cho việc bịhạn chế trên các thủ tục thiết bị bạn vẫn có thể triệu gọi. Có ít sự giới hạn như thế bây giờ bạn đang chạy ở một IRQL thấp hơn bên trong ISR. Trong trường hợp đặc biệt, việc triệu gọi các thủ tục là hợp lý chẳng hạn như IoCompleteRequest và StartNextPacket chúng cần thiết về mặt logic tại thao tác cuối của một I/O

Mọi đối tượng thiết bị đều có một đối tượng DPC “miễn phí”. Điều đó có nghĩa là DEVICE_OBJECT có một đối tượng DPC định rõ, Dpc được xây dựng sẵn. Bạn cần phải thiết lập một cách vắn tắt đối tượng DPC xây dựng sẵn này sau khi bạn tạo đối tượng thiết bị của mình

NTSTATUS AddDevice(...)

  {

  PDEVICE_OBJECT fdo;

  IoCreateDevice(..., &fdo);

  IoInitializeDpcRequest(fdo, DpcForIsr);

  

  }

IoInitializeDpcRequest là một Macro trong WDM.H nó khởi tạo đối tượng xây dựng sẵn DPC (built-in DPC) của đối tượng thiết bị. Đối số thứ 2 là một địa chỉ của DPC thường lệ mà tôi sẽ chỉ ra cho các bạn ngay bây.

Với việc khởi tạo cho đối tượng DPC tại đây, ISR có thể yêu cầu một DPC bằng việc sử dụng Macro dưới đây:

BOOLEAN OnInterrupt(...)

  {

  IoRequestDpc(pdx->DeviceObject, NULL, (PVOID) pdx);

  }

Lời gọi này tới các IoRequestDpc đặt đối tượng DPC của đối tượng thiết bị trong một hàng đợi của hệ thống lớn, như hình minh họa dưới đây:.

Hình 7-5. Tiến trình của DPC requests.

Giá trị NULL các thông số pdx là các giá trị ngữ cảnh (context). Khi không có hoạt động khác đang xảy ra tại DISPATCH_LEVEL, nhân sẽ gỡ bỏ đối tượng DPC của bạn từ hàng đợi và triệu gọi tới DPC thường lệ, có kiểu nguyên mẫu như sau

VOID DpcForIsr(PKDPC Dpc, PDEVICE_OBJECT fdo, PIRP junk,

  PDEVICE_EXTENSION pdx)

  {

  }

Những gì bạn làm bên trong một DPC thường lệ sẽ phụ thuộc vào độ mạnh, lớn trên thiết bị mà bạn làm việc. Một nhiệm vụ thích hợp sẽ được hoàn thành IRP hiện tại và giải phóng IRP tiếp theo từ hàng đợi. Nếu bạn sử dụng một trong số các đối tượng DEVQUEUE cho việc đợi IRP, đoạn mã sẽ là như sau:

VOID DpcForIsr(...)

  {

  PIRP Irp = GetCurrentIrp(&pdx->dqRead);

  StartNextPacket(&pdx->dqRead, fdo);

  IoCompleteRequest(Irp, <boost value>);

  }

Trong đoạn code này, chúng tôi dựa vào sự kiện mà gói DEVQUEUE ghi nhớ IRP nó gửi tới StartIo thường lệ. IRP chúng ta muốn hoàn thành là một IRP hiện hành khi chúng ta bắt đầu một DPC thường lệ. Theo thói quen chúng ta sẽ gọi StartNextPacket trước so với IoCompleteRequest vì thế chúng ta có thể sẽ có thiết bị “bận” với một yêu cầu mới trước khi chúng ta bắt đầu với một tiến trình dài tiềm năng của việc hoàn thành IRP hiện hành.

Việc lập lịch DPC

Đến tận lúc này chúng ta đã che đậy hai chi tiết khá quan trọng và một điểm nhỏ về DPC. Điều chi tiết quan trọng đầu tiên là ẩn trong sự kiện mà bạn có một đối tượng DPC mà nó bị đẩy vào trong một hàng đợi bởi IoRequestDpc. Nếu thiết bị của bạn sinh ra thêm một ngắt phụ trước khi DPC thường lệ thực tế mới chạy, và nếu như ISR yêu cầu một DPC khác, nhân đơn giản sẽ bỏ qua yêu cầu thứ hai. Nói cách khác, đối tượng DPC của bạn sẽ ở trong hàng đợi một lần, không có vấn đề gì có bao nhiêu DPC được yêu cầu kế tiếp nhau của ISR của bạn, và nhân sẽ callback chỉ một lần. Trong suốt một việc “khẩn cầu” này, thì DPC thường lệ của bạn cần phải hoàn thành tất cả các công việc liên quan đến các ngắt mà xảy ra ở DPC cuối cùng..

Để đơn giản nhất, bạn có thể chắc chắn thiết bị của bạn sẽ không bị ngắt trong thời điểm mà bạn yêu cầu DPC và thời điểm mà DPC thường lệcủa bạn kết thúc sự làm việc của nó.

Đối tượng DPC tùy biến (Custom DPC Objects )

Bạn có thể tạo ra các đối tượng DPC khác bên cạnh một DPC với tên Dpc trong một đối tượng thiết bị Đơn giản để hạn chế bộ nhớ- trong phần mở rộng thiết bị của bạn hoặc một vài nơi khác mà không được đánh số (isn’t paged)—với một đối tươngj KDPC, và khởi tạo nó:

typedef struct _DEVICE_EXTENSION {

  KDPC CustomDpc;

  };

KeInitializeDpc(&pdx->CustomDpc,

  (PKDEFERRED_ROUTINE) DpcRoutine, fdo);

Trong lời gọi tới KeInitializeDpc, đối số thứ hai là địa chỉ của DPC thường lệ trong bộ nhớ “nonpaged”, và đối số thứ 3 là một thông số ngữ cảnh tùy ý mà sẽ được gửi tới DPC thường lệ như đối số thứ 2 của nó..

Để yêu cầu một lời gọi trì hoãn (deferred call ) tới một DPC thường lệ tùy biến, gọi KeInsertQueueDpc:

BOOLEAN inserted = KeInsertQueueDpc(&pdx->CustomDpc, arg1, arg2);

Đối số arg1 và arg2 là các con trỏ ngữ cảnh tùy ý mà sẽ được chuyển tới DPC thường lệ tùy biến .Giá trị trả về là FALSE nếu đối tượng DPC đã ở trong một bộ xử lý đợi rồi và bằng TRUE trong trường hợp ngựơc lại.

Ngoài ra, bạn có thể gỡ bỏ một đối tượng DPC từ một hàng đợi xử lý bằng việc triệu gọi KeRemoveQueueDpc.

Chúng ta có một đoạn code chỉ định thiết bị như sau:

NTSTATUS StartDevice(...)

  {

  ResetDevice(pdx);

  status = IoConnectInterrupt(...);

  KeSynchronizeExecution(pdx->InterruptObject,

    (PKSYNCHRONIZE_ROUTINE) SetupDevice, pdx);

  return STATUS_SUCCESS;

  }

Điều này có nghĩa là , chúng ta viện dẫn một thủ tục trợ giúp (ResetDevice) để thiết lập lại phần cứng. Một trong số những nhiệm vụ ResetDevice là để tránh cho thiết bị khỏi sự sinh ra bất kỳ ngắt nào trong mức có thể. Sau đó chúng ta gọi IoConnectInterrupt để kết nối thiết bị ngắt tới ISR của mình. Thậm chí trước cả khi IoConnectInterrupt trở lại, điều này là có thể cho các thiết bị của chúng ta để sản sinh ra một ngắt, vì thế bất kỳ mọi thứ về trình điều khiển của chúng ta và phần cứng phải sẵn sàng để đi trước. Sau khi kết nối ngắt, chúng ta viện dẫn thủ tục trợ giúp khác có tên là SetupDevice để lập trình cho thiết bị làm việc theo cách mà chúng ta muốn. chúng ta phải đồng bộ bước này với ISR của mình bởi vì nó sử dụng cùng thanh ghi phần cứng mà ISR của chúng ta sẽ sử dụng. và chúng ta không muốn mọi khả năng của việc gửi các lệnh mâu thuẫn thiết bị. Lời gọi SetupDevice là bước cuối cùng trong StartDevice của PCI42 bởi vì trái lại với những gì tôi đã nói với các bạn trong chương 2 - PCI42 không được đăng ký mọi giao diện thiết bị và theo đó sẽ không thể có khả năng ở thời điểm này. t.

ResetDevice là một chỉ định thiết bị cao và được đọc như sau:

VOID ResetDevice(PDEVICE_EXTENSION pdx)

  {

  PAGED_CODE();

  WRITE_PORT_ULONG((PULONG) (pdx->portbase + MCSR), MCSR_RESET);

  LARGE_INTEGER timeout;

  timeout.QuadPart = -10 * 10000; // i.e., 10 milliseconds

  KeDelayExecutionThread(KernelMode, FALSE, &timeout);

  WRITE_PORT_ULONG((PULONG) (pdx->portbase + MCSR), 0);

  WRITE_PORT_ULONG((PULONG) (pdx->portbase + INTCSR),

    INTCSR_INTERRUPT_MASK);

  }

  1. S5933 có một điều khiển chủ “master control”/ thanh ghi trạng thái “status register”(MCSR) mà các điều khiển bus-mastering DMA chuyển, di chuyển và các hành động khác. Xác nhận 4 trong số các bit này sẽ đặt lại các đặc trưng khác nhau của thiết bị. Tôi đã định nghĩa hằng số MCSR_RESET để là một mặt nạ “mask” bao gồm cả 4 trong số các cờ thiết lập lại này (all four of these reset flags). hằng số này và các hằng số “hiển nhiên” cho các đặc trưng của S5933 là file S5933.H file mà là bộ phận của đề án PCI42
  2. Ba trong số các cờ thuộc về các đặc trưng bên trong của S5933 và sẽ có ảnh hưởng ngay lập tức. Việc thiết lập cờ thứ 4 để xác nhận tín hiệu cho thiết bị thêm mới. Để xác nhận lại việc thiết lập thêm vào “add-on reset”, bạn phải dứt khoát thiết lập cờ này về 0 Nhìn chung, bạn muốn đưa phần cứng một bit nhỏ của thời điểm để ghi nhận một xung thiết lập lại (In general, you want to give the hardware a little bit of time to recognize a reset pulse.) KeDelayExecutionThread, mà ta đã thảo luận trong chương 4, đặt luồng này trong trạng thái sleep khoảng 10 mili giây. Bạn có thể tăng hoặc giảm hằng số này nếu như phần cứng có các yêu cầu khác nhau, nhưng đừng quên rằng thời gian không được tính “time -out”sẽ không bao giờ nhỏ hơn so với đồng hồ hệ thống. Từ đây chúng ta đang hạn chế luồng của mình, chúng ta cần thiết phải chạy tại PASSIVE_LEVEL trong một ngữ cảnh luồng không tùy ý “ nonarbitrary thread context”. Các điều kiện đó được gặp bởi vì bộ gọi cuối cùng là PnP Manager, mà đã gửi cho chúng ta một IRP_MN_START_DEVICE trong sự mong đợi đầy đủ rằng chúng ta đang hạn chế luồng hệ thống
  3. Bước cuối cùng trong việc thiết lập thiết bi là xóa các ngắt còn treo “pending interupts”.. S5933 có 6 cờ ngắt trong một điều khiển ngắt/đối tượng trạng thái (interrupt control/status register) (INTCSR). Việc viết một 1 bits trong 6 vị trí này sẽ xóa tất cả các ngắt treo. (Nếu chúng ta viết lại một giá trị mặt nạ mà có một bit 0 trong các vị trí cờ ngắt, trạng thái của ngắt đó không ảnh hưởng. Kiểu bit cờ này được gọi là read/write-clear hoặc đơn giản là R/WC.) Các bit khác trong INTCSR cho phép các ngắt của các kiểu khác nhau. Bằng việc viết 0 bit (bit 0 ?) “0 bits”trong các điểm này, chúng ta đang làm mất đi khả năng thiết bị đạt tới qui mô lớn nhất có thể.

Hàm SetupDevice là khá đơn giản :

VOID SetupDevice(PDEVICE_EXTENSION pdx)

  {

  WRITE_PORT_ULONG((PULONG) (pdx->portbase + INTCSR),

    INTCSR_IMBI_ENABLE

    │ (INTCSR_MB1 << INTCSR_IMBI_REG_SELECT_SHIFT)

    │ (INTCSR_BYTE0 << INTCSR_IMBI_BYTE_SELECT_SHIFT)

    );

  }

Hàm này lập trình lại INTCSR để chỉ định điều chúng ta muốn một ngắt được xảy ra khi có sự thay đổi tới byte 0 của thanh ghi hộp thư về 1. chúng ta có thể chỉ định các điều kiện ngắt khác cho chip này, bao gồm sự “trống rỗng” của một byte riêng biệt của một thanh ghi hộp thư đến được chỉ định, việc hoàn thành một tiến trình chuyển DMA đọc, và việc hoàn thành của một tiến trình chuyển DMA ghi

Bắt đầu một thao tác đọc -Starting a Read Operation

PCI42’s StartIo routine cho phép “mô hình” mà chúng ta đã được học rồi.

VOID StartIo(IN PDEVICE_OBJECT fdo, IN PIRP Irp)

  {

  PDEVICE_EXTENSION pdx =

    (PDEVICE_EXTENSION) fdo->DeviceExtension;

  PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);

  if (!stack->Parameters.Read.Length)

    {

    StartNextPacket(&pdx->dqReadWrite, fdo);

    CompleteRequest(Irp, STATUS_SUCCESS, 0);

    return;

    }

  pdx->buffer = (PUCHAR) Irp->AssociatedIrp.SystemBuffer;

  pdx->nbytes = stack->Parameters.Read.Length;

  pdx->numxfer = 0;

  KeSynchronizeExecution(pdx->InterruptObject, 

    (PKSYNCHRONIZE_ROUTINE) TransferFirst, pdx);

  }

1. Ở đây chúng ta ghi lại các thông số trong phần mở rộng thiết bị để mô tả tiến trình vào của một thao tác đầu vào. Chúng ta bảo đảm PCI42 sử dụng phương thức DO_BUFFERED_IO, là không điển hình nhưng nó giúp chúng ta tạo ra trình điều khiển đủ đơn giản để được sử dựng như một ví dụ.

2. Bởi vì ngắt của chúng ta đã được kết nối, thiết bị của chúng ta có thể ngắt bất kỳ lúc nào. ISR sẽ muốn truyền các bytes dữ liệu khi các ngắt xảy ra, nhưng chúng ta muốn được đảm bảo rằng ISR không bao giờ bị lộn xộn, rắc rối về bộ đệm dữ liệu để sử dụng hoặc về số các bytes chúng ta đang cố gắng đọc. Để kiềm chế “tính hám” của ISR, chúng ta đặt một cờ trong phần mở rộng thiết bị định bận rộn mà thông thường là FALSE.

Bây giờ là lúc thiết lập cờ về giá trị TRUE. Giống như thông thường khi chúng ta giải quyết với một tài nguyên chia sẻ , chúng ta cần phải đồng bộ việc thiết lập cờ với đoạn mã trong ISR mà kiểm tra nó, và theo đó cần phải viện dẫn một thủ tục SynchCritSection giống như tôi đã thảo luận từ trước. Ngòai ra, có thể xảy ra một byte dữ liệu đã có sẵn rồi. trong trường hợp đó thì ngắt đầu tiên sẽ không bao giờ xảy ra . TransferFirst là một thủ tục trợ giúp mà kiểm tra việc đọc và những kết quả có thể xảy ra cho byte đầu tiên này. Hàm thêm vào “add-on function” có nhiều cách để nhận biết việc xóa sạch hộp mail (hộp mail rỗng), vì thế nó có thể đoán chừng để gửi byte tiếp theo vào thời điểm thích hợp.

. Đây là TransferFirst:

  1. VOID TransferFirst(PDEVICE_EXTENSION pdx)
  2.   {
  3.   pdx->busy = TRUE;
  4.   ULONG mbef = READ_PORT_ULONG((PULONG) (pdx->portbase + MBEF));
  5.   if (!(mbef & MBEF_IN1_0))
  6.     return;
  7.   *pdx->buffer = READ_PORT_UCHAR(pdx->portbase + IMB1);
  8.   ++pdx->buffer;
  9.   ++pdx->numxfer;
  10.   if (—pdx->nbytes == 0)
  11.     {
  12.     pdx->busy = FALSE;
  13.     PIRP Irp = GetCurrentIrp(&pdx->dqReadWrite);
  14.     Irp->IoStatus.Status = STATUS_SUCCESS;
  15.     Irp->IoStatus.Information = pdx->numxfer;
  16.     IoRequestDpc(pdx->DeviceObject, NULL, pdx);
  17.     }

  }

S5933 có một thanh ghi rỗng/đầy cho hộp thư (empty/full register) (MBEF) các bit của chúng chỉ ra trạng thái hiện hành của mỗi byte của mỗi thanh ghi hộp mail. ở đây chúng ta kiểm tra liệu rằng byte thanh ghi chúng ta sử dụng cho đầu (giá trị thanh ghi hộp mail từ 1, về 0) hiện giờ không được đọc. Nếu như vậy, chúng ta sẽ đọc nó, điều này quả thật làm rỗng bộ đếm truyền. chúng ta đã có một thủ tục con rồi (DpcForIsr) thủ tục này biết phải làm gì với một yêu cầu đầy đủ, vì thế chúng ta yêu cầu một DPC nếu như byte đầu tiên thỏa mãn được yêu cầu. (Gọi lại rằng chúng ta đang thực thi tại DIRQL dưới sự bảo vệ của một khóa quay ngắt bởi vì chúng ta đã viện dẫn như môt thủ tục SynchCritSection vì thế chúng ta ko thể chỉ hoàn thành IRP ngay bây giờ )

Sử dụng ngắt (Handling the Interrupt )

Trong thao tác thông thường với PCI42, các ngắt S5933 khi một byte dữ liệu mới đến một hộp thư 1. ISR sau đây sẽ giành được điều khiển:

BOOLEAN OnInterrupt(PKINTERRUPT InterruptObject,

  PDEVICE_EXTENSION pdx)

  {

  ULONG intcsr =

    READ_PORT_ULONG((PULONG) (pdx->portbase + INTCSR));

  if (!(intcsr & INTCSR_INTERRUPT_PENDING))

    return FALSE;

  BOOLEAN dpc = FALSE;

PIRP Irp = GetCurrentIrp(&pdx->dqReadWrite);

  if (pdx->busy)

    {

    if (Irp->Cancel)

      status = STATUS_CANCELLED;

    else

      status = AreRequestsBeingAborted(&pdx->dqReadWrite);

    if (!NT_SUCCESS(status))

      dpc = TRUE, pdx->nbytes = 0;

    }

  while (intcsr & INTCSR_INTERRUPT_PENDING)

    {

    if (intcsr & INTCSR_IMBI)

      {

      if (pdx->nbytes && pdx->busy)

        {

        *pdx->buffer = READ_PORT_UCHAR(pdx->portbase + IMB1);

        ++pdx->buffer;

        ++pdx->numxfer;

        if (!--pdx->nbytes)

          {

          Irp->IoStatus.Information = pdx->numxfer;

          dpc = TRUE;

          status = STATUS_SUCCESS;

          }

        }

      }

    WRITE_PORT_ULONG((PULONG) (pdx->portbase + INTCSR), intcsr);

        

    intcsr = READ_PORT_ULONG((PULONG) (pdx->portbase + INTCSR));

    }

  if (dpc)

    {

    pdx->busy = FALSE;

    Irp->IoStatus.Status = status;

    IoRequestDpc(pdx->DeviceObject, NULL, NULL);

    }

  return TRUE;

  }

  1. Nhiệm vụ đầu tiên của chúng ta là khám phá xem liệu rằng thiết bị riêng của chúng ta bây giờ đang cố gắng ngắt. chúng ta đọc S5933’s INTCSR và kiểm tra một bit (INTCSR_INTERRUPT_PENDING) mà tóm tắt tất cả các lý do, nguyên nhân treo của các ngắt. Nếu như bit này bị xóa, chúng ta sẽ trả lại ngay lập tức. Lý do chúng ta lựa chọn sử dụng con trỏ mở rộng thiết bị như một đối số ngữ cảnh routine—back này khi tôi gọi IoConnectInterrupt—bây giờ nên bị xóa : chúng ta cần truy cập ngay lập tức tới cấu trúc này để có được địa chỉ cổng cơ sở.
  2. Khi chúng ta sử dụng một DEVQUEUE, chúng ta dựa vào, tin tưởng vào đối tượng hàng đợi để giữ dấu vết của IRP hiện hành. Ngắt này có thể chúng ta không mong đợi bởi vì ở thời điểm hiện tại đó chúng ta không đang phục vụ bất kỳ IRp nào. Trong trường hợp đó, chúng ta vẫn phải xóa ngắt nhưng không nên làm làm bất kỳ điều gì khác.
  3. Ngoài ra vẫn có thể với một sự kiện Plug and Play hoặc sự kiện nguồn đã xảy ra mà sẽ tạo ra bất kỳ IRP mới nào bị loại bỏ bởi thủ tục gửi thông điệp. Hàm AreRequestsBeingAborted của DEVQUEUE’s nói cho chúng ta rằng chúng ta có thể abort (kết thúc sớm )yêu cầu hiện hành ngay bây giờ. Việc kết thúc sớm một yêu cầu đang hoạt động là một điều hợp lý để làm với một thiết bị chẳng hạn như “số thu thập” byte từng byte này (proceeds byte by byte) . Tương tự như vậy, một ý tưởng hay để kiểm tra liệu rằng IRP có bị dừng lại hay không nếu như nó chiếm quá nhiều thời gian để kết thúc IRP. Nếu các thiết bị ngắt của bạn chỉ khi được thực hiện với một việc truyền tải dài, bạn có thể bỏ đi bước kiểm tra này ra khỏi ISR của mình.
  4. Bây giờ chúng ta bắt tay vào một vòng lặp mà sẽ kết thúc khi tất cả các ngắt thiết bị hiện hành đã được xóa. Ở cuối vòng lặp, chúng ta sẽ đọc lại INTCSR để quyết định xem liệu rằng bất kỳ các điều kiện ngắt nào có thể phát sinh. Nếu như vậy, chúng ta sẽ lặp lại vòng lặp . Chúng ta không bàn tới thời gian CPU ở đây- chúng ta muốn tránh việc để các ngắt “chảy như thác nước” vào hệ thống bởi vì việc phục vụ một ngắt là tương đối “đắt đỏ”
  5. Nếu S5933 đã bị ngắt bởi vì một sự kiện mailbox , chúng ta sẽ đọc một byte dữ liệu mới từ mailbox vào trong một bộ đệm I/O cho IRP hiện hành. Nếu bạn tìm kiếm một thanh ghi MBEF ngay sau khi đọc, bạn sẽ thấy rằng việc đọc xóa bit tương ứng của thanh ghi mailbox từ 1 về 0 (inbound mailbox register 1, byte 0). Chú ý rằng chúng ta không cần thiết kiểm tra MBEF để quyết định xem liệu rằng byte của chúng ta thực thế có thay đổi hay không bởi vì chúng ta đã lập trình cho thiết bị để chỉ ngắt vào lúc có một thay đổi tới byte đơn.
  6. Việc ghi INTCSR với nội dung trước đó của nó có ảnh hưởng tới việc xóa bit ngắt thứ 6 R/WC, không thay đổi một vài bit chỉ đọc (read-only bits), và bảo toàn cài đặt gốc của tất cả các bit điều khiển chỉ đọc
  7. Ở đây chúng ta đọc INTCSR để quyết định xem liệu rằng các điều kiện ngắt thêm vào đó có xuất hiện hay không. nếu có chúng ta sẽ lặp lại vòng lặp để phục vụ cho chúng.
  8. Giống như chúng ta đã tiến hành trên các đoạn code trước, chúng ta thiết lập biến BOOLEANdpc trở thành TRUE nếu một DPC bây giờ thích hợp để hoàn thành IRP hiện hành.

DPC thường lệ cho PCI42 như sau::

VOID DpcForIsr(PKDPC Dpc, PDEVICE_OBJECT fdo, PIRP junk,

  PDEVICE_EXTENSION pdx)

  {

  PIRP Irp = GetCurrentIrp(&pdx->dqReadWrite);

  StartNextPacket(&pdx->dqReadWrite, fdo);

  IoCompleteRequest(Irp, IO_NO_INCREMENT);

  }

Testing PCI42

Nếu như bạn muốn kiểm tra PCI42 trong thao tác, bạn cần phải làm một vài việc. Đầu tiên là tìm kiếm và cài đặt một “board” phát triển S5933DK1 bao gồm card giao diện thêm vào ISA (ISA add-in interface card). Sử dụng Add Hardware wizard để cài đặt trình điều khiển S5933DK1.SYS và trình điều khiển PCI42.SYS. ( Tôi phát hiện rằngI Windows 98 đồng nhất thiết lập board phát triển như việc sẽ không chạy sound cord và tôi đã phải gỡ bỏ nó đi trong Device Manager trước khi tô có thể tiến hành cài đặt PCI42 như trình điều khiển cho nó. Nhưng Windows XP thì làm việc bình thường)

Sau đó chạy cả hai chương trình ADDONSIM và TEST chúng thuộc cây thư mục PCI42 trong nội dung sách hướng dẫn. ADDONSIM ghi một giá trị dữ liệu tới một mailbox thông qua giao diện ISA. TEST đọc một byte dữ liệu từ PCI42. Việc quyết dịnh giá trị của byte dữ liệu là bài tập dành cho các bạn.

0