3

程序在 DOS 扩展器+DPMI 环境下运行时如何使用 DMA 传输?

我的意思是我们如何分配和获取分配的 dma 缓冲区的物理地址,以便将该物理地址提供给 DMA 控制器或PCI 总线主设备

有两种可能:

DOS 扩展器或 DPMI 服务器/主机支持虚拟内存。例如铜锣湾

DOS 扩展器或 DPMI 服务器/主机不支持虚拟内存,但启用了分页。例如DOS32a

我正在使用Open Watcom C编译器。

运行时环境是:

FreeDOS + XMS(无 EMS/EMM386)+ DOS 扩展器(DOS32a)

对于 DJGPP,解决方案在这里

但是最后提到的解决方案,即通过 XMS,是否也可以与 DOS32a 一起使用?

DOS32a 文档说,在切换到保护模式之前,它会分配所有可用的扩展内存,然后我们的程序可以通过 DPMI 函数 501h 分配该内存。

注意:dma 缓冲区可以是 1MB 左右,所以我不能使用常规内存。

4

2 回答 2

5

对于干净的 DPMI 解决方案,您可能需要探索以下 DPMI 函数(摘自 Ralf Brown 的中断列表):

INT 31 P - DPMI 1.0+ - MAP DEVICE IN MEMORY BLOCK
        AX = 0508h
        ESI = memory block handle
        EBX = page-aligned offset within memory block of page(s) to be mapped
        ECX = number of pages to map
        EDX = page-aligned physical address of device
Return: CF clear if successful
        CF set on error
            AX = error code (8001h,8003h,8023h,8025h) (see #03143)
Notes:  only supported by 32-bit DPMI hosts, but may be used by 16-bit clients
        support of this function is optional; hosts are also allowed to support
          the function for some devices but not others

INT 31 P - DPMI 1.0+ - MAP CONVENTIONAL MEMORY IN MEMORY BLOCK
        AX = 0509h
        ESI = memory block handle
        EBX = page-aligned offset within memory block of page(s) to map
        ECX = number of pages to map
        EDX = page-aligned linear address of conventional (below 1M) memory
Return: CF clear if successful
        CF set on error
            AX = error code (8001h,8003h,8023h,8025h) (see #03143)
Notes:  only supported by 32-bit DPMI hosts, but may be used by 16-bit clients
        support of this function is optional

INT 31 P - DPMI 0.9+ - PHYSICAL ADDRESS MAPPING
        AX = 0800h
        BX:CX = physical address (should be above 1 MB)
        SI:DI = size in bytes
Return: CF clear if successful
            BX:CX = linear address which maps the requested physical memory
        CF set on error
            AX = error code (DPMI 1.0+) (8003h,8021h) (see #03143)
Notes:  implementations may refuse this call because it can circumvent protects
        the caller must build an appropriate selector for the memory
        do not use for memory mapped in the first megabyte

如果以上都不允许您将虚拟地址映射到物理地址,也不能获取已分配块的物理地址(例如不支持),您需要查看 DPMI 主机的实现细节(例如,如果它不启用页面转换或可以关闭,那么所有地址都是物理的)。

编辑:看起来您应该能够分配内存(超过 1MB)并获得其物理和虚拟地址。首先,使用 XMS/Himem.sys 分配它并锁定它。这会给你物理地址。接下来使用DPMI函数0x800获取对应的虚拟地址。

下面是方法(忽略 16 位版本(使用 Borland/Turbo C/C++ 编译),它仅用于验证 XMS 例程):

// file: dma.c
//
// Compiling with Open Watcom C/C++ and DOS/32 DOS extender/DPMI host:
//   wcl386.exe /q /we /wx /bcl=dos4g dma.c
//   sb.exe /b /bndmados32.exe dma.exe
// Before running dmados32.exe do "set DOS32A=/EXTMEM:4096"
// to limit the amount of extended (XMS) memory allocated by DOS/32
// at program start (by default it allocates everything).
//
// Compiling with 16-bit Borland/Turbo C/C++:
//   tcc.exe dma.c

#include <stdio.h>
#include <string.h>
#include <dos.h>
#include <limits.h>

#if defined(__WATCOMC__)
#if !defined(__386__)
#error unsupported target, must be 32-bit (DPMI) DOS app
#endif
#elif defined(__TURBOC__)
#if !defined(__SMALL__)
#error unsupported target, must be 16-bit DOS app with small memory model
#endif
#else
#error unsupported compiler
#endif

typedef unsigned uint;
typedef unsigned long ulong;

typedef signed char int8;
typedef unsigned char uint8;

typedef short int16;
typedef unsigned short uint16;

#if UINT_MIN >= 0xFFFFFFFF
typedef int int32;
typedef unsigned uint32;
#else
typedef long int32;
typedef unsigned long uint32;
#endif

#pragma pack(push, 1)

typedef struct tDpmiRmInt
{
  uint32 edi, esi, ebp, resz0, ebx, edx, ecx, eax;
  uint16 flags, es, ds, fs, gs, ip, cs, sp, ss;
} tDpmiRmInt;

#pragma pack(pop)

int RmInt(uint8 IntNumber, tDpmiRmInt* pRegs)
{
#if defined(__WATCOMC__)
  union REGS inregs, outregs;

  memset(&inregs, 0, sizeof(inregs));
  memset(&outregs, 0, sizeof(outregs));

  inregs.w.ax = 0x300;
  inregs.h.bl = IntNumber;
  inregs.h.bh = 0;
  inregs.w.cx = 0;
  inregs.x.edi = (uint32)pRegs;

  return int386(0x31, &inregs, &outregs);
#elif defined(__TURBOC__)
  struct REGPACK regs;

  memset(&regs, 0, sizeof(regs));

  regs.r_ax = (uint16)pRegs->eax;
  regs.r_bx = (uint16)pRegs->ebx;
  regs.r_cx = (uint16)pRegs->ecx;
  regs.r_dx = (uint16)pRegs->edx;
  regs.r_si = (uint16)pRegs->esi;
  regs.r_di = (uint16)pRegs->edi;
  regs.r_bp = (uint16)pRegs->ebp;
  regs.r_flags = pRegs->flags;
  regs.r_ds = pRegs->ds;
  regs.r_es = pRegs->es;

  // No fs, gs (16-bit code)
  // No ss:sp, cs:ip (int*()/intr() functions set the right values)

  intr(IntNumber, &regs);

  memset(pRegs, 0, sizeof(*pRegs));

  pRegs->eax = regs.r_ax;
  pRegs->ebx = regs.r_bx;
  pRegs->ecx = regs.r_cx;
  pRegs->edx = regs.r_dx;
  pRegs->esi = regs.r_si;
  pRegs->edi = regs.r_di;
  pRegs->ebp = regs.r_bp;
  pRegs->flags = regs.r_flags;
  pRegs->ds = regs.r_ds;
  pRegs->es = regs.r_es;

  return regs.r_ax;
#endif
}

int RmFarCall(tDpmiRmInt* pRegs)
{
#if defined(__WATCOMC__)
  union REGS inregs, outregs;

  memset(&inregs, 0, sizeof(inregs));
  memset(&outregs, 0, sizeof(outregs));

  inregs.w.ax = 0x301;
  inregs.h.bh = 0;
  inregs.w.cx = 0;
  inregs.x.edi = (uint32)pRegs;

  return int386(0x31, &inregs, &outregs);
#elif defined(__TURBOC__)
  uint8 code[128];
  uint8* p = code;
  void far* codef = &code[0];
  void (far* f)(void) = (void(far*)(void))codef;

  *p++ = 0x60;                                                            // pusha
  *p++ = 0x1E;                                                            // push  ds
  *p++ = 0x06;                                                            // push  es

  *p++ = 0x68; *p++ = (uint8)pRegs->ds; *p++ = (uint8)(pRegs->ds >> 8);   // push #
  *p++ = 0x1F;                                                            // pop  ds
  *p++ = 0x68; *p++ = (uint8)pRegs->es; *p++ = (uint8)(pRegs->es >> 8);   // push #
  *p++ = 0x07;                                                            // pop  es

  *p++ = 0xb8; *p++ = (uint8)pRegs->eax; *p++ = (uint8)(pRegs->eax >> 8); // mov ax, #
  *p++ = 0xbb; *p++ = (uint8)pRegs->ebx; *p++ = (uint8)(pRegs->ebx >> 8); // mov bx, #
  *p++ = 0xb9; *p++ = (uint8)pRegs->ecx; *p++ = (uint8)(pRegs->ecx >> 8); // mov cx, #
  *p++ = 0xba; *p++ = (uint8)pRegs->edx; *p++ = (uint8)(pRegs->edx >> 8); // mov dx, #
  *p++ = 0xbe; *p++ = (uint8)pRegs->esi; *p++ = (uint8)(pRegs->esi >> 8); // mov si, #
  *p++ = 0xbf; *p++ = (uint8)pRegs->edi; *p++ = (uint8)(pRegs->edi >> 8); // mov di, #
  *p++ = 0xbd; *p++ = (uint8)pRegs->ebp; *p++ = (uint8)(pRegs->ebp >> 8); // mov bp, #

  *p++ = 0x9A; *p++ = (uint8)pRegs->ip; *p++ = (uint8)(pRegs->ip >> 8);
               *p++ = (uint8)pRegs->cs; *p++ = (uint8)(pRegs->cs >> 8);   // call far seg:offs

  *p++ = 0x60;                                                            // pusha
  *p++ = 0x1E;                                                            // push  ds
  *p++ = 0x06;                                                            // push  es
  *p++ = 0x89; *p++ = 0xE5;                                               // mov   bp, sp
  *p++ = 0x8E; *p++ = 0x5E; *p++ = 0x16;                                  // mov   ds, [bp + 0x16]
  *p++ = 0x89; *p++ = 0xEE;                                               // mov   si, bp
  *p++ = 0xFC;                                                            // cld

  *p++ = 0xAD;                                                            // lodsw          
  *p++ = 0xA3; *p++ = (uint8)&pRegs->es; *p++ = (uint8)((uint16)&pRegs->es >> 8);  // mov [], ax (es)
  *p++ = 0xAD;                                                            // lodsw          
  *p++ = 0xA3; *p++ = (uint8)&pRegs->ds; *p++ = (uint8)((uint16)&pRegs->ds >> 8);  // mov [], ax (ds)
  *p++ = 0xAD;                                                            // lodsw          
  *p++ = 0xA3; *p++ = (uint8)&pRegs->edi; *p++ = (uint8)((uint16)&pRegs->edi >> 8);  // mov [], ax (di)
  *p++ = 0xAD;                                                            // lodsw          
  *p++ = 0xA3; *p++ = (uint8)&pRegs->esi; *p++ = (uint8)((uint16)&pRegs->esi >> 8);  // mov [], ax (si)
  *p++ = 0xAD;                                                            // lodsw          
  *p++ = 0xA3; *p++ = (uint8)&pRegs->ebp; *p++ = (uint8)((uint16)&pRegs->ebp >> 8);  // mov [], ax (bp)
  *p++ = 0xAD;                                                            // lodsw          
  *p++ = 0xAD;                                                            // lodsw          
  *p++ = 0xA3; *p++ = (uint8)&pRegs->ebx; *p++ = (uint8)((uint16)&pRegs->ebx >> 8);  // mov [], ax (bx)
  *p++ = 0xAD;                                                            // lodsw          
  *p++ = 0xA3; *p++ = (uint8)&pRegs->edx; *p++ = (uint8)((uint16)&pRegs->edx >> 8);  // mov [], ax (dx)
  *p++ = 0xAD;                                                            // lodsw          
  *p++ = 0xA3; *p++ = (uint8)&pRegs->ecx; *p++ = (uint8)((uint16)&pRegs->ecx >> 8);  // mov [], ax (cx)
  *p++ = 0xAD;                                                            // lodsw          
  *p++ = 0xA3; *p++ = (uint8)&pRegs->eax; *p++ = (uint8)((uint16)&pRegs->eax >> 8);  // mov [], ax (ax)

  *p++ = 0x83; *p++ = 0xC4; *p++ = 0x14;                                  // add   sp, 0x14

  *p++ = 0x07;                                                            // pop   es
  *p++ = 0x1F;                                                            // pop   ds
  *p++ = 0x61;                                                            // popa
  *p++ = 0xCB;                                                            // retf

  f();

  return (uint16)pRegs->eax;
#endif
}

struct
{
  uint16 Ip, Cs;
} XmsEntryPoint = { 0 };

int XmsSupported(void)
{
  tDpmiRmInt regs;

  memset(&regs, 0, sizeof(regs));
  regs.eax = 0x4300;
  RmInt(0x2F, &regs);

  return (regs.eax & 0xFF) == 0x80;
}

void XmsInit(void)
{
  tDpmiRmInt regs;

  memset(&regs, 0, sizeof(regs));
  regs.eax = 0x4310;
  RmInt(0x2F, &regs);

  XmsEntryPoint.Cs = regs.es;
  XmsEntryPoint.Ip = (uint16)regs.ebx;
}

int XmsQueryVersions(uint16* pXmsVer, uint16* pHimemVer)
{
  tDpmiRmInt regs;

  memset(&regs, 0, sizeof(regs));
  regs.eax = 0x00 << 8;
  regs.cs = XmsEntryPoint.Cs;
  regs.ip = XmsEntryPoint.Ip;
  RmFarCall(&regs);

  if (pXmsVer != NULL)
    *pXmsVer = (uint16)regs.eax;

  if (pHimemVer != NULL)
    *pHimemVer = (uint16)regs.ebx;

  return (int)(regs.ebx & 0xFF);
}

int XmsQueryFreeMem(uint16* pLargest, uint16* pTotal)
{
  tDpmiRmInt regs;

  memset(&regs, 0, sizeof(regs));
  regs.eax = 0x08 << 8;
  regs.ebx = 0;
  regs.cs = XmsEntryPoint.Cs;
  regs.ip = XmsEntryPoint.Ip;
  RmFarCall(&regs);

  if (pLargest != NULL)
    *pLargest = (uint16)regs.eax;

  if (pTotal != NULL)
    *pTotal = (uint16)regs.edx;

  return (int)(regs.ebx & 0xFF);
}

int XmsAllocMem(uint16* pHandle, uint16 Size)
{
  tDpmiRmInt regs;

  memset(&regs, 0, sizeof(regs));
  regs.eax = 0x09 << 8;
  regs.edx = Size;
  regs.cs = XmsEntryPoint.Cs;
  regs.ip = XmsEntryPoint.Ip;
  RmFarCall(&regs);

  *pHandle = (uint16)regs.edx;

  return (int)(regs.ebx & 0xFF);
}

int XmsFreeMem(uint16 Handle)
{
  tDpmiRmInt regs;

  memset(&regs, 0, sizeof(regs));
  regs.eax = 0x0A << 8;
  regs.edx = Handle;
  regs.cs = XmsEntryPoint.Cs;
  regs.ip = XmsEntryPoint.Ip;
  RmFarCall(&regs);

  return (int)(regs.ebx & 0xFF);
}

int XmsLockMem(uint16 Handle, uint32* pPhysAddr)
{
  tDpmiRmInt regs;

  memset(&regs, 0, sizeof(regs));
  regs.eax = 0x0C << 8;
  regs.edx = Handle;
  regs.cs = XmsEntryPoint.Cs;
  regs.ip = XmsEntryPoint.Ip;
  RmFarCall(&regs);

  *pPhysAddr = ((regs.edx & 0xFFFF) << 16) | (regs.ebx & 0xFFFF);

  return (int)(regs.ebx & 0xFF);
}

#if defined(__TURBOC__)
int XmsCopyMem(uint16 DstHandle, uint32 DstOffs, uint16 SrcHandle, uint32 SrcOffs, uint32 Size)
{
  tDpmiRmInt regs;
#pragma pack(push, 1)
  struct
  {
    uint32 Size;
    uint16 SrcHandle;
    uint32 SrcOffs;
    uint16 DstHandle;
    uint32 DstOffs;
  } emm;
#pragma pack(pop)

  emm.Size      = Size;
  emm.SrcHandle = SrcHandle;
  emm.SrcOffs   = SrcOffs;
  emm.DstHandle = DstHandle;
  emm.DstOffs   = DstOffs;

  memset(&regs, 0, sizeof(regs));
  regs.eax = 0x0B << 8;
  regs.ds = FP_SEG(&emm);
  regs.esi = FP_OFF(&emm);
  regs.cs = XmsEntryPoint.Cs;
  regs.ip = XmsEntryPoint.Ip;
  RmFarCall(&regs);

  return (int)(regs.ebx & 0xFF);
}
#endif

int XmsUnlockMem(uint16 Handle)
{
  tDpmiRmInt regs;

  memset(&regs, 0, sizeof(regs));
  regs.eax = 0x0D << 8;
  regs.edx = Handle;
  regs.cs = XmsEntryPoint.Cs;
  regs.ip = XmsEntryPoint.Ip;
  RmFarCall(&regs);

  return (int)(regs.ebx & 0xFF);
}

#if defined(__WATCOMC__)
int DpmiMap(void** pPtr, uint32 PhysAddr, uint32 Size)
{
  tDpmiRmInt regs;

  memset(&regs, 0, sizeof(regs));
  regs.eax = 0x800;
  regs.ebx = PhysAddr >> 16;
  regs.ecx = PhysAddr & 0xFFFF;
  regs.esi = Size >> 16;
  regs.edi = Size & 0xFFFF;
  RmInt(0x31, &regs);

  *pPtr = (void*)(((regs.ebx & 0xFFFF) << 16) | (regs.ecx & 0xFFFF));

  return regs.flags & 1;
}

int DpmiUnmap(void* Ptr)
{
  tDpmiRmInt regs;

  memset(&regs, 0, sizeof(regs));
  regs.eax = 0x801;
  regs.ebx = (uint32)Ptr >> 16;
  regs.ecx = (uint32)Ptr & 0xFFFF;
  RmInt(0x31, &regs);

  return regs.flags & 1;
}
#endif

int main(void)
{
  uint16 xmsVer, himemVer;
  uint16 largestFreeSz, totalFreeSz;
  uint16 handle;
  uint32 physAddr;

#if defined(__WATCOMC__)
  {
    uint32 cr0__ = 0, cr3__ = 0;
    __asm
    {
      mov eax, cr0
      mov cr0__, eax
      mov eax, cr3
      mov cr3__, eax
    }
    printf("CR0: 0x%08lX, CR3: 0x%08lX\n", (ulong)cr0__, (ulong)cr3__);
  }
#endif

  if (!XmsSupported())
  {
    printf("XMS unsupported\n");
    goto Exit;
  }
  printf("XMS supported\n");

  XmsInit();
  printf("XMS entry point: 0x%04X:0x%04X\n",
         XmsEntryPoint.Cs, XmsEntryPoint.Ip);

  XmsQueryVersions(&xmsVer, &himemVer);
  printf("XMS version: 0x%X  Himem.sys version: 0x%X\n",
         xmsVer, himemVer);

  XmsQueryFreeMem(&largestFreeSz, &totalFreeSz);
  printf("Largest free block size: %u KB  Total free memory: %u KB\n",
         largestFreeSz, totalFreeSz);

  printf("Allocating the DMA buffer...\n");
  if (XmsAllocMem(&handle, 64))
  {
    printf("Failed to allocate the DMA buffer\n");
    goto Exit;
  }

  XmsQueryFreeMem(&largestFreeSz, &totalFreeSz);
  printf("Largest free block size: %u KB  Total free memory: %u KB\n",
         largestFreeSz, totalFreeSz);

  printf("Locking the DMA buffer...\n");
  if (XmsLockMem(handle, &physAddr))
  {
    printf("Failed to lock the DMA buffer\n");
  }
  else
  {
    printf("The DMA buffer is at physical address: 0x%08lX\n", (ulong)physAddr);

#if defined(__WATCOMC__)
    {
      uint8* ptr;

      printf("Mapping the DMA buffer...\n");

      if (DpmiMap((void**)&ptr, physAddr, 64 * 1024UL))
      {
        printf("Failed to map the DMA buffer\n");
      }
      else
      {
        printf("The DMA buffer is at virtual address: 0x%08lX\n", (ulong)ptr);

        printf("Using the DMA buffer...\n");
        strcpy(ptr, "This is a test string in the DMA buffer.");
        printf("%s\n", ptr);

        DpmiUnmap(ptr);
      }
    }
#elif defined(__TURBOC__)
    {
      char testStr[] = "This is a test string copied to and from the DMA buffer.";
      printf("Using the DMA buffer...\n");
      if (XmsCopyMem(handle, 0, 0, ((uint32)FP_SEG(testStr) << 16) + FP_OFF(testStr), sizeof(testStr)))
      {
        printf("Failed to copy to the DMA buffer\n");
      }
      else
      {
        memset(testStr, 0, sizeof(testStr));
        if (XmsCopyMem(0, ((uint32)FP_SEG(testStr) << 16) + FP_OFF(testStr), handle, 0, sizeof(testStr)))
        {
          printf("Failed to copy from the DMA buffer\n");
        }
        else
        {
          printf("%s\n", testStr);
        }
      }
    }
#endif

    XmsUnlockMem(handle);
  }

  XmsFreeMem(handle);

  XmsQueryFreeMem(&largestFreeSz, &totalFreeSz);
  printf("Largest free block size: %u KB  Total free memory: %u KB\n",
         largestFreeSz, totalFreeSz);

Exit:

  return 0;
}

示例输出(在 DosBox 下):

CR0: 0x00000001, CR3: 0x00000000
XMS supported
XMS entry point: 0xC83F:0x0010
XMS version: 0x300  Himem.sys version: 0x301
Largest free block size: 11072 KB  Total free memory: 11072 KB
Allocating the DMA buffer...
Largest free block size: 11008 KB  Total free memory: 11008 KB
Locking the DMA buffer...
The DMA buffer is at physical address: 0x00530000
Mapping the DMA buffer...
The DMA buffer is at virtual address: 0x00530000
Using the DMA buffer...
This is a test string in the DMA buffer.
Largest free block size: 11072 KB  Total free memory: 11072 KB

请注意,DOS/32 不启用页面转换(除非有 VCPI)。CR0的PG位为0,CR3为0,得到的物理地址和虚拟地址是一样的,一切都说明了这一点。所以虚拟地址和物理地址是一回事。

于 2012-07-28T03:59:35.810 回答
2

我编写了一个应用程序来在 DOS 中测试 AHCI(我的环境是 DOS 7.0 + DOS32a + Watcom C),并列出了我如何为 DMA 传输分配内存,供您参考。

(1)以平面模式分配内存(假设分配 1K 内存并且应该是WORD align)

ptr = (pDWORD)calloc(1024+1, sizeof(BYTE));

其中 1024 是我们真正需要的,而 1Byte 用于“容差”,因为返回的指针可能不是字对齐的,最坏的情况是Ex。ptr 指向 0x30000001

(2)调整基于 WORD(2-byte) align

if(inputAddr & 1) { inputAddr &= (~2 + 1); inputAddr += 2; }

(3)将上面的inputAddr赋值给PRDT的DBA(Data Base Address)

笔记:

1)我在makefile中通过“...wpp386 -mf ...”使用“平面内存模式” ,在链接器文件中使用“op stub=dos23a.exe”...

2) 其中ptr是指向已分配内存部分的实际指针,在释放内存时应保留;inputAddr是另一个指向数据传输正确(对齐)内存地址的指针!

通过这种方式 DMA 传输测试是好的,在这种环境下分配的内存可以达到 4MB...

供参考

于 2012-08-16T02:11:58.613 回答