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