| 1 | |
| 2 | |
| 3 | * Copyright © 2023 Intel Corporation
|
| 4 |
|
| 5 |
|
| 6 | #include "xe_devcoredump.h"
|
| 7 | #include "xe_devcoredump_types.h"
|
| 8 |
|
| 9 | #include <linux/ascii85.h>
|
| 10 | #include <linux/devcoredump.h>
|
| 11 | #include <linux/utsname.h>
|
| 12 |
|
| 13 | #include <drm/drm_managed.h>
|
| 14 |
|
| 15 | #include "xe_device.h"
|
| 16 | #include "xe_exec_queue.h"
|
| 17 | #include "xe_force_wake.h"
|
| 18 | #include "xe_gt_printk.h"
|
| 19 | #include "xe_gt_types.h"
|
| 20 | #include "xe_guc_capture.h"
|
| 21 | #include "xe_guc_ct.h"
|
| 22 | #include "xe_guc_log.h"
|
| 23 | #include "xe_guc_submit.h"
|
| 24 | #include "xe_hw_engine.h"
|
| 25 | #include "xe_pm.h"
|
| 26 | #include "xe_sched_job.h"
|
| 27 | #include "xe_vm.h"
|
| 28 |
|
| 29 | |
| 30 | * DOC: Xe device coredump
|
| 31 | *
|
| 32 | * Xe uses dev_coredump infrastructure for exposing the crash errors in a
|
| 33 | * standardized way. Once a crash occurs, devcoredump exposes a temporary
|
| 34 | * node under ``/sys/class/devcoredump/devcd<m>/``. The same node is also
|
| 35 | * accessible in ``/sys/class/drm/card<n>/device/devcoredump/``. The
|
| 36 | * ``failing_device`` symlink points to the device that crashed and created the
|
| 37 | * coredump.
|
| 38 | *
|
| 39 | * The following characteristics are observed by xe when creating a device
|
| 40 | * coredump:
|
| 41 | *
|
| 42 | * **Snapshot at hang**:
|
| 43 | * The 'data' file contains a snapshot of the HW and driver states at the time
|
| 44 | * the hang happened. Due to the driver recovering from resets/crashes, it may
|
| 45 | * not correspond to the state of the system when the file is read by
|
| 46 | * userspace.
|
| 47 | *
|
| 48 | * **Coredump release**:
|
| 49 | * After a coredump is generated, it stays in kernel memory until released by
|
| 50 | * userspace by writing anything to it, or after an internal timer expires. The
|
| 51 | * exact timeout may vary and should not be relied upon. Example to release
|
| 52 | * a coredump:
|
| 53 | *
|
| 54 | * .. code-block:: shell
|
| 55 | *
|
| 56 | * $ > /sys/class/drm/card0/device/devcoredump/data
|
| 57 | *
|
| 58 | * **First failure only**:
|
| 59 | * In general, the first hang is the most critical one since the following
|
| 60 | * hangs can be a consequence of the initial hang. For this reason a snapshot
|
| 61 | * is taken only for the first failure. Until the devcoredump is released by
|
| 62 | * userspace or kernel, all subsequent hangs do not override the snapshot nor
|
| 63 | * create new ones. Devcoredump has a delayed work queue that will eventually
|
| 64 | * delete the file node and free all the dump information.
|
| 65 |
|
| 66 |
|
| 67 | #ifdef CONFIG_DEV_COREDUMP
|
| 68 |
|
| 69 |
|
| 70 | #define XE_COREDUMP_TIMEOUT_JIFFIES (60 * 60 * HZ)
|
| 71 |
|
| 72 | static struct xe_device *coredump_to_xe(const struct xe_devcoredump *coredump)
|
| 73 | {
|
| 74 | return container_of(coredump, struct xe_device, devcoredump);
|
| 75 | }
|
| 76 |
|
| 77 | static struct xe_guc *exec_queue_to_guc(struct xe_exec_queue *q)
|
| 78 | {
|
| 79 | return &q->gt->uc.guc;
|
| 80 | }
|
| 81 |
|
| 82 | static ssize_t __xe_devcoredump_read(char *buffer, ssize_t count,
|
| 83 | ssize_t start,
|
| 84 | struct xe_devcoredump *coredump)
|
| 85 | {
|
| 86 | struct xe_device *xe;
|
| 87 | struct xe_devcoredump_snapshot *ss;
|
| 88 | struct drm_printer p;
|
| 89 | struct drm_print_iterator iter;
|
| 90 | struct timespec64 ts;
|
| 91 | int i;
|
| 92 |
|
| 93 | xe = coredump_to_xe(coredump);
|
| 94 | ss = &coredump->snapshot;
|
| 95 |
|
| 96 | iter.data = buffer;
|
| 97 | iter.start = start;
|
| 98 | iter.remain = count;
|
| 99 |
|
| 100 | p = drm_coredump_printer(&iter);
|
| 101 |
|
| 102 | drm_puts(&p, "**** Xe Device Coredump ****\n");
|
| 103 | drm_printf(&p, "Reason: %s\n", ss->reason);
|
| 104 | drm_printf(&p, "kernel: %s\n", init_utsname()->release);
|
| 105 | drm_puts(&p, "module: " KBUILD_MODNAME "\n");
|
| 106 |
|
| 107 | ts = ktime_to_timespec64(ss->snapshot_time);
|
| 108 | drm_printf(&p, "Snapshot time: %ptSp\n", &ts);
|
| 109 | ts = ktime_to_timespec64(ss->boot_time);
|
| 110 | drm_printf(&p, "Uptime: %ptSp\n", &ts);
|
| 111 | drm_printf(&p, "Process: %s [%d]\n", ss->process_name, ss->pid);
|
| 112 | xe_device_snapshot_print(xe, &p);
|
| 113 |
|
| 114 | drm_printf(&p, "\n**** GT #%d ****\n", ss->gt->info.id);
|
| 115 | drm_printf(&p, "\tTile: %d\n", ss->gt->tile->id);
|
| 116 |
|
| 117 | drm_puts(&p, "\n**** GuC Log ****\n");
|
| 118 | xe_guc_log_snapshot_print(ss->guc.log, &p);
|
| 119 | drm_puts(&p, "\n**** GuC CT ****\n");
|
| 120 | xe_guc_ct_snapshot_print(ss->guc.ct, &p);
|
| 121 |
|
| 122 | drm_puts(&p, "\n**** Contexts ****\n");
|
| 123 | xe_guc_exec_queue_snapshot_print(ss->ge, &p);
|
| 124 |
|
| 125 | drm_puts(&p, "\n**** Job ****\n");
|
| 126 | xe_sched_job_snapshot_print(ss->job, &p);
|
| 127 |
|
| 128 | drm_puts(&p, "\n**** HW Engines ****\n");
|
| 129 | for (i = 0; i < XE_NUM_HW_ENGINES; i++)
|
| 130 | if (ss->hwe[i])
|
| 131 | xe_engine_snapshot_print(ss->hwe[i], &p);
|
| 132 |
|
| 133 | drm_puts(&p, "\n**** VM state ****\n");
|
| 134 | xe_vm_snapshot_print(ss->vm, &p);
|
| 135 |
|
| 136 | return count - iter.remain;
|
| 137 | }
|
| 138 |
|
| 139 | static void xe_devcoredump_snapshot_free(struct xe_devcoredump_snapshot *ss)
|
| 140 | {
|
| 141 | int i;
|
| 142 |
|
| 143 | kfree(ss->reason);
|
| 144 | ss->reason = NULL;
|
| 145 |
|
| 146 | xe_guc_log_snapshot_free(ss->guc.log);
|
| 147 | ss->guc.log = NULL;
|
| 148 |
|
| 149 | xe_guc_ct_snapshot_free(ss->guc.ct);
|
| 150 | ss->guc.ct = NULL;
|
| 151 |
|
| 152 | if (!IS_ERR_OR_NULL(ss->gt))
|
| 153 | xe_guc_capture_put_matched_nodes(&ss->gt->uc.guc);
|
| 154 | ss->matched_node = NULL;
|
| 155 |
|
| 156 | xe_guc_exec_queue_snapshot_free(ss->ge);
|
| 157 | ss->ge = NULL;
|
| 158 |
|
| 159 | xe_sched_job_snapshot_free(ss->job);
|
| 160 | ss->job = NULL;
|
| 161 |
|
| 162 | for (i = 0; i < XE_NUM_HW_ENGINES; i++)
|
| 163 | if (ss->hwe[i]) {
|
| 164 | xe_hw_engine_snapshot_free(ss->hwe[i]);
|
| 165 | ss->hwe[i] = NULL;
|
| 166 | }
|
| 167 |
|
| 168 | xe_vm_snapshot_free(ss->vm);
|
| 169 | ss->vm = NULL;
|
| 170 | }
|
| 171 |
|
| 172 | #define XE_DEVCOREDUMP_CHUNK_MAX (SZ_512M + SZ_1G)
|
| 173 |
|
| 174 | |
| 175 | * xe_devcoredump_read() - Read data from the Xe device coredump snapshot
|
| 176 | * @buffer: Destination buffer to copy the coredump data into
|
| 177 | * @offset: Offset in the coredump data to start reading from
|
| 178 | * @count: Number of bytes to read
|
| 179 | * @data: Pointer to the xe_devcoredump structure
|
| 180 | * @datalen: Length of the data (unused)
|
| 181 | *
|
| 182 | * Reads a chunk of the coredump snapshot data into the provided buffer.
|
| 183 | * If the devcoredump is smaller than 1.5 GB (XE_DEVCOREDUMP_CHUNK_MAX),
|
| 184 | * it is read directly from a pre-written buffer. For larger devcoredumps,
|
| 185 | * the pre-written buffer must be periodically repopulated from the snapshot
|
| 186 | * state due to kmalloc size limitations.
|
| 187 | *
|
| 188 | * Return: Number of bytes copied on success, or a negative error code on failure.
|
| 189 |
|
| 190 | static ssize_t xe_devcoredump_read(char *buffer, loff_t offset,
|
| 191 | size_t count, void *data, size_t datalen)
|
| 192 | {
|
| 193 | struct xe_devcoredump *coredump = data;
|
| 194 | struct xe_devcoredump_snapshot *ss;
|
| 195 | ssize_t byte_copied = 0;
|
| 196 | u32 chunk_offset;
|
| 197 | ssize_t new_chunk_position;
|
| 198 | bool pm_needed = false;
|
| 199 | int ret = 0;
|
| 200 |
|
| 201 | if (!coredump)
|
| 202 | return -ENODEV;
|
| 203 |
|
| 204 | ss = &coredump->snapshot;
|
| 205 |
|
| 206 |
|
| 207 | flush_work(&ss->work);
|
| 208 |
|
| 209 | pm_needed = ss->read.size > XE_DEVCOREDUMP_CHUNK_MAX;
|
| 210 | if (pm_needed)
|
| 211 | xe_pm_runtime_get(gt_to_xe(ss->gt));
|
| 212 |
|
| 213 | mutex_lock(&coredump->lock);
|
| 214 |
|
| 215 | if (!ss->read.buffer) {
|
| 216 | ret = -ENODEV;
|
| 217 | goto unlock;
|
| 218 | }
|
| 219 |
|
| 220 | if (offset >= ss->read.size)
|
| 221 | goto unlock;
|
| 222 |
|
| 223 | new_chunk_position = div_u64_rem(offset,
|
| 224 | XE_DEVCOREDUMP_CHUNK_MAX,
|
| 225 | &chunk_offset);
|
| 226 |
|
| 227 | if (offset >= ss->read.chunk_position + XE_DEVCOREDUMP_CHUNK_MAX ||
|
| 228 | offset < ss->read.chunk_position) {
|
| 229 | ss->read.chunk_position = new_chunk_position *
|
| 230 | XE_DEVCOREDUMP_CHUNK_MAX;
|
| 231 |
|
| 232 | __xe_devcoredump_read(ss->read.buffer,
|
| 233 | XE_DEVCOREDUMP_CHUNK_MAX,
|
| 234 | ss->read.chunk_position, coredump);
|
| 235 | }
|
| 236 |
|
| 237 | byte_copied = count < ss->read.size - offset ? count :
|
| 238 | ss->read.size - offset;
|
| 239 | memcpy(buffer, ss->read.buffer + chunk_offset, byte_copied);
|
| 240 |
|
| 241 | unlock:
|
| 242 | mutex_unlock(&coredump->lock);
|
| 243 |
|
| 244 | if (pm_needed)
|
| 245 | xe_pm_runtime_put(gt_to_xe(ss->gt));
|
| 246 |
|
| 247 | return byte_copied ? byte_copied : ret;
|
| 248 | }
|
| 249 |
|
| 250 | static void xe_devcoredump_free(void *data)
|
| 251 | {
|
| 252 | struct xe_devcoredump *coredump = data;
|
| 253 |
|
| 254 |
|
| 255 | if (!data || !coredump_to_xe(coredump))
|
| 256 | return;
|
| 257 |
|
| 258 | if (coredump->captured)
|
| 259 | cancel_work_sync(&coredump->snapshot.work);
|
| 260 |
|
| 261 | mutex_lock(&coredump->lock);
|
| 262 |
|
| 263 | xe_devcoredump_snapshot_free(&coredump->snapshot);
|
| 264 | kvfree(coredump->snapshot.read.buffer);
|
| 265 |
|
| 266 |
|
| 267 | memset(&coredump->snapshot, 0, sizeof(coredump->snapshot));
|
| 268 | coredump->captured = false;
|
| 269 | drm_info(&coredump_to_xe(coredump)->drm,
|
| 270 | "Xe device coredump has been deleted.\n");
|
| 271 |
|
| 272 | mutex_unlock(&coredump->lock);
|
| 273 | }
|
| 274 |
|
| 275 | static void xe_devcoredump_deferred_snap_work(struct work_struct *work)
|
| 276 | {
|
| 277 | struct xe_devcoredump_snapshot *ss = container_of(work, typeof(*ss), work);
|
| 278 | struct xe_devcoredump *coredump = container_of(ss, typeof(*coredump), snapshot);
|
| 279 | struct xe_device *xe = coredump_to_xe(coredump);
|
| 280 |
|
| 281 | |
| 282 | * NB: Despite passing a GFP_ flags parameter here, more allocations are done
|
| 283 | * internally using GFP_KERNEL explicitly. Hence this call must be in the worker
|
| 284 | * thread and not in the initial capture call.
|
| 285 |
|
| 286 | dev_coredumpm_timeout(gt_to_xe(ss->gt)->drm.dev, THIS_MODULE, coredump, 0, GFP_KERNEL,
|
| 287 | xe_devcoredump_read, xe_devcoredump_free,
|
| 288 | XE_COREDUMP_TIMEOUT_JIFFIES);
|
| 289 |
|
| 290 | guard(xe_pm_runtime)(xe);
|
| 291 |
|
| 292 |
|
| 293 | xe_with_force_wake(fw_ref, gt_to_fw(ss->gt), XE_FORCEWAKE_ALL) {
|
| 294 | if (!xe_force_wake_ref_has_domain(fw_ref.domains, XE_FORCEWAKE_ALL))
|
| 295 | xe_gt_info(ss->gt, "failed to get forcewake for coredump capture\n");
|
| 296 | xe_vm_snapshot_capture_delayed(ss->vm);
|
| 297 | xe_guc_exec_queue_snapshot_capture_delayed(ss->ge);
|
| 298 | }
|
| 299 |
|
| 300 | ss->read.chunk_position = 0;
|
| 301 |
|
| 302 |
|
| 303 | ss->read.size = __xe_devcoredump_read(NULL, LONG_MAX, 0, coredump);
|
| 304 |
|
| 305 | if (ss->read.size > XE_DEVCOREDUMP_CHUNK_MAX) {
|
| 306 | ss->read.buffer = kvmalloc(XE_DEVCOREDUMP_CHUNK_MAX,
|
| 307 | GFP_USER);
|
| 308 | if (!ss->read.buffer)
|
| 309 | return;
|
| 310 |
|
| 311 | __xe_devcoredump_read(ss->read.buffer,
|
| 312 | XE_DEVCOREDUMP_CHUNK_MAX,
|
| 313 | 0, coredump);
|
| 314 | } else {
|
| 315 | ss->read.buffer = kvmalloc(ss->read.size, GFP_USER);
|
| 316 | if (!ss->read.buffer)
|
| 317 | return;
|
| 318 |
|
| 319 | __xe_devcoredump_read(ss->read.buffer, ss->read.size, 0,
|
| 320 | coredump);
|
| 321 | xe_devcoredump_snapshot_free(ss);
|
| 322 | }
|
| 323 | }
|
| 324 |
|
| 325 | static void devcoredump_snapshot(struct xe_devcoredump *coredump,
|
| 326 | struct xe_exec_queue *q,
|
| 327 | struct xe_sched_job *job)
|
| 328 | {
|
| 329 | struct xe_devcoredump_snapshot *ss = &coredump->snapshot;
|
| 330 | struct xe_guc *guc = exec_queue_to_guc(q);
|
| 331 | const char *process_name = "no process";
|
| 332 | bool cookie;
|
| 333 |
|
| 334 | ss->snapshot_time = ktime_get_real();
|
| 335 | ss->boot_time = ktime_get_boottime();
|
| 336 |
|
| 337 | if (q->vm && q->vm->xef) {
|
| 338 | process_name = q->vm->xef->process_name;
|
| 339 | ss->pid = q->vm->xef->pid;
|
| 340 | }
|
| 341 |
|
| 342 | strscpy(ss->process_name, process_name);
|
| 343 |
|
| 344 | ss->gt = q->gt;
|
| 345 | INIT_WORK(&ss->work, xe_devcoredump_deferred_snap_work);
|
| 346 |
|
| 347 |
|
| 348 | CLASS(xe_force_wake, fw_ref)(gt_to_fw(q->gt), XE_FORCEWAKE_ALL);
|
| 349 |
|
| 350 | cookie = dma_fence_begin_signalling();
|
| 351 |
|
| 352 | ss->guc.log = xe_guc_log_snapshot_capture(&guc->log, true);
|
| 353 | ss->guc.ct = xe_guc_ct_snapshot_capture(&guc->ct);
|
| 354 | ss->ge = xe_guc_exec_queue_snapshot_capture(q);
|
| 355 | if (job)
|
| 356 | ss->job = xe_sched_job_snapshot_capture(job);
|
| 357 | ss->vm = xe_vm_snapshot_capture(q->vm);
|
| 358 |
|
| 359 | xe_engine_snapshot_capture_for_queue(q);
|
| 360 |
|
| 361 | queue_work(system_dfl_wq, &ss->work);
|
| 362 |
|
| 363 | dma_fence_end_signalling(cookie);
|
| 364 | }
|
| 365 |
|
| 366 | |
| 367 | * xe_devcoredump - Take the required snapshots and initialize coredump device.
|
| 368 | * @q: The faulty xe_exec_queue, where the issue was detected.
|
| 369 | * @job: The faulty xe_sched_job, where the issue was detected.
|
| 370 | * @fmt: Printf format + args to describe the reason for the core dump
|
| 371 | *
|
| 372 | * This function should be called at the crash time within the serialized
|
| 373 | * gt_reset. It is skipped if we still have the core dump device available
|
| 374 | * with the information of the 'first' snapshot.
|
| 375 |
|
| 376 | __printf(3, 4)
|
| 377 | void xe_devcoredump(struct xe_exec_queue *q, struct xe_sched_job *job, const char *fmt, ...)
|
| 378 | {
|
| 379 | struct xe_device *xe = gt_to_xe(q->gt);
|
| 380 | struct xe_devcoredump *coredump = &xe->devcoredump;
|
| 381 | va_list varg;
|
| 382 |
|
| 383 | mutex_lock(&coredump->lock);
|
| 384 |
|
| 385 | if (coredump->captured) {
|
| 386 | drm_dbg(&xe->drm, "Multiple hangs are occurring, but only the first snapshot was taken\n");
|
| 387 | mutex_unlock(&coredump->lock);
|
| 388 | return;
|
| 389 | }
|
| 390 |
|
| 391 | coredump->captured = true;
|
| 392 |
|
| 393 | va_start(varg, fmt);
|
| 394 | coredump->snapshot.reason = kvasprintf(GFP_ATOMIC, fmt, varg);
|
| 395 | va_end(varg);
|
| 396 |
|
| 397 | devcoredump_snapshot(coredump, q, job);
|
| 398 |
|
| 399 | drm_info(&xe->drm, "Xe device coredump has been created\n");
|
| 400 | drm_info(&xe->drm, "Check your /sys/class/drm/card%d/device/devcoredump/data\n",
|
| 401 | xe->drm.primary->index);
|
| 402 |
|
| 403 | mutex_unlock(&coredump->lock);
|
| 404 | }
|
| 405 |
|
| 406 | static void xe_driver_devcoredump_fini(void *arg)
|
| 407 | {
|
| 408 | struct drm_device *drm = arg;
|
| 409 |
|
| 410 | dev_coredump_put(drm->dev);
|
| 411 | }
|
| 412 |
|
| 413 | int xe_devcoredump_init(struct xe_device *xe)
|
| 414 | {
|
| 415 | int err;
|
| 416 |
|
| 417 | err = drmm_mutex_init(&xe->drm, &xe->devcoredump.lock);
|
| 418 | if (err)
|
| 419 | return err;
|
| 420 |
|
| 421 | if (IS_ENABLED(CONFIG_LOCKDEP)) {
|
| 422 | fs_reclaim_acquire(GFP_KERNEL);
|
| 423 | might_lock(&xe->devcoredump.lock);
|
| 424 | fs_reclaim_release(GFP_KERNEL);
|
| 425 | }
|
| 426 |
|
| 427 | return devm_add_action_or_reset(xe->drm.dev, xe_driver_devcoredump_fini, &xe->drm);
|
| 428 | }
|
| 429 |
|
| 430 | #endif
|
| 431 |
|
| 432 | |
| 433 | * xe_print_blob_ascii85 - print a BLOB to some useful location in ASCII85
|
| 434 | *
|
| 435 | * The output is split into multiple calls to drm_puts() because some print
|
| 436 | * targets, e.g. dmesg, cannot handle arbitrarily long lines. These targets may
|
| 437 | * add newlines, as is the case with dmesg: each drm_puts() call creates a
|
| 438 | * separate line.
|
| 439 | *
|
| 440 | * There is also a scheduler yield call to prevent the 'task has been stuck for
|
| 441 | * 120s' kernel hang check feature from firing when printing to a slow target
|
| 442 | * such as dmesg over a serial port.
|
| 443 | *
|
| 444 | * @p: the printer object to output to
|
| 445 | * @prefix: optional prefix to add to output string
|
| 446 | * @suffix: optional suffix to add at the end. 0 disables it and is
|
| 447 | * not added to the output, which is useful when using multiple calls
|
| 448 | * to dump data to @p
|
| 449 | * @blob: the Binary Large OBject to dump out
|
| 450 | * @offset: offset in bytes to skip from the front of the BLOB, must be a multiple of sizeof(u32)
|
| 451 | * @size: the size in bytes of the BLOB, must be a multiple of sizeof(u32)
|
| 452 |
|
| 453 | void xe_print_blob_ascii85(struct drm_printer *p, const char *prefix, char suffix,
|
| 454 | const void *blob, size_t offset, size_t size)
|
| 455 | {
|
| 456 | const u32 *blob32 = (const u32 *)blob;
|
| 457 | char buff[ASCII85_BUFSZ], *line_buff;
|
| 458 | size_t line_pos = 0;
|
| 459 |
|
| 460 | #define DMESG_MAX_LINE_LEN 800
|
| 461 |
|
| 462 | #define MIN_SPACE (ASCII85_BUFSZ + 2)
|
| 463 |
|
| 464 | if (size & 3)
|
| 465 | drm_printf(p, "Size not word aligned: %zu", size);
|
| 466 | if (offset & 3)
|
| 467 | drm_printf(p, "Offset not word aligned: %zu", offset);
|
| 468 |
|
| 469 | line_buff = kzalloc(DMESG_MAX_LINE_LEN, GFP_ATOMIC);
|
| 470 | if (!line_buff) {
|
| 471 | drm_printf(p, "Failed to allocate line buffer\n");
|
| 472 | return;
|
| 473 | }
|
| 474 |
|
| 475 | blob32 += offset / sizeof(*blob32);
|
| 476 | size /= sizeof(*blob32);
|
| 477 |
|
| 478 | if (prefix) {
|
| 479 | strscpy(line_buff, prefix, DMESG_MAX_LINE_LEN - MIN_SPACE - 2);
|
| 480 | line_pos = strlen(line_buff);
|
| 481 |
|
| 482 | line_buff[line_pos++] = ':';
|
| 483 | line_buff[line_pos++] = ' ';
|
| 484 | }
|
| 485 |
|
| 486 | while (size--) {
|
| 487 | u32 val = *(blob32++);
|
| 488 |
|
| 489 | strscpy(line_buff + line_pos, ascii85_encode(val, buff),
|
| 490 | DMESG_MAX_LINE_LEN - line_pos);
|
| 491 | line_pos += strlen(line_buff + line_pos);
|
| 492 |
|
| 493 | if ((line_pos + MIN_SPACE) >= DMESG_MAX_LINE_LEN) {
|
| 494 | line_buff[line_pos++] = 0;
|
| 495 |
|
| 496 | drm_puts(p, line_buff);
|
| 497 |
|
| 498 | line_pos = 0;
|
| 499 |
|
| 500 |
|
| 501 | cond_resched();
|
| 502 | }
|
| 503 | }
|
| 504 |
|
| 505 | if (suffix)
|
| 506 | line_buff[line_pos++] = suffix;
|
| 507 |
|
| 508 | if (line_pos) {
|
| 509 | line_buff[line_pos++] = 0;
|
| 510 | drm_puts(p, line_buff);
|
| 511 | }
|
| 512 |
|
| 513 | kfree(line_buff);
|
| 514 |
|
| 515 | #undef MIN_SPACE
|
| 516 | #undef DMESG_MAX_LINE_LEN
|
| 517 | }
|