aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/scsi/scsi_logging.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/scsi/scsi_logging.c')
-rw-r--r--drivers/scsi/scsi_logging.c124
1 files changed, 124 insertions, 0 deletions
diff --git a/drivers/scsi/scsi_logging.c b/drivers/scsi/scsi_logging.c
new file mode 100644
index 000000000000..09d65dea9d56
--- /dev/null
+++ b/drivers/scsi/scsi_logging.c
@@ -0,0 +1,124 @@
1/*
2 * scsi_logging.c
3 *
4 * Copyright (C) 2014 SUSE Linux Products GmbH
5 * Copyright (C) 2014 Hannes Reinecke <hare@suse.de>
6 *
7 * This file is released under the GPLv2
8 */
9
10#include <linux/kernel.h>
11#include <linux/atomic.h>
12
13#include <scsi/scsi.h>
14#include <scsi/scsi_cmnd.h>
15#include <scsi/scsi_device.h>
16#include <scsi/scsi_dbg.h>
17
18#define SCSI_LOG_SPOOLSIZE 4096
19#define SCSI_LOG_BUFSIZE 128
20
21#if (SCSI_LOG_SPOOLSIZE / SCSI_LOG_BUFSIZE) > BITS_PER_LONG
22#warning SCSI logging bitmask too large
23#endif
24
25struct scsi_log_buf {
26 char buffer[SCSI_LOG_SPOOLSIZE];
27 unsigned long map;
28};
29
30static DEFINE_PER_CPU(struct scsi_log_buf, scsi_format_log);
31
32static char *scsi_log_reserve_buffer(size_t *len)
33{
34 struct scsi_log_buf *buf;
35 unsigned long map_bits = sizeof(buf->buffer) / SCSI_LOG_BUFSIZE;
36 unsigned long idx = 0;
37
38 preempt_disable();
39 buf = this_cpu_ptr(&scsi_format_log);
40 idx = find_first_zero_bit(&buf->map, map_bits);
41 if (likely(idx < map_bits)) {
42 while (test_and_set_bit(idx, &buf->map)) {
43 idx = find_next_zero_bit(&buf->map, map_bits, idx);
44 if (idx >= map_bits)
45 break;
46 }
47 }
48 if (WARN_ON(idx >= map_bits)) {
49 preempt_enable();
50 return NULL;
51 }
52 *len = SCSI_LOG_BUFSIZE;
53 return buf->buffer + idx * SCSI_LOG_BUFSIZE;
54}
55
56static void scsi_log_release_buffer(char *bufptr)
57{
58 struct scsi_log_buf *buf;
59 unsigned long idx;
60 int ret;
61
62 buf = this_cpu_ptr(&scsi_format_log);
63 if (bufptr >= buf->buffer &&
64 bufptr < buf->buffer + SCSI_LOG_SPOOLSIZE) {
65 idx = (bufptr - buf->buffer) / SCSI_LOG_BUFSIZE;
66 ret = test_and_clear_bit(idx, &buf->map);
67 WARN_ON(!ret);
68 }
69 preempt_enable();
70}
71
72int sdev_prefix_printk(const char *level, const struct scsi_device *sdev,
73 const char *name, const char *fmt, ...)
74{
75 va_list args;
76 char *logbuf;
77 size_t off = 0, logbuf_len;
78 int ret;
79
80 if (!sdev)
81 return 0;
82
83 logbuf = scsi_log_reserve_buffer(&logbuf_len);
84 if (!logbuf)
85 return 0;
86
87 if (name)
88 off += scnprintf(logbuf + off, logbuf_len - off,
89 "[%s] ", name);
90 va_start(args, fmt);
91 off += vscnprintf(logbuf + off, logbuf_len - off, fmt, args);
92 va_end(args);
93 ret = dev_printk(level, &sdev->sdev_gendev, "%s", logbuf);
94 scsi_log_release_buffer(logbuf);
95 return ret;
96}
97EXPORT_SYMBOL(sdev_prefix_printk);
98
99int scmd_printk(const char *level, const struct scsi_cmnd *scmd,
100 const char *fmt, ...)
101{
102 struct gendisk *disk = scmd->request->rq_disk;
103 va_list args;
104 char *logbuf;
105 size_t off = 0, logbuf_len;
106 int ret;
107
108 if (!scmd || !scmd->cmnd)
109 return 0;
110
111 logbuf = scsi_log_reserve_buffer(&logbuf_len);
112 if (!logbuf)
113 return 0;
114 if (disk)
115 off += scnprintf(logbuf + off, logbuf_len - off,
116 "[%s] ", disk->disk_name);
117 va_start(args, fmt);
118 off += vscnprintf(logbuf + off, logbuf_len - off, fmt, args);
119 va_end(args);
120 ret = dev_printk(level, &scmd->device->sdev_gendev, "%s", logbuf);
121 scsi_log_release_buffer(logbuf);
122 return ret;
123}
124EXPORT_SYMBOL(scmd_printk);