1 | /* |
---|
2 | * PCI Frontend Xenbus Setup - handles setup with backend (imports page/evtchn) |
---|
3 | * |
---|
4 | * Author: Ryan Wilson <hap9@epoch.ncsc.mil> |
---|
5 | */ |
---|
6 | #include <linux/module.h> |
---|
7 | #include <linux/init.h> |
---|
8 | #include <linux/mm.h> |
---|
9 | #include <xen/xenbus.h> |
---|
10 | #include <xen/gnttab.h> |
---|
11 | #include "pcifront.h" |
---|
12 | |
---|
13 | #define INVALID_GRANT_REF (0) |
---|
14 | #define INVALID_EVTCHN (-1) |
---|
15 | |
---|
16 | static struct pcifront_device *alloc_pdev(struct xenbus_device *xdev) |
---|
17 | { |
---|
18 | struct pcifront_device *pdev; |
---|
19 | |
---|
20 | pdev = kmalloc(sizeof(struct pcifront_device), GFP_KERNEL); |
---|
21 | if (pdev == NULL) |
---|
22 | goto out; |
---|
23 | |
---|
24 | pdev->sh_info = |
---|
25 | (struct xen_pci_sharedinfo *)__get_free_page(GFP_KERNEL); |
---|
26 | if (pdev->sh_info == NULL) { |
---|
27 | kfree(pdev); |
---|
28 | pdev = NULL; |
---|
29 | goto out; |
---|
30 | } |
---|
31 | pdev->sh_info->flags = 0; |
---|
32 | |
---|
33 | xdev->dev.driver_data = pdev; |
---|
34 | pdev->xdev = xdev; |
---|
35 | |
---|
36 | INIT_LIST_HEAD(&pdev->root_buses); |
---|
37 | |
---|
38 | spin_lock_init(&pdev->dev_lock); |
---|
39 | spin_lock_init(&pdev->sh_info_lock); |
---|
40 | |
---|
41 | pdev->evtchn = INVALID_EVTCHN; |
---|
42 | pdev->gnt_ref = INVALID_GRANT_REF; |
---|
43 | |
---|
44 | dev_dbg(&xdev->dev, "Allocated pdev @ 0x%p pdev->sh_info @ 0x%p\n", |
---|
45 | pdev, pdev->sh_info); |
---|
46 | out: |
---|
47 | return pdev; |
---|
48 | } |
---|
49 | |
---|
50 | static void free_pdev(struct pcifront_device *pdev) |
---|
51 | { |
---|
52 | dev_dbg(&pdev->xdev->dev, "freeing pdev @ 0x%p\n", pdev); |
---|
53 | |
---|
54 | pcifront_free_roots(pdev); |
---|
55 | |
---|
56 | if (pdev->evtchn != INVALID_EVTCHN) |
---|
57 | xenbus_free_evtchn(pdev->xdev, pdev->evtchn); |
---|
58 | |
---|
59 | if (pdev->gnt_ref != INVALID_GRANT_REF) |
---|
60 | gnttab_end_foreign_access(pdev->gnt_ref, 0, |
---|
61 | (unsigned long)pdev->sh_info); |
---|
62 | |
---|
63 | pdev->xdev->dev.driver_data = NULL; |
---|
64 | |
---|
65 | kfree(pdev); |
---|
66 | } |
---|
67 | |
---|
68 | static int pcifront_publish_info(struct pcifront_device *pdev) |
---|
69 | { |
---|
70 | int err = 0; |
---|
71 | struct xenbus_transaction trans; |
---|
72 | |
---|
73 | err = xenbus_grant_ring(pdev->xdev, virt_to_mfn(pdev->sh_info)); |
---|
74 | if (err < 0) |
---|
75 | goto out; |
---|
76 | |
---|
77 | pdev->gnt_ref = err; |
---|
78 | |
---|
79 | err = xenbus_alloc_evtchn(pdev->xdev, &pdev->evtchn); |
---|
80 | if (err) |
---|
81 | goto out; |
---|
82 | |
---|
83 | do_publish: |
---|
84 | err = xenbus_transaction_start(&trans); |
---|
85 | if (err) { |
---|
86 | xenbus_dev_fatal(pdev->xdev, err, |
---|
87 | "Error writing configuration for backend " |
---|
88 | "(start transaction)"); |
---|
89 | goto out; |
---|
90 | } |
---|
91 | |
---|
92 | err = xenbus_printf(trans, pdev->xdev->nodename, |
---|
93 | "pci-op-ref", "%u", pdev->gnt_ref); |
---|
94 | if (!err) |
---|
95 | err = xenbus_printf(trans, pdev->xdev->nodename, |
---|
96 | "event-channel", "%u", pdev->evtchn); |
---|
97 | if (!err) |
---|
98 | err = xenbus_printf(trans, pdev->xdev->nodename, |
---|
99 | "magic", XEN_PCI_MAGIC); |
---|
100 | |
---|
101 | if (err) { |
---|
102 | xenbus_transaction_end(trans, 1); |
---|
103 | xenbus_dev_fatal(pdev->xdev, err, |
---|
104 | "Error writing configuration for backend"); |
---|
105 | goto out; |
---|
106 | } else { |
---|
107 | err = xenbus_transaction_end(trans, 0); |
---|
108 | if (err == -EAGAIN) |
---|
109 | goto do_publish; |
---|
110 | else if (err) { |
---|
111 | xenbus_dev_fatal(pdev->xdev, err, |
---|
112 | "Error completing transaction " |
---|
113 | "for backend"); |
---|
114 | goto out; |
---|
115 | } |
---|
116 | } |
---|
117 | |
---|
118 | xenbus_switch_state(pdev->xdev, XenbusStateInitialised); |
---|
119 | |
---|
120 | dev_dbg(&pdev->xdev->dev, "publishing successful!\n"); |
---|
121 | |
---|
122 | out: |
---|
123 | return err; |
---|
124 | } |
---|
125 | |
---|
126 | static int pcifront_try_connect(struct pcifront_device *pdev) |
---|
127 | { |
---|
128 | int err = -EFAULT; |
---|
129 | int i, num_roots, len; |
---|
130 | char str[64]; |
---|
131 | unsigned int domain, bus; |
---|
132 | |
---|
133 | spin_lock(&pdev->dev_lock); |
---|
134 | |
---|
135 | /* Only connect once */ |
---|
136 | if (xenbus_read_driver_state(pdev->xdev->nodename) != |
---|
137 | XenbusStateInitialised) |
---|
138 | goto out; |
---|
139 | |
---|
140 | err = pcifront_connect(pdev); |
---|
141 | if (err) { |
---|
142 | xenbus_dev_fatal(pdev->xdev, err, |
---|
143 | "Error connecting PCI Frontend"); |
---|
144 | goto out; |
---|
145 | } |
---|
146 | |
---|
147 | err = xenbus_scanf(XBT_NIL, pdev->xdev->otherend, |
---|
148 | "root_num", "%d", &num_roots); |
---|
149 | if (err == -ENOENT) { |
---|
150 | xenbus_dev_error(pdev->xdev, err, |
---|
151 | "No PCI Roots found, trying 0000:00"); |
---|
152 | err = pcifront_scan_root(pdev, 0, 0); |
---|
153 | num_roots = 0; |
---|
154 | } else if (err != 1) { |
---|
155 | if (err == 0) |
---|
156 | err = -EINVAL; |
---|
157 | xenbus_dev_fatal(pdev->xdev, err, |
---|
158 | "Error reading number of PCI roots"); |
---|
159 | goto out; |
---|
160 | } |
---|
161 | |
---|
162 | for (i = 0; i < num_roots; i++) { |
---|
163 | len = snprintf(str, sizeof(str), "root-%d", i); |
---|
164 | if (unlikely(len >= (sizeof(str) - 1))) { |
---|
165 | err = -ENOMEM; |
---|
166 | goto out; |
---|
167 | } |
---|
168 | |
---|
169 | err = xenbus_scanf(XBT_NIL, pdev->xdev->otherend, str, |
---|
170 | "%x:%x", &domain, &bus); |
---|
171 | if (err != 2) { |
---|
172 | if (err >= 0) |
---|
173 | err = -EINVAL; |
---|
174 | xenbus_dev_fatal(pdev->xdev, err, |
---|
175 | "Error reading PCI root %d", i); |
---|
176 | goto out; |
---|
177 | } |
---|
178 | |
---|
179 | err = pcifront_scan_root(pdev, domain, bus); |
---|
180 | if (err) { |
---|
181 | xenbus_dev_fatal(pdev->xdev, err, |
---|
182 | "Error scanning PCI root %04x:%02x", |
---|
183 | domain, bus); |
---|
184 | goto out; |
---|
185 | } |
---|
186 | } |
---|
187 | |
---|
188 | err = xenbus_switch_state(pdev->xdev, XenbusStateConnected); |
---|
189 | if (err) |
---|
190 | goto out; |
---|
191 | |
---|
192 | out: |
---|
193 | spin_unlock(&pdev->dev_lock); |
---|
194 | return err; |
---|
195 | } |
---|
196 | |
---|
197 | static int pcifront_try_disconnect(struct pcifront_device *pdev) |
---|
198 | { |
---|
199 | int err = 0; |
---|
200 | enum xenbus_state prev_state; |
---|
201 | |
---|
202 | spin_lock(&pdev->dev_lock); |
---|
203 | |
---|
204 | prev_state = xenbus_read_driver_state(pdev->xdev->nodename); |
---|
205 | |
---|
206 | if (prev_state < XenbusStateClosing) |
---|
207 | err = xenbus_switch_state(pdev->xdev, XenbusStateClosing); |
---|
208 | |
---|
209 | if (!err && prev_state == XenbusStateConnected) |
---|
210 | pcifront_disconnect(pdev); |
---|
211 | |
---|
212 | spin_unlock(&pdev->dev_lock); |
---|
213 | |
---|
214 | return err; |
---|
215 | } |
---|
216 | |
---|
217 | static void pcifront_backend_changed(struct xenbus_device *xdev, |
---|
218 | enum xenbus_state be_state) |
---|
219 | { |
---|
220 | struct pcifront_device *pdev = xdev->dev.driver_data; |
---|
221 | |
---|
222 | switch (be_state) { |
---|
223 | case XenbusStateClosing: |
---|
224 | dev_warn(&xdev->dev, "backend going away!\n"); |
---|
225 | pcifront_try_disconnect(pdev); |
---|
226 | break; |
---|
227 | |
---|
228 | case XenbusStateUnknown: |
---|
229 | case XenbusStateClosed: |
---|
230 | dev_warn(&xdev->dev, "backend went away!\n"); |
---|
231 | pcifront_try_disconnect(pdev); |
---|
232 | |
---|
233 | device_unregister(&pdev->xdev->dev); |
---|
234 | break; |
---|
235 | |
---|
236 | case XenbusStateConnected: |
---|
237 | pcifront_try_connect(pdev); |
---|
238 | break; |
---|
239 | |
---|
240 | default: |
---|
241 | break; |
---|
242 | } |
---|
243 | } |
---|
244 | |
---|
245 | static int pcifront_xenbus_probe(struct xenbus_device *xdev, |
---|
246 | const struct xenbus_device_id *id) |
---|
247 | { |
---|
248 | int err = 0; |
---|
249 | struct pcifront_device *pdev = alloc_pdev(xdev); |
---|
250 | |
---|
251 | if (pdev == NULL) { |
---|
252 | err = -ENOMEM; |
---|
253 | xenbus_dev_fatal(xdev, err, |
---|
254 | "Error allocating pcifront_device struct"); |
---|
255 | goto out; |
---|
256 | } |
---|
257 | |
---|
258 | err = pcifront_publish_info(pdev); |
---|
259 | |
---|
260 | out: |
---|
261 | return err; |
---|
262 | } |
---|
263 | |
---|
264 | static int pcifront_xenbus_remove(struct xenbus_device *xdev) |
---|
265 | { |
---|
266 | if (xdev->dev.driver_data) |
---|
267 | free_pdev(xdev->dev.driver_data); |
---|
268 | |
---|
269 | return 0; |
---|
270 | } |
---|
271 | |
---|
272 | static struct xenbus_device_id xenpci_ids[] = { |
---|
273 | {"pci"}, |
---|
274 | {{0}}, |
---|
275 | }; |
---|
276 | |
---|
277 | static struct xenbus_driver xenbus_pcifront_driver = { |
---|
278 | .name = "pcifront", |
---|
279 | .owner = THIS_MODULE, |
---|
280 | .ids = xenpci_ids, |
---|
281 | .probe = pcifront_xenbus_probe, |
---|
282 | .remove = pcifront_xenbus_remove, |
---|
283 | .otherend_changed = pcifront_backend_changed, |
---|
284 | }; |
---|
285 | |
---|
286 | static int __init pcifront_init(void) |
---|
287 | { |
---|
288 | if (!is_running_on_xen()) |
---|
289 | return -ENODEV; |
---|
290 | |
---|
291 | return xenbus_register_frontend(&xenbus_pcifront_driver); |
---|
292 | } |
---|
293 | |
---|
294 | /* Initialize after the Xen PCI Frontend Stub is initialized */ |
---|
295 | subsys_initcall(pcifront_init); |
---|