.. | .. |
---|
6 | 6 | #include <linux/errno.h> |
---|
7 | 7 | #include <linux/init.h> |
---|
8 | 8 | #include <linux/slab.h> |
---|
| 9 | +#include <linux/spinlock.h> |
---|
| 10 | +#include <linux/uaccess.h> |
---|
| 11 | +#include <linux/miscdevice.h> |
---|
| 12 | +#include <linux/list.h> |
---|
| 13 | +#include <linux/slab.h> |
---|
| 14 | +#include <linux/fs.h> |
---|
| 15 | +#include <linux/mm.h> |
---|
| 16 | +#include <linux/mman.h> |
---|
| 17 | +#include <linux/device.h> |
---|
9 | 18 | |
---|
10 | | -struct clocksource_mmio { |
---|
11 | | - void __iomem *reg; |
---|
12 | | - struct clocksource clksrc; |
---|
| 19 | +struct clocksource_user_mapping { |
---|
| 20 | + struct mm_struct *mm; |
---|
| 21 | + struct clocksource_user_mmio *ucs; |
---|
| 22 | + void *regs; |
---|
| 23 | + struct hlist_node link; |
---|
| 24 | + atomic_t refs; |
---|
13 | 25 | }; |
---|
| 26 | + |
---|
| 27 | +static struct class *user_mmio_class; |
---|
| 28 | +static dev_t user_mmio_devt; |
---|
| 29 | + |
---|
| 30 | +static DEFINE_SPINLOCK(user_clksrcs_lock); |
---|
| 31 | +static unsigned int user_clksrcs_count; |
---|
| 32 | +static LIST_HEAD(user_clksrcs); |
---|
14 | 33 | |
---|
15 | 34 | static inline struct clocksource_mmio *to_mmio_clksrc(struct clocksource *c) |
---|
16 | 35 | { |
---|
.. | .. |
---|
38 | 57 | return ~(u64)readw_relaxed(to_mmio_clksrc(c)->reg) & c->mask; |
---|
39 | 58 | } |
---|
40 | 59 | |
---|
| 60 | +static inline struct clocksource_user_mmio * |
---|
| 61 | +to_mmio_ucs(struct clocksource *c) |
---|
| 62 | +{ |
---|
| 63 | + return container_of(c, struct clocksource_user_mmio, mmio.clksrc); |
---|
| 64 | +} |
---|
| 65 | + |
---|
| 66 | +u64 clocksource_dual_mmio_readl_up(struct clocksource *c) |
---|
| 67 | +{ |
---|
| 68 | + struct clocksource_user_mmio *ucs = to_mmio_ucs(c); |
---|
| 69 | + u32 upper, old_upper, lower; |
---|
| 70 | + |
---|
| 71 | + upper = readl_relaxed(ucs->reg_upper); |
---|
| 72 | + do { |
---|
| 73 | + old_upper = upper; |
---|
| 74 | + lower = readl_relaxed(ucs->mmio.reg); |
---|
| 75 | + upper = readl_relaxed(ucs->reg_upper); |
---|
| 76 | + } while (upper != old_upper); |
---|
| 77 | + |
---|
| 78 | + return (((u64)upper) << ucs->bits_lower) | lower; |
---|
| 79 | +} |
---|
| 80 | + |
---|
| 81 | +u64 clocksource_dual_mmio_readw_up(struct clocksource *c) |
---|
| 82 | +{ |
---|
| 83 | + struct clocksource_user_mmio *ucs = to_mmio_ucs(c); |
---|
| 84 | + u16 upper, old_upper, lower; |
---|
| 85 | + |
---|
| 86 | + upper = readw_relaxed(ucs->reg_upper); |
---|
| 87 | + do { |
---|
| 88 | + old_upper = upper; |
---|
| 89 | + lower = readw_relaxed(ucs->mmio.reg); |
---|
| 90 | + upper = readw_relaxed(ucs->reg_upper); |
---|
| 91 | + } while (upper != old_upper); |
---|
| 92 | + |
---|
| 93 | + return (((u64)upper) << ucs->bits_lower) | lower; |
---|
| 94 | +} |
---|
| 95 | + |
---|
| 96 | +static void mmio_base_init(const char *name,int rating, unsigned int bits, |
---|
| 97 | + u64 (*read)(struct clocksource *), |
---|
| 98 | + struct clocksource *cs) |
---|
| 99 | +{ |
---|
| 100 | + cs->name = name; |
---|
| 101 | + cs->rating = rating; |
---|
| 102 | + cs->read = read; |
---|
| 103 | + cs->mask = CLOCKSOURCE_MASK(bits); |
---|
| 104 | + cs->flags = CLOCK_SOURCE_IS_CONTINUOUS; |
---|
| 105 | +} |
---|
| 106 | + |
---|
41 | 107 | /** |
---|
42 | 108 | * clocksource_mmio_init - Initialize a simple mmio based clocksource |
---|
43 | 109 | * @base: Virtual address of the clock readout register |
---|
.. | .. |
---|
52 | 118 | u64 (*read)(struct clocksource *)) |
---|
53 | 119 | { |
---|
54 | 120 | struct clocksource_mmio *cs; |
---|
| 121 | + int err; |
---|
55 | 122 | |
---|
56 | 123 | if (bits > 64 || bits < 16) |
---|
57 | 124 | return -EINVAL; |
---|
.. | .. |
---|
61 | 128 | return -ENOMEM; |
---|
62 | 129 | |
---|
63 | 130 | cs->reg = base; |
---|
64 | | - cs->clksrc.name = name; |
---|
65 | | - cs->clksrc.rating = rating; |
---|
66 | | - cs->clksrc.read = read; |
---|
67 | | - cs->clksrc.mask = CLOCKSOURCE_MASK(bits); |
---|
68 | | - cs->clksrc.flags = CLOCK_SOURCE_IS_CONTINUOUS; |
---|
| 131 | + mmio_base_init(name, rating, bits, read, &cs->clksrc); |
---|
69 | 132 | |
---|
70 | | - return clocksource_register_hz(&cs->clksrc, hz); |
---|
| 133 | + err = clocksource_register_hz(&cs->clksrc, hz); |
---|
| 134 | + if (err < 0) { |
---|
| 135 | + kfree(cs); |
---|
| 136 | + return err; |
---|
| 137 | + } |
---|
| 138 | + |
---|
| 139 | + return err; |
---|
71 | 140 | } |
---|
72 | | -EXPORT_SYMBOL_GPL(clocksource_mmio_init); |
---|
| 141 | + |
---|
| 142 | +static void mmio_ucs_vmopen(struct vm_area_struct *vma) |
---|
| 143 | +{ |
---|
| 144 | + struct clocksource_user_mapping *mapping, *clone; |
---|
| 145 | + struct clocksource_user_mmio *ucs; |
---|
| 146 | + unsigned long h_key; |
---|
| 147 | + |
---|
| 148 | + mapping = vma->vm_private_data; |
---|
| 149 | + |
---|
| 150 | + if (mapping->mm == vma->vm_mm) { |
---|
| 151 | + atomic_inc(&mapping->refs); |
---|
| 152 | + } else if (mapping->mm) { |
---|
| 153 | + /* |
---|
| 154 | + * We must be duplicating the original mm upon fork(), |
---|
| 155 | + * clone the parent ucs mapping struct then rehash it |
---|
| 156 | + * on the child mm key. If we cannot get memory for |
---|
| 157 | + * this, mitigate the issue for users by preventing a |
---|
| 158 | + * stale parent mm from being matched later on by a |
---|
| 159 | + * process which reused its mm_struct (h_key is based |
---|
| 160 | + * on this struct address). |
---|
| 161 | + */ |
---|
| 162 | + clone = kmalloc(sizeof(*mapping), GFP_KERNEL); |
---|
| 163 | + if (clone == NULL) { |
---|
| 164 | + pr_alert("out-of-memory for UCS mapping!\n"); |
---|
| 165 | + atomic_inc(&mapping->refs); |
---|
| 166 | + mapping->mm = NULL; |
---|
| 167 | + return; |
---|
| 168 | + } |
---|
| 169 | + ucs = mapping->ucs; |
---|
| 170 | + clone->mm = vma->vm_mm; |
---|
| 171 | + clone->ucs = ucs; |
---|
| 172 | + clone->regs = mapping->regs; |
---|
| 173 | + atomic_set(&clone->refs, 1); |
---|
| 174 | + vma->vm_private_data = clone; |
---|
| 175 | + h_key = (unsigned long)vma->vm_mm / sizeof(*vma->vm_mm); |
---|
| 176 | + spin_lock(&ucs->lock); |
---|
| 177 | + hash_add(ucs->mappings, &clone->link, h_key); |
---|
| 178 | + spin_unlock(&ucs->lock); |
---|
| 179 | + } |
---|
| 180 | +} |
---|
| 181 | + |
---|
| 182 | +static void mmio_ucs_vmclose(struct vm_area_struct *vma) |
---|
| 183 | +{ |
---|
| 184 | + struct clocksource_user_mapping *mapping; |
---|
| 185 | + |
---|
| 186 | + mapping = vma->vm_private_data; |
---|
| 187 | + |
---|
| 188 | + if (atomic_dec_and_test(&mapping->refs)) { |
---|
| 189 | + spin_lock(&mapping->ucs->lock); |
---|
| 190 | + hash_del(&mapping->link); |
---|
| 191 | + spin_unlock(&mapping->ucs->lock); |
---|
| 192 | + kfree(mapping); |
---|
| 193 | + } |
---|
| 194 | +} |
---|
| 195 | + |
---|
| 196 | +static const struct vm_operations_struct mmio_ucs_vmops = { |
---|
| 197 | + .open = mmio_ucs_vmopen, |
---|
| 198 | + .close = mmio_ucs_vmclose, |
---|
| 199 | +}; |
---|
| 200 | + |
---|
| 201 | +static int mmio_ucs_mmap(struct file *file, struct vm_area_struct *vma) |
---|
| 202 | +{ |
---|
| 203 | + unsigned long addr, upper_pfn, lower_pfn; |
---|
| 204 | + struct clocksource_user_mapping *mapping, *tmp; |
---|
| 205 | + struct clocksource_user_mmio *ucs; |
---|
| 206 | + unsigned int bits_upper; |
---|
| 207 | + unsigned long h_key; |
---|
| 208 | + pgprot_t prot; |
---|
| 209 | + size_t pages; |
---|
| 210 | + int err; |
---|
| 211 | + |
---|
| 212 | + pages = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT; |
---|
| 213 | + if (pages > 2) |
---|
| 214 | + return -EINVAL; |
---|
| 215 | + |
---|
| 216 | + vma->vm_private_data = NULL; |
---|
| 217 | + |
---|
| 218 | + ucs = file->private_data; |
---|
| 219 | + upper_pfn = ucs->phys_upper >> PAGE_SHIFT; |
---|
| 220 | + lower_pfn = ucs->phys_lower >> PAGE_SHIFT; |
---|
| 221 | + bits_upper = fls(ucs->mmio.clksrc.mask) - ucs->bits_lower; |
---|
| 222 | + if (pages == 2 && (!bits_upper || upper_pfn == lower_pfn)) |
---|
| 223 | + return -EINVAL; |
---|
| 224 | + |
---|
| 225 | + mapping = kmalloc(sizeof(*mapping), GFP_KERNEL); |
---|
| 226 | + if (!mapping) |
---|
| 227 | + return -ENOSPC; |
---|
| 228 | + |
---|
| 229 | + mapping->mm = vma->vm_mm; |
---|
| 230 | + mapping->ucs = ucs; |
---|
| 231 | + mapping->regs = (void *)vma->vm_start; |
---|
| 232 | + atomic_set(&mapping->refs, 1); |
---|
| 233 | + |
---|
| 234 | + vma->vm_private_data = mapping; |
---|
| 235 | + vma->vm_ops = &mmio_ucs_vmops; |
---|
| 236 | + prot = pgprot_noncached(vma->vm_page_prot); |
---|
| 237 | + addr = vma->vm_start; |
---|
| 238 | + |
---|
| 239 | + err = remap_pfn_range(vma, addr, lower_pfn, PAGE_SIZE, prot); |
---|
| 240 | + if (err < 0) |
---|
| 241 | + goto fail; |
---|
| 242 | + |
---|
| 243 | + if (pages > 1) { |
---|
| 244 | + addr += PAGE_SIZE; |
---|
| 245 | + err = remap_pfn_range(vma, addr, upper_pfn, PAGE_SIZE, prot); |
---|
| 246 | + if (err < 0) |
---|
| 247 | + goto fail; |
---|
| 248 | + } |
---|
| 249 | + |
---|
| 250 | + h_key = (unsigned long)vma->vm_mm / sizeof(*vma->vm_mm); |
---|
| 251 | + |
---|
| 252 | + spin_lock(&ucs->lock); |
---|
| 253 | + hash_for_each_possible(ucs->mappings, tmp, link, h_key) { |
---|
| 254 | + if (tmp->mm == vma->vm_mm) { |
---|
| 255 | + spin_unlock(&ucs->lock); |
---|
| 256 | + err = -EBUSY; |
---|
| 257 | + goto fail; |
---|
| 258 | + } |
---|
| 259 | + } |
---|
| 260 | + hash_add(ucs->mappings, &mapping->link, h_key); |
---|
| 261 | + spin_unlock(&ucs->lock); |
---|
| 262 | + |
---|
| 263 | + return 0; |
---|
| 264 | +fail: |
---|
| 265 | + kfree(mapping); |
---|
| 266 | + |
---|
| 267 | + return err; |
---|
| 268 | +} |
---|
| 269 | + |
---|
| 270 | +static long |
---|
| 271 | +mmio_ucs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
---|
| 272 | +{ |
---|
| 273 | + struct clocksource_user_mapping *mapping; |
---|
| 274 | + struct clksrc_user_mmio_info __user *u; |
---|
| 275 | + unsigned long upper_pfn, lower_pfn; |
---|
| 276 | + struct clksrc_user_mmio_info info; |
---|
| 277 | + struct clocksource_user_mmio *ucs; |
---|
| 278 | + unsigned int bits_upper; |
---|
| 279 | + void __user *map_base; |
---|
| 280 | + unsigned long h_key; |
---|
| 281 | + size_t size; |
---|
| 282 | + |
---|
| 283 | + u = (struct clksrc_user_mmio_info __user *)arg; |
---|
| 284 | + |
---|
| 285 | + switch (cmd) { |
---|
| 286 | + case CLKSRC_USER_MMIO_MAP: |
---|
| 287 | + break; |
---|
| 288 | + default: |
---|
| 289 | + return -ENOTTY; |
---|
| 290 | + } |
---|
| 291 | + |
---|
| 292 | + h_key = (unsigned long)current->mm / sizeof(*current->mm); |
---|
| 293 | + |
---|
| 294 | + ucs = file->private_data; |
---|
| 295 | + upper_pfn = ucs->phys_upper >> PAGE_SHIFT; |
---|
| 296 | + lower_pfn = ucs->phys_lower >> PAGE_SHIFT; |
---|
| 297 | + bits_upper = fls(ucs->mmio.clksrc.mask) - ucs->bits_lower; |
---|
| 298 | + size = PAGE_SIZE; |
---|
| 299 | + if (bits_upper && upper_pfn != lower_pfn) |
---|
| 300 | + size += PAGE_SIZE; |
---|
| 301 | + |
---|
| 302 | + do { |
---|
| 303 | + spin_lock(&ucs->lock); |
---|
| 304 | + hash_for_each_possible(ucs->mappings, mapping, link, h_key) { |
---|
| 305 | + if (mapping->mm == current->mm) { |
---|
| 306 | + spin_unlock(&ucs->lock); |
---|
| 307 | + map_base = mapping->regs; |
---|
| 308 | + goto found; |
---|
| 309 | + } |
---|
| 310 | + } |
---|
| 311 | + spin_unlock(&ucs->lock); |
---|
| 312 | + |
---|
| 313 | + map_base = (void *) |
---|
| 314 | + vm_mmap(file, 0, size, PROT_READ, MAP_SHARED, 0); |
---|
| 315 | + } while (IS_ERR(map_base) && PTR_ERR(map_base) == -EBUSY); |
---|
| 316 | + |
---|
| 317 | + if (IS_ERR(map_base)) |
---|
| 318 | + return PTR_ERR(map_base); |
---|
| 319 | + |
---|
| 320 | +found: |
---|
| 321 | + info.type = ucs->type; |
---|
| 322 | + info.reg_lower = map_base + offset_in_page(ucs->phys_lower); |
---|
| 323 | + info.mask_lower = ucs->mmio.clksrc.mask; |
---|
| 324 | + info.bits_lower = ucs->bits_lower; |
---|
| 325 | + info.reg_upper = NULL; |
---|
| 326 | + if (ucs->phys_upper) |
---|
| 327 | + info.reg_upper = map_base + (size - PAGE_SIZE) |
---|
| 328 | + + offset_in_page(ucs->phys_upper); |
---|
| 329 | + info.mask_upper = ucs->mask_upper; |
---|
| 330 | + |
---|
| 331 | + return copy_to_user(u, &info, sizeof(*u)); |
---|
| 332 | +} |
---|
| 333 | + |
---|
| 334 | +static int mmio_ucs_open(struct inode *inode, struct file *file) |
---|
| 335 | +{ |
---|
| 336 | + struct clocksource_user_mmio *ucs; |
---|
| 337 | + |
---|
| 338 | + if (file->f_mode & FMODE_WRITE) |
---|
| 339 | + return -EINVAL; |
---|
| 340 | + |
---|
| 341 | + ucs = container_of(inode->i_cdev, typeof(*ucs), cdev); |
---|
| 342 | + file->private_data = ucs; |
---|
| 343 | + |
---|
| 344 | + return 0; |
---|
| 345 | +} |
---|
| 346 | + |
---|
| 347 | +static const struct file_operations mmio_ucs_fops = { |
---|
| 348 | + .owner = THIS_MODULE, |
---|
| 349 | + .unlocked_ioctl = mmio_ucs_ioctl, |
---|
| 350 | + .open = mmio_ucs_open, |
---|
| 351 | + .mmap = mmio_ucs_mmap, |
---|
| 352 | +}; |
---|
| 353 | + |
---|
| 354 | +static int __init |
---|
| 355 | +ucs_create_cdev(struct class *class, struct clocksource_user_mmio *ucs) |
---|
| 356 | +{ |
---|
| 357 | + int err; |
---|
| 358 | + |
---|
| 359 | + ucs->dev = device_create(class, NULL, |
---|
| 360 | + MKDEV(MAJOR(user_mmio_devt), ucs->id), |
---|
| 361 | + ucs, "ucs/%d", ucs->id); |
---|
| 362 | + if (IS_ERR(ucs->dev)) |
---|
| 363 | + return PTR_ERR(ucs->dev); |
---|
| 364 | + |
---|
| 365 | + spin_lock_init(&ucs->lock); |
---|
| 366 | + hash_init(ucs->mappings); |
---|
| 367 | + |
---|
| 368 | + cdev_init(&ucs->cdev, &mmio_ucs_fops); |
---|
| 369 | + ucs->cdev.kobj.parent = &ucs->dev->kobj; |
---|
| 370 | + |
---|
| 371 | + err = cdev_add(&ucs->cdev, ucs->dev->devt, 1); |
---|
| 372 | + if (err < 0) |
---|
| 373 | + goto err_device_destroy; |
---|
| 374 | + |
---|
| 375 | + return 0; |
---|
| 376 | + |
---|
| 377 | +err_device_destroy: |
---|
| 378 | + device_destroy(class, MKDEV(MAJOR(user_mmio_devt), ucs->id)); |
---|
| 379 | + return err; |
---|
| 380 | +} |
---|
| 381 | + |
---|
| 382 | +static unsigned long default_revmap(void *virt) |
---|
| 383 | +{ |
---|
| 384 | + struct vm_struct *vm; |
---|
| 385 | + |
---|
| 386 | + vm = find_vm_area(virt); |
---|
| 387 | + if (!vm) |
---|
| 388 | + return 0; |
---|
| 389 | + |
---|
| 390 | + return vm->phys_addr + (virt - vm->addr); |
---|
| 391 | +} |
---|
| 392 | + |
---|
| 393 | +int __init clocksource_user_mmio_init(struct clocksource_user_mmio *ucs, |
---|
| 394 | + const struct clocksource_mmio_regs *regs, |
---|
| 395 | + unsigned long hz) |
---|
| 396 | +{ |
---|
| 397 | + static u64 (*user_types[CLKSRC_MMIO_TYPE_NR])(struct clocksource *) = { |
---|
| 398 | + [CLKSRC_MMIO_L_UP] = clocksource_mmio_readl_up, |
---|
| 399 | + [CLKSRC_MMIO_L_DOWN] = clocksource_mmio_readl_down, |
---|
| 400 | + [CLKSRC_DMMIO_L_UP] = clocksource_dual_mmio_readl_up, |
---|
| 401 | + [CLKSRC_MMIO_W_UP] = clocksource_mmio_readw_up, |
---|
| 402 | + [CLKSRC_MMIO_W_DOWN] = clocksource_mmio_readw_down, |
---|
| 403 | + [CLKSRC_DMMIO_W_UP] = clocksource_dual_mmio_readw_up, |
---|
| 404 | + }; |
---|
| 405 | + const char *name = ucs->mmio.clksrc.name; |
---|
| 406 | + unsigned long phys_upper = 0, phys_lower; |
---|
| 407 | + enum clksrc_user_mmio_type type; |
---|
| 408 | + unsigned long (*revmap)(void *); |
---|
| 409 | + int err; |
---|
| 410 | + |
---|
| 411 | + if (regs->bits_lower > 32 || regs->bits_lower < 16 || |
---|
| 412 | + regs->bits_upper > 32) |
---|
| 413 | + return -EINVAL; |
---|
| 414 | + |
---|
| 415 | + for (type = 0; type < ARRAY_SIZE(user_types); type++) |
---|
| 416 | + if (ucs->mmio.clksrc.read == user_types[type]) |
---|
| 417 | + break; |
---|
| 418 | + |
---|
| 419 | + if (type == ARRAY_SIZE(user_types)) |
---|
| 420 | + return -EINVAL; |
---|
| 421 | + |
---|
| 422 | + if (!(ucs->mmio.clksrc.flags & CLOCK_SOURCE_IS_CONTINUOUS)) |
---|
| 423 | + return -EINVAL; |
---|
| 424 | + |
---|
| 425 | + revmap = regs->revmap; |
---|
| 426 | + if (!revmap) |
---|
| 427 | + revmap = default_revmap; |
---|
| 428 | + |
---|
| 429 | + phys_lower = revmap(regs->reg_lower); |
---|
| 430 | + if (!phys_lower) |
---|
| 431 | + return -EINVAL; |
---|
| 432 | + |
---|
| 433 | + if (regs->bits_upper) { |
---|
| 434 | + phys_upper = revmap(regs->reg_upper); |
---|
| 435 | + if (!phys_upper) |
---|
| 436 | + return -EINVAL; |
---|
| 437 | + } |
---|
| 438 | + |
---|
| 439 | + ucs->mmio.reg = regs->reg_lower; |
---|
| 440 | + ucs->type = type; |
---|
| 441 | + ucs->bits_lower = regs->bits_lower; |
---|
| 442 | + ucs->reg_upper = regs->reg_upper; |
---|
| 443 | + ucs->mask_lower = CLOCKSOURCE_MASK(regs->bits_lower); |
---|
| 444 | + ucs->mask_upper = CLOCKSOURCE_MASK(regs->bits_upper); |
---|
| 445 | + ucs->phys_lower = phys_lower; |
---|
| 446 | + ucs->phys_upper = phys_upper; |
---|
| 447 | + spin_lock_init(&ucs->lock); |
---|
| 448 | + |
---|
| 449 | + err = clocksource_register_hz(&ucs->mmio.clksrc, hz); |
---|
| 450 | + if (err < 0) |
---|
| 451 | + return err; |
---|
| 452 | + |
---|
| 453 | + spin_lock(&user_clksrcs_lock); |
---|
| 454 | + |
---|
| 455 | + ucs->id = user_clksrcs_count++; |
---|
| 456 | + if (ucs->id < CLKSRC_USER_MMIO_MAX) |
---|
| 457 | + list_add_tail(&ucs->link, &user_clksrcs); |
---|
| 458 | + |
---|
| 459 | + spin_unlock(&user_clksrcs_lock); |
---|
| 460 | + |
---|
| 461 | + if (ucs->id >= CLKSRC_USER_MMIO_MAX) { |
---|
| 462 | + pr_warn("%s: Too many clocksources\n", name); |
---|
| 463 | + err = -EAGAIN; |
---|
| 464 | + goto fail; |
---|
| 465 | + } |
---|
| 466 | + |
---|
| 467 | + ucs->mmio.clksrc.vdso_type = CLOCKSOURCE_VDSO_MMIO + ucs->id; |
---|
| 468 | + |
---|
| 469 | + if (user_mmio_class) { |
---|
| 470 | + err = ucs_create_cdev(user_mmio_class, ucs); |
---|
| 471 | + if (err < 0) { |
---|
| 472 | + pr_warn("%s: Failed to add character device\n", name); |
---|
| 473 | + goto fail; |
---|
| 474 | + } |
---|
| 475 | + } |
---|
| 476 | + |
---|
| 477 | + return 0; |
---|
| 478 | + |
---|
| 479 | +fail: |
---|
| 480 | + clocksource_unregister(&ucs->mmio.clksrc); |
---|
| 481 | + |
---|
| 482 | + return err; |
---|
| 483 | +} |
---|
| 484 | + |
---|
| 485 | +int __init clocksource_user_single_mmio_init( |
---|
| 486 | + void __iomem *base, const char *name, |
---|
| 487 | + unsigned long hz, int rating, unsigned int bits, |
---|
| 488 | + u64 (*read)(struct clocksource *)) |
---|
| 489 | +{ |
---|
| 490 | + struct clocksource_user_mmio *ucs; |
---|
| 491 | + struct clocksource_mmio_regs regs; |
---|
| 492 | + int ret; |
---|
| 493 | + |
---|
| 494 | + ucs = kzalloc(sizeof(*ucs), GFP_KERNEL); |
---|
| 495 | + if (!ucs) |
---|
| 496 | + return -ENOMEM; |
---|
| 497 | + |
---|
| 498 | + mmio_base_init(name, rating, bits, read, &ucs->mmio.clksrc); |
---|
| 499 | + regs.reg_lower = base; |
---|
| 500 | + regs.reg_upper = NULL; |
---|
| 501 | + regs.bits_lower = bits; |
---|
| 502 | + regs.bits_upper = 0; |
---|
| 503 | + regs.revmap = NULL; |
---|
| 504 | + |
---|
| 505 | + ret = clocksource_user_mmio_init(ucs, ®s, hz); |
---|
| 506 | + if (ret) |
---|
| 507 | + kfree(ucs); |
---|
| 508 | + |
---|
| 509 | + return ret; |
---|
| 510 | +} |
---|
| 511 | + |
---|
| 512 | +static int __init mmio_clksrc_chr_dev_init(void) |
---|
| 513 | +{ |
---|
| 514 | + struct clocksource_user_mmio *ucs; |
---|
| 515 | + struct class *class; |
---|
| 516 | + int err; |
---|
| 517 | + |
---|
| 518 | + class = class_create(THIS_MODULE, "mmio_ucs"); |
---|
| 519 | + if (IS_ERR(class)) { |
---|
| 520 | + pr_err("couldn't create user mmio clocksources class\n"); |
---|
| 521 | + return PTR_ERR(class); |
---|
| 522 | + } |
---|
| 523 | + |
---|
| 524 | + err = alloc_chrdev_region(&user_mmio_devt, 0, CLKSRC_USER_MMIO_MAX, |
---|
| 525 | + "mmio_ucs"); |
---|
| 526 | + if (err < 0) { |
---|
| 527 | + pr_err("failed to allocate user mmio clocksources character devivces region\n"); |
---|
| 528 | + goto err_class_destroy; |
---|
| 529 | + } |
---|
| 530 | + |
---|
| 531 | + /* |
---|
| 532 | + * Calling list_for_each_entry is safe here: clocksources are always |
---|
| 533 | + * added to the list tail, never removed. |
---|
| 534 | + */ |
---|
| 535 | + spin_lock(&user_clksrcs_lock); |
---|
| 536 | + list_for_each_entry(ucs, &user_clksrcs, link) { |
---|
| 537 | + spin_unlock(&user_clksrcs_lock); |
---|
| 538 | + |
---|
| 539 | + err = ucs_create_cdev(class, ucs); |
---|
| 540 | + if (err < 0) |
---|
| 541 | + pr_err("%s: Failed to add character device\n", |
---|
| 542 | + ucs->mmio.clksrc.name); |
---|
| 543 | + |
---|
| 544 | + spin_lock(&user_clksrcs_lock); |
---|
| 545 | + } |
---|
| 546 | + user_mmio_class = class; |
---|
| 547 | + spin_unlock(&user_clksrcs_lock); |
---|
| 548 | + |
---|
| 549 | + return 0; |
---|
| 550 | + |
---|
| 551 | +err_class_destroy: |
---|
| 552 | + class_destroy(class); |
---|
| 553 | + return err; |
---|
| 554 | +} |
---|
| 555 | +device_initcall(mmio_clksrc_chr_dev_init); |
---|