1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | |
3 | /* |
4 | * IBM ASM Service Processor Device Driver |
5 | * |
6 | * Copyright (C) IBM Corporation, 2004 |
7 | * |
8 | * Author: Max Asböck <amax@us.ibm.com> |
9 | * |
10 | * This driver is based on code originally written by Pete Reynolds |
11 | * and others. |
12 | */ |
13 | |
14 | /* |
15 | * The ASM device driver does the following things: |
16 | * |
17 | * 1) When loaded it sends a message to the service processor, |
18 | * indicating that an OS is * running. This causes the service processor |
19 | * to send periodic heartbeats to the OS. |
20 | * |
21 | * 2) Answers the periodic heartbeats sent by the service processor. |
22 | * Failure to do so would result in system reboot. |
23 | * |
24 | * 3) Acts as a pass through for dot commands sent from user applications. |
25 | * The interface for this is the ibmasmfs file system. |
26 | * |
27 | * 4) Allows user applications to register for event notification. Events |
28 | * are sent to the driver through interrupts. They can be read from user |
29 | * space through the ibmasmfs file system. |
30 | * |
31 | * 5) Allows user space applications to send heartbeats to the service |
32 | * processor (aka reverse heartbeats). Again this happens through ibmasmfs. |
33 | * |
34 | * 6) Handles remote mouse and keyboard event interrupts and makes them |
35 | * available to user applications through ibmasmfs. |
36 | * |
37 | */ |
38 | |
39 | #include <linux/pci.h> |
40 | #include <linux/init.h> |
41 | #include <linux/slab.h> |
42 | #include "ibmasm.h" |
43 | #include "lowlevel.h" |
44 | #include "remote.h" |
45 | |
46 | int ibmasm_debug = 0; |
47 | module_param(ibmasm_debug, int , S_IRUGO | S_IWUSR); |
48 | MODULE_PARM_DESC(ibmasm_debug, " Set debug mode on or off" ); |
49 | |
50 | |
51 | static int ibmasm_init_one(struct pci_dev *pdev, const struct pci_device_id *id) |
52 | { |
53 | int result; |
54 | struct service_processor *sp; |
55 | |
56 | if ((result = pci_enable_device(dev: pdev))) { |
57 | dev_err(&pdev->dev, "Failed to enable PCI device\n" ); |
58 | return result; |
59 | } |
60 | if ((result = pci_request_regions(pdev, DRIVER_NAME))) { |
61 | dev_err(&pdev->dev, "Failed to allocate PCI resources\n" ); |
62 | goto error_resources; |
63 | } |
64 | /* vnc client won't work without bus-mastering */ |
65 | pci_set_master(dev: pdev); |
66 | |
67 | sp = kzalloc(size: sizeof(struct service_processor), GFP_KERNEL); |
68 | if (sp == NULL) { |
69 | dev_err(&pdev->dev, "Failed to allocate memory\n" ); |
70 | result = -ENOMEM; |
71 | goto error_kmalloc; |
72 | } |
73 | |
74 | spin_lock_init(&sp->lock); |
75 | INIT_LIST_HEAD(list: &sp->command_queue); |
76 | |
77 | pci_set_drvdata(pdev, data: (void *)sp); |
78 | sp->dev = &pdev->dev; |
79 | sp->number = pdev->bus->number; |
80 | snprintf(buf: sp->dirname, IBMASM_NAME_SIZE, fmt: "%d" , sp->number); |
81 | snprintf(buf: sp->devname, IBMASM_NAME_SIZE, fmt: "%s%d" , DRIVER_NAME, sp->number); |
82 | |
83 | result = ibmasm_event_buffer_init(sp); |
84 | if (result) { |
85 | dev_err(sp->dev, "Failed to allocate event buffer\n" ); |
86 | goto error_eventbuffer; |
87 | } |
88 | |
89 | result = ibmasm_heartbeat_init(sp); |
90 | if (result) { |
91 | dev_err(sp->dev, "Failed to allocate heartbeat command\n" ); |
92 | goto error_heartbeat; |
93 | } |
94 | |
95 | sp->irq = pdev->irq; |
96 | sp->base_address = pci_ioremap_bar(pdev, bar: 0); |
97 | if (!sp->base_address) { |
98 | dev_err(sp->dev, "Failed to ioremap pci memory\n" ); |
99 | result = -ENODEV; |
100 | goto error_ioremap; |
101 | } |
102 | |
103 | result = request_irq(irq: sp->irq, handler: ibmasm_interrupt_handler, IRQF_SHARED, name: sp->devname, dev: (void*)sp); |
104 | if (result) { |
105 | dev_err(sp->dev, "Failed to register interrupt handler\n" ); |
106 | goto error_request_irq; |
107 | } |
108 | |
109 | enable_sp_interrupts(base_address: sp->base_address); |
110 | |
111 | result = ibmasm_init_remote_input_dev(sp); |
112 | if (result) { |
113 | dev_err(sp->dev, "Failed to initialize remote queue\n" ); |
114 | goto error_init_remote; |
115 | } |
116 | |
117 | result = ibmasm_send_driver_vpd(sp); |
118 | if (result) { |
119 | dev_err(sp->dev, "Failed to send driver VPD to service processor\n" ); |
120 | goto error_send_message; |
121 | } |
122 | result = ibmasm_send_os_state(sp, SYSTEM_STATE_OS_UP); |
123 | if (result) { |
124 | dev_err(sp->dev, "Failed to send OS state to service processor\n" ); |
125 | goto error_send_message; |
126 | } |
127 | ibmasmfs_add_sp(sp); |
128 | |
129 | ibmasm_register_uart(sp); |
130 | |
131 | return 0; |
132 | |
133 | error_send_message: |
134 | ibmasm_free_remote_input_dev(sp); |
135 | error_init_remote: |
136 | disable_sp_interrupts(base_address: sp->base_address); |
137 | free_irq(sp->irq, (void *)sp); |
138 | error_request_irq: |
139 | iounmap(addr: sp->base_address); |
140 | error_ioremap: |
141 | ibmasm_heartbeat_exit(sp); |
142 | error_heartbeat: |
143 | ibmasm_event_buffer_exit(sp); |
144 | error_eventbuffer: |
145 | kfree(objp: sp); |
146 | error_kmalloc: |
147 | pci_release_regions(pdev); |
148 | error_resources: |
149 | pci_disable_device(dev: pdev); |
150 | |
151 | return result; |
152 | } |
153 | |
154 | static void ibmasm_remove_one(struct pci_dev *pdev) |
155 | { |
156 | struct service_processor *sp = pci_get_drvdata(pdev); |
157 | |
158 | dbg("Unregistering UART\n" ); |
159 | ibmasm_unregister_uart(sp); |
160 | dbg("Sending OS down message\n" ); |
161 | if (ibmasm_send_os_state(sp, SYSTEM_STATE_OS_DOWN)) |
162 | err("failed to get response to 'Send OS State' command\n" ); |
163 | dbg("Disabling heartbeats\n" ); |
164 | ibmasm_heartbeat_exit(sp); |
165 | dbg("Disabling interrupts\n" ); |
166 | disable_sp_interrupts(base_address: sp->base_address); |
167 | dbg("Freeing SP irq\n" ); |
168 | free_irq(sp->irq, (void *)sp); |
169 | dbg("Cleaning up\n" ); |
170 | ibmasm_free_remote_input_dev(sp); |
171 | iounmap(addr: sp->base_address); |
172 | ibmasm_event_buffer_exit(sp); |
173 | kfree(objp: sp); |
174 | pci_release_regions(pdev); |
175 | pci_disable_device(dev: pdev); |
176 | } |
177 | |
178 | static struct pci_device_id ibmasm_pci_table[] = |
179 | { |
180 | { PCI_DEVICE(VENDORID_IBM, DEVICEID_RSA) }, |
181 | {}, |
182 | }; |
183 | |
184 | static struct pci_driver ibmasm_driver = { |
185 | .name = DRIVER_NAME, |
186 | .id_table = ibmasm_pci_table, |
187 | .probe = ibmasm_init_one, |
188 | .remove = ibmasm_remove_one, |
189 | }; |
190 | |
191 | static void __exit ibmasm_exit (void) |
192 | { |
193 | ibmasm_unregister_panic_notifier(); |
194 | ibmasmfs_unregister(); |
195 | pci_unregister_driver(dev: &ibmasm_driver); |
196 | info(DRIVER_DESC " version " DRIVER_VERSION " unloaded" ); |
197 | } |
198 | |
199 | static int __init ibmasm_init(void) |
200 | { |
201 | int result = pci_register_driver(&ibmasm_driver); |
202 | if (result) |
203 | return result; |
204 | |
205 | result = ibmasmfs_register(); |
206 | if (result) { |
207 | pci_unregister_driver(dev: &ibmasm_driver); |
208 | err("Failed to register ibmasmfs file system" ); |
209 | return result; |
210 | } |
211 | |
212 | ibmasm_register_panic_notifier(); |
213 | info(DRIVER_DESC " version " DRIVER_VERSION " loaded" ); |
214 | return 0; |
215 | } |
216 | |
217 | module_init(ibmasm_init); |
218 | module_exit(ibmasm_exit); |
219 | |
220 | MODULE_AUTHOR(DRIVER_AUTHOR); |
221 | MODULE_DESCRIPTION(DRIVER_DESC); |
222 | MODULE_LICENSE("GPL" ); |
223 | MODULE_DEVICE_TABLE(pci, ibmasm_pci_table); |
224 | |
225 | |