PDF全文下载地址:http://download.csdn.net/source/2320280
http://bbs.driverdevelop.com/read.php?tid-120461.html
《USB 软件结构 》
软件结构比硬件来的复杂很多。因为它包含了许多从表面上看不到的层次。比如总线驱动、功能驱动、过滤驱动等。套用社会学的话,这体现了功能应用中的分工和统筹。下面我们逐层来看它们。
总线驱动
总线驱动位于驱动栈的最低层,处理复杂的任务,必须资源分配,子设备管理。作为下层驱动,负责处理上层驱动发下来的请求。 USB 设备中的总线驱动主要有两类:控制器驱动、 HUB 驱动;另外还有一个端口驱动。
1 ) 控制器驱动: Ushohci.sys 、 Usbuhci.sys 、 Usbehci.sys 。
首先解释一下 HCI ,它是主机控制接口( Host Control Interface )的缩写。前后一共有三种 HCI 协议出现: USB 1.1 时代,有 OHCI (开发 HCI )协议和 UHCI (通用 HCI )协议; USB2.0 时代,有 EHCI (扩展 HCI )协议。三个协议分别对应了上面的三个驱动程序。因为 USB 是向后兼容的,所以 UsbEHC.sys 中也包含了 UsbOhci 和 UsbUhci 的功能。请看下图。
图 1 总线设备
上图中设备 1 、 3 是控制器设,从名称上就可以区别它们的不同: Universal Host Controller 和 Enhanced Host Controller 。所以设备 1 的驱动程序是 USBUhci.sys ,设备 3 的驱动程序是 USBEhci.sys 。
这说明同一台主机特别是笔记本电脑中, 1.1 和 2.0 的控制器并存,像笔记本电脑中的键盘通过内置 USB 接口与系统连接,其数据吞吐量小,只需要 1.1 的控制器就能满足;而暴露在外供用户使用的接口则需要 2.0 。
2) Hub 驱动: UsbHub.sys 。 Hub 驱动是所有 USB 设备的父驱动。下图描绘了这一景况:
图2
上图中看到, Hub 驱动的子设备要不是独立设备,如左侧图;要么是一个含有多个子设备的父设备,如右侧图。 Hub 驱动只为直系子设备创建唯一的物理设备对象。上图中, Hub 驱动为设备 1 和设备 2 创建物理设备对象,但并不为设备 2 的子设备创建物理设备对象。
3 ) Port 驱动: UsbPort.sys 。这是个框架驱动,比较复杂,很少人会用到。而上面的 Ushohci.sys 、 Usbuhci.sys 、 Usbehci.sys 其实都是他的微端口驱动。对于这么偏门的框架,不提也罢。
系统类驱动
所以出现类驱动,体现了 USB 总线在应用上的繁荣景象。只有用得多了,才有被归类的可能。就像人类社会中有几百个国家,几千个民族,正是体现了人类这个团体的多样性与繁荣。只有在“多”的基础上,分类才是有必要的;少数人的小群体,再怎么独立特行,也都不足以被分类,甚至定义为“ XX 民族”。
USB 设备包含很多的通用的功能类,比如: USB 集线器设备, USB HID 设备, USB 音频设备, USB MIDI 设备, USB 存储设备。为了让开发工作变得更加简单, Windows 操作系统为他们提供了系统驱动程序。
大部分时候,系统类驱动就是功能驱动。下表是系统提供的 USB 类驱动。
USB 类名称
类代码
驱动名称
系统支持
蓝牙设备
0xE0
Bthusb.sys
Vista 、 XP
USB 芯片智能卡接口设备
(CCID)
0x0B
Usbccid.sys
2K 、 XP 、 2K3 、 Vista 、 2K8
Hub 设备
0x09
Usbhub.sys
2K 、 XP 、 2K3 、 Vista 、 2K8
HID 设备
0x03
Hidusb.sys
2K 、 XP 、 2K3 、 Vista 、 2K8
USB 大容量存储设备
0x08
Usbstor.sys
2K 、 XP 、 2K3 、 Vista 、 2K8
打印设备
0x07
Usbprint.sys
2K 、 XP 、 2K3 、 Vista 、 2K8
扫描设备
0x06
WpdUsb.sys Usbscan.sys
2K 、 XP 、 2K3 、 Vista 、 2K8
媒体传输设备 (MTP)
0x06
WpdUsb.sys
XP 、 2K3 、 Vista 、 2K8
音频设备
0x01
Usbaudio.sys
2K 、 XP 、 2K3 、 Vista 、 2K8
Modem 设备 (CDC)
0x02
Usbser.sys
2K 、 XP 、 2K3 、 Vista 、 2K8
视频设备 (UVC)
0x0E
Usbvideo.sys
XP 、 Vista
表 1 系统提供的 USB 类驱动
功能驱动
不是所有的 USB 设备都有类驱动,但功能驱动却是它们唯一的身份证。没有功能驱动,设备就不足以在系统中存在。它的作用是为设备创造一个独一无二的内核设备对象( DEVICE_OBJCET ),并因此而在需要的时候,系统能够通过此内核设备对象找到它。
如果要让用户层也能够知道并使用 USB 设备,功能驱动更加不可少。它为设备在用户程序可见的名字空间中,为它起一个别名,这个别名可以是一个符号链接,也可以是一个由 GUID 定义的设备 Interface 。通过对这个别名进行操作,也就是对设备本身进行操作。
OK ,上面的话说得太满了,有例外的!唯一的例外是以 RAW 模式驱动的设备。这种设备直接由总线驱动来驱动其工作,不需要功能驱动。这种例子真的不多见,也许只有很很底层的控制器设备、 Hub 设备之类,才会这样做。对于 RAW 模式驱动的设备,当收到 IRP_MN_QUERY_CAPABILITIES 查询请求的时候,在返回的 DEVICE_CAPABILITIES 结构体中,必须将 RawDeviceOK 位设置为 TRUE 。
建议读者在需要的时候使用 WinOBJ.exe 工具查看系统空间中的设备与别名。
父驱动与混合设备
通过一定的设置, USB 设备中的每个接口可以拥有不同的 Class 和 Protocol 定义,从而实现:一个设备,多个功能。这种设备被称为混合设备。混合设备的前提是拥有多个接口,对单接口设备谈 “ 混合 ” 是没有意义的。
满足了如下两个条件的多接口 USB 设备,被系统认为是混合设备:
1. 设备描述符中,设备类的值为 0 : (bDeviceClass , bDeviceSubClass, bDeviceProtocol ) = (0, 0, 0);
2. 只有唯一的配置描述符,即设备描述符中: (bNumConfigurations ) = (1)
从 WinXP SP2 以后,还支持另外一种混合设备的判别方式,称作: Interface Association Descriptor ( IAD )。其实 IAD 描述符是用来组织 “ 接口组( Interfaces Group ) ” 的。配置描述符中可以有多个 IAD 存在,如果将某两个接口将组成接口组,那么首先这两个接口必须是紧挨着的,其次,必须有一个 IAD 描述符位于这两个接口描述符的前面,也必须是紧挨着的, IAD 描述符中的 bFirstInterface 用来描述接口组中的第一个接口 ID , bInterfaceCount 用来描述接口组中包含多少个接口。这样接口集合 : [bFirstInterface, bFirstInterface+bInterfaceCount) 为一个接口组。
当然,能够用上 IAD 的设备,一定是有多接口存在了,否则就是多此一举了。 IAD 描述符的识别和实现是通用父驱动完成的,所以有 IAD 支持的设备,都被认作混合设备。而识别设备是否有 IAD 支持,是通过设备描述符中的如下值判断的:
(bDeviceClass, bDeviceSubClass, bDeviceProtocol) = (0xEF, 0x02, 0x01)
IAD 普及率不是很广,一个原因就是 XP sp2 以后的操作系统版本才对它支持,这样如果把设备插入到 Win 2000 甚至 XP SP1 上,都不能被正确识别。这样,厂商可能必须为不同的系统写两套驱动程序。
我们下面来说说当一个多接口混合设备插入电脑后,系统是如何识别它,并为它加载驱动的。这里大家要注意到一点,就是系统是如何把一个设备,通过多接口,识别为多个物理设备的。
一开始,系统 PNP 管理器安装常规,读取并分析 USB 设备描述符,然后为它分配如下设备 ID :
USB\VID_vvvv&PID_pppp
USB\VID_vvvv&PID_pppp&REV_rrrr
(vvvv, pppp, rrrr: 4 位 16 进制数,分别代表了厂商 ID ,产品 ID , USB 版本。对应于设备描述符中的这些值: idVendor/ idProduct/ bcdDevice)
和兼容 ID :
USB\COMPOSITE
PNP 管理器首先按照常规,根据上述的设备 ID 为设备寻找合适的驱动程序:根据设备 ID 到注册表的设备安装信息库(对应于 Enum 和 Class 两个键)中进行搜索,如果找到了安装记录,就根据记录中的信息加载驱动程序。
问题是如果找不到合法的记录怎么办?这时候就用的着兼容 ID 了,兼容 ID “ USB\COMPOSITE ”是在系统中有注册记录的,并且就对应着通用父设备驱动( USBCCGP.sys) 。于是,系统为混合设备加载通用父设备驱动。读者可到目录 Windows\Inf 下查看 usb.inf 文件,此文件中包含了通用父设备驱动的安装信息。
通用父设备驱动通过分析配置描述符,完成两个动作:首先为每个 USB 接口分配一个的设备 ID 和兼容 ID ;然后为每个 USB 接口创建一个物理设备对象( Physical Device Object )。设备 ID 形式如下:
USB\VID_vvvv&PID_pppp&MI_mm
USB\VID_vvvv&PID_pppp&REV_rrrr&MI_mm
(mm: 两个 16 位数字表示的接口号 )
根据接口描述符中的 clsss 类型,分配兼容 ID ,其形式如下:
USB\CLASS_cc
USB\CLASS_cc&SUBCLASS_ss
USB\CLASS_cc&SUBCLASS_ss&PROT_pp
(cc/ ss / pp: 两位 16 进制数。分别对应于接口描述符中的: bInterfaceClass/ bInterfaceSubClass/ bInterfaceProtocol)
通用父设备驱动为每个接口创建了物理设备对象后,将接口的设备 ID 、兼容 ID 信息提交给 PNP 管理器, PNP 管理器就有责任为这些“虚假的”物理设备安装驱动。仍然重复上面的过程:根据每个接口的设备 ID 和兼容 ID 在注册表的设备安装信息库中搜索,试图找到相关的安装记录,如果找到了,就为接口加载相应的驱动程序。如果找不到,系统就会弹出“发现新设备”的对话框,启动驱动安装向导。此时用户需为这些接口手动安装驱动。
所以,对于多接口的混合设备( composite device ),其设备驱动的安装分为两个过程,先尝试安装混合驱动,如果找不到,就默认安装通用父设备驱动;接下来通用父设备驱动为每个接口分配设备 ID ,并要求系统为每个接口启动 PNP 过程。
最后,那么来说,在 USB 混合设备中,一定是 X 个接口对应于 X 个功能设备(有一个物理设备对象)吗?一般情况下是这样的,但也有例外,这就是接口组:在符合一定条件的情况下,多个接口中的某些接口可以被组合为一个接口组,一个接口组代表一个功能,父设备驱动只为之创建一个物理设备对象。
接口组的详情,大家看后面的章节内容。
过滤驱动
过滤驱动无处不在。在很多时候,它被称作 Hook ,是一种 Hack 手段;很多时候它又是必不可少的,这种技术甚至被操作系统自己使用。没有哪一个杀防毒软件不使用过滤驱动,我们在使用网上银行的时候,会用到一些安全控件,基本上都借助了过滤驱动的技术。
过滤驱动可以位于任何一层驱动的上面,或下面。过滤的对象也包括已经存在于系统中的其他的过滤驱动。当它位于某层驱动( D 驱动)上面的时候,所有目标发往 D 驱动的请求,都首先被它截取;当它位于某层驱动下面的时候,所有和 D 驱动相关的从更底层驱动反馈回来的的“完成消息”都预先被过滤驱动截取。这正是它威力强大的原因所在。对于被过滤的驱动来说,过滤驱动简直就是它的先知了。
但使用过滤驱动,要很慎重。很容易把系统搞得很不稳定。读者在写过滤驱动的时候,要明白这样一件事:你想过滤谁,得先了解谁;好像你追求一个人,要先认识这个人。否则死机蓝屏都会与你不期而遇。
USB 驱动栈、设备栈
请大家不要把这里的“栈”理解成“程序堆栈”的那个栈,朋友们要回到这个字最简单的本意来理解它,而不是想象成一个数据结构。就看成草垛柴堆一样。
驱动栈、设备栈本质上是并行概念。驱动之间的联系是通过设备对象进行的,所以驱动之间是间接联系的,驱动栈多少也就只是概念上的,他用来表示一个设备能够在系统中识别、运行,从上到下中共需要哪些驱动程序支持。
设备栈则是由据可查的。系统中每个 DevNode 就表现一个设备栈。可以这样理解,多个设备栈,串联成了驱动栈。
使用 WinDBG 的 !devnode 命令,可以列举系统中的设备树。下图截取了 CY001 相关片段。
图 3 CY001 DevNode 片段
上图中红色框标出的三个 DevNode ,正好对应于三个内核驱动,建立了 CY001 的驱动栈。从上到下分别是控制器驱动 (usbEHCI) ,集线器驱动 (usbHUB) ,和功能驱动 (CY001) 。下图以 CY001 为例,更加清晰详细地描绘了驱动栈、设备栈的面貌。
图 4 CY001 的驱动栈和设备栈
上图是单接口 CY001 设备的驱动栈、设备栈全图。也适用于所有其他的单接口 USB 设备。如果是多接口混合设备,就要多一个通用父驱动,稍微复杂一点。
最上面是可能存在的过滤驱动,因为只是“可能存在”,所以都用虚框表示。其实过滤驱动可以存在于设备栈的任何一个位置,而不仅仅是最上层。笔者不可能尽皆画全,以上层过滤为例,能说明问题也就可以了。
过滤驱动生成的过滤设备对象,挂载到 CY001 驱动生成的功能设备对象上;这样所有发送给 CY001 功能设备对象的请求,过滤设备对象总是先得到。根集线器生成了 CY001 驱动的物理设备对象。三个设备连在一起,就是 CY001 驱动程序的设备栈。
但还没有完,根集线器驱动并不是最底层驱动,他必须得到控制器驱动的支持,于是加上可能存在的过滤驱动,由此形成中间的那条设备栈。
仍旧未结束,控制器驱动也不是直接和系统交互的,而是通过更底层的系统总线如 PCI 、 ACPI 进行的。这样,右侧的第三条设备栈也形成了。
由此也可以看出设备栈、驱动栈之间的关系了。驱动栈是形而上的,设备栈是形而下的。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/changpei/archive/2010/05/06/5562542.aspx
在MFC类库提供了CWnd::OnCtlColor函数,在工作框架的子窗口被重画时将调用该成员函数.因此可以重载WM_CTLCOLOR消息的响应函数.此函数的原型:
afx_msg HBRUSH OnCtlColor(CDC *pDC,CWnd *pWnd,UINT nCtlColor);
参数nCtlColor用于指定控件的类型,可以是:
.CTLCOLOR_BTN 按钮控件
.CTLCOLOR_DLG 对话框
.CTLCOLOR_EDIT 编辑框
.CTLCOLOR_LISTBOX 列表控件
.CTLCOLOR_MSGBOX 消息控件
.CTLCOLOR_SCROLLBAR 滚动条控件
.CTLCOLOR_STATIC 静态控件
[程序实现]
假设你已有了名为My的对话框工程.你有了一个STATIC的控件,ID为IDC_STATIC1.
HBRUSH CMyDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
// TODO: Change any attributes of the DC here
if (nCtlColor==CTLCOLOR_STATIC)
{
pDC->SetTextColor(RGB(255,0,0)); //字体颜色
pDC->SetBkColor(RGB(0, 0, 255)); //字体背景色
}
// TODO: Return a different brush if the default is not desired
return hbr;
}
如果要指定某个特定控件可以这样写:ID为IDC_STATIC1
if (pWnd->GetDlgCtrlID()==IDC_STATIC1)
{
pDC->SetTextColor(RGB(255,0,0)); //设置字体颜色
pDC->SetBkMode(TRANSPARENT); //设置字体背景为透明
// TODO: Return a different brush if the default is not desired
return (HBRUSH)::GetStockObject(BLACK_BRUSH); // 设置背景色
}
else
return hbr;
【注】
BLACK_BRUSH:黑色
WHITE_BRUSH:白色
GRAY_BRUSH:灰色
NULL_BRUSH:透明
HOLLOW_BRUSH :透明
1.为对话框类添加WM_CTLCOLOR的响应函数afx_msg HBRUSH OnCtlColor(CDC*pDC,CWnd*pWnd,UINT nCtlColor){...}
2.定义一个m_brush(CBrush类型)的成员变量和一个m_font(CFont类型)成员变量,在构造函数中初始化,例如:m_brush.CreateSolidBrush(RGB(0,0,255));m_font.CreatePointFont(200,"华文行楷");
3.改变背景颜色和文本颜色和字体:在OnCtlColor()添加代码:
if(pWnd->GetDlgCtrlID()==IDC_LINE_STYLE/*控件ID*/)
{
pDC->SetTextColor(RGB(255,0,0));
pDC->SetBkMode(TRANSPARENT);//设置文本背景色为透明
pDC->SelectObject(&m_font);//设置字体
return m_brush;//设置控件背景颜色
}
//对于按钮来说上面的方法无效
此为我程序中的一个类,本用于WinCE,但在桌面系统上也同样适用!
使用方法(在WM_INITDIALOG或WM_CREATE消息中加入):
CWindowAnchor::BeginControlBound(hwnd)
手动调整控件位置:
CWindowAnchor::AddControl(hwnd,IDC_STATIC1,&WindowAnchorInfo(WAT_LEFT|WAT_TOP,2,8,4,10));
CWindowAnchor::AddControl(hwnd,IDC_STATIC1,&WindowAnchorInfo(WAT_LEFT|WAT_TOP|WAT_RIGHT,2,20,4,10));
CWindowAnchor::AddControl(hwnd,IDC_STATIC1,&WindowAnchorInfo(WAT_LEFT|WAT_TOP,2,8,40,10));
自动调整控件位置(跟据设计时资源文件中控件的大小及位置):
CWindowAnchor::AddControl(hwnd,IDC_STATIC1,&WindowAnchorInfo(WAT_LEFT|WAT_TOP));
CWindowAnchor::AddControl(hwnd,IDC_STATIC1,&WindowAnchorInfo(WAT_LEFT|WAT_TOP|WAT_RIGHT));
响应WM_SIZE消息:
case WM_SIZE:
return HANDLE_WM_SIZE(hwndDlg,wParam,lParam,CWindowAnchor::OnSize);
响应WM_DESTROY消息:
CWindowAnchor::EndControlBound(hwnd);
代码:
#pragma once
#include <map>
#if defined (_MSC_VER)
#pragma warning(disable: 4786)
#endif
/*用于WindowAnchorInfo结构的停靠类型*/
typedef enum WindowAnchorType
{
WAT_TOP=0x0001,
WAT_LEFT=0x0002,
WAT_RIGHT=0x0004,
WAT_BOTTOM=0x0008
};
/*控件定位描述信息*/
typedef struct WindowAnchorInfo{
DWORD dwAnchor; //WAT_*
RECT rcOriginalRect; //控件的原始边距,如果为空则自动获取(仅适用于WM_INIT中)
WindowAnchorInfo(DWORD pAnchor=WAT_TOP|WAT_LEFT,LONG pLeft=0,LONG pTop=0,LONG pRight=0,LONG pBottom=0)
{
dwAnchor=pAnchor;
rcOriginalRect.left=pLeft;
rcOriginalRect.top=pTop;
rcOriginalRect.right=pRight;
rcOriginalRect.bottom=pBottom;
};
};
typedef std::map<HWND,WindowAnchorInfo> ControlHashtable;
typedef struct{
INT nWidth; //对话框宽度
INT nHeight; //对话框高度
INT nMinHeight; //对话框最小高度
ControlHashtable mapControls; //对话框所有子控件
}WindowAnchorDialog;
/*
* 对话框子控件定位
* 2009.03.29 By Frank
*/
static class CWindowAnchor
{
private:
static BOOL _ReSize(HWND hwndDlg, const WindowAnchorDialog *wad, HWND hwndCtrl, const WindowAnchorInfo *wai);
public:
/*
* 开始调整(此调用中会获取当前对话框的大小,如果在设计后要调整对话框大小,请先调用此方法)
* hwndDlg:对话框句柄
*/
static BOOL BeginControlBound(HWND hwndDlg);
/*
* 结束调整
* hwndDlg:对话框句柄
*/
static BOOL EndControlBound(HWND hwndDlg);
/*
* 添加一个控件到调整列表
* hWndInsertAfter:HWND_BOTTOM |HWND_NOTOPMOST | HWND_TOP | HWND_TOPMOST |-2不改变 | Is Hwnd
*/
static BOOL AddControl(HWND hwndDlg, INT nCtrlID, WindowAnchorInfo *wai, HWND hWndInsertAfter=(HWND)-2);
/*
* 调整一个指定控件的大小
*/
static BOOL ReSize(HWND hwndDlg, HWND hwndCtrl);
/*
* 响应WM_SIZE消息
*/
static BOOL OnSize(HWND hwndDlg, UINT state, int cx, int cy);
/*相应WM_VSCROLL消息*/
static BOOL OnVScroll(HWND hwnd, HWND hwndCtl, UINT code, int pos);
};
下载地址:单击下载
1.首先在初始化函数中,FormView在OnInitialUpdate(),Dialog在OnInitDialog()中初始化控件的大小。
view plaincopy to clipboardprint?
01.//开始初始化控件大小
02. m_IsInitialed = false;
03.
04. CRect m_ClientRect;
05. this->GetClientRect(&m_ClientRect);
06. CSize m_Forsize;
07. m_Forsize = GetTotalSize();//在资源编辑器中定好大小后,程序运行时大小(不管最大化和最小化,该大小均为同一个值),客户区大于或等于显示的大小
08. double m_x = (double)m_ClientRect.Width() / m_Forsize.cx;//宽度方向发大倍数
09. double m_y = (double)m_ClientRect.Height() / m_Forsize.cy;//高度方向发大倍数
10.
11. //调整控件的大小
12. CWnd *pWnd = NULL;
13. pWnd = GetWindow(GW_CHILD);
14. while(pWnd)//判断是否为空,因为对话框创建时会调用此函数,而当时控件还未创建
15. {
16. CRect rect; //获取控件变化前大小
17. pWnd->GetWindowRect(&rect);
18. ScreenToClient(&rect);//将控件大小转换为在对话框中的区域坐标
19. m_ControlRect.insert(pair<int, CRect>(pWnd->GetDlgCtrlID(), rect));//保存控件的初始大小,以便在OnSize函数中继续使用
20. int width = rect.Width();
21. int height = rect.Height();
22.
23. WCHAR szBuf[256];
24. GetClassName(pWnd->m_hWnd,szBuf,256);
25. if( _tcsicmp(szBuf,_T("Edit")) == 0)
26. {
27. //Edit只是位置变化,大小没有变
28. rect.top = m_y * rect.top;
29. rect.left = m_x * rect.left;
30. rect.bottom = rect.top + height;
31. rect.right = rect.left + width;
32. }
33. else
34. {
35. //其它控件位置和大小均变化
36. rect.top = m_y * rect.top;
37. rect.left = m_x * rect.left;
38. rect.bottom = m_y * rect.bottom;
39. rect.right = m_x * rect.right;
40. }
41.
42. pWnd->MoveWindow(&rect);//设置控件大小
43. pWnd = pWnd->GetWindow(GW_HWNDNEXT);
44. }
45.
46. //控件初始化结束
47. m_IsInitialed = true;
//开始初始化控件大小
m_IsInitialed = false;
CRect m_ClientRect;
this->GetClientRect(&m_ClientRect);
CSize m_Forsize;
m_Forsize = GetTotalSize();//在资源编辑器中定好大小后,程序运行时大小(不管最大化和最小化,该大小均为同一个值),客户区大于或等于显示的大小
double m_x = (double)m_ClientRect.Width() / m_Forsize.cx;//宽度方向发大倍数
double m_y = (double)m_ClientRect.Height() / m_Forsize.cy;//高度方向发大倍数
//调整控件的大小
CWnd *pWnd = NULL;
pWnd = GetWindow(GW_CHILD);
while(pWnd)//判断是否为空,因为对话框创建时会调用此函数,而当时控件还未创建
{
CRect rect; //获取控件变化前大小
pWnd->GetWindowRect(&rect);
ScreenToClient(&rect);//将控件大小转换为在对话框中的区域坐标
m_ControlRect.insert(pair<int, CRect>(pWnd->GetDlgCtrlID(), rect));//保存控件的初始大小,以便在OnSize函数中继续使用
int width = rect.Width();
int height = rect.Height();
WCHAR szBuf[256];
GetClassName(pWnd->m_hWnd,szBuf,256);
if( _tcsicmp(szBuf,_T("Edit")) == 0)
{
//Edit只是位置变化,大小没有变
rect.top = m_y * rect.top;
rect.left = m_x * rect.left;
rect.bottom = rect.top + height;
rect.right = rect.left + width;
}
else
{
//其它控件位置和大小均变化
rect.top = m_y * rect.top;
rect.left = m_x * rect.left;
rect.bottom = m_y * rect.bottom;
rect.right = m_x * rect.right;
}
pWnd->MoveWindow(&rect);//设置控件大小
pWnd = pWnd->GetWindow(GW_HWNDNEXT);
}
//控件初始化结束
m_IsInitialed = true;
2.如果界面在运行时大小可以改变,则在OnSize函数中加入如下代码
view plaincopy to clipboardprint?
01.// TODO: 在此处添加消息处理程序代码
02. CFormView::ShowScrollBar(SB_BOTH, false);//设置没有滚动条,视情况而定。
03. //在界面不是最小化并且已经初始化完毕
04. if (!IsIconic() && m_IsInitialed)
05. {
06. CSize m_Forsize;
07. m_Forsize = GetTotalSize();
08. double m_x = (double)cx / m_Forsize.cx;
09. double m_y = (double)cy / m_Forsize.cy;
10.
11. //读取控件的初始大小
12. map<int, CRect>::iterator pos = m_ControlRect.begin();
13. for (; pos != m_ControlRect.end(); ++pos)
14. {
15. CRect rect = pos->second;
16. int width = rect.Width();
17. int height = rect.Height();
18.
19. WCHAR szBuf[256];
20. GetClassName(GetDlgItem(pos->first)->m_hWnd,szBuf,256);
21. if( _tcsicmp(szBuf,_T("Edit")) == 0)
22. {
23. rect.top = m_y * rect.top;
24. rect.left = m_x * rect.left;
25. rect.bottom = rect.top + height;
26. rect.right = rect.left + width;
27. }
28. else
29. {
30. rect.top = m_y * rect.top;
31. rect.left = m_x * rect.left;
32. rect.bottom = m_y * rect.bottom;
33. rect.right = m_x * rect.right;
34. }
35. GetDlgItem(pos->first)->MoveWindow(rect);
36. }
37. }
// TODO: 在此处添加消息处理程序代码
CFormView::ShowScrollBar(SB_BOTH, false);//设置没有滚动条,视情况而定。
//在界面不是最小化并且已经初始化完毕
if (!IsIconic() && m_IsInitialed)
{
CSize m_Forsize;
m_Forsize = GetTotalSize();
double m_x = (double)cx / m_Forsize.cx;
double m_y = (double)cy / m_Forsize.cy;
//读取控件的初始大小
map<int, CRect>::iterator pos = m_ControlRect.begin();
for (; pos != m_ControlRect.end(); ++pos)
{
CRect rect = pos->second;
int width = rect.Width();
int height = rect.Height();
WCHAR szBuf[256];
GetClassName(GetDlgItem(pos->first)->m_hWnd,szBuf,256);
if( _tcsicmp(szBuf,_T("Edit")) == 0)
{
rect.top = m_y * rect.top;
rect.left = m_x * rect.left;
rect.bottom = rect.top + height;
rect.right = rect.left + width;
}
else
{
rect.top = m_y * rect.top;
rect.left = m_x * rect.left;
rect.bottom = m_y * rect.bottom;
rect.right = m_x * rect.right;
}
GetDlgItem(pos->first)->MoveWindow(rect);
}
}
或在OnShowWindow()函数中加入也可以(特别是在对话框作为tabpage时)
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/ybw20041910/archive/2010/06/19/5679730.aspx
1.
重载OnCtlColor (CDC* pDC, CWnd* pWnd, UINT nCtlColor),即WM_CTLCOLOR消息。
---- ①在CExampleDlgDlg的头文件中,添加一CBrush的成员变量:
class CExampleDlgDlg : public CDialog
{...
protected:
CBrush m_brush;
...
};
---- ②在OnInitDialog()函数中添加如下代码:
BOOL CExampleDlgDlg::OnInitDialog()
{
...
// TODO: Add extra initialization here
m_brush.CreateSolidBrush(RGB(0, 255, 0)); // 生成一绿色刷子
...
}
---- ③利用ClassWizard重载OnCtlColor(…),即WM_CTLCOLOR消息:
HBRUSH CExampleDlgDlg::OnCtlColor
(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
/*
** 这里不必编写任何代码!
**下行代码要注释掉
** HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
*/
return m_brush; //返加绿色刷子
}
2.
修改对话框的OnPaint,在else中添加如下代码
CPaintDC dc(this);
CRect rect;
GetClientRect(rect);
dc.FillSolidRect(rect, RGB(0,0,0));
CDialog::OnPaint();
3.
在对话框的应用类(App)的.cpp的Initinstance()中加入代码:
//加在int nResponse=dlg.DoModal();
前一个RGB设置背景色,第二个设置字体颜色
SetDialogBkColor(RGB(0,0,255),RGB(0,255,0));
4.
1.在对话框类中添加成员变量:
public:
CBrush m_brushBlue;
2.在对话框类的OnInitDialog()中添加代码:
m_brushBlue.CreateSolidBrush(RGB(0,0,255));
3.用ClassWizard在对话框类中添加成员函数OnCtlCollor(),并在其中添加代码:
if(nCtlColor==CTLCOLOR_DLG)
return m_brushBlue;
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/mfreesky/archive/2007/08/27/1760222.aspx
=============获取设备描述符
bRequestType:80
bRequest :06
wValue :0100
wIndex :0000
wLength :0040 期望长度64字节
usb bus Reset总线复位我的usb设备
=============发出为我的usb设备设置地址指令,我的usb地址被设置为0x04
bRequestType:00
bRequest :05
wValue :0004
wIndex :0000
wLength :0000
=============获取配置描述符
bRequestType:80
bRequest :06
wValue :0200
wIndex :0000
wLength :0009 期望长度9字节
=============尝试读取配置描述符0xff长度
bRequestType:80
bRequest :06
wValue :0200
wIndex :0000
wLength :00ff
=============尝试读取配置描述符0x12长度,不会超时
bRequestType:80
bRequest :06
wValue :0200
wIndex :0000
wLength :0012
=============尝试读取配置描述符0x09长度,正好
bRequestType:80
bRequest :06
wValue :0200
wIndex :0000
wLength :0009
=============读取配置描述符总长度0x22
bRequestType:80
bRequest :06
wValue :0200
wIndex :0000
wLength :0022
=============设置配置,将配置生效,使能cpu上的endpoint端点
bRequestType:00
bRequest :09
wValue :0001 将配置数值设置为1
wIndex :0000
wLength :0000
=============
bRequestType:00
bRequest :09
wValue :0001
wIndex :0000
wLength :0000
=============
bRequestType:81 读取接口
bRequest :06 读取接口描述符
wValue :2200 读取报告描述符
wIndex :0000
wLength :0072
=============
bRequestType:81
bRequest :06
wValue :2200
wIndex :0000
wLength :0072
============= 读取配置描述符
bRequestType:80
bRequest :06
wValue :0200
wIndex :0000
wLength :0022
摘要: 一、USB命令
在USB规范里,对命令一词提供的单词为“Request”,但这里为了更好的理解主机与设备之间的主从关系,将它定义成“命令”。
所有的USB设备都要求对主机发给自己的控制命令作出响应,USB规范定义了11个标准命令,它们分别是:C...
阅读全文
在《USB系列之三》中,我们实现了一系列的SCSI命令,在这个系列中,我们要实现向U盘上写扇区的命令,所以,本文相对比较容易,更多地是给出一个实现的源程序。
在《USB系列之三》中,我们实现的SCSI命令有:INQUIRY、READ CAPACITY(10)、TEST UNIT READY、REQUEST SENSE、READ(10);都是一些读出的命令,所以不会破坏U盘的内容,在文档SBC-2的第29页有一个SCSI命令的表,在这个表中列出了所有的命令,其TYPE为“M”的都是SCSI设备必须实现的命令,这些命令有:
Num
|
Command Name
|
Operation Code
|
Type
|
Reference
|
1
|
FORMAT UNIT |
04h
|
M
|
SBC-2 |
2
|
INQUIRY |
12h
|
M
|
SPC-3 |
3
|
READ(6) |
08h
|
M
|
SBC-2 |
4
|
READ(10) |
28h
|
M
|
SBC-2 |
5
|
READ(16) |
88h
|
M
|
SBC-2 |
6
|
READ CAPACITY(10) |
25h
|
M
|
SBC-2 |
7
|
READ CAPACITY(16) |
9Eh/10h
|
M
|
SBC-2 |
8
|
REQUEST SENSE |
03h
|
M
|
SPC-3 |
9
|
SEND DIAGNOSTIC |
1Dh
|
M
|
SPC-3 |
10
|
TEST UNIT READY |
00h
|
M
|
SPC-3 |
11
|
WRITE(10) |
2Ah
|
O
|
SBC-2 |
这里面最后的一个命令并不是SBC-2中要求强制实现的,而是可选的,但如果我们不去实现,U盘的操作将失色很多;我们不打算去实现序号为1、3、5、7和9的命令,READ(6)、READ(16)和READ(10)十分相似,只是LBA的长度不同而已,如果需要实现,参考READ(10)就可以了,FORMAT和SEND DIAGNOSTIC两个命令对使用芯片的U盘来说没有什么意义,当然对硬盘是有意义的,所以在本文中,我们只需要实现一个很重要的WRTE(10),向U盘上写数据,我们需要准备一张没有有用数据的U盘,因为我们要改变其中的内容。
WRITE(10)源代码下载地址:
http://blog.hengch.com/source/usb-write.zip
程序中,我们向《USB系列三》中的程序一样,先reset,然后得到最大的LUN,这个步骤不是必须的,然后我们向device发出WRITE(10)命令,注意,这是一个OUT事务,所以,CBW_FLAGS=0X00而不是像以前一样是0X80,发出WRITE(10)命令后,我们还要向device发送要写入的数据,每次64个字节,一个扇区512字节需要启动8个OUT事务,这个工作又函数putData完成,每次发送的64个字节我们分别写入了0--63,程序中,我们把这些数据写入到了LBA=100的扇区中,写入后,我们在使用在《USB系列之三》中介绍过的READ(10)命令把相同的扇区读出来,我们会看到我们所希望的结果,由于在读之前,我们已经把buffer全部清为0了,所以我们有把握相信,我们读到的数据是真实的。
到这里,我们已经把控制U盘的主要命令都介绍完了,利用DOSUSB,我们已经有可能为U盘编写一个简单的驱动程序,但可能我们还不知道DOS下的驱动程序该如何写,从下一篇文章开始,我们将暂时放下USB系列文章,介绍一下DOS下驱动程序的写法。
U盘是我们最常使用的一种USB设备,本文继续使用DOSUSB做驱动,试图以读取扇区的方式读取你的U盘。
本文可能涉及的协议可能会比较多。
一、了解你的U盘
首先我们用上一篇文章介绍的程序usbview.exe去看一下你的U盘,我在本文中用于测试的U盘情况如下:
Device Descriptor: (设备描述符)
USB Address: 1
Length: 18
Descriptor Type: 1
USB Specification nr.: 0x0110
Calss Code: Class code specified by interface
Subclass Code: 0x00
Protocol Code: 0x00
MAX Packet Size: 0x08
Vendor ID: 0x058f
Product ID: 0x9321
Device Code: 0x0100
Manufacture Index: 1
Product Index: 2
Serial Number Index: 0
Number of Configuration: 1
String Descriptor: (字符串描述符)
Manufacturer: Alcor Micro
Product: Mass Storage Device
Configuration Descriptor: (配置描述符)
Length: 9
Descriptor Type: 2
Total Length: 32
Number of Interfaces: 1
Configuration Value: 1
Configuration Index: 0
Attributes: Bus Powered
Max Power: 50mA
Interface Descriptor: (接口描述符)
Length: 9
Descriptor Type: 4
Interface Number: 0
Alternate Setting: 0
Number of Endpoints: 2
Interface Class: Mass Storage Device
Interface Sub Class: 6
Interface Protocol: 80
Interface Index: 0
Endpoint Descriptor: (端点描述符)
Length: 7
Descriptor Type: 5
Endpoint Address: 1 OUT endpoint
Attributes: Bulk
Max Packet Size: 64
Interval: 0
Endpoint Descriptor: (端点描述符)
Length: 7
Descriptor Type: 5
Endpoint Address: 2 IN endpoint
Attributes: Bulk
Max Packet Size: 64
Interval: 0
各种描述符的含义在以前的文章中介绍过了,或者去翻阅USB的specification,这里就不多说了,我们从接口描述符开始就一些关键点进行一下说明。
首先看接口描述符,Interface Class = 8,表明是Mass Storage Device;Sub Class = 6,表明执行SCSI命令;Interface Protocol = 0x80,表明支持Bulk传输;另外,Number of Endpoints = 2,表明有两个端点。
两个端点描述符要注意的是,Endpoint Address = 1的是OUT端点,Endpoint Address = 2的是IN端点,有些可能会不一样;有些U盘可能还会有第三个端点,比如支持中断传输的U盘还会有一个Interrupt端点,不过这都没有关系。
我大概看了我手头有的5个U盘,都支持批量传输,且支持SCSI命令,所以,这可能是一个比较典型的例子,我们就以它为例。
二、CBW(Command Block Wrapper)和CSW(Command Status Wrapper)
在《USB系列之一》中,我们安装了一个DOSUSB,在《USB系列之二》中,我们利用USBDOS读取了所有的描述表,掌握这些内容需要了解USB协议1.1(USB Specification Revision 1.1)即可,当然还要了解USBDOS,不过这个比较简单。
在系列一和系列二中,我们已经对DOSUSB的一个数据结构URB有所了解,本文中还要大量用到,我们还接触了一个结构叫device_request,这个结构是在USB协议中定义的,用于向设备发送命令(Request),本文也会用到。
与前面不同的是,前面的两个系列可以针对任何USB设备,比如U盘、摄像头、打印机等,而本文将只针对我们经常使用的USB设备----U盘,如果你打算尝试本文所介绍的内容,请准备好一个U盘,什么样子的都行,或者是一个USB读卡器,不过要记得插一张卡进去,实际上本文所载范例就是使用一个USB的CF卡读卡器完成的,不用担心损害你的U盘中的数据,本文不会对U盘进行任何写操作,仅仅做一些读操作。
这个系列中我们需要针对U盘读更多的规范,如下:
不用为规范发愁,实际上,前两个规范都很短,其中第一个对实际编程没有什么作用,但最好看一下;第二个规范连目录一共22页,其中13页以前的内容可以跳过(很多和USB Specification中相同),第三个规范主要看第6章,第四个规范主要看第5章,后两个规范在编程时需要经常翻阅,以便了解你正在实现的SCSI命令的具体格式和参数。
本节我们主要介绍两个新的数据结构,这两个结构都是在第二个规范中定义的。
第一个数据结构叫CBW(Command Block Wrapper)
这个结构将承载具体的与设备有关的命令发送到设备上去,这个结构分成两部分,第一部分从byte[0]--byte[14]共15个字节,第而部分从byte[15]--byte[30]共16个字节,整个数据结构为31个字节。规范中并没有定义第二部分的内容,这是因为第二部分承载的具体的命令,既与命令集(SCSI命令集)有关,也与具体的命令有关,我们使用SCSI命令集,所以后16个字节的内容在前面提到的后面两个规范中有定义。
比如我们要向设备发出一个SCSI命令INQUIRY(我们姑且先不要管命令的含义),那么这个命令的结构在SPC-3的第142页有定义,如下:
对于SCSI INQUIRY这条命令而言,CBW的第二部分的定义就是上面的这六个字节,不同的命令,定义也会不同。
好,我们回到CSW的结构上来,根据规范,dCBWSignature的值必须是0X43425355,其实就是USBC这几个字母倒过来,这是因为CBW的字符顺序是little endian(这个东东在以前有关网络编程的文章中介绍过),而我们PC机中的字符顺序是big endian,所以要颠倒一下,总之写dCBWSignature = 0X43425355就OK了;dCBWTag仅仅是一个标志,你可以填任何值,这里要先说一下CSW(Command Status Wrapper),我们每发出一个命令,设备都会返回一个CSW(这个东东下面很快就要介绍了),以说明命令的执行状态,这个结构中也有Signature和Tag这两个字段,其中Tag字段和发出命令时CBW中的Tag字段相同,这样就可以区分这个CSW是和那个CBW对应的了,至于Signature,下面再说。
下一个字段是dCBWDataTransferLength,表示的是当这个命令发出后,我们希望设备返回数据的字符数或者我们要向设备传输的字符数,本文仅涉及从设备返回数据,不涉及向设备传输数据;举例来说:我们发送INQIURY命令到设备,按照SPC-3第144页的说明,该命令返回的数据至少为36个字节,所以,此时这个字节应该填36;再如:我们读取U盘的一个扇区,如果扇区的长度是512个字节,那么这个字段就要填512。
再下来是bmCBWFlags字段,这个字段只有bit 7有意义,为0表示要向设备传输数据,为1表示要从设备获得数据。
bCBWLUN字段总是填0,因为绝大多数的U盘都不支持多LUN(Logical Unit Number),只有一个逻辑单元自然好吗就是0了。
bCBWCBLength字段是只CBW第二部分的长度,像前面举例的INQUIRY命令,长度为6个字节,则这个字段就应该填6,再如:READ(10)命令的长度是10个字节(SBC-2第42页有定义),这个字段当然要填10了。
第二个要说的数据结构是CSW,当host向device发送一个CBW后,接着就可以从device收到数据(或者发数据到device),当接受完所需的的数据后,就可以从device获得一个CSW(Command Status Wrapper),CSW的结构如下:
前面说过,在CBW中的dCBWSignature的值恒为:0x43425355,得到的CSW中的dCSWSignature的值为:0x53425355,dCSWTag与dCBWTag中的一致。
在得到的CSW中,恒定有13个字节,bCSWStatus的定义如下:
三、发送命令和接收数据
我们知道USB协议中定义了三种传输方式,控制传输、批量传输、中断传输和实时传输,在《USB系列二》中我们一直都在使用控制传输,我们应该比较熟悉了,本文中将涉及批量传输。
我们在使用控制传输时,我们设置好URB启动传输事务,相应的结果将返回到制定得buffer中,批量传输没有那么简单,批量传输分为输出事务和输入事务,我们应该注意到,前面在看U盘的描述表时,在端点这一级有两个端点,一个叫OUT端点,一个叫IN端点,当我们启动一个输出事务时,一定要发送给OUT端点,当我们启动一个输入事务时,一定要发送到输入端点。下面我们简单描述一下如何启动批量传输事务。
在使用控制传输时,我们应该阅读过DOSUSB的说明,并且对URB结构比较熟悉,URB中有一个字段叫transation_type,当这个值为0x2d时为控制传输;当为0x69时为批量传输的IN事务;当为0xe1时为批量传输的OUT事务;当我们启动一个传输时,一定要正确地设置这个值。
我们以一个具体的例子来说明如何启动一个传输,我们以SCSI INQUIRY命令为了,关于这个命令的定义在SPC-3的第142页--157页有说明,篇幅很长,但绝大多数篇幅用来解释返回数据的含义,我们可以暂时不去理会。首先我们要填写CBW结构,CBW结构的第一部分的填写前面已经说的很明白了,第二部分的定义在SPC-3的第142页,共有6个字节,我们要按照定义填写好,实际上只要填两个字段,一个是OPERATION CODE = 0X12,第二个就是ALLOCATION CODE = 36,表示需要返回36个字节的内容;CBW填好后,我们开始填写URB,首先把CBW的偏移和段地址放到URB的buffer_off和buffer_seg中,把transation_type=0xe1,表示一个输出事务,注意把end_point字段一定要放OUT endpoint的地址,从前面的描述符表中看,应该是1(2是IN endpoint的地址,你的机器可能不同),其它字段的填法在《USB系列二》中已经介绍过了,填完以后调用DOSUSB,这样,一个承载着INQUIRY命令的输出事务就发送到由URB中dev_add和end_point两个字段指定的端点上去了。
接下来我们要接收device返回的执行INQUIRY命令的结果,这要启动一个输入事务,相对容易一些,只要填写URB就可以了,把transation_type=0x69,把end_point填上OUT endpoint的地址,本例中为2,buffer_off和buffer_seg指向缓冲区buffer,把buffer_length和actual_length均填为64,因为前面端点描述符表中写明包的最大长度为64,其它字段按常规填写,调用DOSUSB,在buffer中就可以得到返回的内容,按照SPC-3中对返回内容的解释即可了解设备的一些情况。
接收晚数据后,不要忘了接收CSW,方法也是启动一个输入事务,与接收数据完全相同,然后根据CSW的结构解释其含义。至此一个命令执行完毕。
四、范例
在本文的范例中,我们实现了如下内容:
- 实现了Bulk-Only Mass Storage Reset
- 实现了Get Max LUN
- 实现了SCSI INQUIRY Command
- 实现了SCSI READ CAPACITY (10) Command
- 实现了SCSI REQUEST SENSE Command
- 实现了SCSI TEST UNIT READY Command
- 实现了SCSI READ (10) Command
最后的一个命令,我将从你的U盘上读出一个扇区。
最前面的两个命令,请翻阅《Universal Serial Bus Mass Storage Class - Bulk-Only Transport》第7页;INQUIRY、REQUEST SENSE、TEST UNIT READY三个命令请翻阅SPC-3的第142、221和232页;READ CAPACITY(10)和READ(10)命令,请翻阅SBC-2的第42和44页。
源代码请在下面网址下载:
http://blog.hengch.com/source/reader.rar
各种概念在前面已经介绍过了,程序无非就是实现这些概念,几乎所有的代码都是围绕着填写数据结构和显示返回结果的,所以代码本身并不难,更重要的是理解数据结构中个字段的含义,这可能不得不阅读一些规范,我想我不可能比规范说的更严谨更完整。要注意的是,你使用的U盘不可能和我的完全一致,一般情况下有可能有变化的是:设备地址devAddr、输出端点地址outEndpoint和输入端点地址inEndpoint,所以在编译程序之前一定要使用《USB系列之二》中的方法仔细查看一下你的U盘的各种描述符表,如果这些值和我的U盘不同,请在主程序开始的地方,更改这几个变量;另外,在主程序6th step中,scsiRead10(0),传递给scsiRead10的参数为0,含义是从LBA(Logical Block Address)为0的地方读取一个扇区,如果你向读取其它扇区,可以更改这个值,其最大值我们在实现 READ CAPACITY时已经读出了,可以参考;此外,注意CBW的字符顺序是little endian,所以我们在填写LBA和读取最大LBA时都做了相应的转换。
好了,应该没有什么了!
Enjoy it.
USB现在已经成为PC机必不可少的接口之一,几乎所有的设备都可以接在USB设备上,USB键盘、鼠标、打印机、摄像头,还有常用的U盘等等,从本篇文章开始,将集中篇幅介绍一下在DOS中使用USB设备的方法,具体会有几篇暂不好定,写到哪里算哪里吧,三、四篇总是少不了的。
本文介绍如何使用我以前文章中介绍过的知识在你的机器中找到USB设备,并判定设备类型。
一个USB系统一般由一个USB主机(HOST)、一个或多个USB集线器(HUB,但不是局域网里的集线器)和一个或多个USB设备节点(NODE)组成,一个系统中只有一个HOST,我们PC机里的USB实际上就是HOST和HUB两部分,你的PC机可能会有4个USB口,其实是一个HOST,一个HUB,HUB为你提供了4个端口,我们插在USB口上的器件,一般是USB设备,比如U盘,USB打印机等,当然我们也可以插一个集线器上去,使你的一个USB口扩展成多个。
实际上我们说在DOS下使用USB,就是对USB系统中的HOST进行编程管理,根据USB的规范,HOST将对连接在上面的HUB和USB设备进行管理,不用我们操心。HOST器件目前有三个规范,OHCI(Open Host Controller Interface)、UHCI(Universal Host Controller Interface)支持USB1.1,EHCI(Enhanced Host Controller Interface)支持USB2.0,以后的文章中,我们将侧重介绍OHCI和EHCI。
学习USB编程,读规范是少不了的,以下是一些应该阅读的规范下载:
OHCI规范:http://blog.hengch.com/specification/usb_ohci_r10a.pdf
EHCI规范:http://blog.hengch.com/specification/usb_ehci_r10.pdf
USB规范1.1:http://blog.hengch.com/specification/usb_spec11.pdf
USB规范2.0:http://blog.hengch.com/specification/usb_spec20.pdf
本文介绍的内容不需要学习规范。
下面进入正题,列出你的USB设备,USB的HOST是挂接在PCI总线上的,所以通过PCI设备的遍历就可以找到你的机器上的所有USB设备,在以前介绍PCI的配置空间时,曾经介绍过在配置空间中有一个占三个字节的分类代码字段(如果不知道,请参阅我以前的博文《遍历PCI设备》),在偏移为0x0B的字节叫基本分类代码,在偏移为0x0A的字节叫子分类代码,在偏移为0x09的字节叫编程接口代码,对于USB设备类说,基本分类代码为0x0C,子分类代码为0x03,对于符合不同规范的HOST器件而言,编程接口代码是不同的,UHCI的编程接口代码是0x00,OHCI的编程接口代码是0x10,EHCI的编程接口代码是0x20,我想了解这些就足够了。
下面列出USB设备的源程序。
#include <stdio.h>
#include <stdlib.h>
#include <dpmi.h>
typedef unsigned long UDWORD;
typedef short int WORD;
typedef unsigned short int UWORD;
typedef unsigned char UBYTE;
typedef union {
struct {
UDWORD edi;
UDWORD esi;
UDWORD ebp;
UDWORD res;
UDWORD ebx;
UDWORD edx;
UDWORD ecx;
UDWORD eax;
} d;
struct {
UWORD di, di_hi;
UWORD si, si_hi;
UWORD bp, bp_hi;
UWORD res, res_hi;
UWORD bx, bx_hi;
UWORD dx, dx_hi;
UWORD cx, cx_hi;
UWORD ax, ax_hi;
UWORD flags;
UWORD es;
UWORD ds;
UWORD fs;
UWORD gs;
UWORD ip;
UWORD cs;
UWORD sp;
UWORD ss;
} x;
struct {
UBYTE edi[4];
UBYTE esi[4];
UBYTE ebp[4];
UBYTE res[4];
UBYTE bl, bh, ebx_b2, ebx_b3;
UBYTE dl, dh, edx_b2, edx_b3;
UBYTE cl, ch, ecx_b2, ecx_b3;
UBYTE al, ah, eax_b2, eax_b3;
} h;
} X86_REGS;
/*************************************************************
* Excute soft interrupt in real mode
*************************************************************/
int x86_int(int int_num, X86_REGS *x86_reg) {
__dpmi_regs d_regs;
int return_value;
d_regs.d.edi = x86_reg->d.edi;
d_regs.d.esi = x86_reg->d.esi;
d_regs.d.ebp = x86_reg->d.ebp;
d_regs.d.res = x86_reg->d.res;
d_regs.d.ebx = x86_reg->d.ebx;
d_regs.d.ecx = x86_reg->d.ecx;
d_regs.d.edx = x86_reg->d.edx;
d_regs.d.eax = x86_reg->d.eax;
d_regs.x.flags = x86_reg->x.flags;
d_regs.x.es = x86_reg->x.es;
d_regs.x.ds = x86_reg->x.ds;
d_regs.x.fs = x86_reg->x.fs;
d_regs.x.gs = x86_reg->x.gs;
d_regs.x.ip = x86_reg->x.ip;
d_regs.x.cs = x86_reg->x.cs;
d_regs.x.sp = x86_reg->x.sp;
d_regs.x.ss = x86_reg->x.ss;
return_value = __dpmi_int(int_num, &d_regs);
x86_reg->d.edi = d_regs.d.edi;
x86_reg->d.esi = d_regs.d.esi;
x86_reg->d.ebp = d_regs.d.ebp;
x86_reg->d.res = d_regs.d.res;
x86_reg->d.ebx = d_regs.d.ebx;
x86_reg->d.ecx = d_regs.d.ecx;
x86_reg->d.edx = d_regs.d.edx;
x86_reg->d.eax = d_regs.d.eax;
x86_reg->x.flags = d_regs.x.flags;
x86_reg->x.es = d_regs.x.es;
x86_reg->x.ds = d_regs.x.ds;
x86_reg->x.fs = d_regs.x.fs;
x86_reg->x.gs = d_regs.x.gs;
x86_reg->x.ip = d_regs.x.ip;
x86_reg->x.cs = d_regs.x.cs;
x86_reg->x.sp = d_regs.x.sp;
x86_reg->x.ss = d_regs.x.ss;
return return_value;
}
/**********************************
* Read Configuration WORD if PCI
**********************************/
UWORD ReadConfigWORD(WORD pciAddr, int reg) {
X86_REGS inregs;
inregs.x.ax = 0xB109; // Read Configuration word
inregs.x.bx = pciAddr;
inregs.x.di = reg; // Register number
x86_int(0x1A, &inregs);
return inregs.d.ecx; // the value
}
// main program
int main(void) {
UWORD pciAddr;
UWORD subClass;
int ehciCount = 0, ohciCount = 0, uhciCount = 0;
for (pciAddr = 0; pciAddr < 0xffff; pciAddr++) {
if (ReadConfigWORD(pciAddr, 0) != 0xFFFF) {
// Read Class Code
if (ReadConfigWORD(pciAddr, 0x000a ) == 0x0c03) { // Usb Host Controller
// Read SubClass Code
subClass = ReadConfigWORD(pciAddr, 0x0008);
if ((subClass & 0xff00) == 0x2000) { // uhci
ehciCount++;
} else if ((subClass & 0xff00) == 0x1000) { // ohci
ohciCount++;
} else if ((subClass & 0xff00) == 0x00) { // uhci
uhciCount++;
}
}
}
}
printf("There are %d ohci device(s).\n", ohciCount);
printf("There are %d ehci device(s).\n", ehciCount);
printf("There are %d uhci device(s).\n", uhciCount);
}
程序非常简单,所有概念在以前的博文中均有过介绍,其中的子程序大多是以前程序范例中使用过的,所以在这里就不做更多的解释了,程序中,我们仅仅列出了设备的数量,但很显然,用这种方法,我们可以从配置空间里读出基地址等信息,这些在以后的文章中会用到。