1 /*
2  *  arch/s390/kernel/cpcmd.c
3  *
4  *  S390 version
5  *    Copyright IBM Corp. 1999,2007
6  *    Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com),
7  *               Christian Borntraeger (cborntra@de.ibm.com),
8  */
9 
10 #define KMSG_COMPONENT "cpcmd"
11 #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
12 
13 #include <linux/kernel.h>
14 #include <linux/module.h>
15 #include <linux/slab.h>
16 #include <linux/spinlock.h>
17 #include <linux/stddef.h>
18 #include <linux/string.h>
19 #include <asm/ebcdic.h>
20 #include <asm/cpcmd.h>
21 #include <asm/system.h>
22 #include <asm/io.h>
23 
24 static DEFINE_SPINLOCK(cpcmd_lock);
25 static char cpcmd_buf[241];
26 
diag8_noresponse(int cmdlen)27 static int diag8_noresponse(int cmdlen)
28 {
29 	register unsigned long reg2 asm ("2") = (addr_t) cpcmd_buf;
30 	register unsigned long reg3 asm ("3") = cmdlen;
31 
32 	asm volatile(
33 #ifndef CONFIG_64BIT
34 		"	diag	%1,%0,0x8\n"
35 #else /* CONFIG_64BIT */
36 		"	sam31\n"
37 		"	diag	%1,%0,0x8\n"
38 		"	sam64\n"
39 #endif /* CONFIG_64BIT */
40 		: "+d" (reg3) : "d" (reg2) : "cc");
41 	return reg3;
42 }
43 
diag8_response(int cmdlen,char * response,int * rlen)44 static int diag8_response(int cmdlen, char *response, int *rlen)
45 {
46 	register unsigned long reg2 asm ("2") = (addr_t) cpcmd_buf;
47 	register unsigned long reg3 asm ("3") = (addr_t) response;
48 	register unsigned long reg4 asm ("4") = cmdlen | 0x40000000L;
49 	register unsigned long reg5 asm ("5") = *rlen;
50 
51 	asm volatile(
52 #ifndef CONFIG_64BIT
53 		"	diag	%2,%0,0x8\n"
54 		"	brc	8,1f\n"
55 		"	ar	%1,%4\n"
56 #else /* CONFIG_64BIT */
57 		"	sam31\n"
58 		"	diag	%2,%0,0x8\n"
59 		"	sam64\n"
60 		"	brc	8,1f\n"
61 		"	agr	%1,%4\n"
62 #endif /* CONFIG_64BIT */
63 		"1:\n"
64 		: "+d" (reg4), "+d" (reg5)
65 		: "d" (reg2), "d" (reg3), "d" (*rlen) : "cc");
66 	*rlen = reg5;
67 	return reg4;
68 }
69 
70 /*
71  * __cpcmd has some restrictions over cpcmd
72  *  - the response buffer must reside below 2GB (if any)
73  *  - __cpcmd is unlocked and therefore not SMP-safe
74  */
__cpcmd(const char * cmd,char * response,int rlen,int * response_code)75 int  __cpcmd(const char *cmd, char *response, int rlen, int *response_code)
76 {
77 	int cmdlen;
78 	int rc;
79 	int response_len;
80 
81 	cmdlen = strlen(cmd);
82 	BUG_ON(cmdlen > 240);
83 	memcpy(cpcmd_buf, cmd, cmdlen);
84 	ASCEBC(cpcmd_buf, cmdlen);
85 
86 	if (response) {
87 		memset(response, 0, rlen);
88 		response_len = rlen;
89 		rc = diag8_response(cmdlen, response, &rlen);
90 		EBCASC(response, response_len);
91         } else {
92 		rc = diag8_noresponse(cmdlen);
93         }
94 	if (response_code)
95 		*response_code = rc;
96 	return rlen;
97 }
98 EXPORT_SYMBOL(__cpcmd);
99 
cpcmd(const char * cmd,char * response,int rlen,int * response_code)100 int cpcmd(const char *cmd, char *response, int rlen, int *response_code)
101 {
102 	char *lowbuf;
103 	int len;
104 	unsigned long flags;
105 
106 	if ((virt_to_phys(response) != (unsigned long) response) ||
107 			(((unsigned long)response + rlen) >> 31)) {
108 		lowbuf = kmalloc(rlen, GFP_KERNEL | GFP_DMA);
109 		if (!lowbuf) {
110 			pr_warning("The cpcmd kernel function failed to "
111 				   "allocate a response buffer\n");
112 			return -ENOMEM;
113 		}
114 		spin_lock_irqsave(&cpcmd_lock, flags);
115 		len = __cpcmd(cmd, lowbuf, rlen, response_code);
116 		spin_unlock_irqrestore(&cpcmd_lock, flags);
117 		memcpy(response, lowbuf, rlen);
118 		kfree(lowbuf);
119 	} else {
120 		spin_lock_irqsave(&cpcmd_lock, flags);
121 		len = __cpcmd(cmd, response, rlen, response_code);
122 		spin_unlock_irqrestore(&cpcmd_lock, flags);
123 	}
124 	return len;
125 }
126 EXPORT_SYMBOL(cpcmd);
127