xref: /linux/drivers/gpu/drm/drm_lease.c (revision 2ed077e467eedb033032bc4b6e349365517662d6)
1*2ed077e4SKeith Packard /*
2*2ed077e4SKeith Packard  * Copyright © 2017 Keith Packard <keithp@keithp.com>
3*2ed077e4SKeith Packard  *
4*2ed077e4SKeith Packard  * This program is free software; you can redistribute it and/or modify
5*2ed077e4SKeith Packard  * it under the terms of the GNU General Public License as published by
6*2ed077e4SKeith Packard  * the Free Software Foundation, either version 2 of the License, or
7*2ed077e4SKeith Packard  * (at your option) any later version.
8*2ed077e4SKeith Packard  *
9*2ed077e4SKeith Packard  * This program is distributed in the hope that it will be useful, but
10*2ed077e4SKeith Packard  * WITHOUT ANY WARRANTY; without even the implied warranty of
11*2ed077e4SKeith Packard  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12*2ed077e4SKeith Packard  * General Public License for more details.
13*2ed077e4SKeith Packard  */
14*2ed077e4SKeith Packard 
15*2ed077e4SKeith Packard #include <drm/drmP.h>
16*2ed077e4SKeith Packard #include "drm_internal.h"
17*2ed077e4SKeith Packard #include "drm_legacy.h"
18*2ed077e4SKeith Packard #include "drm_crtc_internal.h"
19*2ed077e4SKeith Packard #include <drm/drm_lease.h>
20*2ed077e4SKeith Packard #include <drm/drm_auth.h>
21*2ed077e4SKeith Packard #include <drm/drm_crtc_helper.h>
22*2ed077e4SKeith Packard 
23*2ed077e4SKeith Packard #define drm_for_each_lessee(lessee, lessor) \
24*2ed077e4SKeith Packard 	list_for_each_entry((lessee), &(lessor)->lessees, lessee_list)
25*2ed077e4SKeith Packard 
26*2ed077e4SKeith Packard /**
27*2ed077e4SKeith Packard  * drm_lease_owner - return ancestor owner drm_master
28*2ed077e4SKeith Packard  * @master: drm_master somewhere within tree of lessees and lessors
29*2ed077e4SKeith Packard  *
30*2ed077e4SKeith Packard  * RETURN:
31*2ed077e4SKeith Packard  *
32*2ed077e4SKeith Packard  * drm_master at the top of the tree (i.e, with lessor NULL
33*2ed077e4SKeith Packard  */
34*2ed077e4SKeith Packard struct drm_master *drm_lease_owner(struct drm_master *master)
35*2ed077e4SKeith Packard {
36*2ed077e4SKeith Packard 	while (master->lessor != NULL)
37*2ed077e4SKeith Packard 		master = master->lessor;
38*2ed077e4SKeith Packard 	return master;
39*2ed077e4SKeith Packard }
40*2ed077e4SKeith Packard EXPORT_SYMBOL(drm_lease_owner);
41*2ed077e4SKeith Packard 
42*2ed077e4SKeith Packard /**
43*2ed077e4SKeith Packard  * _drm_find_lessee - find lessee by id (idr_mutex held)
44*2ed077e4SKeith Packard  * @master: drm_master of lessor
45*2ed077e4SKeith Packard  * @id: lessee_id
46*2ed077e4SKeith Packard  *
47*2ed077e4SKeith Packard  * RETURN:
48*2ed077e4SKeith Packard  *
49*2ed077e4SKeith Packard  * drm_master of the lessee if valid, NULL otherwise
50*2ed077e4SKeith Packard  */
51*2ed077e4SKeith Packard 
52*2ed077e4SKeith Packard static struct drm_master*
53*2ed077e4SKeith Packard _drm_find_lessee(struct drm_master *master, int lessee_id)
54*2ed077e4SKeith Packard {
55*2ed077e4SKeith Packard 	lockdep_assert_held(&master->dev->mode_config.idr_mutex);
56*2ed077e4SKeith Packard 	return idr_find(&drm_lease_owner(master)->lessee_idr, lessee_id);
57*2ed077e4SKeith Packard }
58*2ed077e4SKeith Packard 
59*2ed077e4SKeith Packard /**
60*2ed077e4SKeith Packard  * _drm_lease_held_master - check to see if an object is leased (or owned) by master (idr_mutex held)
61*2ed077e4SKeith Packard  * @master: the master to check the lease status of
62*2ed077e4SKeith Packard  * @id: the id to check
63*2ed077e4SKeith Packard  *
64*2ed077e4SKeith Packard  * Checks if the specified master holds a lease on the object. Return
65*2ed077e4SKeith Packard  * value:
66*2ed077e4SKeith Packard  *
67*2ed077e4SKeith Packard  *	true		'master' holds a lease on (or owns) the object
68*2ed077e4SKeith Packard  *	false		'master' does not hold a lease.
69*2ed077e4SKeith Packard  */
70*2ed077e4SKeith Packard static int _drm_lease_held_master(struct drm_master *master, int id)
71*2ed077e4SKeith Packard {
72*2ed077e4SKeith Packard 	lockdep_assert_held(&master->dev->mode_config.idr_mutex);
73*2ed077e4SKeith Packard 	if (master->lessor)
74*2ed077e4SKeith Packard 		return idr_find(&master->leases, id) != NULL;
75*2ed077e4SKeith Packard 	return true;
76*2ed077e4SKeith Packard }
77*2ed077e4SKeith Packard 
78*2ed077e4SKeith Packard /**
79*2ed077e4SKeith Packard  * _drm_has_leased - check to see if an object has been leased (idr_mutex held)
80*2ed077e4SKeith Packard  * @master: the master to check the lease status of
81*2ed077e4SKeith Packard  * @id: the id to check
82*2ed077e4SKeith Packard  *
83*2ed077e4SKeith Packard  * Checks if any lessee of 'master' holds a lease on 'id'. Return
84*2ed077e4SKeith Packard  * value:
85*2ed077e4SKeith Packard  *
86*2ed077e4SKeith Packard  *	true		Some lessee holds a lease on the object.
87*2ed077e4SKeith Packard  *	false		No lessee has a lease on the object.
88*2ed077e4SKeith Packard  */
89*2ed077e4SKeith Packard static bool _drm_has_leased(struct drm_master *master, int id)
90*2ed077e4SKeith Packard {
91*2ed077e4SKeith Packard 	struct drm_master *lessee;
92*2ed077e4SKeith Packard 
93*2ed077e4SKeith Packard 	lockdep_assert_held(&master->dev->mode_config.idr_mutex);
94*2ed077e4SKeith Packard 	drm_for_each_lessee(lessee, master)
95*2ed077e4SKeith Packard 		if (_drm_lease_held_master(lessee, id))
96*2ed077e4SKeith Packard 			return true;
97*2ed077e4SKeith Packard 	return false;
98*2ed077e4SKeith Packard }
99*2ed077e4SKeith Packard 
100*2ed077e4SKeith Packard /**
101*2ed077e4SKeith Packard  * _drm_lease_held - check drm_mode_object lease status (idr_mutex held)
102*2ed077e4SKeith Packard  * @master: the drm_master
103*2ed077e4SKeith Packard  * @id: the object id
104*2ed077e4SKeith Packard  *
105*2ed077e4SKeith Packard  * Checks if the specified master holds a lease on the object. Return
106*2ed077e4SKeith Packard  * value:
107*2ed077e4SKeith Packard  *
108*2ed077e4SKeith Packard  *	true		'master' holds a lease on (or owns) the object
109*2ed077e4SKeith Packard  *	false		'master' does not hold a lease.
110*2ed077e4SKeith Packard  */
111*2ed077e4SKeith Packard bool _drm_lease_held(struct drm_file *file_priv, int id)
112*2ed077e4SKeith Packard {
113*2ed077e4SKeith Packard 	if (file_priv == NULL || file_priv->master == NULL)
114*2ed077e4SKeith Packard 		return true;
115*2ed077e4SKeith Packard 
116*2ed077e4SKeith Packard 	return _drm_lease_held_master(file_priv->master, id);
117*2ed077e4SKeith Packard }
118*2ed077e4SKeith Packard EXPORT_SYMBOL(_drm_lease_held);
119*2ed077e4SKeith Packard 
120*2ed077e4SKeith Packard /**
121*2ed077e4SKeith Packard  * drm_lease_held - check drm_mode_object lease status (idr_mutex not held)
122*2ed077e4SKeith Packard  * @master: the drm_master
123*2ed077e4SKeith Packard  * @id: the object id
124*2ed077e4SKeith Packard  *
125*2ed077e4SKeith Packard  * Checks if the specified master holds a lease on the object. Return
126*2ed077e4SKeith Packard  * value:
127*2ed077e4SKeith Packard  *
128*2ed077e4SKeith Packard  *	true		'master' holds a lease on (or owns) the object
129*2ed077e4SKeith Packard  *	false		'master' does not hold a lease.
130*2ed077e4SKeith Packard  */
131*2ed077e4SKeith Packard bool drm_lease_held(struct drm_file *file_priv, int id)
132*2ed077e4SKeith Packard {
133*2ed077e4SKeith Packard 	struct drm_master *master;
134*2ed077e4SKeith Packard 	bool ret;
135*2ed077e4SKeith Packard 
136*2ed077e4SKeith Packard 	if (file_priv == NULL || file_priv->master == NULL)
137*2ed077e4SKeith Packard 		return true;
138*2ed077e4SKeith Packard 
139*2ed077e4SKeith Packard 	master = file_priv->master;
140*2ed077e4SKeith Packard 	mutex_lock(&master->dev->mode_config.idr_mutex);
141*2ed077e4SKeith Packard 	ret = _drm_lease_held_master(master, id);
142*2ed077e4SKeith Packard 	mutex_unlock(&master->dev->mode_config.idr_mutex);
143*2ed077e4SKeith Packard 	return ret;
144*2ed077e4SKeith Packard }
145*2ed077e4SKeith Packard EXPORT_SYMBOL(drm_lease_held);
146*2ed077e4SKeith Packard 
147*2ed077e4SKeith Packard /**
148*2ed077e4SKeith Packard  * drm_lease_filter_crtcs - restricted crtc set to leased values (idr_mutex not held)
149*2ed077e4SKeith Packard  * @file_priv: requestor file
150*2ed077e4SKeith Packard  * @crtcs: bitmask of crtcs to check
151*2ed077e4SKeith Packard  *
152*2ed077e4SKeith Packard  * Reconstructs a crtc mask based on the crtcs which are visible
153*2ed077e4SKeith Packard  * through the specified file.
154*2ed077e4SKeith Packard  */
155*2ed077e4SKeith Packard uint32_t drm_lease_filter_crtcs(struct drm_file *file_priv, uint32_t crtcs_in)
156*2ed077e4SKeith Packard {
157*2ed077e4SKeith Packard 	struct drm_master *master;
158*2ed077e4SKeith Packard 	struct drm_device *dev;
159*2ed077e4SKeith Packard 	struct drm_crtc *crtc;
160*2ed077e4SKeith Packard 	int count_in, count_out;
161*2ed077e4SKeith Packard 	uint32_t crtcs_out = 0;
162*2ed077e4SKeith Packard 
163*2ed077e4SKeith Packard 	if (file_priv == NULL || file_priv->master == NULL)
164*2ed077e4SKeith Packard 		return crtcs_in;
165*2ed077e4SKeith Packard 
166*2ed077e4SKeith Packard 	master = file_priv->master;
167*2ed077e4SKeith Packard 	dev = master->dev;
168*2ed077e4SKeith Packard 
169*2ed077e4SKeith Packard 	count_in = count_out = 0;
170*2ed077e4SKeith Packard 	mutex_lock(&master->dev->mode_config.idr_mutex);
171*2ed077e4SKeith Packard 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
172*2ed077e4SKeith Packard 		if (_drm_lease_held_master(master, crtc->base.id)) {
173*2ed077e4SKeith Packard 			uint32_t mask_in = 1ul << count_in;
174*2ed077e4SKeith Packard 			if ((crtcs_in & mask_in) != 0) {
175*2ed077e4SKeith Packard 				uint32_t mask_out = 1ul << count_out;
176*2ed077e4SKeith Packard 				crtcs_out |= mask_out;
177*2ed077e4SKeith Packard 			}
178*2ed077e4SKeith Packard 			count_out++;
179*2ed077e4SKeith Packard 		}
180*2ed077e4SKeith Packard 		count_in++;
181*2ed077e4SKeith Packard 	}
182*2ed077e4SKeith Packard 	mutex_unlock(&master->dev->mode_config.idr_mutex);
183*2ed077e4SKeith Packard 	return crtcs_out;
184*2ed077e4SKeith Packard }
185*2ed077e4SKeith Packard EXPORT_SYMBOL(drm_lease_filter_crtcs);
186*2ed077e4SKeith Packard 
187*2ed077e4SKeith Packard /*
188*2ed077e4SKeith Packard  * drm_lease_create - create a new drm_master with leased objects (idr_mutex not held)
189*2ed077e4SKeith Packard  * @lessor: lease holder (or owner) of objects
190*2ed077e4SKeith Packard  * @leases: objects to lease to the new drm_master
191*2ed077e4SKeith Packard  *
192*2ed077e4SKeith Packard  * Uses drm_master_create to allocate a new drm_master, then checks to
193*2ed077e4SKeith Packard  * make sure all of the desired objects can be leased, atomically
194*2ed077e4SKeith Packard  * leasing them to the new drmmaster.
195*2ed077e4SKeith Packard  *
196*2ed077e4SKeith Packard  * 	ERR_PTR(-EACCESS)	some other master holds the title to any object
197*2ed077e4SKeith Packard  * 	ERR_PTR(-ENOENT)	some object is not a valid DRM object for this device
198*2ed077e4SKeith Packard  * 	ERR_PTR(-EBUSY)		some other lessee holds title to this object
199*2ed077e4SKeith Packard  *	ERR_PTR(-EEXIST)	same object specified more than once in the provided list
200*2ed077e4SKeith Packard  *	ERR_PTR(-ENOMEM)	allocation failed
201*2ed077e4SKeith Packard  */
202*2ed077e4SKeith Packard static struct drm_master *drm_lease_create(struct drm_master *lessor, struct idr *leases)
203*2ed077e4SKeith Packard {
204*2ed077e4SKeith Packard 	struct drm_device *dev = lessor->dev;
205*2ed077e4SKeith Packard 	int error;
206*2ed077e4SKeith Packard 	struct drm_master *lessee;
207*2ed077e4SKeith Packard 	int object;
208*2ed077e4SKeith Packard 	int id;
209*2ed077e4SKeith Packard 	void *entry;
210*2ed077e4SKeith Packard 
211*2ed077e4SKeith Packard 	DRM_DEBUG_LEASE("lessor %d\n", lessor->lessee_id);
212*2ed077e4SKeith Packard 
213*2ed077e4SKeith Packard 	lessee = drm_master_create(lessor->dev);
214*2ed077e4SKeith Packard 	if (!lessee) {
215*2ed077e4SKeith Packard 		DRM_DEBUG_LEASE("drm_master_create failed\n");
216*2ed077e4SKeith Packard 		return ERR_PTR(-ENOMEM);
217*2ed077e4SKeith Packard 	}
218*2ed077e4SKeith Packard 
219*2ed077e4SKeith Packard 	mutex_lock(&dev->mode_config.idr_mutex);
220*2ed077e4SKeith Packard 
221*2ed077e4SKeith Packard 	/* Insert the new lessee into the tree */
222*2ed077e4SKeith Packard 	id = idr_alloc(&(drm_lease_owner(lessor)->lessee_idr), lessee, 1, 0, GFP_KERNEL);
223*2ed077e4SKeith Packard 	if (id < 0) {
224*2ed077e4SKeith Packard 		error = id;
225*2ed077e4SKeith Packard 		goto out_lessee;
226*2ed077e4SKeith Packard 	}
227*2ed077e4SKeith Packard 
228*2ed077e4SKeith Packard 	lessee->lessee_id = id;
229*2ed077e4SKeith Packard 	lessee->lessor = drm_master_get(lessor);
230*2ed077e4SKeith Packard 	list_add_tail(&lessee->lessee_list, &lessor->lessees);
231*2ed077e4SKeith Packard 
232*2ed077e4SKeith Packard 	idr_for_each_entry(leases, entry, object) {
233*2ed077e4SKeith Packard 		error = 0;
234*2ed077e4SKeith Packard 		if (!idr_find(&dev->mode_config.crtc_idr, object))
235*2ed077e4SKeith Packard 			error = -ENOENT;
236*2ed077e4SKeith Packard 		else if (!_drm_lease_held_master(lessor, object))
237*2ed077e4SKeith Packard 			error = -EACCES;
238*2ed077e4SKeith Packard 		else if (_drm_has_leased(lessor, object))
239*2ed077e4SKeith Packard 			error = -EBUSY;
240*2ed077e4SKeith Packard 
241*2ed077e4SKeith Packard 		if (error != 0) {
242*2ed077e4SKeith Packard 			DRM_DEBUG_LEASE("object %d failed %d\n", object, error);
243*2ed077e4SKeith Packard 			goto out_lessee;
244*2ed077e4SKeith Packard 		}
245*2ed077e4SKeith Packard 	}
246*2ed077e4SKeith Packard 
247*2ed077e4SKeith Packard 	/* Move the leases over */
248*2ed077e4SKeith Packard 	lessee->leases = *leases;
249*2ed077e4SKeith Packard 	DRM_DEBUG_LEASE("new lessee %d %p, lessor %d %p\n", lessee->lessee_id, lessee, lessor->lessee_id, lessor);
250*2ed077e4SKeith Packard 
251*2ed077e4SKeith Packard 	mutex_unlock(&dev->mode_config.idr_mutex);
252*2ed077e4SKeith Packard 	return lessee;
253*2ed077e4SKeith Packard 
254*2ed077e4SKeith Packard out_lessee:
255*2ed077e4SKeith Packard 	drm_master_put(&lessee);
256*2ed077e4SKeith Packard 
257*2ed077e4SKeith Packard 	mutex_unlock(&dev->mode_config.idr_mutex);
258*2ed077e4SKeith Packard 
259*2ed077e4SKeith Packard 	return ERR_PTR(error);
260*2ed077e4SKeith Packard }
261*2ed077e4SKeith Packard 
262*2ed077e4SKeith Packard /**
263*2ed077e4SKeith Packard  * drm_lease_destroy - a master is going away (idr_mutex not held)
264*2ed077e4SKeith Packard  * @master: the drm_master being destroyed
265*2ed077e4SKeith Packard  *
266*2ed077e4SKeith Packard  * All lessees will have been destroyed as they
267*2ed077e4SKeith Packard  * hold a reference on their lessor. Notify any
268*2ed077e4SKeith Packard  * lessor for this master so that it can check
269*2ed077e4SKeith Packard  * the list of lessees.
270*2ed077e4SKeith Packard  */
271*2ed077e4SKeith Packard void drm_lease_destroy(struct drm_master *master)
272*2ed077e4SKeith Packard {
273*2ed077e4SKeith Packard 	struct drm_device *dev = master->dev;
274*2ed077e4SKeith Packard 
275*2ed077e4SKeith Packard 	mutex_lock(&dev->mode_config.idr_mutex);
276*2ed077e4SKeith Packard 
277*2ed077e4SKeith Packard 	DRM_DEBUG_LEASE("drm_lease_destroy %d\n", master->lessee_id);
278*2ed077e4SKeith Packard 
279*2ed077e4SKeith Packard 	/* This master is referenced by all lessees, hence it cannot be destroyed
280*2ed077e4SKeith Packard 	 * until all of them have been
281*2ed077e4SKeith Packard 	 */
282*2ed077e4SKeith Packard 	WARN_ON(!list_empty(&master->lessees));
283*2ed077e4SKeith Packard 
284*2ed077e4SKeith Packard 	/* Remove this master from the lessee idr in the owner */
285*2ed077e4SKeith Packard 	if (master->lessee_id != 0) {
286*2ed077e4SKeith Packard 		DRM_DEBUG_LEASE("remove master %d from device list of lessees\n", master->lessee_id);
287*2ed077e4SKeith Packard 		idr_remove(&(drm_lease_owner(master)->lessee_idr), master->lessee_id);
288*2ed077e4SKeith Packard 	}
289*2ed077e4SKeith Packard 
290*2ed077e4SKeith Packard 	/* Remove this master from any lessee list it may be on */
291*2ed077e4SKeith Packard 	list_del(&master->lessee_list);
292*2ed077e4SKeith Packard 
293*2ed077e4SKeith Packard 	mutex_unlock(&dev->mode_config.idr_mutex);
294*2ed077e4SKeith Packard 
295*2ed077e4SKeith Packard 	if (master->lessor) {
296*2ed077e4SKeith Packard 		/* Tell the master to check the lessee list */
297*2ed077e4SKeith Packard 		drm_sysfs_hotplug_event(dev);
298*2ed077e4SKeith Packard 		drm_master_put(&master->lessor);
299*2ed077e4SKeith Packard 	}
300*2ed077e4SKeith Packard 
301*2ed077e4SKeith Packard 	DRM_DEBUG_LEASE("drm_lease_destroy done %d\n", master->lessee_id);
302*2ed077e4SKeith Packard }
303*2ed077e4SKeith Packard 
304*2ed077e4SKeith Packard /**
305*2ed077e4SKeith Packard  * _drm_lease_revoke - revoke access to all leased objects (idr_mutex held)
306*2ed077e4SKeith Packard  * @master: the master losing its lease
307*2ed077e4SKeith Packard  */
308*2ed077e4SKeith Packard static void _drm_lease_revoke(struct drm_master *top)
309*2ed077e4SKeith Packard {
310*2ed077e4SKeith Packard 	int object;
311*2ed077e4SKeith Packard 	void *entry;
312*2ed077e4SKeith Packard 	struct drm_master *master = top;
313*2ed077e4SKeith Packard 
314*2ed077e4SKeith Packard 	lockdep_assert_held(&top->dev->mode_config.idr_mutex);
315*2ed077e4SKeith Packard 
316*2ed077e4SKeith Packard 	/*
317*2ed077e4SKeith Packard 	 * Walk the tree starting at 'top' emptying all leases. Because
318*2ed077e4SKeith Packard 	 * the tree is fully connected, we can do this without recursing
319*2ed077e4SKeith Packard 	 */
320*2ed077e4SKeith Packard 	for (;;) {
321*2ed077e4SKeith Packard 		DRM_DEBUG_LEASE("revoke leases for %p %d\n", master, master->lessee_id);
322*2ed077e4SKeith Packard 
323*2ed077e4SKeith Packard 		/* Evacuate the lease */
324*2ed077e4SKeith Packard 		idr_for_each_entry(&master->leases, entry, object)
325*2ed077e4SKeith Packard 			idr_remove(&master->leases, object);
326*2ed077e4SKeith Packard 
327*2ed077e4SKeith Packard 		/* Depth-first list walk */
328*2ed077e4SKeith Packard 
329*2ed077e4SKeith Packard 		/* Down */
330*2ed077e4SKeith Packard 		if (!list_empty(&master->lessees)) {
331*2ed077e4SKeith Packard 			master = list_first_entry(&master->lessees, struct drm_master, lessee_list);
332*2ed077e4SKeith Packard 		} else {
333*2ed077e4SKeith Packard 			/* Up */
334*2ed077e4SKeith Packard 			while (master != top && master == list_last_entry(&master->lessor->lessees, struct drm_master, lessee_list))
335*2ed077e4SKeith Packard 				master = master->lessor;
336*2ed077e4SKeith Packard 
337*2ed077e4SKeith Packard 			if (master == top)
338*2ed077e4SKeith Packard 				break;
339*2ed077e4SKeith Packard 
340*2ed077e4SKeith Packard 			/* Over */
341*2ed077e4SKeith Packard 			master = list_entry(master->lessee_list.next, struct drm_master, lessee_list);
342*2ed077e4SKeith Packard 		}
343*2ed077e4SKeith Packard 	}
344*2ed077e4SKeith Packard }
345*2ed077e4SKeith Packard 
346*2ed077e4SKeith Packard /**
347*2ed077e4SKeith Packard  * drm_lease_revoke - revoke access to all leased objects (idr_mutex not held)
348*2ed077e4SKeith Packard  * @top: the master losing its lease
349*2ed077e4SKeith Packard  */
350*2ed077e4SKeith Packard void drm_lease_revoke(struct drm_master *top)
351*2ed077e4SKeith Packard {
352*2ed077e4SKeith Packard 	mutex_lock(&top->dev->mode_config.idr_mutex);
353*2ed077e4SKeith Packard 	_drm_lease_revoke(top);
354*2ed077e4SKeith Packard 	mutex_unlock(&top->dev->mode_config.idr_mutex);
355*2ed077e4SKeith Packard }
356