代码改变世界

Cosmos的里程碑2(Mile Stone 2)之浅尝PCI总线、设备编程--.net/C#开源操作系统学习系列九

2012-01-16 08:22  Hundre  阅读(3065)  评论(1编辑  收藏  举报

书接上文(这两篇文章之间隔得有点久,唉,最近工作比较忙,加上基础不够,学习PCI知识花了不少时间),话说在上一篇中,我们直接使用系统提供的类来获取了RTL8139网卡的类,这一篇我们就来一起学习COSMOS是如何找到网卡芯片并实例化相应的类的。

直接来到RTL8139Test这个类下,定位到语句List<Cosmos.Driver.RTL8139.RTL8139> nics = Cosmos.Driver.RTL8139.RTL8139.FindRTL8139Devices();上一篇中我们直接跳过了这个语句,这次就进入到FindRTL8139Devices()方法里面,看看这个方法是如何找到PCI上的RTL8139网卡的。进入到方法的代码里面

public static List<RTL8139> FindRTL8139Devices()
        {
            List<RTL8139> found = new List<RTL8139>();
 
            foreach (PCIDevice device in Cosmos.Hardware.PC.Bus.PCIBus.Devices)
            {
                if (device.VendorID == 0x10EC && device.DeviceID == 0x8139)
                    found.Add(new RTL8139(device));
            }
 
            return found;
        }

可以看到该方法是通过遍历PCI总线上的所有设备然后比较设备的VendorID和DeviceID来确认遍历到的设备是不是我们要找的RTL8139网卡。这里的VendorID和DeviceID都是PCI规范中规定厂家必须要实现的参数(除非哪家厂子不想让自己的产品卖不出去),其中VendorID是由PCI SIG(貌似是叫做PCI标准组织)规定的,可以在http://www.pcisig.com/ 这个网站查找到所有获得该组织授权的生产产商的ID,而DeviceID是由生产厂商自己规定的一个编号,用来识别自己生产的不同产品。这里的10EC就是realtek公司的编号,8139就是这家公司生产的网卡芯片的编号。在这里再附上PCI 3.0标准的文档 中文版 英文原版 (个人觉得中文版翻译得实在不咋地,英文过得去的强烈图推荐直接看英文版),文档其中对我们编程有用的只有第六章的内容,其他的都可以忽略。

好接下来我们再来学习PCI总线类(PCIBus)是如何获得所有PCI设备的,首先我们准备点预备知识,通过资料我们可以找到下面一幅图(左边的为中文文档的翻译,右边的是英文原版,SubsystemID和Subsystem Vendor ID居然都被忽略掉了,所谓的“信达雅”……唉,不吐槽了):

imageimage

这里加上点个人见解,Status应译为状态寄存器,Command应译为命令寄存器,DeviceID是设备识别代码(就少两个字,少引两个字会死人吗)

这是Header Type为00H的PCI设备的,配置空间中的头部区域中各个寄存器在内存中的排列规范(这里的寄存器已不是传统意义上的CPU中的寄存器了,而是内容中从特定位置右特定长度的一段内存),关于这些寄存器字段的详细说明文档中都有,在此就不献丑了。要说的是“配置空间”这个说法个人感觉应该是针对硬件产商来说的,应该是硬件厂商在生产他们自己的PCI设备,在设备出厂前就应该完成对这部分空间的配置,而我们程序员只需从内容中读取出这些信息来辅助我们编程实现功能即可。预备知识到此结束,下面来看下PCIBus类中的代码(代码比较长,就把分析写在代买的注释里面吧,为节省篇幅,也删除了部分不必要的代码和注释):

public class PCIBus : Cosmos.Hardware.Bus.PCIBus
    {
        //先是Devices该属性,可以看到在上一层的代码中,RTL81139的驱动程序类就是通过遍历这个集合来确定它要找的设备的
        
//该集合直接势力化了一个PCIDevice设备的数组,我们看到这里初始化的时候这个数组只有0个元素,这样的话怎么可能能找得出设备呢?
        /*现在我们倒回去一下,还记得我们选择的启动项目的program.cs文件里面的第一条语句吗:Cosmos.Kernel.Boot.Default();在Default()方法里面调用了Cosmos.Hardware.PC.Global.Init();方法,而该方法有调用了方法HW.PC.Bus.PCIBus.Init();啊哈,这个就是我们这个类的初始化方法。这样,在我们获取Devcies数据之前,已经对数组完成了新的初始化,把所有的PCI设备都添加进去了。好,接下来我们直接到Init()方法里面看个究竟*/
        public static PCIDevice[] Devices = new PCIDevice[0];
        private static bool haveEnumerated = false;
        public static PCIDevice GetPCIDevice(byte bus, byte slot, byte function)
        {
            if (!haveEnumerated)
                Init();
 
            foreach (PCIDevice dev in PCIBus.Devices)
            {
                if (dev.Bus == bus &&
                    dev.Slot == slot &&
                    dev.Function == function)
                    return dev;
            }
            return null;
        }
        //该方法直接调用EnumerateBus(byte Bus, ref List<PCIDevice> Devices)方法来查找系统中的PCI设备,此处ref Bus参数不知是什么东西,求解?
        public static void Init()
        {
            //Console.WriteLine("Cosmos.Hardware.PC.Bus.Init()");
            List<PCIDevice> devices = new List<PCIDevice>();
            //Console.WriteLine("- created generic");
            EnumerateBus(0ref devices);
            Devices = devices.ToArray();
 
            haveEnumerated = true;
        }
        //遍历本机上的所有PCI设备,遍历到的设备存储到参数Devices中
        private static void EnumerateBus(byte Bus, ref List<PCIDevice> Devices)
        {
            for (byte xSlot = 0; xSlot < 32; xSlot++)
            {                
                byte xMaxFunctions = 1;
                for (byte xFunction = 0; xFunction < xMaxFunctions; xFunction++)
                {
                    //这里先初始化一个普通的PCI设备,然后再通过读取设备中的参数来确定是不是我们想要的设备
                    /*求高人指点此处Bus,xSlot,xFunction的意思,xSlot个人认为应该是插槽的数量,由于是32位的系统,所以是可以有32个插槽,然后逐个遍历,但是其他两个参数的话……*/
                    //以下部分请参考下面的关于PCIDevice类的学习
                    PCIDevice xPCIDevice = new PCIDeviceNormal(Bus, xSlot, xFunction);
                    if (xPCIDevice.DeviceExists)
                    {
                        if (xPCIDevice.HeaderType == 2 /* PCIHeaderType.Cardbus */)
                            xPCIDevice = new PCIDeviceCardBus(Bus, xSlot, xFunction);
 
                        if (xPCIDevice.HeaderType == 1 /* PCIHeaderType.Bridge */)
                            xPCIDevice = new PCIDeviceBridge(Bus, xSlot, xFunction);
 

                        Devices.Add(xPCIDevice);
 
                        if (xPCIDevice is PCIDeviceBridge)
                            EnumerateBus(((PCIDeviceBridge)xPCIDevice).SecondaryBus, ref Devices);
 
                        if (xPCIDevice.IsMultiFunction)
                            xMaxFunctions = 8;
                    }
                }
            }
        }
        //貌似是调试用的一段代码,此次分析中没用到,也忽略之
        public static void DebugLSPCI()
        {……}
        }
    }

呼,终于能从代买里面出来一会了,从上面的代码中我们新发现了三个类PCIDevice, PCIDeviceNormal, PCIDeviceBridge。后面两个其实都是从PCIDevice中继承而来,基本没有自己的代码,主要看一下PCIDevice这个类(好长,看得我头晕晕眼花花,同样删除了不分不必要的代码和注释):

 

/*由构造函数得到,在这里PCI设备的创建就是配置了几个参数,仔细阅读代码之后可以发现,这里基本上都是从PCI设备中读取设备的参数,这可能是因为这个为PCI设备通用的抽象类,所实现的还只是些PCI设备通用的功能,例如读取所有PCI设备都必须实现的一些参数。*/

public abstract class PCIDevice
    {
        public abstract int NumberOfBaseAddresses();
       private static string[] classtext = new string[]          
       {
        "pre pci 2.0",        // 00
        "disk",        // 01
        "network",        // 02
        "display",        // 03
        "multimedia",    // 04
        "memory",        // 05
        "bridge",        // 06
        "communication",    // 07
        "system peripheral",// 08
        "input",        // 09
        "docking station",    // 0A
        "CPU",        // 0B
        "serial bus",    // 0C
       };
 
        private static string[][] subclasstext = new string[][]
        { 
            new string[] { "VGA Device""non VGA device"},
            new string[] { "SCSI" ,"IDE" , "floppy","IPI","RAID""other" },
            new string[] { "Ethernet""TokenRing""FDDI" , "ATM" , "other" },
            new string[] { "VGA""SuperVGA","XGA""other"},
            new string[] { "video" ,"audio""other"},
            new string[] { "RAM""Flash memory" , "other"},
            new string[] { "CPU/PCI" ,"PCI/ISA" , "PCI/EISA" , "PCI/MCA","PCI/PCI" , "PCI/PCMCIA""PCI/NuBus""PCI/CardBus""other"},
            new string[] { "serial""parallel""other"},
            new string[] { "PIC""DMAC" , "timer" ,"RTC""other"},
            new string[] { "keyboard","digitizer","mouse""other" },
            new string[] { "generic" , "other" },
            new string[] { "386""486","Pentium" , "P6" ,"Alpha","coproc","other" },
            new string[] { "Firewire""ACCESS.bus" , "SSA""USB" ,"Fiber Channel" , "other"},
        };
 
        /*获取设备的分类信息,从代码中可以看到使用了ClassCode, SubClass这两个字段,参考这两个字段的代码可以发现都是使用ReadX方法把数据从配置空间中读取出来,这里的ReadX方法放到下面再说*/
        public string GetClassInfo()
        {
            int cc = ClassCode;
            int sc = SubClass; 
            
            if (cc >= classtext.Length)            
                return "unknown class (" + cc.ToString() + ") / subclass (" + sc.ToString() + ")";
            
            if (sc >= subclasstext[cc].Length)            
                return String.Concat(classtext[cc], " / unknown subclass (", sc.ToString(), ")");
            
            return String.Concat( classtext[cc] , " / " , subclasstext[cc][sc]);
        }
        //PCI 3.0规范中定义的一些PCI设备使用的参数
        private const UInt32 PCI_BASE_ADDRESS_SPACE = 0x01;    /* 0 = memory, 1 = I/O */
        private const UInt32 PCI_BASE_ADDRESS_SPACE_IO = 0x01;
        private const UInt32 PCI_BASE_ADDRESS_SPACE_MEMORY = 0x00;
        private const UInt32 PCI_BASE_ADDRESS_MEM_TYPE_MASK = 0x06;
        private const UInt32 PCI_BASE_ADDRESS_MEM_TYPE_32 = 0x00;   /* 32 bit address */
        private const UInt32 PCI_BASE_ADDRESS_MEM_TYPE_1M = 0x02;   /* Below 1M [obsolete] */
        private const UInt32 PCI_BASE_ADDRESS_MEM_TYPE_64 = 0x04;   /* 64 bit address */
        private const UInt32 PCI_BASE_ADDRESS_MEM_PREFETCH = 0x08;  /* prefetchable? */
        private const UInt32 PCI_BASE_ADDRESS_MEM_MASK = 0xfffffff0;
        private const UInt32 PCI_BASE_ADDRESS_IO_MASK = 0xfffffffc;
        //构造函数,给一些参数赋值
        protected PCIDevice(byte bus, byte slot, byte function)
        {
            this.Bus = bus;
            this.Slot = slot;
            this.Function = function;
        }
 
        private bool NeedsIO = false;
        private bool NeedsMemory = false;
        private bool _NeedsLayingout = true;
        //IO布局,惭愧,真不知道这段是用来干什么的,还好分析的代码里面好像没有对该函数进行的调用
        private void LayoutIO()
        {
            //Console.WriteLine("Checking AdressSpaces of PCI(" + Bus + ", " + Slot + ", " + Function + ")");
            IOMaps = new AddressSpace[NumberOfBaseAddresses()];
            for (byte i = 0; i < NumberOfBaseAddresses(); i++)
            {
                UInt32 address = GetBaseAddress(i);
                SetBaseAddress(i, 0xffffffff);
                UInt32 flags = GetBaseAddress(i);
                SetBaseAddress(i, address);
                if (address == 0)
                {
                    //Console.WriteLine("register " + i + " - none " + PCIBus.ToHex(flags,8));
                    IOMaps[i] = null;
                }
                else if ((address & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_MEMORY)
                {
                    UInt32 size = ~(PCI_BASE_ADDRESS_MEM_MASK & flags)+1;                  
                    IOMaps[i] = new MemoryAddressSpace(address, size);
                    //Console.WriteLine("register " + i + " - " + size + "b mem");
                    NeedsIO = true;
                }
                else if ((address & PCI_BASE_ADDRESS_SPACE) == PCI_BASE_ADDRESS_SPACE_IO)
                {
                    UInt32 size = ~(PCI_BASE_ADDRESS_IO_MASK & flags) +1;                    
                    IOMaps[i] = new IOAddressSpace(address, size);
                    //Console.WriteLine("register " + i + " - " + size + "b io");
                    NeedsMemory = true;
                }
            }
            _NeedsLayingout = false;
        }
 
        private AddressSpace[] IOMaps;
        public AddressSpace GetAddressSpace(byte index)
        {
            if (index < 0 || index >= NumberOfBaseAddresses())
                throw new ArgumentOutOfRangeException("index");
            
            if (_NeedsLayingout)
                LayoutIO();
            return IOMaps[index];
        }

        public byte Bus { getprivate set; }
        public byte Slot { getprivate set; }
        public byte Function { getprivate set; }
        public bool DeviceExists { get { return VendorID != 0xFFFF && VendorID != 0x0; } }
        public bool IsMultiFunction { get { return (Read8(0x0e) & 0xf0) != 0; } }
        //以下为各种字段的定义,可以看到读取的地址参数都和上面两张布局截图中的地址编号相对应
        public UInt32 VendorID { get { return Read16(0x0); } }
        public UInt16 DeviceID { get { return Read16(0x2); } }
        public PCICommand Command { get { return (PCICommand)Read16(0x4); } set { Write16(0x4, (ushort)value); } }
        public PCIStatus Status { get { return (PCIStatus)Read16(0x6); } set { Write16(0x6, (ushort)value); } }
        public byte RevisionID { get { return Read8(0x8); } }
        public byte ProgIF { get { return Read8(0x9); } }
        public byte SubClass { get { return Read8(0xa); } }
        public byte ClassCode { get { return Read8(0xb); } }
        public byte CacheLineSize { get { return Read8(0x0c); } set { Write8(0x0c, value); } }
        public byte LatencyTimer { get { return Read8(0x0d); } set { Write8(0x0d, value); } }
        public byte HeaderType { get { return (byte)(Read8(0x0e) & 0xf); } }
        public PCIBist Bist { get { return (PCIBist)Read8(0x0f); } set { Write8(0x0f, (byte)value); } }
        public UInt32 GetBaseAddress(byte index)
        {
            return Read32((byte)(0x10 + index * 4));
        }
        public void SetBaseAddress(byte index, UInt32 value)
        {
            Write32((byte)(0x10 + index *4), value);
        }
        public UInt32 BaseAddress0 { get { return Read32(0x10); } set { Write32(0x10, value); } }
        public UInt32 BaseAddress1 { get { return Read32(0x14); } set { Write32(0x14, value); } }
        public byte InterruptLine { get { return Read8(0x3c); } set { Write8(0x3c, value); } }
        public byte InterruptPin { get { return Read8(0x3d); } set { Write8(0x3d, value); } }
        public byte MinGNT { get { return Read8(0x3e); } set { Write8(0x3e, value); } }
        public byte MaxLAT { get { return Read8(0x3f); } set { Write8(0x3f, value); } }
        //访问PCI配置空间可通过两个访问寄存器,CONFIG_ADDRESS寄存器和CONFIG_DATA寄存器。这两个寄存器在PC中分别对应着CF8h和CFCh端口
        protected const ushort ConfigAddr = 0xCF8;
        protected const ushort ConfigData = 0xCFC;
        
        public void DisableDevice()
        {
            Command = Command & (~PCICommand.IO & ~PCICommand.Master & PCICommand.Memort);
        }
        public void EnableDevice()
        {
            Command = Command & ((NeedsIO ? PCICommand.IO : 0) & PCICommand.Master & (NeedsMemory ? PCICommand.Memort : 0));
        }
        private UInt32 GetAddress(byte aRegister)
        {
            return (UInt32)(
                // Bits 23-16
                ((UInt32)Bus << 16)
                // Bits 15-11
                | (((UInt32)Slot & 0x1F) << 11)
                // Bits 10-8
                | (((UInt32)Function & 0x07) << 8)
                // Bits 7-0
                | ((UInt32)aRegister & 0xFF)
                // Enable bit - must be set
                | 0x80000000);
        }
 
        public UInt32 Read32(byte aRegister)
        {
            CPUBus.Write32(ConfigAddr, GetAddress(aRegister));
            return CPUBus.Read32(ConfigData);
        }
        //........
        /*此处为节省篇幅,省略源码中的一些各种ReadX和WriteX方法,其实这些函数都和这两个类似,只是读取和写入的位数不一样而已
        public void Write8(byte aRegister, byte value)
        {
            CPUBus.Write32(ConfigAddr, GetAddress(aRegister));
            CPUBus.Write8(ConfigData, value);
        } 
    }

终于从PCIDevice中出来了,下面重点说一下Read8, Read32, Write8, Write32这几个函数,这里先来说说如何对PCI的配置空间进行访问,因为这几个函数都是用来读写配置空间的。访问PCI配置空间可通过两个访问寄存器,CONFIG_ADDRESS寄存器和CONFIG_DATA寄存器。这两个寄存器在PC中分别对应着CF8h和CFCh端口,并且是32位端口,即读写要用的32为IN和OUT汇编指令。当然Cosmos已经对汇编指令进行了封装,我们就不需要直接对汇编指令进行操作了,直接调用即可。

从源代码中也可以看到对PCI设备中各种寄存器的访问都是通过ReadX和WriteX方法配合完成的,跟进这两个方法之后,我们可以看到代码:

public class CPUBus : Cosmos.Hardware.Bus.CPUBus {
        // all plugs
        public static void Write8(UInt16 aPort, byte aData) { }
        public static void Write16(UInt16 aPort, UInt16 aData) { }
        public static void Write32(UInt16 aPort, UInt32 aData) { }
 
        public static byte Read8(UInt16 aPort) { return 0; }
        public static UInt16 Read16(UInt16 aPort) { return 0; }
        public static UInt32 Read32(UInt16 aPort) { return 0; }
    }

可以看到没有任何实现代码,不过请注意注释中的//all plugs,这里所有的操作都被plug了(关于plug,请参考本系列六,嘿嘿),由编译器完成实际的代码的生成操作。这样我们就明白了PCIBus这个类是怎么读出PCI设备中的VendorID和DeviceID的了。

好了,到这里这次对PCI粗浅的学习就算完了,下一篇文章中将会使用这次学习到的内容实际动手做个小程序,来遍历并显示小弟自己机子上的PCI设备的VendorID和DeviceID,欢迎大家继续期待。哇哈哈哈,当然这次有什么说得不对的地方也欢迎大家指出来

 

最后是广告时间,大家懂得……

欢迎大家经常光顾小弟的淘宝充值店:http://hundre.taobao.com/

最后是多谢大家捧场 微笑