本文 接着前面的文章 《Android下USB Accessory的实现分析 (二)— 底层驱动设计实现》
为了支持USB Accessory,让USB主从设备双方能够互相识别和兼容对方,Google定义了一套Android OpenAccessory Protocol(简称AOA),
此协议目前有两个版本:Version 1.0和Version2.0。
2.0版本是对对1.0版本的补充,增加了对Audio和HID类Accessory设备的支持。具体协议内容,
可参见如下链接:
http://source.android.com/accessories/aoa.html
http://source.android.com/accessories/aoa2.html
协议内容其实比较简单,主要是定义了一套USB控制传输命令,Accessory设备可以发起这些控制传输命令来获取和设置对应的Accessory功能。
另外,在Accessory模式下,USB Device端上报的VID和PID是Google指定的,V
ID固定为Google的官方VID – 0x18D1,PID则在不同的模式下定义如下:
● 0x2D00 - accessory
● 0x2D01 - accessory + adb
● 0x2D02 - audio
● 0x2D03 - audio + adb
● 0x2D04 - accessory + audio
● 0x2D05 - accessory + audio + adb
USB Accessory设备和Android设备两者双方整个枚举识别工作过程如下:
(D从 -> H主 请求HOST枚举)
USB Accessory设备发起USB控制传输进行正常的USB设备枚举,获取设备描述符和配置描述符等信息,
此时大部分Android设备上报的还只是普通的U盘或MTP设备;
(D从 -> H主 请求支持的AOA协议)
接下来USB Accessory设备发起Vendor类型,request值为51(0x33)的控制传输命令(ACCESSORY_GET_PROTOCOL),
看看该Android设备是否支持USB Accessory功能,如果支持的话会返回所支持的AOA协议版本;
(D从 -> H主 上报设备信息)
USB Accessory判断到该Android设备支持Accessory功能后,
发起request值为52(0x34)的控制传输命令(ACCESSORY_SEND_STRING),
并把该Accessory设备的相关信息(包括厂家,序列号等)告知Android设备;
(D从 -> H主 请求进入Audio模式)
如果是USB Audio Accessory设备,还会发起request值为58(0x3A)的控制传输命令(SET_AUDIO_MODE命令),
通知Android设备进入到Audio Accessory模式;
(D从 -> H主 请求AOA开始工作)
最终,USB Accessory设备发起request值为53(0x35)的控制传输命令(ACCESSORY_START),
通知Android设备切换到Accessory功能模式开始工作。
接下来Android设备收到此信息后,会先把sys.usb.config设置为包含accessory功能
(H主 根据init.usb.rc的配置USB)
剩下的事情就是如前小节所述,按init.usb.rc的设置进行了,
此时Android设备先断开与Accessory设备连接,/sys/class/android_usb/android0/functions节点写入accessory字符串,
然后重新连接,使能Accessory接口,正式工作在USB Accessory模式;
下图是USB协议分析仪抓取的协议数据:
AOA 2.0协议中还有部分和HID设备有关的控制传输命令,具体可参见前面的Google官方链接,这里不再详叙。
f_accessory.c和f_audio_source.c文件为实现USB Accessory和USB Audio Accessory功能所对应的驱动代码。
我们先看f_accessory.c文件的处理内容。
f_accessory.c文件内所实现的功能主要有如下:
// /kernel/msm-3.18/drivers/usb/gadget/android.c
static int accessory_function_init(struct android_usb_function *f,
struct usb_composite_dev *cdev)
{
return acc_setup();
}
static void accessory_function_cleanup(struct android_usb_function *f)
{
acc_cleanup();
}
static int accessory_function_bind_config(struct android_usb_function *f,
struct usb_configuration *c)
{
return acc_bind_config(c);
}
static int accessory_function_ctrlrequest(struct android_usb_function *f,
struct usb_composite_dev *cdev,
const struct usb_ctrlrequest *c)
{
return acc_ctrlrequest(cdev, c);
}
static struct android_usb_function accessory_function = {
.name = "accessory",
.init = accessory_function_init,
.cleanup = accessory_function_cleanup,
.bind_config = accessory_function_bind_config,
.ctrlrequest = accessory_function_ctrlrequest,
};
// /kernel/msm-3.18/drivers/usb/gadget/function/f_accessory.c
static struct usb_function *acc_alloc(struct usb_function_instance *fi)
{
struct acc_dev *dev = _acc_dev;
pr_info("acc_alloc\n");
dev->function.name = "accessory";
dev->function.strings = acc_strings,
dev->function.fs_descriptors = fs_acc_descs;
dev->function.hs_descriptors = hs_acc_descs;
dev->function.bind = acc_function_bind_configfs;
dev->function.unbind = acc_function_unbind;
dev->function.set_alt = acc_function_set_alt;
dev->function.disable = acc_function_disable;
dev->function.free_func = acc_free;
dev->function.setup = acc_ctrlrequest_configfs;
return &dev->function;
}
DECLARE_USB_FUNCTION_INIT(accessory, acc_alloc_inst, acc_alloc);
MODULE_LICENSE("GPL");
int acc_ctrlrequest(struct usb_composite_dev *cdev,
const struct usb_ctrlrequest *ctrl)
{
struct acc_dev *dev = _acc_dev;
int value = -EOPNOTSUPP;
struct acc_hid_dev *hid;
int offset;
u8 b_requestType = ctrl->bRequestType;
u8 b_request = ctrl->bRequest;
u16 w_index = le16_to_cpu(ctrl->wIndex);
u16 w_value = le16_to_cpu(ctrl->wValue);
u16 w_length = le16_to_cpu(ctrl->wLength);
unsigned long flags;
/*
printk(KERN_INFO "acc_ctrlrequest "
"%02x.%02x v%04x i%04x l%u\n",
b_requestType, b_request,
w_value, w_index, w_length);
*/
if (b_requestType == (USB_DIR_OUT | USB_TYPE_VENDOR)) {
if (b_request == ACCESSORY_START) {
dev->start_requested = 1;
schedule_delayed_work( &dev->start_work, msecs_to_jiffies(10));
value = 0;
} else if (b_request == ACCESSORY_SEND_STRING) {
dev->string_index = w_index;
cdev->gadget->ep0->driver_data = dev;
cdev->req->complete = acc_complete_set_string;
value = w_length;
} else if (b_request == ACCESSORY_SET_AUDIO_MODE && w_index == 0 && w_length == 0) {
dev->audio_mode = w_value;
value = 0;
} else if (b_request == ACCESSORY_REGISTER_HID) {
value = acc_register_hid(dev, w_value, w_index);
} else if (b_request == ACCESSORY_UNREGISTER_HID) {
value = acc_unregister_hid(dev, w_value);
} else if (b_request == ACCESSORY_SET_HID_REPORT_DESC) {
spin_lock_irqsave(&dev->lock, flags);
hid = acc_hid_get(&dev->new_hid_list, w_value);
spin_unlock_irqrestore(&dev->lock, flags);
offset = w_index;
if (offset != hid->report_desc_offset || offset + w_length > hid->report_desc_len) {
value = -EINVAL;
goto err;
}
cdev->req->context = hid;
cdev->req->complete = acc_complete_set_hid_report_desc;
value = w_length;
} else if (b_request == ACCESSORY_SEND_HID_EVENT) {
spin_lock_irqsave(&dev->lock, flags);
hid = acc_hid_get(&dev->hid_list, w_value);
spin_unlock_irqrestore(&dev->lock, flags);
cdev->req->context = hid;
cdev->req->complete = acc_complete_send_hid_event;
value = w_length;
}
} else if (b_requestType == (USB_DIR_IN | USB_TYPE_VENDOR)) {
if (b_request == ACCESSORY_GET_PROTOCOL) {
*((u16 *)cdev->req->buf) = PROTOCOL_VERSION;
value = sizeof(u16);
/* clear any string left over from a previous session */
memset(dev->manufacturer, 0, sizeof(dev->manufacturer));
memset(dev->model, 0, sizeof(dev->model));
memset(dev->description, 0, sizeof(dev->description));
memset(dev->version, 0, sizeof(dev->version));
memset(dev->uri, 0, sizeof(dev->uri));
memset(dev->serial, 0, sizeof(dev->serial));
dev->start_requested = 0;
dev->audio_mode = 0;
strlcpy(dev->manufacturer, "Android", ACC_STRING_SIZE);
strlcpy(dev->model, "Android", ACC_STRING_SIZE);
}
}
if (value >= 0) {
cdev->req->zero = 0;
cdev->req->length = value;
value = usb_ep_queue(cdev->gadget->ep0, cdev->req, GFP_ATOMIC);
if (value < 0)
ERROR(cdev, "%s setup response queue error\n",
__func__);
}
err:
if (value == -EOPNOTSUPP)
VDBG(cdev,"unknown class-specific control req "
"%02x.%02x v%04x i%04x l%u\n",
ctrl->bRequestType, ctrl->bRequest, w_value, w_index, w_length);
return value;
}
EXPORT_SYMBOL_GPL(acc_ctrlrequest);
以“ACCESSORY_START”命令为例,
收到该命令后驱动会调用schedule_delayed_work(&dev->start_work,msecs_to_jiffies(10))来延时10毫秒后发出"ACCESSORY=START"的UEVENT消息,dev->start_work对应的处理函数如下:
static void acc_start_work(struct work_struct *data)
{
char *envp[2] = { "ACCESSORY=START", NULL };
kobject_uevent_env(&acc_device.this_device->kobj, KOBJ_CHANGE, envp);
}
/* file operations for /dev/usb_accessory */
static const struct file_operations acc_fops = {
.owner = THIS_MODULE,
.read = acc_read,
.write = acc_write,
.unlocked_ioctl = acc_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = acc_ioctl,
#endif
.open = acc_open,
.release = acc_release,
};
static struct hid_driver acc_hid_driver = {
.name = "USB accessory",
.id_table = acc_hid_table,
.probe = acc_hid_probe,
};
“/dev/usb_accessory”向应用层提供了read、write和ioctl接口,通过这些接口,应用层可以获取Accessory设备信息,实现Android设备与Accessory设备的数据交互工作。
static const struct hid_device_id acc_hid_table[] = {
{ HID_USB_DEVICE(HID_ANY_ID, HID_ANY_ID) },
{ }
};
static struct hid_driver acc_hid_driver = {
.name = "USB accessory",
.id_table = acc_hid_table,
.probe = acc_hid_probe,
};
并在收到控制传输命令ACCESSORY_REGISTER_HID和ACCESSORY_UNREGISTER_HID时,
调用acc_register_hid和acc_unregister_hid进行相应处理。
以上是f_accessory.c文件主要的处理内容。
f_audio_source.c文件则针对Audio Accessory设备来进行处理,实现了音频相关接口函数,
并调用snd_card_register(card)函数向内核注册声卡设备。
其中snd_pcm_ops结构体定义如下:
static int snd_card_setup(struct usb_configuration *c,
struct audio_source_config *config)
{
err = snd_card_new(&c->cdev->gadget->dev,
SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,THIS_MODULE, 0, &card);
err = snd_pcm_new(card, "USB audio source", 0, 1, 0, &pcm);
strlcpy(pcm->name, "USB gadget audio", sizeof(pcm->name));
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &audio_playback_ops);
}
static struct snd_pcm_ops audio_playback_ops = {
.open = audio_pcm_open,
.close = audio_pcm_close,
.ioctl = snd_pcm_lib_ioctl,
.hw_params = audio_pcm_hw_params,
.hw_free = audio_pcm_hw_free,
.prepare = audio_pcm_prepare,
.trigger = audio_pcm_playback_trigger,
.pointer = audio_pcm_pointer,
.mmap = audio_pcm_mmap,
};
f_audio_source.c文件主要涉及音频处理相关代码,这里不再深入研究。
接下来,我们再看在连接到USB Accessory设备时Android上层的整个工作流程。
本文参考:
Android USB Accessory分析
Android USB通讯(完整版)