-
Notifications
You must be signed in to change notification settings - Fork 65
/
fetch26.c
154 lines (126 loc) · 3.53 KB
/
fetch26.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/*
* Routines based on linux/lib/scatterlist.c
*/
/**
* sg_copy_buffer - Copy data between a linear buffer and an SG list
* @sgl: The SG list
* @nents: Number of SG entries
* @buf: Where to copy from
* @buflen: The number of bytes to copy
* @to_buffer: transfer direction (non zero == from an sg list to a
* buffer, 0 == from a buffer to an sg list
*
* Returns the number of copied bytes.
*
**/
//static size_t sg_copy_buffer(struct scatterlist *sgl, unsigned int nents,
// void *buf, size_t buflen, int to_buffer)
static size_t vtl_sg_copy_user(struct scatterlist *sgl, unsigned int nents,
__user void *buf, size_t buflen, int to_buffer)
{
struct scatterlist *sg;
size_t buf_off = 0;
int i;
int ret;
for_each_sg(sgl, sg, nents, i) {
struct page *page;
int n = 0;
unsigned int sg_off = sg->offset;
unsigned int sg_copy = sg->length;
if (sg_copy > buflen)
sg_copy = buflen;
buflen -= sg_copy;
while (sg_copy > 0) {
unsigned int page_copy;
void *p;
page_copy = PAGE_SIZE - sg_off;
if (page_copy > sg_copy)
page_copy = sg_copy;
page = nth_page(sg_page(sg), n);
p = kmap_atomic(page, KM_BIO_SRC_IRQ);
if (to_buffer)
ret = copy_to_user(buf + buf_off, p + sg_off, page_copy);
else {
ret = copy_from_user(p + sg_off, buf + buf_off, page_copy);
flush_kernel_dcache_page(page);
}
kunmap_atomic(p, KM_BIO_SRC_IRQ);
buf_off += page_copy;
sg_off += page_copy;
if (sg_off == PAGE_SIZE) {
sg_off = 0;
n++;
}
sg_copy -= page_copy;
}
if (!buflen)
break;
}
return buf_off;
}
size_t vtl_copy_from_user(struct scatterlist *sgl, unsigned int nents,
char __user *buf, size_t buflen)
{
return vtl_sg_copy_user(sgl, nents, buf, buflen, 0);
}
size_t vtl_copy_to_user(struct scatterlist *sgl, unsigned int nents,
char __user *buf, size_t buflen)
{
return vtl_sg_copy_user(sgl, nents, buf, buflen, 1);
}
/*
* Copy data from SCSI command buffer to device buffer
* (SCSI command buffer -> user space)
*
* Returns number of bytes fetched into 'arr' or -1 if error.
*/
static int fetch_to_dev_buffer(struct scsi_cmnd *scp, char __user *arr,
int len)
{
struct scsi_data_buffer *sdb = scsi_out(scp);
if (!scsi_bufflen(scp))
return 0;
if (!(scsi_bidi_cmnd(scp) || scp->sc_data_direction == DMA_TO_DEVICE))
return -1;
return vtl_copy_to_user(sdb->table.sgl, sdb->table.nents, arr, len);
}
/*
* fill_from_user_buffer : Retrieves data from user-space into SCSI
* buffer(s)
Returns 0 if ok else (DID_ERROR << 16). Sets scp->resid .
*/
static int fill_from_user_buffer(struct scsi_cmnd *scp, char __user *arr,
int arr_len)
{
int act_len;
struct scsi_data_buffer *sdb = scsi_in(scp);
if (!sdb->length)
return 0;
if (!(scsi_bidi_cmnd(scp) || scp->sc_data_direction == DMA_FROM_DEVICE))
return (DID_ERROR << 16);
act_len = vtl_copy_from_user(sdb->table.sgl, sdb->table.nents,
arr, arr_len);
if (sdb->resid)
sdb->resid -= act_len;
else
sdb->resid = scsi_bufflen(scp) - act_len;
return 0;
}
/* Returns 0 if ok else (DID_ERROR << 16). Sets scp->resid . */
static int fill_from_dev_buffer(struct scsi_cmnd *scp, unsigned char *arr,
int arr_len)
{
int act_len;
struct scsi_data_buffer *sdb = scsi_in(scp);
if (!sdb->length)
return 0;
if (!(scsi_bidi_cmnd(scp) || scp->sc_data_direction == DMA_FROM_DEVICE))
return (DID_ERROR << 16);
act_len = sg_copy_from_buffer(sdb->table.sgl, sdb->table.nents,
arr, arr_len);
if (sdb->resid)
sdb->resid -= act_len;
else
sdb->resid = scsi_bufflen(scp) - act_len;
return 0;
}