📄 mf.c
字号:
/* * mf.c * Copyright (C) 2001 Troy D. Armstrong IBM Corporation * * This modules exists as an interface between a Linux secondary partition * running on an iSeries and the primary partition's Virtual Service * Processor (VSP) object. The VSP has final authority over powering on/off * all partitions in the iSeries. It also provides miscellaneous low-level * machine facility type operations. * * * 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 of the License, or * (at your option) any later version. * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */#include <asm/iSeries/mf.h>#include <linux/types.h>#include <linux/errno.h>#include <linux/kernel.h>#include <linux/init.h>#include <linux/mm.h>#include <linux/module.h>#include <linux/completion.h>#include <asm/iSeries/HvLpConfig.h>#include <linux/slab.h>#include <linux/delay.h>#include <asm/nvram.h>#include <asm/time.h>#include <asm/iSeries/ItSpCommArea.h>#include <asm/uaccess.h>#include <linux/dma-mapping.h>#include <linux/bcd.h>#include <asm/iSeries/vio.h>/* * This is the structure layout for the Machine Facilites LPAR event * flows. */union safe_cast { u64 ptr_as_u64; void *ptr;};struct VspCmdData { union safe_cast token; u16 cmd; HvLpIndex lp_index; u8 result_code; u32 reserved; union { u64 state; /* GetStateOut */ u64 ipl_type; /* GetIplTypeOut, Function02SelectIplTypeIn */ u64 ipl_mode; /* GetIplModeOut, Function02SelectIplModeIn */ u64 page[4]; /* GetSrcHistoryIn */ u64 flag; /* GetAutoIplWhenPrimaryIplsOut, SetAutoIplWhenPrimaryIplsIn, WhiteButtonPowerOffIn, Function08FastPowerOffIn, IsSpcnRackPowerIncompleteOut */ struct { u64 token; u64 address_type; u64 side; u32 length; u32 offset; } kern; /* SetKernelImageIn, GetKernelImageIn, SetKernelCmdLineIn, GetKernelCmdLineIn */ u32 length_out; /* GetKernelImageOut, GetKernelCmdLineOut */ u8 reserved[80]; } sub_data;};struct VspRspData { struct completion com; struct VspCmdData *response;};struct AllocData { u16 size; u16 type; u32 count; u16 reserved1; u8 reserved2; HvLpIndex target_lp;};struct CeMsgData;typedef void (*CeMsgCompleteHandler)(void *token, struct CeMsgData *vspCmdRsp);struct CeMsgCompleteData { CeMsgCompleteHandler handler; void *token;};struct CeMsgData { u8 ce_msg[12]; char reserved[4]; struct CeMsgCompleteData *completion;};struct IoMFLpEvent { struct HvLpEvent hp_lp_event; u16 subtype_result_code; u16 reserved1; u32 reserved2; union { struct AllocData alloc; struct CeMsgData ce_msg; struct VspCmdData vsp_cmd; } data;};#define subtype_data(a, b, c, d) \ (((a) << 24) + ((b) << 16) + ((c) << 8) + (d))/* * All outgoing event traffic is kept on a FIFO queue. The first * pointer points to the one that is outstanding, and all new * requests get stuck on the end. Also, we keep a certain number of * preallocated pending events so that we can operate very early in * the boot up sequence (before kmalloc is ready). */struct pending_event { struct pending_event *next; struct IoMFLpEvent event; MFCompleteHandler hdlr; char dma_data[72]; unsigned dma_data_length; unsigned remote_address;};static spinlock_t pending_event_spinlock;static struct pending_event *pending_event_head;static struct pending_event *pending_event_tail;static struct pending_event *pending_event_avail;static struct pending_event pending_event_prealloc[16];/* * Put a pending event onto the available queue, so it can get reused. * Attention! You must have the pending_event_spinlock before calling! */static void free_pending_event(struct pending_event *ev){ if (ev != NULL) { ev->next = pending_event_avail; pending_event_avail = ev; }}/* * Enqueue the outbound event onto the stack. If the queue was * empty to begin with, we must also issue it via the Hypervisor * interface. There is a section of code below that will touch * the first stack pointer without the protection of the pending_event_spinlock. * This is OK, because we know that nobody else will be modifying * the first pointer when we do this. */static int signal_event(struct pending_event *ev){ int rc = 0; unsigned long flags; int go = 1; struct pending_event *ev1; HvLpEvent_Rc hvRc; /* enqueue the event */ if (ev != NULL) { ev->next = NULL; spin_lock_irqsave(&pending_event_spinlock, flags); if (pending_event_head == NULL) pending_event_head = ev; else { go = 0; pending_event_tail->next = ev; } pending_event_tail = ev; spin_unlock_irqrestore(&pending_event_spinlock, flags); } /* send the event */ while (go) { go = 0; /* any DMA data to send beforehand? */ if (pending_event_head->dma_data_length > 0) HvCallEvent_dmaToSp(pending_event_head->dma_data, pending_event_head->remote_address, pending_event_head->dma_data_length, HvLpDma_Direction_LocalToRemote); hvRc = HvCallEvent_signalLpEvent( &pending_event_head->event.hp_lp_event); if (hvRc != HvLpEvent_Rc_Good) { printk(KERN_ERR "mf.c: HvCallEvent_signalLpEvent() failed with %d\n", (int)hvRc); spin_lock_irqsave(&pending_event_spinlock, flags); ev1 = pending_event_head; pending_event_head = pending_event_head->next; if (pending_event_head != NULL) go = 1; spin_unlock_irqrestore(&pending_event_spinlock, flags); if (ev1 == ev) rc = -EIO; else if (ev1->hdlr != NULL) { union safe_cast mySafeCast; mySafeCast.ptr_as_u64 = ev1->event.hp_lp_event.xCorrelationToken; (*ev1->hdlr)(mySafeCast.ptr, -EIO); } spin_lock_irqsave(&pending_event_spinlock, flags); free_pending_event(ev1); spin_unlock_irqrestore(&pending_event_spinlock, flags); } } return rc;}/* * Allocate a new pending_event structure, and initialize it. */static struct pending_event *new_pending_event(void){ struct pending_event *ev = NULL; HvLpIndex primaryLp = HvLpConfig_getPrimaryLpIndex(); unsigned long flags; struct HvLpEvent *hev; spin_lock_irqsave(&pending_event_spinlock, flags); if (pending_event_avail != NULL) { ev = pending_event_avail; pending_event_avail = pending_event_avail->next; } spin_unlock_irqrestore(&pending_event_spinlock, flags); if (ev == NULL) ev = kmalloc(sizeof(struct pending_event),GFP_ATOMIC); if (ev == NULL) { printk(KERN_ERR "mf.c: unable to kmalloc %ld bytes\n", sizeof(struct pending_event)); return NULL; } memset(ev, 0, sizeof(struct pending_event)); hev = &ev->event.hp_lp_event; hev->xFlags.xValid = 1; hev->xFlags.xAckType = HvLpEvent_AckType_ImmediateAck; hev->xFlags.xAckInd = HvLpEvent_AckInd_DoAck; hev->xFlags.xFunction = HvLpEvent_Function_Int; hev->xType = HvLpEvent_Type_MachineFac; hev->xSourceLp = HvLpConfig_getLpIndex(); hev->xTargetLp = primaryLp; hev->xSizeMinus1 = sizeof(ev->event)-1; hev->xRc = HvLpEvent_Rc_Good; hev->xSourceInstanceId = HvCallEvent_getSourceLpInstanceId(primaryLp, HvLpEvent_Type_MachineFac); hev->xTargetInstanceId = HvCallEvent_getTargetLpInstanceId(primaryLp, HvLpEvent_Type_MachineFac); return ev;}static int signal_vsp_instruction(struct VspCmdData *vspCmd){ struct pending_event *ev = new_pending_event(); int rc; struct VspRspData response; if (ev == NULL) return -ENOMEM; init_completion(&response.com); response.response = vspCmd; ev->event.hp_lp_event.xSubtype = 6; ev->event.hp_lp_event.x.xSubtypeData = subtype_data('M', 'F', 'V', 'I'); ev->event.data.vsp_cmd.token.ptr = &response; ev->event.data.vsp_cmd.cmd = vspCmd->cmd; ev->event.data.vsp_cmd.lp_index = HvLpConfig_getLpIndex(); ev->event.data.vsp_cmd.result_code = 0xFF; ev->event.data.vsp_cmd.reserved = 0; memcpy(&(ev->event.data.vsp_cmd.sub_data), &(vspCmd->sub_data), sizeof(vspCmd->sub_data)); mb(); rc = signal_event(ev); if (rc == 0) wait_for_completion(&response.com); return rc;}/* * Send a 12-byte CE message to the primary partition VSP object */static int signal_ce_msg(char *ce_msg, struct CeMsgCompleteData *completion){ struct pending_event *ev = new_pending_event(); if (ev == NULL) return -ENOMEM; ev->event.hp_lp_event.xSubtype = 0; ev->event.hp_lp_event.x.xSubtypeData = subtype_data('M', 'F', 'C', 'E'); memcpy(ev->event.data.ce_msg.ce_msg, ce_msg, 12); ev->event.data.ce_msg.completion = completion; return signal_event(ev);}/* * Send a 12-byte CE message and DMA data to the primary partition VSP object */static int dma_and_signal_ce_msg(char *ce_msg, struct CeMsgCompleteData *completion, void *dma_data, unsigned dma_data_length, unsigned remote_address){ struct pending_event *ev = new_pending_event(); if (ev == NULL) return -ENOMEM; ev->event.hp_lp_event.xSubtype = 0; ev->event.hp_lp_event.x.xSubtypeData = subtype_data('M', 'F', 'C', 'E'); memcpy(ev->event.data.ce_msg.ce_msg, ce_msg, 12); ev->event.data.ce_msg.completion = completion; memcpy(ev->dma_data, dma_data, dma_data_length); ev->dma_data_length = dma_data_length; ev->remote_address = remote_address; return signal_event(ev);}/* * Initiate a nice (hopefully) shutdown of Linux. We simply are * going to try and send the init process a SIGINT signal. If * this fails (why?), we'll simply force it off in a not-so-nice * manner. */static int shutdown(void){ int rc = kill_proc(1, SIGINT, 1); if (rc) { printk(KERN_ALERT "mf.c: SIGINT to init failed (%d), " "hard shutdown commencing\n", rc); mf_powerOff(); } else printk(KERN_INFO "mf.c: init has been successfully notified " "to proceed with shutdown\n"); return rc;}/* * The primary partition VSP object is sending us a new * event flow. Handle it... */static void intReceived(struct IoMFLpEvent *event){ int freeIt = 0; struct pending_event *two = NULL; /* ack the interrupt */ event->hp_lp_event.xRc = HvLpEvent_Rc_Good; HvCallEvent_ackLpEvent(&event->hp_lp_event); /* process interrupt */ switch (event->hp_lp_event.xSubtype) { case 0: /* CE message */ switch (event->data.ce_msg.ce_msg[3]) { case 0x5B: /* power control notification */ if ((event->data.ce_msg.ce_msg[5] & 0x20) != 0) { printk(KERN_INFO "mf.c: Commencing partition shutdown\n"); if (shutdown() == 0) signal_ce_msg("\x00\x00\x00\xDB\x00\x00\x00\x00\x00\x00\x00\x00", NULL); } break; case 0xC0: /* get time */ if ((pending_event_head == NULL) || (pending_event_head->event.data.ce_msg.ce_msg[3] != 0x40)) break; freeIt = 1; if (pending_event_head->event.data.ce_msg.completion != 0) { CeMsgCompleteHandler handler = pending_event_head->event.data.ce_msg.completion->handler; void *token = pending_event_head->event.data.ce_msg.completion->token; if (handler != NULL) (*handler)(token, &(event->data.ce_msg)); } break; } /* remove from queue */ if (freeIt == 1) { unsigned long flags; spin_lock_irqsave(&pending_event_spinlock, flags); if (pending_event_head != NULL) { struct pending_event *oldHead = pending_event_head; pending_event_head = pending_event_head->next; two = pending_event_head; free_pending_event(oldHead); } spin_unlock_irqrestore(&pending_event_spinlock, flags); } /* send next waiting event */ if (two != NULL) signal_event(NULL); break; case 1: /* IT sys shutdown */ printk(KERN_INFO "mf.c: Commencing system shutdown\n"); shutdown(); break; }}/* * The primary partition VSP object is acknowledging the receipt * of a flow we sent to them. If there are other flows queued * up, we must send another one now... */static void ackReceived(struct IoMFLpEvent *event){ unsigned long flags; struct pending_event * two = NULL; unsigned long freeIt = 0; /* handle current event */ if (pending_event_head != NULL) { switch (event->hp_lp_event.xSubtype) { case 0: /* CE msg */ if (event->data.ce_msg.ce_msg[3] == 0x40) { if (event->data.ce_msg.ce_msg[2] != 0) { freeIt = 1; if (pending_event_head->event.data.ce_msg.completion != 0) { CeMsgCompleteHandler handler = pending_event_head->event.data.ce_msg.completion->handler; void *token = pending_event_head->event.data.ce_msg.completion->token; if (handler != NULL) (*handler)(token, &(event->data.ce_msg)); } } } else freeIt = 1; break; case 4: /* allocate */ case 5: /* deallocate */ if (pending_event_head->hdlr != NULL) { union safe_cast mySafeCast; mySafeCast.ptr_as_u64 = event->hp_lp_event.xCorrelationToken; (*pending_event_head->hdlr)(mySafeCast.ptr, event->data.alloc.count); } freeIt = 1; break; case 6: { struct VspRspData *rsp = (struct VspRspData *)event->data.vsp_cmd.token.ptr; if (rsp != NULL) { if (rsp->response != NULL) memcpy(rsp->response, &(event->data.vsp_cmd), sizeof(event->data.vsp_cmd)); complete(&rsp->com); } else printk(KERN_ERR "mf.c: no rsp\n"); freeIt = 1; } break; } } else printk(KERN_ERR "mf.c: stack empty for receiving ack\n"); /* remove from queue */ spin_lock_irqsave(&pending_event_spinlock, flags); if ((pending_event_head != NULL) && (freeIt == 1)) { struct pending_event *oldHead = pending_event_head; pending_event_head = pending_event_head->next; two = pending_event_head; free_pending_event(oldHead); } spin_unlock_irqrestore(&pending_event_spinlock, flags); /* send next waiting event */ if (two != NULL) signal_event(NULL);}/* * This is the generic event handler we are registering with * the Hypervisor. Ensure the flows are for us, and then * parse it enough to know if it is an interrupt or an * acknowledge. */static void hvHandler(struct HvLpEvent *event, struct pt_regs *regs){ if ((event != NULL) && (event->xType == HvLpEvent_Type_MachineFac)) { switch(event->xFlags.xFunction) { case HvLpEvent_Function_Ack: ackReceived((struct IoMFLpEvent *)event); break; case HvLpEvent_Function_Int: intReceived((struct IoMFLpEvent *)event); break; default: printk(KERN_ERR "mf.c: non ack/int event received\n"); break; } } else printk(KERN_ERR "mf.c: alien event received\n");}/* * Global kernel interface to allocate and seed events into the * Hypervisor. */void mf_allocateLpEvents(HvLpIndex targetLp, HvLpEvent_Type type, unsigned size, unsigned count, MFCompleteHandler hdlr, void *userToken){ struct pending_event *ev = new_pending_event(); int rc;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -