]>
Commit | Line | Data |
---|---|---|
e450992d DM |
1 | /* ds.c: Domain Services driver for Logical Domains |
2 | * | |
3 | * Copyright (C) 2007 David S. Miller <davem@davemloft.net> | |
4 | */ | |
5 | ||
6 | #include <linux/kernel.h> | |
7 | #include <linux/module.h> | |
8 | #include <linux/types.h> | |
9 | #include <linux/module.h> | |
10 | #include <linux/string.h> | |
11 | #include <linux/slab.h> | |
12 | #include <linux/sched.h> | |
13 | #include <linux/delay.h> | |
14 | ||
15 | #include <asm/ldc.h> | |
16 | #include <asm/vio.h> | |
17 | #include <asm/power.h> | |
18 | ||
19 | #define DRV_MODULE_NAME "ds" | |
20 | #define PFX DRV_MODULE_NAME ": " | |
21 | #define DRV_MODULE_VERSION "1.0" | |
22 | #define DRV_MODULE_RELDATE "Jul 11, 2007" | |
23 | ||
24 | static char version[] __devinitdata = | |
25 | DRV_MODULE_NAME ".c:v" DRV_MODULE_VERSION " (" DRV_MODULE_RELDATE ")\n"; | |
26 | MODULE_AUTHOR("David S. Miller (davem@davemloft.net)"); | |
27 | MODULE_DESCRIPTION("Sun LDOM domain services driver"); | |
28 | MODULE_LICENSE("GPL"); | |
29 | MODULE_VERSION(DRV_MODULE_VERSION); | |
30 | ||
31 | struct ds_msg_tag { | |
32 | __u32 type; | |
33 | #define DS_INIT_REQ 0x00 | |
34 | #define DS_INIT_ACK 0x01 | |
35 | #define DS_INIT_NACK 0x02 | |
36 | #define DS_REG_REQ 0x03 | |
37 | #define DS_REG_ACK 0x04 | |
38 | #define DS_REG_NACK 0x05 | |
39 | #define DS_UNREG_REQ 0x06 | |
40 | #define DS_UNREG_ACK 0x07 | |
41 | #define DS_UNREG_NACK 0x08 | |
42 | #define DS_DATA 0x09 | |
43 | #define DS_NACK 0x0a | |
44 | ||
45 | __u32 len; | |
46 | }; | |
47 | ||
48 | /* Result codes */ | |
49 | #define DS_OK 0x00 | |
50 | #define DS_REG_VER_NACK 0x01 | |
51 | #define DS_REG_DUP 0x02 | |
52 | #define DS_INV_HDL 0x03 | |
53 | #define DS_TYPE_UNKNOWN 0x04 | |
54 | ||
55 | struct ds_version { | |
56 | __u16 major; | |
57 | __u16 minor; | |
58 | }; | |
59 | ||
60 | struct ds_ver_req { | |
61 | struct ds_msg_tag tag; | |
62 | struct ds_version ver; | |
63 | }; | |
64 | ||
65 | struct ds_ver_ack { | |
66 | struct ds_msg_tag tag; | |
67 | __u16 minor; | |
68 | }; | |
69 | ||
70 | struct ds_ver_nack { | |
71 | struct ds_msg_tag tag; | |
72 | __u16 major; | |
73 | }; | |
74 | ||
75 | struct ds_reg_req { | |
76 | struct ds_msg_tag tag; | |
77 | __u64 handle; | |
78 | __u16 major; | |
79 | __u16 minor; | |
80 | char svc_id[0]; | |
81 | }; | |
82 | ||
83 | struct ds_reg_ack { | |
84 | struct ds_msg_tag tag; | |
85 | __u64 handle; | |
86 | __u16 minor; | |
87 | }; | |
88 | ||
89 | struct ds_reg_nack { | |
90 | struct ds_msg_tag tag; | |
91 | __u64 handle; | |
92 | __u16 major; | |
93 | }; | |
94 | ||
95 | struct ds_unreg_req { | |
96 | struct ds_msg_tag tag; | |
97 | __u64 handle; | |
98 | }; | |
99 | ||
100 | struct ds_unreg_ack { | |
101 | struct ds_msg_tag tag; | |
102 | __u64 handle; | |
103 | }; | |
104 | ||
105 | struct ds_unreg_nack { | |
106 | struct ds_msg_tag tag; | |
107 | __u64 handle; | |
108 | }; | |
109 | ||
110 | struct ds_data { | |
111 | struct ds_msg_tag tag; | |
112 | __u64 handle; | |
113 | }; | |
114 | ||
115 | struct ds_data_nack { | |
116 | struct ds_msg_tag tag; | |
117 | __u64 handle; | |
118 | __u64 result; | |
119 | }; | |
120 | ||
121 | struct ds_cap_state { | |
122 | __u64 handle; | |
123 | ||
124 | void (*data)(struct ldc_channel *lp, | |
125 | struct ds_cap_state *dp, | |
126 | void *buf, int len); | |
127 | ||
128 | const char *service_id; | |
129 | ||
130 | u8 state; | |
131 | #define CAP_STATE_UNKNOWN 0x00 | |
132 | #define CAP_STATE_REG_SENT 0x01 | |
133 | #define CAP_STATE_REGISTERED 0x02 | |
134 | }; | |
135 | ||
136 | static int ds_send(struct ldc_channel *lp, void *data, int len) | |
137 | { | |
138 | int err, limit = 1000; | |
139 | ||
140 | err = -EINVAL; | |
141 | while (limit-- > 0) { | |
142 | err = ldc_write(lp, data, len); | |
143 | if (!err || (err != -EAGAIN)) | |
144 | break; | |
145 | udelay(1); | |
146 | } | |
147 | ||
148 | return err; | |
149 | } | |
150 | ||
151 | struct ds_md_update_req { | |
152 | __u64 req_num; | |
153 | }; | |
154 | ||
155 | struct ds_md_update_res { | |
156 | __u64 req_num; | |
157 | __u32 result; | |
158 | }; | |
159 | ||
160 | static void md_update_data(struct ldc_channel *lp, | |
161 | struct ds_cap_state *dp, | |
162 | void *buf, int len) | |
163 | { | |
164 | struct ds_data *dpkt = buf; | |
165 | struct ds_md_update_req *rp; | |
166 | struct { | |
167 | struct ds_data data; | |
168 | struct ds_md_update_res res; | |
169 | } pkt; | |
170 | ||
171 | rp = (struct ds_md_update_req *) (dpkt + 1); | |
172 | ||
173 | printk(KERN_ERR PFX "MD update REQ [%lx] len=%d\n", | |
174 | rp->req_num, len); | |
175 | ||
176 | memset(&pkt, 0, sizeof(pkt)); | |
177 | pkt.data.tag.type = DS_DATA; | |
178 | pkt.data.tag.len = sizeof(pkt) - sizeof(struct ds_msg_tag); | |
179 | pkt.data.handle = dp->handle; | |
180 | pkt.res.req_num = rp->req_num; | |
181 | pkt.res.result = DS_OK; | |
182 | ||
183 | ds_send(lp, &pkt, sizeof(pkt)); | |
184 | } | |
185 | ||
186 | struct ds_shutdown_req { | |
187 | __u64 req_num; | |
188 | __u32 ms_delay; | |
189 | }; | |
190 | ||
191 | struct ds_shutdown_res { | |
192 | __u64 req_num; | |
193 | __u32 result; | |
194 | char reason[1]; | |
195 | }; | |
196 | ||
197 | static void domain_shutdown_data(struct ldc_channel *lp, | |
198 | struct ds_cap_state *dp, | |
199 | void *buf, int len) | |
200 | { | |
201 | struct ds_data *dpkt = buf; | |
202 | struct ds_shutdown_req *rp; | |
203 | struct { | |
204 | struct ds_data data; | |
205 | struct ds_shutdown_res res; | |
206 | } pkt; | |
207 | ||
208 | rp = (struct ds_shutdown_req *) (dpkt + 1); | |
209 | ||
210 | printk(KERN_ALERT PFX "Shutdown request from " | |
211 | "LDOM manager received.\n"); | |
212 | ||
213 | memset(&pkt, 0, sizeof(pkt)); | |
214 | pkt.data.tag.type = DS_DATA; | |
215 | pkt.data.tag.len = sizeof(pkt) - sizeof(struct ds_msg_tag); | |
216 | pkt.data.handle = dp->handle; | |
217 | pkt.res.req_num = rp->req_num; | |
218 | pkt.res.result = DS_OK; | |
219 | pkt.res.reason[0] = 0; | |
220 | ||
221 | ds_send(lp, &pkt, sizeof(pkt)); | |
222 | ||
223 | wake_up_powerd(); | |
224 | } | |
225 | ||
226 | struct ds_panic_req { | |
227 | __u64 req_num; | |
228 | }; | |
229 | ||
230 | struct ds_panic_res { | |
231 | __u64 req_num; | |
232 | __u32 result; | |
233 | char reason[1]; | |
234 | }; | |
235 | ||
236 | static void domain_panic_data(struct ldc_channel *lp, | |
237 | struct ds_cap_state *dp, | |
238 | void *buf, int len) | |
239 | { | |
240 | struct ds_data *dpkt = buf; | |
241 | struct ds_panic_req *rp; | |
242 | struct { | |
243 | struct ds_data data; | |
244 | struct ds_panic_res res; | |
245 | } pkt; | |
246 | ||
247 | rp = (struct ds_panic_req *) (dpkt + 1); | |
248 | ||
249 | printk(KERN_ERR PFX "Panic REQ [%lx], len=%d\n", | |
250 | rp->req_num, len); | |
251 | ||
252 | memset(&pkt, 0, sizeof(pkt)); | |
253 | pkt.data.tag.type = DS_DATA; | |
254 | pkt.data.tag.len = sizeof(pkt) - sizeof(struct ds_msg_tag); | |
255 | pkt.data.handle = dp->handle; | |
256 | pkt.res.req_num = rp->req_num; | |
257 | pkt.res.result = DS_OK; | |
258 | pkt.res.reason[0] = 0; | |
259 | ||
260 | ds_send(lp, &pkt, sizeof(pkt)); | |
261 | ||
262 | panic("PANIC requested by LDOM manager."); | |
263 | } | |
264 | ||
265 | struct ds_cpu_tag { | |
266 | __u64 req_num; | |
267 | __u32 type; | |
268 | #define DS_CPU_CONFIGURE 0x43 | |
269 | #define DS_CPU_UNCONFIGURE 0x55 | |
270 | #define DS_CPU_FORCE_UNCONFIGURE 0x46 | |
271 | #define DS_CPU_STATUS 0x53 | |
272 | ||
273 | /* Responses */ | |
274 | #define DS_CPU_OK 0x6f | |
275 | #define DS_CPU_ERROR 0x65 | |
276 | ||
277 | __u32 num_records; | |
278 | }; | |
279 | ||
280 | struct ds_cpu_record { | |
281 | __u32 cpu_id; | |
282 | }; | |
283 | ||
284 | static void dr_cpu_data(struct ldc_channel *lp, | |
285 | struct ds_cap_state *dp, | |
286 | void *buf, int len) | |
287 | { | |
288 | struct ds_data *dpkt = buf; | |
289 | struct ds_cpu_tag *rp; | |
290 | ||
291 | rp = (struct ds_cpu_tag *) (dpkt + 1); | |
292 | ||
293 | printk(KERN_ERR PFX "CPU REQ [%lx:%x], len=%d\n", | |
294 | rp->req_num, rp->type, len); | |
295 | } | |
296 | ||
297 | struct ds_pri_msg { | |
298 | __u64 req_num; | |
299 | __u64 type; | |
300 | #define DS_PRI_REQUEST 0x00 | |
301 | #define DS_PRI_DATA 0x01 | |
302 | #define DS_PRI_UPDATE 0x02 | |
303 | }; | |
304 | ||
305 | static void ds_pri_data(struct ldc_channel *lp, | |
306 | struct ds_cap_state *dp, | |
307 | void *buf, int len) | |
308 | { | |
309 | struct ds_data *dpkt = buf; | |
310 | struct ds_pri_msg *rp; | |
311 | ||
312 | rp = (struct ds_pri_msg *) (dpkt + 1); | |
313 | ||
314 | printk(KERN_ERR PFX "PRI REQ [%lx:%lx], len=%d\n", | |
315 | rp->req_num, rp->type, len); | |
316 | } | |
317 | ||
318 | struct ds_cap_state ds_states[] = { | |
319 | { | |
320 | .service_id = "md-update", | |
321 | .data = md_update_data, | |
322 | }, | |
323 | { | |
324 | .service_id = "domain-shutdown", | |
325 | .data = domain_shutdown_data, | |
326 | }, | |
327 | { | |
328 | .service_id = "domain-panic", | |
329 | .data = domain_panic_data, | |
330 | }, | |
331 | { | |
332 | .service_id = "dr-cpu", | |
333 | .data = dr_cpu_data, | |
334 | }, | |
335 | { | |
336 | .service_id = "pri", | |
337 | .data = ds_pri_data, | |
338 | }, | |
339 | }; | |
340 | ||
341 | static struct ds_cap_state *find_cap(u64 handle) | |
342 | { | |
343 | unsigned int index = handle >> 32; | |
344 | ||
345 | if (index >= ARRAY_SIZE(ds_states)) | |
346 | return NULL; | |
347 | return &ds_states[index]; | |
348 | } | |
349 | ||
350 | static DEFINE_SPINLOCK(ds_lock); | |
351 | ||
352 | struct ds_info { | |
353 | struct ldc_channel *lp; | |
354 | u8 hs_state; | |
355 | #define DS_HS_START 0x01 | |
356 | #define DS_HS_DONE 0x02 | |
357 | ||
358 | void *rcv_buf; | |
359 | int rcv_buf_len; | |
360 | }; | |
361 | ||
362 | static void ds_conn_reset(struct ds_info *dp) | |
363 | { | |
364 | printk(KERN_ERR PFX "ds_conn_reset() from %p\n", | |
365 | __builtin_return_address(0)); | |
366 | } | |
367 | ||
368 | static int register_services(struct ds_info *dp) | |
369 | { | |
370 | struct ldc_channel *lp = dp->lp; | |
371 | int i; | |
372 | ||
373 | for (i = 0; i < ARRAY_SIZE(ds_states); i++) { | |
374 | struct { | |
375 | struct ds_reg_req req; | |
376 | u8 id_buf[256]; | |
377 | } pbuf; | |
378 | struct ds_cap_state *cp = &ds_states[i]; | |
379 | int err, msg_len; | |
380 | u64 new_count; | |
381 | ||
382 | if (cp->state == CAP_STATE_REGISTERED) | |
383 | continue; | |
384 | ||
385 | new_count = sched_clock() & 0xffffffff; | |
386 | cp->handle = ((u64) i << 32) | new_count; | |
387 | ||
388 | msg_len = (sizeof(struct ds_reg_req) + | |
389 | strlen(cp->service_id)); | |
390 | ||
391 | memset(&pbuf, 0, sizeof(pbuf)); | |
392 | pbuf.req.tag.type = DS_REG_REQ; | |
393 | pbuf.req.tag.len = (msg_len - sizeof(struct ds_msg_tag)); | |
394 | pbuf.req.handle = cp->handle; | |
395 | pbuf.req.major = 1; | |
396 | pbuf.req.minor = 0; | |
397 | strcpy(pbuf.req.svc_id, cp->service_id); | |
398 | ||
399 | err = ds_send(lp, &pbuf, msg_len); | |
400 | if (err > 0) | |
401 | cp->state = CAP_STATE_REG_SENT; | |
402 | } | |
403 | return 0; | |
404 | } | |
405 | ||
406 | static int ds_handshake(struct ds_info *dp, struct ds_msg_tag *pkt) | |
407 | { | |
408 | ||
409 | if (dp->hs_state == DS_HS_START) { | |
410 | if (pkt->type != DS_INIT_ACK) | |
411 | goto conn_reset; | |
412 | ||
413 | dp->hs_state = DS_HS_DONE; | |
414 | ||
415 | return register_services(dp); | |
416 | } | |
417 | ||
418 | if (dp->hs_state != DS_HS_DONE) | |
419 | goto conn_reset; | |
420 | ||
421 | if (pkt->type == DS_REG_ACK) { | |
422 | struct ds_reg_ack *ap = (struct ds_reg_ack *) pkt; | |
423 | struct ds_cap_state *cp = find_cap(ap->handle); | |
424 | ||
425 | if (!cp) { | |
426 | printk(KERN_ERR PFX "REG ACK for unknown handle %lx\n", | |
427 | ap->handle); | |
428 | return 0; | |
429 | } | |
430 | printk(KERN_INFO PFX "Registered %s service.\n", | |
431 | cp->service_id); | |
432 | cp->state = CAP_STATE_REGISTERED; | |
433 | } else if (pkt->type == DS_REG_NACK) { | |
434 | struct ds_reg_nack *np = (struct ds_reg_nack *) pkt; | |
435 | struct ds_cap_state *cp = find_cap(np->handle); | |
436 | ||
437 | if (!cp) { | |
438 | printk(KERN_ERR PFX "REG NACK for " | |
439 | "unknown handle %lx\n", | |
440 | np->handle); | |
441 | return 0; | |
442 | } | |
443 | printk(KERN_ERR PFX "Could not register %s service\n", | |
444 | cp->service_id); | |
445 | cp->state = CAP_STATE_UNKNOWN; | |
446 | } | |
447 | ||
448 | return 0; | |
449 | ||
450 | conn_reset: | |
451 | ds_conn_reset(dp); | |
452 | return -ECONNRESET; | |
453 | } | |
454 | ||
455 | static int ds_data(struct ds_info *dp, struct ds_msg_tag *pkt, int len) | |
456 | { | |
457 | struct ds_data *dpkt = (struct ds_data *) pkt; | |
458 | struct ds_cap_state *cp = find_cap(dpkt->handle); | |
459 | ||
460 | if (!cp) { | |
461 | struct ds_data_nack nack = { | |
462 | .tag = { | |
463 | .type = DS_NACK, | |
464 | .len = (sizeof(struct ds_data_nack) - | |
465 | sizeof(struct ds_msg_tag)), | |
466 | }, | |
467 | .handle = dpkt->handle, | |
468 | .result = DS_INV_HDL, | |
469 | }; | |
470 | ||
471 | printk(KERN_ERR PFX "Data for unknown handle %lu\n", | |
472 | dpkt->handle); | |
473 | ds_send(dp->lp, &nack, sizeof(nack)); | |
474 | } else { | |
475 | cp->data(dp->lp, cp, dpkt, len); | |
476 | } | |
477 | return 0; | |
478 | } | |
479 | ||
480 | static void ds_up(struct ds_info *dp) | |
481 | { | |
482 | struct ldc_channel *lp = dp->lp; | |
483 | struct ds_ver_req req; | |
484 | int err; | |
485 | ||
486 | req.tag.type = DS_INIT_REQ; | |
487 | req.tag.len = sizeof(req) - sizeof(struct ds_msg_tag); | |
488 | req.ver.major = 1; | |
489 | req.ver.minor = 0; | |
490 | ||
491 | err = ds_send(lp, &req, sizeof(req)); | |
492 | if (err > 0) | |
493 | dp->hs_state = DS_HS_START; | |
494 | } | |
495 | ||
496 | static void ds_event(void *arg, int event) | |
497 | { | |
498 | struct ds_info *dp = arg; | |
499 | struct ldc_channel *lp = dp->lp; | |
500 | unsigned long flags; | |
501 | int err; | |
502 | ||
503 | spin_lock_irqsave(&ds_lock, flags); | |
504 | ||
505 | if (event == LDC_EVENT_UP) { | |
506 | ds_up(dp); | |
507 | spin_unlock_irqrestore(&ds_lock, flags); | |
508 | return; | |
509 | } | |
510 | ||
511 | if (event != LDC_EVENT_DATA_READY) { | |
512 | printk(KERN_WARNING PFX "Unexpected LDC event %d\n", event); | |
513 | spin_unlock_irqrestore(&ds_lock, flags); | |
514 | return; | |
515 | } | |
516 | ||
517 | err = 0; | |
518 | while (1) { | |
519 | struct ds_msg_tag *tag; | |
520 | ||
521 | err = ldc_read(lp, dp->rcv_buf, sizeof(*tag)); | |
522 | ||
523 | if (unlikely(err < 0)) { | |
524 | if (err == -ECONNRESET) | |
525 | ds_conn_reset(dp); | |
526 | break; | |
527 | } | |
528 | if (err == 0) | |
529 | break; | |
530 | ||
531 | tag = dp->rcv_buf; | |
532 | err = ldc_read(lp, tag + 1, tag->len); | |
533 | ||
534 | if (unlikely(err < 0)) { | |
535 | if (err == -ECONNRESET) | |
536 | ds_conn_reset(dp); | |
537 | break; | |
538 | } | |
539 | if (err < tag->len) | |
540 | break; | |
541 | ||
542 | if (tag->type < DS_DATA) | |
543 | err = ds_handshake(dp, dp->rcv_buf); | |
544 | else | |
545 | err = ds_data(dp, dp->rcv_buf, | |
546 | sizeof(*tag) + err); | |
547 | if (err == -ECONNRESET) | |
548 | break; | |
549 | } | |
550 | ||
551 | spin_unlock_irqrestore(&ds_lock, flags); | |
552 | } | |
553 | ||
554 | static int __devinit ds_probe(struct vio_dev *vdev, | |
555 | const struct vio_device_id *id) | |
556 | { | |
557 | static int ds_version_printed; | |
558 | struct mdesc_node *endp; | |
559 | struct ldc_channel_config ds_cfg = { | |
560 | .event = ds_event, | |
561 | .mtu = 4096, | |
562 | .mode = LDC_MODE_STREAM, | |
563 | }; | |
564 | struct ldc_channel *lp; | |
565 | struct ds_info *dp; | |
566 | const u64 *chan_id; | |
567 | int err; | |
568 | ||
569 | if (ds_version_printed++ == 0) | |
570 | printk(KERN_INFO "%s", version); | |
571 | ||
572 | endp = vio_find_endpoint(vdev); | |
573 | if (!endp) | |
574 | return -ENODEV; | |
575 | ||
576 | chan_id = md_get_property(endp, "id", NULL); | |
577 | if (!chan_id) | |
578 | return -ENODEV; | |
579 | ||
580 | dp = kzalloc(sizeof(*dp), GFP_KERNEL); | |
581 | err = -ENOMEM; | |
582 | if (!dp) | |
583 | goto out_err; | |
584 | ||
585 | dp->rcv_buf = kzalloc(4096, GFP_KERNEL); | |
586 | if (!dp->rcv_buf) | |
587 | goto out_free_dp; | |
588 | ||
589 | dp->rcv_buf_len = 4096; | |
590 | ||
591 | ds_cfg.tx_irq = endp->irqs[0]; | |
592 | ds_cfg.rx_irq = endp->irqs[1]; | |
593 | ||
594 | lp = ldc_alloc(*chan_id, &ds_cfg, dp); | |
595 | if (IS_ERR(lp)) { | |
596 | err = PTR_ERR(lp); | |
597 | goto out_free_rcv_buf; | |
598 | } | |
599 | dp->lp = lp; | |
600 | ||
601 | err = ldc_bind(lp); | |
602 | if (err) | |
603 | goto out_free_ldc; | |
604 | ||
605 | start_powerd(); | |
606 | ||
607 | return err; | |
608 | ||
609 | out_free_ldc: | |
610 | ldc_free(dp->lp); | |
611 | ||
612 | out_free_rcv_buf: | |
613 | kfree(dp->rcv_buf); | |
614 | ||
615 | out_free_dp: | |
616 | kfree(dp); | |
617 | ||
618 | out_err: | |
619 | return err; | |
620 | } | |
621 | ||
622 | static int ds_remove(struct vio_dev *vdev) | |
623 | { | |
624 | return 0; | |
625 | } | |
626 | ||
627 | static struct vio_device_id ds_match[] = { | |
628 | { | |
629 | .type = "domain-services-port", | |
630 | }, | |
631 | {}, | |
632 | }; | |
633 | ||
634 | static struct vio_driver ds_driver = { | |
635 | .id_table = ds_match, | |
636 | .probe = ds_probe, | |
637 | .remove = ds_remove, | |
638 | .driver = { | |
639 | .name = "ds", | |
640 | .owner = THIS_MODULE, | |
641 | } | |
642 | }; | |
643 | ||
644 | static int __init ds_init(void) | |
645 | { | |
646 | int i; | |
647 | ||
648 | for (i = 0; i < ARRAY_SIZE(ds_states); i++) | |
649 | ds_states[i].handle = ((u64)i << 32); | |
650 | ||
651 | return vio_register_driver(&ds_driver); | |
652 | } | |
653 | ||
654 | subsys_initcall(ds_init); |