/* * QEMU Bluetooth HCI USB Transport Layer v1.0 * * Copyright (C) 2007 OpenMoko, Inc. * Copyright (C) 2008 Andrzej Zaborowski <balrog@zabor.org> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 or * (at your option) version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see <http://www.gnu.org/licenses/>. */#include"qemu-common.h"#include"usb.h"#include"usb-desc.h"#include"net.h"#include"bt.h"structUSBBtState{USBDevicedev;structHCIInfo*hci;intconfig;#define CFIFO_LEN_MASK 255#define DFIFO_LEN_MASK 4095structusb_hci_in_fifo_s{uint8_tdata[(DFIFO_LEN_MASK+1)*2];struct{uint8_t*data;intlen;}fifo[CFIFO_LEN_MASK+1];intdstart,dlen,dsize,start,len;}evt,acl,sco;structusb_hci_out_fifo_s{uint8_tdata[4096];intlen;}outcmd,outacl,outsco;};#define USB_EVT_EP 1#define USB_ACL_EP 2#define USB_SCO_EP 3enum{STR_MANUFACTURER=1,STR_SERIALNUMBER,};staticconstUSBDescStringsdesc_strings={[STR_MANUFACTURER]="QEMU "QEMU_VERSION,[STR_SERIALNUMBER]="1",};staticconstUSBDescIfacedesc_iface_bluetooth[]={{.bInterfaceNumber=0,.bNumEndpoints=3,.bInterfaceClass=0xe0,/* Wireless */.bInterfaceSubClass=0x01,/* Radio Frequency */.bInterfaceProtocol=0x01,/* Bluetooth */.eps=(USBDescEndpoint[]){{.bEndpointAddress=USB_DIR_IN|USB_EVT_EP,.bmAttributes=USB_ENDPOINT_XFER_INT,.wMaxPacketSize=0x10,.bInterval=0x02,},{.bEndpointAddress=USB_DIR_OUT|USB_ACL_EP,.bmAttributes=USB_ENDPOINT_XFER_BULK,.wMaxPacketSize=0x40,.bInterval=0x0a,},{.bEndpointAddress=USB_DIR_IN|USB_ACL_EP,.bmAttributes=USB_ENDPOINT_XFER_BULK,.wMaxPacketSize=0x40,.bInterval=0x0a,},},},{.bInterfaceNumber=1,.bAlternateSetting=0,.bNumEndpoints=2,.bInterfaceClass=0xe0,/* Wireless */.bInterfaceSubClass=0x01,/* Radio Frequency */.bInterfaceProtocol=0x01,/* Bluetooth */.eps=(USBDescEndpoint[]){{.bEndpointAddress=USB_DIR_OUT|USB_SCO_EP,.bmAttributes=USB_ENDPOINT_XFER_ISOC,.wMaxPacketSize=0,.bInterval=0x01,},{.bEndpointAddress=USB_DIR_IN|USB_SCO_EP,.bmAttributes=USB_ENDPOINT_XFER_ISOC,.wMaxPacketSize=0,.bInterval=0x01,},},},{.bInterfaceNumber=1,.bAlternateSetting=1,.bNumEndpoints=2,.bInterfaceClass=0xe0,/* Wireless */.bInterfaceSubClass=0x01,/* Radio Frequency */.bInterfaceProtocol=0x01,/* Bluetooth */.eps=(USBDescEndpoint[]){{.bEndpointAddress=USB_DIR_OUT|USB_SCO_EP,.bmAttributes=USB_ENDPOINT_XFER_ISOC,.wMaxPacketSize=0x09,.bInterval=0x01,},{.bEndpointAddress=USB_DIR_IN|USB_SCO_EP,.bmAttributes=USB_ENDPOINT_XFER_ISOC,.wMaxPacketSize=0x09,.bInterval=0x01,},},},{.bInterfaceNumber=1,.bAlternateSetting=2,.bNumEndpoints=2,.bInterfaceClass=0xe0,/* Wireless */.bInterfaceSubClass=0x01,/* Radio Frequency */.bInterfaceProtocol=0x01,/* Bluetooth */.eps=(USBDescEndpoint[]){{.bEndpointAddress=USB_DIR_OUT|USB_SCO_EP,.bmAttributes=USB_ENDPOINT_XFER_ISOC,.wMaxPacketSize=0x11,.bInterval=0x01,},{.bEndpointAddress=USB_DIR_IN|USB_SCO_EP,.bmAttributes=USB_ENDPOINT_XFER_ISOC,.wMaxPacketSize=0x11,.bInterval=0x01,},},},{.bInterfaceNumber=1,.bAlternateSetting=3,.bNumEndpoints=2,.bInterfaceClass=0xe0,/* Wireless */.bInterfaceSubClass=0x01,/* Radio Frequency */.bInterfaceProtocol=0x01,/* Bluetooth */.eps=(USBDescEndpoint[]){{.bEndpointAddress=USB_DIR_OUT|USB_SCO_EP,.bmAttributes=USB_ENDPOINT_XFER_ISOC,.wMaxPacketSize=0x19,.bInterval=0x01,},{.bEndpointAddress=USB_DIR_IN|USB_SCO_EP,.bmAttributes=USB_ENDPOINT_XFER_ISOC,.wMaxPacketSize=0x19,.bInterval=0x01,},},},{.bInterfaceNumber=1,.bAlternateSetting=4,.bNumEndpoints=2,.bInterfaceClass=0xe0,/* Wireless */.bInterfaceSubClass=0x01,/* Radio Frequency */.bInterfaceProtocol=0x01,/* Bluetooth */.eps=(USBDescEndpoint[]){{.bEndpointAddress=USB_DIR_OUT|USB_SCO_EP,.bmAttributes=USB_ENDPOINT_XFER_ISOC,.wMaxPacketSize=0x21,.bInterval=0x01,},{.bEndpointAddress=USB_DIR_IN|USB_SCO_EP,.bmAttributes=USB_ENDPOINT_XFER_ISOC,.wMaxPacketSize=0x21,.bInterval=0x01,},},},{.bInterfaceNumber=1,.bAlternateSetting=5,.bNumEndpoints=2,.bInterfaceClass=0xe0,/* Wireless */.bInterfaceSubClass=0x01,/* Radio Frequency */.bInterfaceProtocol=0x01,/* Bluetooth */.eps=(USBDescEndpoint[]){{.bEndpointAddress=USB_DIR_OUT|USB_SCO_EP,.bmAttributes=USB_ENDPOINT_XFER_ISOC,.wMaxPacketSize=0x31,.bInterval=0x01,},{.bEndpointAddress=USB_DIR_IN|USB_SCO_EP,.bmAttributes=USB_ENDPOINT_XFER_ISOC,.wMaxPacketSize=0x31,.bInterval=0x01,},},}};staticconstUSBDescDevicedesc_device_bluetooth={.bcdUSB=0x0110,.bDeviceClass=0xe0,/* Wireless */.bDeviceSubClass=0x01,/* Radio Frequency */.bDeviceProtocol=0x01,/* Bluetooth */.bMaxPacketSize0=64,.bNumConfigurations=1,.confs=(USBDescConfig[]){{.bNumInterfaces=2,.bConfigurationValue=1,.bmAttributes=0xc0,.bMaxPower=0,.nif=ARRAY_SIZE(desc_iface_bluetooth),.ifs=desc_iface_bluetooth,},},};staticconstUSBDescdesc_bluetooth={.id={.idVendor=0x0a12,.idProduct=0x0001,.bcdDevice=0x1958,.iManufacturer=STR_MANUFACTURER,.iProduct=0,.iSerialNumber=STR_SERIALNUMBER,},.full=&desc_device_bluetooth,.str=desc_strings,};staticvoidusb_bt_fifo_reset(structusb_hci_in_fifo_s*fifo){fifo->dstart=0;fifo->dlen=0;fifo->dsize=DFIFO_LEN_MASK+1;fifo->start=0;fifo->len=0;}staticvoidusb_bt_fifo_enqueue(structusb_hci_in_fifo_s*fifo,constuint8_t*data,intlen){intoff=fifo->dstart+fifo->dlen;uint8_t*buf;fifo->dlen+=len;if(off<=DFIFO_LEN_MASK){if(off+len>DFIFO_LEN_MASK+1&&(fifo->dsize=off+len)>(DFIFO_LEN_MASK+1)*2){fprintf(stderr,"%s: can't alloc %i bytes\n",__FUNCTION__,len);exit(-1);}buf=fifo->data+off;}else{if(fifo->dlen>fifo->dsize){fprintf(stderr,"%s: can't alloc %i bytes\n",__FUNCTION__,len);exit(-1);}buf=fifo->data+off-fifo->dsize;}off=(fifo->start+fifo->len++)&CFIFO_LEN_MASK;fifo->fifo[off].data=memcpy(buf,data,len);fifo->fifo[off].len=len;}staticinlineintusb_bt_fifo_dequeue(structusb_hci_in_fifo_s*fifo,USBPacket*p){intlen;if(likely(!fifo->len))returnUSB_RET_STALL;len=MIN(p->iov.size,fifo->fifo[fifo->start].len);usb_packet_copy(p,fifo->fifo[fifo->start].data,len);if(len==p->iov.size){fifo->fifo[fifo->start].len-=len;fifo->fifo[fifo->start].data+=len;}else{fifo->start++;fifo->start&=CFIFO_LEN_MASK;fifo->len--;}fifo->dstart+=len;fifo->dlen-=len;if(fifo->dstart>=fifo->dsize){fifo->dstart=0;fifo->dsize=DFIFO_LEN_MASK+1;}returnlen;}staticinlinevoidusb_bt_fifo_out_enqueue(structUSBBtState*s,structusb_hci_out_fifo_s*fifo,void(*send)(structHCIInfo*,constuint8_t*,int),int(*complete)(constuint8_t*,int),USBPacket*p){usb_packet_copy(p,fifo->data+fifo->len,p->iov.size);fifo->len+=p->iov.size;if(complete(fifo->data,fifo->len)){send(s->hci,fifo->data,fifo->len);fifo->len=0;}/* TODO: do we need to loop? */}staticintusb_bt_hci_cmd_complete(constuint8_t*data,intlen){len-=HCI_COMMAND_HDR_SIZE;returnlen>=0&&len>=((structhci_command_hdr*)data)->plen;}staticintusb_bt_hci_acl_complete(constuint8_t*data,intlen){len-=HCI_ACL_HDR_SIZE;returnlen>=0&&len>=le16_to_cpu(((structhci_acl_hdr*)data)->dlen);}staticintusb_bt_hci_sco_complete(constuint8_t*data,intlen){len-=HCI_SCO_HDR_SIZE;returnlen>=0&&len>=((structhci_sco_hdr*)data)->dlen;}staticvoidusb_bt_handle_reset(USBDevice*dev){structUSBBtState*s=(structUSBBtState*)dev->opaque;usb_bt_fifo_reset(&s->evt);usb_bt_fifo_reset(&s->acl);usb_bt_fifo_reset(&s->sco);s->outcmd.len=0;s->outacl.len=0;s->outsco.len=0;}staticintusb_bt_handle_control(USBDevice*dev,USBPacket*p,intrequest,intvalue,intindex,intlength,uint8_t*data){structUSBBtState*s=(structUSBBtState*)dev->opaque;intret;ret=usb_desc_handle_control(dev,p,request,value,index,length,data);if(ret>=0){switch(request){caseDeviceRequest|USB_REQ_GET_CONFIGURATION:s->config=0;break;caseDeviceOutRequest|USB_REQ_SET_CONFIGURATION:s->config=1;usb_bt_fifo_reset(&s->evt);usb_bt_fifo_reset(&s->acl);usb_bt_fifo_reset(&s->sco);break;}returnret;}ret=0;switch(request){caseInterfaceRequest|USB_REQ_GET_STATUS:caseEndpointRequest|USB_REQ_GET_STATUS:data[0]=0x00;data[1]=0x00;ret=2;break;caseInterfaceOutRequest|USB_REQ_CLEAR_FEATURE:caseEndpointOutRequest|USB_REQ_CLEAR_FEATURE:gotofail;caseInterfaceOutRequest|USB_REQ_SET_FEATURE:caseEndpointOutRequest|USB_REQ_SET_FEATURE:gotofail;break;case((USB_DIR_OUT|USB_TYPE_CLASS|USB_RECIP_DEVICE)<<8):if(s->config)usb_bt_fifo_out_enqueue(s,&s->outcmd,s->hci->cmd_send,usb_bt_hci_cmd_complete,p);break;default:fail:ret=USB_RET_STALL;break;}returnret;}staticintusb_bt_handle_data(USBDevice*dev,USBPacket*p){structUSBBtState*s=(structUSBBtState*)dev->opaque;intret=0;if(!s->config)gotofail;switch(p->pid){caseUSB_TOKEN_IN:switch(p->ep->nr){caseUSB_EVT_EP:ret=usb_bt_fifo_dequeue(&s->evt,p);break;caseUSB_ACL_EP:ret=usb_bt_fifo_dequeue(&s->acl,p);break;caseUSB_SCO_EP:ret=usb_bt_fifo_dequeue(&s->sco,p);break;default:gotofail;}break;caseUSB_TOKEN_OUT:switch(p->ep->nr){caseUSB_ACL_EP:usb_bt_fifo_out_enqueue(s,&s->outacl,s->hci->acl_send,usb_bt_hci_acl_complete,p);break;caseUSB_SCO_EP:usb_bt_fifo_out_enqueue(s,&s->outsco,s->hci->sco_send,usb_bt_hci_sco_complete,p);break;default:gotofail;}break;default:fail:ret=USB_RET_STALL;break;}returnret;}staticvoidusb_bt_out_hci_packet_event(void*opaque,constuint8_t*data,intlen){structUSBBtState*s=(structUSBBtState*)opaque;usb_bt_fifo_enqueue(&s->evt,data,len);}staticvoidusb_bt_out_hci_packet_acl(void*opaque,constuint8_t*data,intlen){structUSBBtState*s=(structUSBBtState*)opaque;usb_bt_fifo_enqueue(&s->acl,data,len);}staticvoidusb_bt_handle_destroy(USBDevice*dev){structUSBBtState*s=(structUSBBtState*)dev->opaque;s->hci->opaque=NULL;s->hci->evt_recv=NULL;s->hci->acl_recv=NULL;}staticintusb_bt_initfn(USBDevice*dev){usb_desc_init(dev);return0;}USBDevice*usb_bt_init(HCIInfo*hci){USBDevice*dev;structUSBBtState*s;if(!hci)returnNULL;dev=usb_create_simple(NULL/* FIXME */,"usb-bt-dongle");if(!dev){returnNULL;}s=DO_UPCAST(structUSBBtState,dev,dev);s->dev.opaque=s;s->hci=hci;s->hci->opaque=s;s->hci->evt_recv=usb_bt_out_hci_packet_event;s->hci->acl_recv=usb_bt_out_hci_packet_acl;usb_bt_handle_reset(&s->dev);returndev;}staticconstVMStateDescriptionvmstate_usb_bt={.name="usb-bt",.unmigratable=1,};staticvoidusb_bt_class_initfn(ObjectClass*klass,void*data){DeviceClass*dc=DEVICE_CLASS(klass);USBDeviceClass*uc=USB_DEVICE_CLASS(klass);uc->init=usb_bt_initfn;uc->product_desc="QEMU BT dongle";uc->usb_desc=&desc_bluetooth;uc->handle_reset=usb_bt_handle_reset;uc->handle_control=usb_bt_handle_control;uc->handle_data=usb_bt_handle_data;uc->handle_destroy=usb_bt_handle_destroy;dc->vmsd=&vmstate_usb_bt;}staticTypeInfobt_info={.name="usb-bt-dongle",.parent=TYPE_USB_DEVICE,.instance_size=sizeof(structUSBBtState),.class_init=usb_bt_class_initfn,};staticvoidusb_bt_register_types(void){type_register_static(&bt_info);}type_init(usb_bt_register_types)