This commit was generated by cvs2svn to compensate for changes in r925,
[linux-2.6.git] / drivers / xen / usbback / interface.c
1 /******************************************************************************
2  * arch/xen/drivers/usbif/backend/interface.c
3  * 
4  * USB device interface management.
5  * 
6  * by Mark Williamson, Copyright (c) 2004
7  */
8
9
10 /******************************************************************************
11  * arch/xen/drivers/blkif/backend/interface.c
12  * 
13  * Block-device interface management.
14  * 
15  * Copyright (c) 2004, Keir Fraser
16  */
17
18 #include "common.h"
19
20 #define USBIF_HASHSZ 1024
21 #define USBIF_HASH(_d) (((int)(_d))&(USBIF_HASHSZ-1))
22
23 static kmem_cache_t      *usbif_priv_cachep;
24 static usbif_priv_t      *usbif_priv_hash[USBIF_HASHSZ];
25
26 usbif_priv_t *usbif_find(domid_t domid)
27 {
28     usbif_priv_t *up = usbif_priv_hash[USBIF_HASH(domid)];
29     while ( (up != NULL ) && ( up->domid != domid ) )
30         up = up->hash_next;
31     return up;
32 }
33
34 static void __usbif_disconnect_complete(void *arg)
35 {
36     usbif_priv_t         *usbif = (usbif_priv_t *)arg;
37     ctrl_msg_t            cmsg;
38     usbif_be_disconnect_t disc;
39
40     /*
41      * These can't be done in usbif_disconnect() because at that point there
42      * may be outstanding requests at the device whose asynchronous responses
43      * must still be notified to the remote driver.
44      */
45     unbind_evtchn_from_irq(usbif->evtchn);
46     vfree(usbif->usb_ring.sring);
47
48     /* Construct the deferred response message. */
49     cmsg.type         = CMSG_USBIF_BE;
50     cmsg.subtype      = CMSG_USBIF_BE_DISCONNECT;
51     cmsg.id           = usbif->disconnect_rspid;
52     cmsg.length       = sizeof(usbif_be_disconnect_t);
53     disc.domid        = usbif->domid;
54     disc.status       = USBIF_BE_STATUS_OKAY;
55     memcpy(cmsg.msg, &disc, sizeof(disc));
56
57     /*
58      * Make sure message is constructed /before/ status change, because
59      * after the status change the 'usbif' structure could be deallocated at
60      * any time. Also make sure we send the response /after/ status change,
61      * as otherwise a subsequent CONNECT request could spuriously fail if
62      * another CPU doesn't see the status change yet.
63      */
64     mb();
65     if ( usbif->status != DISCONNECTING )
66         BUG();
67     usbif->status = DISCONNECTED;
68     mb();
69
70     /* Send the successful response. */
71     ctrl_if_send_response(&cmsg);
72 }
73
74 void usbif_disconnect_complete(usbif_priv_t *up)
75 {
76     INIT_WORK(&up->work, __usbif_disconnect_complete, (void *)up);
77     schedule_work(&up->work);
78 }
79
80 void usbif_create(usbif_be_create_t *create)
81 {
82     domid_t       domid  = create->domid;
83     usbif_priv_t **pup, *up;
84
85     if ( (up = kmem_cache_alloc(usbif_priv_cachep, GFP_KERNEL)) == NULL )
86     {
87         DPRINTK("Could not create usbif: out of memory\n");
88         create->status = USBIF_BE_STATUS_OUT_OF_MEMORY;
89         return;
90     }
91
92     memset(up, 0, sizeof(*up));
93     up->domid  = domid;
94     up->status = DISCONNECTED;
95     spin_lock_init(&up->usb_ring_lock);
96     atomic_set(&up->refcnt, 0);
97
98     pup = &usbif_priv_hash[USBIF_HASH(domid)];
99     while ( *pup != NULL )
100     {
101         if ( (*pup)->domid == domid )
102         {
103             create->status = USBIF_BE_STATUS_INTERFACE_EXISTS;
104             kmem_cache_free(usbif_priv_cachep, up);
105             return;
106         }
107         pup = &(*pup)->hash_next;
108     }
109
110     up->hash_next = *pup;
111     *pup = up;
112
113     create->status = USBIF_BE_STATUS_OKAY;
114 }
115
116 void usbif_destroy(usbif_be_destroy_t *destroy)
117 {
118     domid_t       domid  = destroy->domid;
119     usbif_priv_t  **pup, *up;
120
121     pup = &usbif_priv_hash[USBIF_HASH(domid)];
122     while ( (up = *pup) != NULL )
123     {
124         if ( up->domid == domid )
125         {
126             if ( up->status != DISCONNECTED )
127                 goto still_connected;
128             goto destroy;
129         }
130         pup = &up->hash_next;
131     }
132
133     destroy->status = USBIF_BE_STATUS_INTERFACE_NOT_FOUND;
134     return;
135
136  still_connected:
137     destroy->status = USBIF_BE_STATUS_INTERFACE_CONNECTED;
138     return;
139
140  destroy:
141     *pup = up->hash_next;
142     usbif_release_ports(up);
143     kmem_cache_free(usbif_priv_cachep, up);
144     destroy->status = USBIF_BE_STATUS_OKAY;
145 }
146
147 void usbif_connect(usbif_be_connect_t *connect)
148 {
149     domid_t       domid  = connect->domid;
150     unsigned int  evtchn = connect->evtchn;
151     unsigned long shmem_frame = connect->shmem_frame;
152     struct vm_struct *vma;
153     pgprot_t      prot;
154     int           error;
155     usbif_priv_t *up;
156     usbif_sring_t *sring;
157
158     up = usbif_find(domid);
159     if ( unlikely(up == NULL) )
160     {
161         DPRINTK("usbif_connect attempted for non-existent usbif (%u)\n", 
162                 connect->domid); 
163         connect->status = USBIF_BE_STATUS_INTERFACE_NOT_FOUND;
164         return;
165     }
166
167     if ( (vma = get_vm_area(PAGE_SIZE, VM_IOREMAP)) == NULL )
168     {
169         connect->status = USBIF_BE_STATUS_OUT_OF_MEMORY;
170         return;
171     }
172
173     prot = __pgprot(_PAGE_PRESENT | _PAGE_RW | _PAGE_DIRTY | _PAGE_ACCESSED);
174     error = direct_remap_area_pages(&init_mm, VMALLOC_VMADDR(vma->addr),
175                                     shmem_frame<<PAGE_SHIFT, PAGE_SIZE,
176                                     prot, domid);
177     if ( error != 0 )
178     {
179         if ( error == -ENOMEM )
180             connect->status = USBIF_BE_STATUS_OUT_OF_MEMORY;
181         else if ( error == -EFAULT )
182             connect->status = USBIF_BE_STATUS_MAPPING_ERROR;
183         else
184             connect->status = USBIF_BE_STATUS_ERROR;
185         vfree(vma->addr);
186         return;
187     }
188
189     if ( up->status != DISCONNECTED )
190     {
191         connect->status = USBIF_BE_STATUS_INTERFACE_CONNECTED;
192         vfree(vma->addr);
193         return;
194     }
195
196     sring = (usbif_sring_t *)vma->addr;
197     SHARED_RING_INIT(sring);
198     BACK_RING_INIT(&up->usb_ring, sring, PAGE_SIZE);
199
200     up->evtchn        = evtchn;
201     up->irq           = bind_evtchn_to_irq(evtchn);
202     up->shmem_frame   = shmem_frame;
203     up->status        = CONNECTED;
204     usbif_get(up);
205
206     request_irq(up->irq, usbif_be_int, 0, "usbif-backend", up);
207
208     connect->status = USBIF_BE_STATUS_OKAY;
209 }
210
211 /* Remove URBs for this interface before destroying it. */
212 void usbif_deschedule(usbif_priv_t *up)
213 {
214     remove_from_usbif_list(up);
215 }
216
217 int usbif_disconnect(usbif_be_disconnect_t *disconnect, u8 rsp_id)
218 {
219     domid_t       domid  = disconnect->domid;
220     usbif_priv_t *up;
221
222     up = usbif_find(domid);
223     if ( unlikely(up == NULL) )
224     {
225         DPRINTK("usbif_disconnect attempted for non-existent usbif"
226                 " (%u)\n", disconnect->domid); 
227         disconnect->status = USBIF_BE_STATUS_INTERFACE_NOT_FOUND;
228         return 1; /* Caller will send response error message. */
229     }
230
231     if ( up->status == CONNECTED )
232     {
233         up->status = DISCONNECTING;
234         up->disconnect_rspid = rsp_id;
235         wmb(); /* Let other CPUs see the status change. */
236         free_irq(up->irq, up);
237         usbif_deschedule(up);
238         usbif_put(up);
239         return 0; /* Caller should not send response message. */
240     }
241
242     disconnect->status = USBIF_BE_STATUS_OKAY;
243     return 1;
244 }
245
246 void __init usbif_interface_init(void)
247 {
248     usbif_priv_cachep = kmem_cache_create("usbif_priv_cache",
249                                           sizeof(usbif_priv_t), 
250                                           0, 0, NULL, NULL);
251     memset(usbif_priv_hash, 0, sizeof(usbif_priv_hash));
252 }